mirror of
https://github.com/ARM-software/devlib.git
synced 2025-01-31 02:00:45 +00:00
utils/misc: Add tls_property()
Similar to a regular property(), with the following differences: * Values are memoized and are threadlocal * The value returned by the property needs to be called (like a weakref) to get the actual value. This level of indirection is needed to allow methods to be implemented in the proxy object without clashing with the value's methods. * If the above is too annoying, a "sub property" can be created with the regular property() behavior (and therefore without the additional methods) using tls_property.basic_property .
This commit is contained in:
parent
98e2e51d09
commit
922686a348
@ -23,6 +23,7 @@ from contextlib import contextmanager
|
||||
from functools import partial, reduce
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
from weakref import WeakKeyDictionary, WeakSet
|
||||
|
||||
import ctypes
|
||||
import functools
|
||||
@ -705,3 +706,120 @@ def batch_contextmanager(f, kwargs_list):
|
||||
for kwargs in kwargs_list:
|
||||
stack.enter_context(f(**kwargs))
|
||||
yield
|
||||
|
||||
class tls_property:
|
||||
"""
|
||||
Use it like `property` decorator, but the result will be memoized per
|
||||
thread. When the owning thread dies, the values for that thread will be
|
||||
destroyed.
|
||||
|
||||
In order to get the values, it's necessary to call the object
|
||||
given by the property. This is necessary in order to be able to add methods
|
||||
to that object, like :meth:`_BoundTLSProperty.get_all_values`.
|
||||
|
||||
Values can be set and deleted as well, which will be a thread-local set.
|
||||
"""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.factory.__name__
|
||||
|
||||
def __init__(self, factory):
|
||||
self.factory = factory
|
||||
# Lock accesses to shared WeakKeyDictionary and WeakSet
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def __get__(self, instance, owner=None):
|
||||
return _BoundTLSProperty(self, instance, owner)
|
||||
|
||||
def _get_value(self, instance, owner):
|
||||
tls, values = self._get_tls(instance)
|
||||
try:
|
||||
return tls.value
|
||||
except AttributeError:
|
||||
# Bind the method to `instance`
|
||||
f = self.factory.__get__(instance, owner)
|
||||
obj = f()
|
||||
tls.value = obj
|
||||
# Since that's a WeakSet, values will be removed automatically once
|
||||
# the threading.local variable that holds them is destroyed
|
||||
with self.lock:
|
||||
values.add(obj)
|
||||
return obj
|
||||
|
||||
def _get_all_values(self, instance, owner):
|
||||
with self.lock:
|
||||
# Grab a reference to all the objects at the time of the call by
|
||||
# using a regular set
|
||||
tls, values = self._get_tls(instance=instance)
|
||||
return set(values)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
tls, values = self._get_tls(instance)
|
||||
tls.value = value
|
||||
with self.lock:
|
||||
values.add(value)
|
||||
|
||||
def __delete__(self, instance):
|
||||
tls, values = self._get_tls(instance)
|
||||
with self.lock:
|
||||
values.discard(tls.value)
|
||||
del tls.value
|
||||
|
||||
def _get_tls(self, instance):
|
||||
dct = instance.__dict__
|
||||
name = self.name
|
||||
try:
|
||||
# Using instance.__dict__[self.name] is safe as
|
||||
# getattr(instance, name) will return the property instead, as
|
||||
# the property is a descriptor
|
||||
tls = dct[name]
|
||||
except KeyError:
|
||||
with self.lock:
|
||||
# Double check after taking the lock to avoid a race
|
||||
if name not in dct:
|
||||
tls = (threading.local(), WeakSet())
|
||||
dct[name] = tls
|
||||
|
||||
return tls
|
||||
|
||||
@property
|
||||
def basic_property(self):
|
||||
"""
|
||||
Return a basic property that can be used to access the TLS value
|
||||
without having to call it first.
|
||||
|
||||
The drawback is that it's not possible to do anything over than
|
||||
getting/setting/deleting.
|
||||
"""
|
||||
def getter(instance, owner=None):
|
||||
prop = self.__get__(instance, owner)
|
||||
return prop()
|
||||
|
||||
return property(getter, self.__set__, self.__delete__)
|
||||
|
||||
class _BoundTLSProperty:
|
||||
"""
|
||||
Simple proxy object to allow either calling it to get the TLS value, or get
|
||||
some other informations by calling methods.
|
||||
"""
|
||||
def __init__(self, tls_property, instance, owner):
|
||||
self.tls_property = tls_property
|
||||
self.instance = instance
|
||||
self.owner = owner
|
||||
|
||||
def __call__(self):
|
||||
return self.tls_property._get_value(
|
||||
instance=self.instance,
|
||||
owner=self.owner,
|
||||
)
|
||||
|
||||
def get_all_values(self):
|
||||
"""
|
||||
Returns all the thread-local values currently in use in the process for
|
||||
that property for that instance.
|
||||
"""
|
||||
return self.tls_property._get_all_values(
|
||||
instance=self.instance,
|
||||
owner=self.owner,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user