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