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