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