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