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