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