xref: /xnu-10002.41.9/tools/lldbmacros/core/caching.py (revision 699cd48037512bf4380799317ca44ca453c82f57)
1*699cd480SApple OSS Distributions"""
2*699cd480SApple OSS DistributionsA basic caching module for xnu debug macros to use.
3*699cd480SApple OSS Distributions
4*699cd480SApple OSS Distributions
5*699cd480SApple OSS DistributionsWhen to use caching?
6*699cd480SApple OSS Distributions~~~~~~~~~~~~~~~~~~~~
7*699cd480SApple OSS Distributions
8*699cd480SApple OSS DistributionsVery often you do not need to: LLDB already provides extensive data caching.
9*699cd480SApple OSS Distributions
10*699cd480SApple OSS DistributionsThe most common things that need caching are:
11*699cd480SApple OSS Distributions- types (the gettype() function provides this)
12*699cd480SApple OSS Distributions- globals (kern.globals / kern.GetGlobalVariable() provides this)
13*699cd480SApple OSS Distributions
14*699cd480SApple OSS Distributions
15*699cd480SApple OSS DistributionsIf your macro is slow to get some data, before slapping a caching decorator,
16*699cd480SApple OSS Distributionsplease profile your code using `xnudebug profile`. Very often slowness happens
17*699cd480SApple OSS Distributionsdue to the usage of CreateValueFromExpression() which spins a full compiler
18*699cd480SApple OSS Distributionsto parse relatively trivial expressions and is easily 10-100x as slow as
19*699cd480SApple OSS Distributionsalternatives like CreateValueFromAddress().
20*699cd480SApple OSS Distributions
21*699cd480SApple OSS DistributionsOnly use caching once you have eliminated those obvious performance hogs
22*699cd480SApple OSS Distributionsand an A/B shows meaningful speed improvements over the base line.
23*699cd480SApple OSS Distributions
24*699cd480SApple OSS Distributions
25*699cd480SApple OSS DistributionsI really need caching how does it work?
26*699cd480SApple OSS Distributions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27*699cd480SApple OSS Distributions
28*699cd480SApple OSS DistributionsThis module provides function decorators to easily cache the result of
29*699cd480SApple OSS Distributionsfunctions based on their parameters, while keeping the caches separated
30*699cd480SApple OSS Distributionsper lldb target.
31*699cd480SApple OSS Distributions
32*699cd480SApple OSS Distributions@cache_statically can be used to cache data once per target,
33*699cd480SApple OSS Distributionsbecause it won't change if the process is resumed and stopped again.
34*699cd480SApple OSS DistributionsFor example: types, constant values, global addresses, ...
35*699cd480SApple OSS Distributions
36*699cd480SApple OSS Distributions@cache_dynamically can be used to cache data that is expensive to compute
37*699cd480SApple OSS Distributionsbut which content depends on the state of the process. It will be
38*699cd480SApple OSS Distributionsautomatically invalidated for you if the process is resumed and
39*699cd480SApple OSS Distributionsthen stops or hits a breakpoint.
40*699cd480SApple OSS Distributions
41*699cd480SApple OSS Distributions@dyn_cached_property can be used on instances to turn a member into
42*699cd480SApple OSS Distributionsa per-target cached dynamic property that will be cleaned up when
43*699cd480SApple OSS Distributionsthe object dies or if the process is resumed and then stops
44*699cd480SApple OSS Distributionsor hits a breakpoint.
45*699cd480SApple OSS Distributions
46*699cd480SApple OSS DistributionsFunctions using these decorators, must have a `target=None` named argument
47*699cd480SApple OSS Distributionsthat no caller of those functions need to pass explicitly, the decorator
48*699cd480SApple OSS Distributionswill take care of passing the proper current target value (equivalent to
49*699cd480SApple OSS DistributionsLazyTarget.GetTarget() except more efficiently).
50*699cd480SApple OSS Distributions
51*699cd480SApple OSS Distributions
52*699cd480SApple OSS DistributionsCache Invalidation
53*699cd480SApple OSS Distributions~~~~~~~~~~~~~~~~~~
54*699cd480SApple OSS Distributions
55*699cd480SApple OSS DistributionsThis module make the crucial assumption that no XNU lldb macro
56*699cd480SApple OSS Distributionswill step/resume/... processes as the invalidation only happens
57*699cd480SApple OSS Distributionswhen a new command is being run.
58*699cd480SApple OSS Distributions
59*699cd480SApple OSS DistributionsIf some macro needs to step/resume it has to provide its own
60*699cd480SApple OSS Distributionscache invalidation, by e.g. pushing its own ImplicitContext()
61*699cd480SApple OSS Distributionsaround such process state manipulations.
62*699cd480SApple OSS Distributions"""
63*699cd480SApple OSS Distributions
64*699cd480SApple OSS Distributions# Private Routines and objects
65*699cd480SApple OSS Distributionsfrom __future__ import absolute_import
66*699cd480SApple OSS Distributions
67*699cd480SApple OSS Distributionsfrom builtins import (
68*699cd480SApple OSS Distributions    dict,
69*699cd480SApple OSS Distributions    hash,
70*699cd480SApple OSS Distributions    list,
71*699cd480SApple OSS Distributions    object,
72*699cd480SApple OSS Distributions    tuple,
73*699cd480SApple OSS Distributions)
74*699cd480SApple OSS Distributionsfrom collections import namedtuple
75*699cd480SApple OSS Distributions
76*699cd480SApple OSS Distributionsimport functools
77*699cd480SApple OSS Distributionsimport gc
78*699cd480SApple OSS Distributionsimport inspect
79*699cd480SApple OSS Distributionsimport sys
80*699cd480SApple OSS Distributionsimport weakref
81*699cd480SApple OSS Distributions
82*699cd480SApple OSS Distributionsimport lldb
83*699cd480SApple OSS Distributions
84*699cd480SApple OSS Distributionsfrom .compat import valueint
85*699cd480SApple OSS Distributionsfrom .configuration import config
86*699cd480SApple OSS Distributionsfrom . import lldbwrap
87*699cd480SApple OSS Distributions
88*699cd480SApple OSS Distributions
89*699cd480SApple OSS Distributionsclass _Registry(list):
90*699cd480SApple OSS Distributions    """ Private class that holds a list of _Cache instances """
91*699cd480SApple OSS Distributions
92*699cd480SApple OSS Distributions    __slots__ = ('name')
93*699cd480SApple OSS Distributions
94*699cd480SApple OSS Distributions    def __init__(self, name):
95*699cd480SApple OSS Distributions        super(_Registry, self).__init__()
96*699cd480SApple OSS Distributions        self.name = name
97*699cd480SApple OSS Distributions
98*699cd480SApple OSS Distributions    @property
99*699cd480SApple OSS Distributions    def size(self):
100*699cd480SApple OSS Distributions        return sys.getsizeof(self)
101*699cd480SApple OSS Distributions
102*699cd480SApple OSS Distributions    def invalidate(self, pid):
103*699cd480SApple OSS Distributions        for cache in self:
104*699cd480SApple OSS Distributions            cache.invalidate(pid)
105*699cd480SApple OSS Distributions
106*699cd480SApple OSS Distributions    def clear(self):
107*699cd480SApple OSS Distributions        for cache in self:
108*699cd480SApple OSS Distributions            cache.clear()
109*699cd480SApple OSS Distributions
110*699cd480SApple OSS Distributions    def __repr__(self):
111*699cd480SApple OSS Distributions        return "_Registry({}, {})".format(
112*699cd480SApple OSS Distributions            self.name, super(_Registry, self).__repr__())
113*699cd480SApple OSS Distributions
114*699cd480SApple OSS Distributions    def __str__(self):
115*699cd480SApple OSS Distributions        return "_Registry({}, {} caches, size {})".format(
116*699cd480SApple OSS Distributions            self.name, len(self), self.size)
117*699cd480SApple OSS Distributions
118*699cd480SApple OSS Distributions
119*699cd480SApple OSS Distributionsclass _Cache(dict):
120*699cd480SApple OSS Distributions    """ Private class that implements a given global function _Cache
121*699cd480SApple OSS Distributions
122*699cd480SApple OSS Distributions        Those are created with the @cache_statically/@cache_dynamically
123*699cd480SApple OSS Distributions        decorators
124*699cd480SApple OSS Distributions    """
125*699cd480SApple OSS Distributions
126*699cd480SApple OSS Distributions    __slots__ = ('name')
127*699cd480SApple OSS Distributions
128*699cd480SApple OSS Distributions    def __init__(self, name, registry):
129*699cd480SApple OSS Distributions        super(_Cache, self).__init__()
130*699cd480SApple OSS Distributions        self.name = name
131*699cd480SApple OSS Distributions        registry.append(self)
132*699cd480SApple OSS Distributions
133*699cd480SApple OSS Distributions    @property
134*699cd480SApple OSS Distributions    def size(self):
135*699cd480SApple OSS Distributions        return sys.getsizeof(self)
136*699cd480SApple OSS Distributions
137*699cd480SApple OSS Distributions    def invalidate(self, pid):
138*699cd480SApple OSS Distributions        if pid in self: del self[pid]
139*699cd480SApple OSS Distributions
140*699cd480SApple OSS Distributions    def __repr__(self):
141*699cd480SApple OSS Distributions        return "_Cache({}, {})".format(
142*699cd480SApple OSS Distributions            self.name, super(_Cache, self).__repr__())
143*699cd480SApple OSS Distributions
144*699cd480SApple OSS Distributions    def __str__(self):
145*699cd480SApple OSS Distributions        return "_Cache({}, {} entries, size {})".format(
146*699cd480SApple OSS Distributions            self.name, len(self), self.size)
147*699cd480SApple OSS Distributions
148*699cd480SApple OSS Distributions
149*699cd480SApple OSS Distributions_static_registry     = _Registry('static')
150*699cd480SApple OSS Distributions_dynamic_registry    = _Registry('dynamic')
151*699cd480SApple OSS Distributions_dynamic_keys        = {}
152*699cd480SApple OSS Distributions
153*699cd480SApple OSS Distributions_implicit_target     = None
154*699cd480SApple OSS Distributions_implicit_process    = None
155*699cd480SApple OSS Distributions_implicit_exe_id     = None
156*699cd480SApple OSS Distributions_implicit_dynkey     = None
157*699cd480SApple OSS Distributions
158*699cd480SApple OSS Distributions
159*699cd480SApple OSS Distributionsclass _DynamicKey(object):
160*699cd480SApple OSS Distributions    """ Wraps a process StopID as a key that can be used as caches keys """
161*699cd480SApple OSS Distributions
162*699cd480SApple OSS Distributions    def __init__(self, exe_id, stop_id):
163*699cd480SApple OSS Distributions        self.exe_id  = exe_id
164*699cd480SApple OSS Distributions        self.stop_id = stop_id
165*699cd480SApple OSS Distributions
166*699cd480SApple OSS Distributions    def __hash__(self):
167*699cd480SApple OSS Distributions        return id(self)
168*699cd480SApple OSS Distributions
169*699cd480SApple OSS Distributions
170*699cd480SApple OSS Distributionsclass LazyTarget(object):
171*699cd480SApple OSS Distributions    """ A common object that lazy-evaluates and caches the lldb.SBTarget
172*699cd480SApple OSS Distributions        and lldb.SBProcess for the current interactive debugging session.
173*699cd480SApple OSS Distributions    """
174*699cd480SApple OSS Distributions
175*699cd480SApple OSS Distributions    @staticmethod
176*699cd480SApple OSS Distributions    def _CacheUpdateAnchor(exe_id, process):
177*699cd480SApple OSS Distributions        global _dynamic_keys, _dynamic_registry
178*699cd480SApple OSS Distributions
179*699cd480SApple OSS Distributions        stop_id = process.GetStopID()
180*699cd480SApple OSS Distributions        dyn_key = _dynamic_keys.get(exe_id)
181*699cd480SApple OSS Distributions
182*699cd480SApple OSS Distributions        if dyn_key is None:
183*699cd480SApple OSS Distributions            _dynamic_keys[exe_id] = dyn_key = _DynamicKey(exe_id, stop_id)
184*699cd480SApple OSS Distributions        elif dyn_key.stop_id != stop_id:
185*699cd480SApple OSS Distributions            _dynamic_registry.invalidate(exe_id)
186*699cd480SApple OSS Distributions            _dynamic_keys[exe_id] = dyn_key = _DynamicKey(exe_id, stop_id)
187*699cd480SApple OSS Distributions            gc.collect()
188*699cd480SApple OSS Distributions
189*699cd480SApple OSS Distributions        return dyn_key
190*699cd480SApple OSS Distributions
191*699cd480SApple OSS Distributions    @staticmethod
192*699cd480SApple OSS Distributions    def _CacheGC():
193*699cd480SApple OSS Distributions        global _dynamic_keys, _dynamic_registry, _static_registry
194*699cd480SApple OSS Distributions
195*699cd480SApple OSS Distributions        exe_ids = _dynamic_keys.keys() - set(
196*699cd480SApple OSS Distributions            tg.GetProcess().GetUniqueID()
197*699cd480SApple OSS Distributions            for tg in lldb.debugger
198*699cd480SApple OSS Distributions        )
199*699cd480SApple OSS Distributions
200*699cd480SApple OSS Distributions        for exe_id in exe_ids:
201*699cd480SApple OSS Distributions            _static_registry.invalidate(exe_id)
202*699cd480SApple OSS Distributions            _dynamic_registry.invalidate(exe_id)
203*699cd480SApple OSS Distributions            del _dynamic_keys[exe_id]
204*699cd480SApple OSS Distributions
205*699cd480SApple OSS Distributions        if len(exe_ids):
206*699cd480SApple OSS Distributions            gc.collect()
207*699cd480SApple OSS Distributions
208*699cd480SApple OSS Distributions    @staticmethod
209*699cd480SApple OSS Distributions    def _CacheGetDynamicKey():
210*699cd480SApple OSS Distributions        """ Get a _DynamicKey for the most likely current process
211*699cd480SApple OSS Distributions        """
212*699cd480SApple OSS Distributions        global _implicit_dynkey
213*699cd480SApple OSS Distributions
214*699cd480SApple OSS Distributions        dyn_key = _implicit_dynkey
215*699cd480SApple OSS Distributions        if dyn_key is None:
216*699cd480SApple OSS Distributions            process = lldbwrap.GetProcess()
217*699cd480SApple OSS Distributions            exe_id  = process.GetUniqueID()
218*699cd480SApple OSS Distributions            dyn_key = LazyTarget._CacheUpdateAnchor(exe_id, process)
219*699cd480SApple OSS Distributions
220*699cd480SApple OSS Distributions        return dyn_key
221*699cd480SApple OSS Distributions
222*699cd480SApple OSS Distributions    @staticmethod
223*699cd480SApple OSS Distributions    def _CacheClear():
224*699cd480SApple OSS Distributions        """ remove all cached data.
225*699cd480SApple OSS Distributions        """
226*699cd480SApple OSS Distributions        global _dynamic_registry, _static_registry, _dynamic_keys
227*699cd480SApple OSS Distributions
228*699cd480SApple OSS Distributions        _dynamic_registry.clear()
229*699cd480SApple OSS Distributions        _static_registry.clear()
230*699cd480SApple OSS Distributions        _dynamic_keys.clear()
231*699cd480SApple OSS Distributions
232*699cd480SApple OSS Distributions    @staticmethod
233*699cd480SApple OSS Distributions    def _CacheSize():
234*699cd480SApple OSS Distributions        """ Returns number of bytes held in cache.
235*699cd480SApple OSS Distributions            returns:
236*699cd480SApple OSS Distributions                int - size of cache including static and dynamic
237*699cd480SApple OSS Distributions        """
238*699cd480SApple OSS Distributions        global _dynamic_registry, _static_registry
239*699cd480SApple OSS Distributions
240*699cd480SApple OSS Distributions        return _dynamic_registry.size + _static_registry.size
241*699cd480SApple OSS Distributions
242*699cd480SApple OSS Distributions
243*699cd480SApple OSS Distributions    @staticmethod
244*699cd480SApple OSS Distributions    def GetTarget():
245*699cd480SApple OSS Distributions        """ Get the SBTarget that is the most likely current target
246*699cd480SApple OSS Distributions        """
247*699cd480SApple OSS Distributions        global _implicit_target
248*699cd480SApple OSS Distributions
249*699cd480SApple OSS Distributions        return _implicit_target or lldbwrap.GetTarget()
250*699cd480SApple OSS Distributions
251*699cd480SApple OSS Distributions    @staticmethod
252*699cd480SApple OSS Distributions    def GetProcess():
253*699cd480SApple OSS Distributions        """ Get an SBProcess for the most likely current process
254*699cd480SApple OSS Distributions        """
255*699cd480SApple OSS Distributions        global _implicit_process
256*699cd480SApple OSS Distributions
257*699cd480SApple OSS Distributions        return _implicit_process or lldbwrap.GetProcess()
258*699cd480SApple OSS Distributions
259*699cd480SApple OSS Distributions
260*699cd480SApple OSS Distributionsclass ImplicitContext(object):
261*699cd480SApple OSS Distributions    """ This class sets up the implicit target/process
262*699cd480SApple OSS Distributions        being used by the XNu lldb macros system.
263*699cd480SApple OSS Distributions
264*699cd480SApple OSS Distributions        In order for lldb macros to function properly, such a context
265*699cd480SApple OSS Distributions        must be used around code being run, otherwise macros will try
266*699cd480SApple OSS Distributions        to infer it from the current lldb selected target which is
267*699cd480SApple OSS Distributions        incorrect in certain contexts.
268*699cd480SApple OSS Distributions
269*699cd480SApple OSS Distributions        typical usage is:
270*699cd480SApple OSS Distributions
271*699cd480SApple OSS Distributions            with ImplicitContext(thing):
272*699cd480SApple OSS Distributions                # code
273*699cd480SApple OSS Distributions
274*699cd480SApple OSS Distributions        where @c thing is any of an SBExecutionContext, an SBValue,
275*699cd480SApple OSS Distributions        an SBBreakpoint, an SBProcess, or an SBTarget.
276*699cd480SApple OSS Distributions    """
277*699cd480SApple OSS Distributions
278*699cd480SApple OSS Distributions    __slots__ = ('target', 'process', 'exe_id', 'old_ctx')
279*699cd480SApple OSS Distributions
280*699cd480SApple OSS Distributions    def __init__(self, arg):
281*699cd480SApple OSS Distributions        if isinstance(arg, lldb.SBExecutionContext):
282*699cd480SApple OSS Distributions            exe_ctx = lldbwrap.SBExecutionContext(arg)
283*699cd480SApple OSS Distributions            target  = exe_ctx.GetTarget()
284*699cd480SApple OSS Distributions            process = exe_ctx.GetProcess()
285*699cd480SApple OSS Distributions        elif isinstance(arg, lldb.SBValue):
286*699cd480SApple OSS Distributions            target  = lldbwrap.SBTarget(arg.GetTarget())
287*699cd480SApple OSS Distributions            process = target.GetProcess()
288*699cd480SApple OSS Distributions        elif isinstance(arg, lldb.SBBreakpoint):
289*699cd480SApple OSS Distributions            bpoint  = lldbwrap.SBBreakpoint(arg)
290*699cd480SApple OSS Distributions            target  = bpoint.GetTarget()
291*699cd480SApple OSS Distributions            process = target.GetProcess()
292*699cd480SApple OSS Distributions        elif isinstance(arg, lldb.SBProcess):
293*699cd480SApple OSS Distributions            process = lldbwrap.SBProcess(arg)
294*699cd480SApple OSS Distributions            target  = process.GetTarget()
295*699cd480SApple OSS Distributions        elif isinstance(arg, lldb.SBTarget):
296*699cd480SApple OSS Distributions            target  = lldbwrap.SBTarget(arg)
297*699cd480SApple OSS Distributions            process = target.GetProcess()
298*699cd480SApple OSS Distributions        else:
299*699cd480SApple OSS Distributions            raise TypeError("argument type unsupported {}".format(
300*699cd480SApple OSS Distributions                arg.__class__.__name__))
301*699cd480SApple OSS Distributions
302*699cd480SApple OSS Distributions        self.target  = target
303*699cd480SApple OSS Distributions        self.process = process
304*699cd480SApple OSS Distributions        self.exe_id  = process.GetUniqueID()
305*699cd480SApple OSS Distributions        self.old_ctx = None
306*699cd480SApple OSS Distributions
307*699cd480SApple OSS Distributions    def __enter__(self):
308*699cd480SApple OSS Distributions        global _implicit_target, _implicit_process, _implicit_exe_id
309*699cd480SApple OSS Distributions        global _implicit_dynkey, _dynamic_keys
310*699cd480SApple OSS Distributions
311*699cd480SApple OSS Distributions        self.old_ctx = (_implicit_target, _implicit_process, _implicit_exe_id)
312*699cd480SApple OSS Distributions
313*699cd480SApple OSS Distributions        _implicit_target  = self.target
314*699cd480SApple OSS Distributions        _implicit_process = process = self.process
315*699cd480SApple OSS Distributions        _implicit_exe_id  = exe_id = self.exe_id
316*699cd480SApple OSS Distributions        _implicit_dynkey  = LazyTarget._CacheUpdateAnchor(exe_id, process)
317*699cd480SApple OSS Distributions
318*699cd480SApple OSS Distributions        if len(_dynamic_keys) > 1:
319*699cd480SApple OSS Distributions            LazyTarget._CacheGC()
320*699cd480SApple OSS Distributions
321*699cd480SApple OSS Distributions    def __exit__(self, *args):
322*699cd480SApple OSS Distributions        global _implicit_target, _implicit_process, _implicit_exe_id
323*699cd480SApple OSS Distributions        global _implicit_dynkey, _dynamic_keys
324*699cd480SApple OSS Distributions
325*699cd480SApple OSS Distributions        target, process, exe_id = self.old_ctx
326*699cd480SApple OSS Distributions        self.old_ctx = None
327*699cd480SApple OSS Distributions
328*699cd480SApple OSS Distributions        _implicit_target  = target
329*699cd480SApple OSS Distributions        _implicit_process = process
330*699cd480SApple OSS Distributions        _implicit_exe_id  = exe_id
331*699cd480SApple OSS Distributions
332*699cd480SApple OSS Distributions        if process:
333*699cd480SApple OSS Distributions            _implicit_dynkey = LazyTarget._CacheUpdateAnchor(exe_id, process)
334*699cd480SApple OSS Distributions        else:
335*699cd480SApple OSS Distributions            _implicit_dynkey = None
336*699cd480SApple OSS Distributions
337*699cd480SApple OSS Distributions
338*699cd480SApple OSS Distributionsclass _HashedSeq(list):
339*699cd480SApple OSS Distributions    """ This class guarantees that hash() will be called no more than once
340*699cd480SApple OSS Distributions        per element.  This is important because the lru_cache() will hash
341*699cd480SApple OSS Distributions        the key multiple times on a cache miss.
342*699cd480SApple OSS Distributions
343*699cd480SApple OSS Distributions        Inspired by python3's lru_cache decorator implementation
344*699cd480SApple OSS Distributions    """
345*699cd480SApple OSS Distributions
346*699cd480SApple OSS Distributions    __slots__ = 'hashvalue'
347*699cd480SApple OSS Distributions
348*699cd480SApple OSS Distributions    def __init__(self, tup, hash=hash):
349*699cd480SApple OSS Distributions        self[:] = tup
350*699cd480SApple OSS Distributions        self.hashvalue = hash(tup)
351*699cd480SApple OSS Distributions
352*699cd480SApple OSS Distributions    def __hash__(self):
353*699cd480SApple OSS Distributions        return self.hashvalue
354*699cd480SApple OSS Distributions
355*699cd480SApple OSS Distributions    @classmethod
356*699cd480SApple OSS Distributions    def make_key(cls, args, kwds, kwd_mark = (object(),),
357*699cd480SApple OSS Distributions        fasttypes = {valueint, int, str}, tuple=tuple, type=type, len=len):
358*699cd480SApple OSS Distributions
359*699cd480SApple OSS Distributions        """ Inspired from python3's cache implementation """
360*699cd480SApple OSS Distributions
361*699cd480SApple OSS Distributions        key = args
362*699cd480SApple OSS Distributions        if kwds:
363*699cd480SApple OSS Distributions            key += kwd_mark
364*699cd480SApple OSS Distributions            key += tuple(kwd.items())
365*699cd480SApple OSS Distributions        elif len(key) == 0:
366*699cd480SApple OSS Distributions            return None
367*699cd480SApple OSS Distributions        elif len(key) == 1 and type(key[0]) in fasttypes:
368*699cd480SApple OSS Distributions            return key[0]
369*699cd480SApple OSS Distributions        return cls(key)
370*699cd480SApple OSS Distributions
371*699cd480SApple OSS Distributions
372*699cd480SApple OSS Distributionsdef _cache_with_registry(fn, registry, maxsize=128, sentinel=object()):
373*699cd480SApple OSS Distributions    """ Internal function """
374*699cd480SApple OSS Distributions
375*699cd480SApple OSS Distributions    nokey = False
376*699cd480SApple OSS Distributions
377*699cd480SApple OSS Distributions    if hasattr(inspect, 'signature'): # PY3
378*699cd480SApple OSS Distributions        sig = inspect.signature(fn)
379*699cd480SApple OSS Distributions        tg  = sig.parameters.get('target')
380*699cd480SApple OSS Distributions        if not tg or tg.default is not None:
381*699cd480SApple OSS Distributions            raise ValueError("function doesn't have a 'target=None' argument")
382*699cd480SApple OSS Distributions
383*699cd480SApple OSS Distributions        nokey = len(sig.parameters) == 1
384*699cd480SApple OSS Distributions        cache = _Cache(fn.__qualname__, registry)
385*699cd480SApple OSS Distributions    else:
386*699cd480SApple OSS Distributions        spec = inspect.getargspec(fn)
387*699cd480SApple OSS Distributions        try:
388*699cd480SApple OSS Distributions            index = spec.args.index('target')
389*699cd480SApple OSS Distributions            offs  = len(spec.args) - len(spec.defaults)
390*699cd480SApple OSS Distributions            if index < offs or spec.defaults[index - offs] is not None:
391*699cd480SApple OSS Distributions                raise ValueError
392*699cd480SApple OSS Distributions        except:
393*699cd480SApple OSS Distributions            raise ValueError("function doesn't have a 'target=None' argument")
394*699cd480SApple OSS Distributions
395*699cd480SApple OSS Distributions        nokey = len(spec.args) == 1 and spec.varargs is None and spec.keywords is None
396*699cd480SApple OSS Distributions        cache = _Cache(fn.__name__, registry)
397*699cd480SApple OSS Distributions
398*699cd480SApple OSS Distributions    c_setdef = cache.setdefault
399*699cd480SApple OSS Distributions    c_get    = cache.get
400*699cd480SApple OSS Distributions    make_key = _HashedSeq.make_key
401*699cd480SApple OSS Distributions    getdynk  = LazyTarget._CacheGetDynamicKey
402*699cd480SApple OSS Distributions    gettg    = LazyTarget.GetTarget
403*699cd480SApple OSS Distributions
404*699cd480SApple OSS Distributions    if nokey:
405*699cd480SApple OSS Distributions        def caching_wrapper(*args, **kwds):
406*699cd480SApple OSS Distributions            global _implicit_exe_id, _implicit_target
407*699cd480SApple OSS Distributions
408*699cd480SApple OSS Distributions            key = _implicit_exe_id or getdynk().exe_id
409*699cd480SApple OSS Distributions            result = c_get(key, sentinel)
410*699cd480SApple OSS Distributions            if result is not sentinel:
411*699cd480SApple OSS Distributions                return result
412*699cd480SApple OSS Distributions
413*699cd480SApple OSS Distributions            kwds['target'] = _implicit_target or gettg()
414*699cd480SApple OSS Distributions            return c_setdef(key, fn(*args, **kwds))
415*699cd480SApple OSS Distributions
416*699cd480SApple OSS Distributions        def cached(*args, **kwds):
417*699cd480SApple OSS Distributions            global _implicit_exe_id
418*699cd480SApple OSS Distributions
419*699cd480SApple OSS Distributions            return c_get(_implicit_exe_id or getdynk().exe_id, sentinel) != sentinel
420*699cd480SApple OSS Distributions    else:
421*699cd480SApple OSS Distributions        def caching_wrapper(*args, **kwds):
422*699cd480SApple OSS Distributions            global _implicit_exe_id, _implicit_target
423*699cd480SApple OSS Distributions
424*699cd480SApple OSS Distributions            tg_d   = c_setdef(_implicit_exe_id or getdynk().exe_id, {})
425*699cd480SApple OSS Distributions            c_key  = make_key(args, kwds)
426*699cd480SApple OSS Distributions            result = tg_d.get(c_key, sentinel)
427*699cd480SApple OSS Distributions            if result is not sentinel:
428*699cd480SApple OSS Distributions                return result
429*699cd480SApple OSS Distributions
430*699cd480SApple OSS Distributions            #
431*699cd480SApple OSS Distributions            # Blunt policy to avoid exploding memory,
432*699cd480SApple OSS Distributions            # that is simpler than an actual LRU.
433*699cd480SApple OSS Distributions            #
434*699cd480SApple OSS Distributions            # TODO: be smarter?
435*699cd480SApple OSS Distributions            #
436*699cd480SApple OSS Distributions            if len(tg_d) >= maxsize: tg_d.clear()
437*699cd480SApple OSS Distributions
438*699cd480SApple OSS Distributions            kwds['target'] = _implicit_target or gettg()
439*699cd480SApple OSS Distributions            return tg_d.setdefault(c_key, fn(*args, **kwds))
440*699cd480SApple OSS Distributions
441*699cd480SApple OSS Distributions        def cached(*args, **kwds):
442*699cd480SApple OSS Distributions            global _implicit_exe_id
443*699cd480SApple OSS Distributions
444*699cd480SApple OSS Distributions            tg_d = c_get(_implicit_exe_id or getdynk().exe_id)
445*699cd480SApple OSS Distributions            return tg_d and tg_d.get(make_key(args, kwds), sentinel) != sentinel
446*699cd480SApple OSS Distributions
447*699cd480SApple OSS Distributions    caching_wrapper.cached = cached
448*699cd480SApple OSS Distributions    return functools.update_wrapper(caching_wrapper, fn)
449*699cd480SApple OSS Distributions
450*699cd480SApple OSS Distributions
451*699cd480SApple OSS Distributionsdef cache_statically(fn):
452*699cd480SApple OSS Distributions    """ Decorator to cache the results statically
453*699cd480SApple OSS Distributions
454*699cd480SApple OSS Distributions        This basically makes the decorated function cache its result based
455*699cd480SApple OSS Distributions        on its arguments with an automatic static per target cache
456*699cd480SApple OSS Distributions
457*699cd480SApple OSS Distributions        The function must have a named parameter called 'target' defaulting
458*699cd480SApple OSS Distributions        to None, with no clients ever passing it explicitly.  It will be
459*699cd480SApple OSS Distributions        passed the proper SBTarget when called.
460*699cd480SApple OSS Distributions
461*699cd480SApple OSS Distributions        @cache_statically(user_function)
462*699cd480SApple OSS Distributions            Cache the results of this function automatically per target,
463*699cd480SApple OSS Distributions            using the arguments of the function as the cache key.
464*699cd480SApple OSS Distributions    """
465*699cd480SApple OSS Distributions
466*699cd480SApple OSS Distributions    return _cache_with_registry(fn, _static_registry)
467*699cd480SApple OSS Distributions
468*699cd480SApple OSS Distributions
469*699cd480SApple OSS Distributionsdef cache_dynamically(fn):
470*699cd480SApple OSS Distributions    """ Decorator to cache the results dynamically
471*699cd480SApple OSS Distributions
472*699cd480SApple OSS Distributions        This basically makes the decorated function cache its result based
473*699cd480SApple OSS Distributions        on its arguments with an automatic dynamic cache that is reset
474*699cd480SApple OSS Distributions        every time the process state changes
475*699cd480SApple OSS Distributions
476*699cd480SApple OSS Distributions        The function must have a named parameter called 'target' defaulting
477*699cd480SApple OSS Distributions        to None, with no clients ever passing it explicitly.  It will be
478*699cd480SApple OSS Distributions        passed the proper SBTarget when called.
479*699cd480SApple OSS Distributions
480*699cd480SApple OSS Distributions        @cache_dynamically(user_function)
481*699cd480SApple OSS Distributions            Cache the results of this function automatically per target,
482*699cd480SApple OSS Distributions            using the arguments of the function as the cache key.
483*699cd480SApple OSS Distributions    """
484*699cd480SApple OSS Distributions
485*699cd480SApple OSS Distributions    return _cache_with_registry(fn, _dynamic_registry)
486*699cd480SApple OSS Distributions
487*699cd480SApple OSS Distributions
488*699cd480SApple OSS Distributionsdef dyn_cached_property(fn, sentinel=object()):
489*699cd480SApple OSS Distributions    """ Decorator to make a class or method property cached per instance
490*699cd480SApple OSS Distributions
491*699cd480SApple OSS Distributions        The method must have the prototype:
492*699cd480SApple OSS Distributions
493*699cd480SApple OSS Distributions            def foo(self, target=None)
494*699cd480SApple OSS Distributions
495*699cd480SApple OSS Distributions        and will generate the property "foo".
496*699cd480SApple OSS Distributions    """
497*699cd480SApple OSS Distributions
498*699cd480SApple OSS Distributions    if hasattr(inspect, 'signature'): # PY3
499*699cd480SApple OSS Distributions        if list(inspect.signature(fn).parameters) != ['self', 'target']:
500*699cd480SApple OSS Distributions            raise ValueError("function signature must be (self, target=None)")
501*699cd480SApple OSS Distributions    else:
502*699cd480SApple OSS Distributions        spec = inspect.getargspec(fn)
503*699cd480SApple OSS Distributions        if spec.args != ['self', 'target'] or \
504*699cd480SApple OSS Distributions                spec.varargs is not None or spec.keywords is not None:
505*699cd480SApple OSS Distributions            raise ValueError("function signature must be (self, target=None)")
506*699cd480SApple OSS Distributions
507*699cd480SApple OSS Distributions    getdynk  = LazyTarget._CacheGetDynamicKey
508*699cd480SApple OSS Distributions    gettg    = LazyTarget.GetTarget
509*699cd480SApple OSS Distributions    c_attr   = "_dyn_key__" + fn.__name__
510*699cd480SApple OSS Distributions
511*699cd480SApple OSS Distributions    def dyn_cached_property_wrapper(self, target=None):
512*699cd480SApple OSS Distributions        global _implicit_dynkey, _implicit_target
513*699cd480SApple OSS Distributions
514*699cd480SApple OSS Distributions        cache = getattr(self, c_attr, None)
515*699cd480SApple OSS Distributions        if cache is None:
516*699cd480SApple OSS Distributions            cache = weakref.WeakKeyDictionary()
517*699cd480SApple OSS Distributions            setattr(self, c_attr, cache)
518*699cd480SApple OSS Distributions
519*699cd480SApple OSS Distributions        c_key  = _implicit_dynkey or getdynk()
520*699cd480SApple OSS Distributions        result = cache.get(c_key, sentinel)
521*699cd480SApple OSS Distributions        if result is not sentinel:
522*699cd480SApple OSS Distributions            return result
523*699cd480SApple OSS Distributions
524*699cd480SApple OSS Distributions        return cache.setdefault(c_key, fn(self, _implicit_target or gettg()))
525*699cd480SApple OSS Distributions
526*699cd480SApple OSS Distributions    return property(functools.update_wrapper(dyn_cached_property_wrapper, fn))
527*699cd480SApple OSS Distributions
528*699cd480SApple OSS Distributions
529*699cd480SApple OSS DistributionsClearAllCache = LazyTarget._CacheClear
530*699cd480SApple OSS Distributions
531*699cd480SApple OSS DistributionsGetSizeOfCache = LazyTarget._CacheSize
532*699cd480SApple OSS Distributions
533*699cd480SApple OSS Distributions__all__ = [
534*699cd480SApple OSS Distributions    LazyTarget.__name__,
535*699cd480SApple OSS Distributions    ImplicitContext.__name__,
536*699cd480SApple OSS Distributions
537*699cd480SApple OSS Distributions    cache_statically.__name__,
538*699cd480SApple OSS Distributions    cache_dynamically.__name__,
539*699cd480SApple OSS Distributions    dyn_cached_property.__name__,
540*699cd480SApple OSS Distributions
541*699cd480SApple OSS Distributions    ClearAllCache.__name__,
542*699cd480SApple OSS Distributions    GetSizeOfCache.__name__,
543*699cd480SApple OSS Distributions]
544