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