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