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