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