xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/_threading_local.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""Thread-local objects.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard Worker(Note that this module provides a Python version of the threading.local
4*cda5da8dSAndroid Build Coastguard Worker class.  Depending on the version of Python you're using, there may be a
5*cda5da8dSAndroid Build Coastguard Worker faster one available.  You should always import the `local` class from
6*cda5da8dSAndroid Build Coastguard Worker `threading`.)
7*cda5da8dSAndroid Build Coastguard Worker
8*cda5da8dSAndroid Build Coastguard WorkerThread-local objects support the management of thread-local data.
9*cda5da8dSAndroid Build Coastguard WorkerIf you have data that you want to be local to a thread, simply create
10*cda5da8dSAndroid Build Coastguard Workera thread-local object and use its attributes:
11*cda5da8dSAndroid Build Coastguard Worker
12*cda5da8dSAndroid Build Coastguard Worker  >>> mydata = local()
13*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.number = 42
14*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.number
15*cda5da8dSAndroid Build Coastguard Worker  42
16*cda5da8dSAndroid Build Coastguard Worker
17*cda5da8dSAndroid Build Coastguard WorkerYou can also access the local-object's dictionary:
18*cda5da8dSAndroid Build Coastguard Worker
19*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.__dict__
20*cda5da8dSAndroid Build Coastguard Worker  {'number': 42}
21*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.__dict__.setdefault('widgets', [])
22*cda5da8dSAndroid Build Coastguard Worker  []
23*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.widgets
24*cda5da8dSAndroid Build Coastguard Worker  []
25*cda5da8dSAndroid Build Coastguard Worker
26*cda5da8dSAndroid Build Coastguard WorkerWhat's important about thread-local objects is that their data are
27*cda5da8dSAndroid Build Coastguard Workerlocal to a thread. If we access the data in a different thread:
28*cda5da8dSAndroid Build Coastguard Worker
29*cda5da8dSAndroid Build Coastguard Worker  >>> log = []
30*cda5da8dSAndroid Build Coastguard Worker  >>> def f():
31*cda5da8dSAndroid Build Coastguard Worker  ...     items = sorted(mydata.__dict__.items())
32*cda5da8dSAndroid Build Coastguard Worker  ...     log.append(items)
33*cda5da8dSAndroid Build Coastguard Worker  ...     mydata.number = 11
34*cda5da8dSAndroid Build Coastguard Worker  ...     log.append(mydata.number)
35*cda5da8dSAndroid Build Coastguard Worker
36*cda5da8dSAndroid Build Coastguard Worker  >>> import threading
37*cda5da8dSAndroid Build Coastguard Worker  >>> thread = threading.Thread(target=f)
38*cda5da8dSAndroid Build Coastguard Worker  >>> thread.start()
39*cda5da8dSAndroid Build Coastguard Worker  >>> thread.join()
40*cda5da8dSAndroid Build Coastguard Worker  >>> log
41*cda5da8dSAndroid Build Coastguard Worker  [[], 11]
42*cda5da8dSAndroid Build Coastguard Worker
43*cda5da8dSAndroid Build Coastguard Workerwe get different data.  Furthermore, changes made in the other thread
44*cda5da8dSAndroid Build Coastguard Workerdon't affect data seen in this thread:
45*cda5da8dSAndroid Build Coastguard Worker
46*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.number
47*cda5da8dSAndroid Build Coastguard Worker  42
48*cda5da8dSAndroid Build Coastguard Worker
49*cda5da8dSAndroid Build Coastguard WorkerOf course, values you get from a local object, including a __dict__
50*cda5da8dSAndroid Build Coastguard Workerattribute, are for whatever thread was current at the time the
51*cda5da8dSAndroid Build Coastguard Workerattribute was read.  For that reason, you generally don't want to save
52*cda5da8dSAndroid Build Coastguard Workerthese values across threads, as they apply only to the thread they
53*cda5da8dSAndroid Build Coastguard Workercame from.
54*cda5da8dSAndroid Build Coastguard Worker
55*cda5da8dSAndroid Build Coastguard WorkerYou can create custom local objects by subclassing the local class:
56*cda5da8dSAndroid Build Coastguard Worker
57*cda5da8dSAndroid Build Coastguard Worker  >>> class MyLocal(local):
58*cda5da8dSAndroid Build Coastguard Worker  ...     number = 2
59*cda5da8dSAndroid Build Coastguard Worker  ...     def __init__(self, /, **kw):
60*cda5da8dSAndroid Build Coastguard Worker  ...         self.__dict__.update(kw)
61*cda5da8dSAndroid Build Coastguard Worker  ...     def squared(self):
62*cda5da8dSAndroid Build Coastguard Worker  ...         return self.number ** 2
63*cda5da8dSAndroid Build Coastguard Worker
64*cda5da8dSAndroid Build Coastguard WorkerThis can be useful to support default values, methods and
65*cda5da8dSAndroid Build Coastguard Workerinitialization.  Note that if you define an __init__ method, it will be
66*cda5da8dSAndroid Build Coastguard Workercalled each time the local object is used in a separate thread.  This
67*cda5da8dSAndroid Build Coastguard Workeris necessary to initialize each thread's dictionary.
68*cda5da8dSAndroid Build Coastguard Worker
69*cda5da8dSAndroid Build Coastguard WorkerNow if we create a local object:
70*cda5da8dSAndroid Build Coastguard Worker
71*cda5da8dSAndroid Build Coastguard Worker  >>> mydata = MyLocal(color='red')
72*cda5da8dSAndroid Build Coastguard Worker
73*cda5da8dSAndroid Build Coastguard WorkerNow we have a default number:
74*cda5da8dSAndroid Build Coastguard Worker
75*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.number
76*cda5da8dSAndroid Build Coastguard Worker  2
77*cda5da8dSAndroid Build Coastguard Worker
78*cda5da8dSAndroid Build Coastguard Workeran initial color:
79*cda5da8dSAndroid Build Coastguard Worker
80*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.color
81*cda5da8dSAndroid Build Coastguard Worker  'red'
82*cda5da8dSAndroid Build Coastguard Worker  >>> del mydata.color
83*cda5da8dSAndroid Build Coastguard Worker
84*cda5da8dSAndroid Build Coastguard WorkerAnd a method that operates on the data:
85*cda5da8dSAndroid Build Coastguard Worker
86*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.squared()
87*cda5da8dSAndroid Build Coastguard Worker  4
88*cda5da8dSAndroid Build Coastguard Worker
89*cda5da8dSAndroid Build Coastguard WorkerAs before, we can access the data in a separate thread:
90*cda5da8dSAndroid Build Coastguard Worker
91*cda5da8dSAndroid Build Coastguard Worker  >>> log = []
92*cda5da8dSAndroid Build Coastguard Worker  >>> thread = threading.Thread(target=f)
93*cda5da8dSAndroid Build Coastguard Worker  >>> thread.start()
94*cda5da8dSAndroid Build Coastguard Worker  >>> thread.join()
95*cda5da8dSAndroid Build Coastguard Worker  >>> log
96*cda5da8dSAndroid Build Coastguard Worker  [[('color', 'red')], 11]
97*cda5da8dSAndroid Build Coastguard Worker
98*cda5da8dSAndroid Build Coastguard Workerwithout affecting this thread's data:
99*cda5da8dSAndroid Build Coastguard Worker
100*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.number
101*cda5da8dSAndroid Build Coastguard Worker  2
102*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.color
103*cda5da8dSAndroid Build Coastguard Worker  Traceback (most recent call last):
104*cda5da8dSAndroid Build Coastguard Worker  ...
105*cda5da8dSAndroid Build Coastguard Worker  AttributeError: 'MyLocal' object has no attribute 'color'
106*cda5da8dSAndroid Build Coastguard Worker
107*cda5da8dSAndroid Build Coastguard WorkerNote that subclasses can define slots, but they are not thread
108*cda5da8dSAndroid Build Coastguard Workerlocal. They are shared across threads:
109*cda5da8dSAndroid Build Coastguard Worker
110*cda5da8dSAndroid Build Coastguard Worker  >>> class MyLocal(local):
111*cda5da8dSAndroid Build Coastguard Worker  ...     __slots__ = 'number'
112*cda5da8dSAndroid Build Coastguard Worker
113*cda5da8dSAndroid Build Coastguard Worker  >>> mydata = MyLocal()
114*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.number = 42
115*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.color = 'red'
116*cda5da8dSAndroid Build Coastguard Worker
117*cda5da8dSAndroid Build Coastguard WorkerSo, the separate thread:
118*cda5da8dSAndroid Build Coastguard Worker
119*cda5da8dSAndroid Build Coastguard Worker  >>> thread = threading.Thread(target=f)
120*cda5da8dSAndroid Build Coastguard Worker  >>> thread.start()
121*cda5da8dSAndroid Build Coastguard Worker  >>> thread.join()
122*cda5da8dSAndroid Build Coastguard Worker
123*cda5da8dSAndroid Build Coastguard Workeraffects what we see:
124*cda5da8dSAndroid Build Coastguard Worker
125*cda5da8dSAndroid Build Coastguard Worker  >>> mydata.number
126*cda5da8dSAndroid Build Coastguard Worker  11
127*cda5da8dSAndroid Build Coastguard Worker
128*cda5da8dSAndroid Build Coastguard Worker>>> del mydata
129*cda5da8dSAndroid Build Coastguard Worker"""
130*cda5da8dSAndroid Build Coastguard Worker
131*cda5da8dSAndroid Build Coastguard Workerfrom weakref import ref
132*cda5da8dSAndroid Build Coastguard Workerfrom contextlib import contextmanager
133*cda5da8dSAndroid Build Coastguard Worker
134*cda5da8dSAndroid Build Coastguard Worker__all__ = ["local"]
135*cda5da8dSAndroid Build Coastguard Worker
136*cda5da8dSAndroid Build Coastguard Worker# We need to use objects from the threading module, but the threading
137*cda5da8dSAndroid Build Coastguard Worker# module may also want to use our `local` class, if support for locals
138*cda5da8dSAndroid Build Coastguard Worker# isn't compiled in to the `thread` module.  This creates potential problems
139*cda5da8dSAndroid Build Coastguard Worker# with circular imports.  For that reason, we don't import `threading`
140*cda5da8dSAndroid Build Coastguard Worker# until the bottom of this file (a hack sufficient to worm around the
141*cda5da8dSAndroid Build Coastguard Worker# potential problems).  Note that all platforms on CPython do have support
142*cda5da8dSAndroid Build Coastguard Worker# for locals in the `thread` module, and there is no circular import problem
143*cda5da8dSAndroid Build Coastguard Worker# then, so problems introduced by fiddling the order of imports here won't
144*cda5da8dSAndroid Build Coastguard Worker# manifest.
145*cda5da8dSAndroid Build Coastguard Worker
146*cda5da8dSAndroid Build Coastguard Workerclass _localimpl:
147*cda5da8dSAndroid Build Coastguard Worker    """A class managing thread-local dicts"""
148*cda5da8dSAndroid Build Coastguard Worker    __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
149*cda5da8dSAndroid Build Coastguard Worker
150*cda5da8dSAndroid Build Coastguard Worker    def __init__(self):
151*cda5da8dSAndroid Build Coastguard Worker        # The key used in the Thread objects' attribute dicts.
152*cda5da8dSAndroid Build Coastguard Worker        # We keep it a string for speed but make it unlikely to clash with
153*cda5da8dSAndroid Build Coastguard Worker        # a "real" attribute.
154*cda5da8dSAndroid Build Coastguard Worker        self.key = '_threading_local._localimpl.' + str(id(self))
155*cda5da8dSAndroid Build Coastguard Worker        # { id(Thread) -> (ref(Thread), thread-local dict) }
156*cda5da8dSAndroid Build Coastguard Worker        self.dicts = {}
157*cda5da8dSAndroid Build Coastguard Worker
158*cda5da8dSAndroid Build Coastguard Worker    def get_dict(self):
159*cda5da8dSAndroid Build Coastguard Worker        """Return the dict for the current thread. Raises KeyError if none
160*cda5da8dSAndroid Build Coastguard Worker        defined."""
161*cda5da8dSAndroid Build Coastguard Worker        thread = current_thread()
162*cda5da8dSAndroid Build Coastguard Worker        return self.dicts[id(thread)][1]
163*cda5da8dSAndroid Build Coastguard Worker
164*cda5da8dSAndroid Build Coastguard Worker    def create_dict(self):
165*cda5da8dSAndroid Build Coastguard Worker        """Create a new dict for the current thread, and return it."""
166*cda5da8dSAndroid Build Coastguard Worker        localdict = {}
167*cda5da8dSAndroid Build Coastguard Worker        key = self.key
168*cda5da8dSAndroid Build Coastguard Worker        thread = current_thread()
169*cda5da8dSAndroid Build Coastguard Worker        idt = id(thread)
170*cda5da8dSAndroid Build Coastguard Worker        def local_deleted(_, key=key):
171*cda5da8dSAndroid Build Coastguard Worker            # When the localimpl is deleted, remove the thread attribute.
172*cda5da8dSAndroid Build Coastguard Worker            thread = wrthread()
173*cda5da8dSAndroid Build Coastguard Worker            if thread is not None:
174*cda5da8dSAndroid Build Coastguard Worker                del thread.__dict__[key]
175*cda5da8dSAndroid Build Coastguard Worker        def thread_deleted(_, idt=idt):
176*cda5da8dSAndroid Build Coastguard Worker            # When the thread is deleted, remove the local dict.
177*cda5da8dSAndroid Build Coastguard Worker            # Note that this is suboptimal if the thread object gets
178*cda5da8dSAndroid Build Coastguard Worker            # caught in a reference loop. We would like to be called
179*cda5da8dSAndroid Build Coastguard Worker            # as soon as the OS-level thread ends instead.
180*cda5da8dSAndroid Build Coastguard Worker            local = wrlocal()
181*cda5da8dSAndroid Build Coastguard Worker            if local is not None:
182*cda5da8dSAndroid Build Coastguard Worker                dct = local.dicts.pop(idt)
183*cda5da8dSAndroid Build Coastguard Worker        wrlocal = ref(self, local_deleted)
184*cda5da8dSAndroid Build Coastguard Worker        wrthread = ref(thread, thread_deleted)
185*cda5da8dSAndroid Build Coastguard Worker        thread.__dict__[key] = wrlocal
186*cda5da8dSAndroid Build Coastguard Worker        self.dicts[idt] = wrthread, localdict
187*cda5da8dSAndroid Build Coastguard Worker        return localdict
188*cda5da8dSAndroid Build Coastguard Worker
189*cda5da8dSAndroid Build Coastguard Worker
190*cda5da8dSAndroid Build Coastguard Worker@contextmanager
191*cda5da8dSAndroid Build Coastguard Workerdef _patch(self):
192*cda5da8dSAndroid Build Coastguard Worker    impl = object.__getattribute__(self, '_local__impl')
193*cda5da8dSAndroid Build Coastguard Worker    try:
194*cda5da8dSAndroid Build Coastguard Worker        dct = impl.get_dict()
195*cda5da8dSAndroid Build Coastguard Worker    except KeyError:
196*cda5da8dSAndroid Build Coastguard Worker        dct = impl.create_dict()
197*cda5da8dSAndroid Build Coastguard Worker        args, kw = impl.localargs
198*cda5da8dSAndroid Build Coastguard Worker        self.__init__(*args, **kw)
199*cda5da8dSAndroid Build Coastguard Worker    with impl.locallock:
200*cda5da8dSAndroid Build Coastguard Worker        object.__setattr__(self, '__dict__', dct)
201*cda5da8dSAndroid Build Coastguard Worker        yield
202*cda5da8dSAndroid Build Coastguard Worker
203*cda5da8dSAndroid Build Coastguard Worker
204*cda5da8dSAndroid Build Coastguard Workerclass local:
205*cda5da8dSAndroid Build Coastguard Worker    __slots__ = '_local__impl', '__dict__'
206*cda5da8dSAndroid Build Coastguard Worker
207*cda5da8dSAndroid Build Coastguard Worker    def __new__(cls, /, *args, **kw):
208*cda5da8dSAndroid Build Coastguard Worker        if (args or kw) and (cls.__init__ is object.__init__):
209*cda5da8dSAndroid Build Coastguard Worker            raise TypeError("Initialization arguments are not supported")
210*cda5da8dSAndroid Build Coastguard Worker        self = object.__new__(cls)
211*cda5da8dSAndroid Build Coastguard Worker        impl = _localimpl()
212*cda5da8dSAndroid Build Coastguard Worker        impl.localargs = (args, kw)
213*cda5da8dSAndroid Build Coastguard Worker        impl.locallock = RLock()
214*cda5da8dSAndroid Build Coastguard Worker        object.__setattr__(self, '_local__impl', impl)
215*cda5da8dSAndroid Build Coastguard Worker        # We need to create the thread dict in anticipation of
216*cda5da8dSAndroid Build Coastguard Worker        # __init__ being called, to make sure we don't call it
217*cda5da8dSAndroid Build Coastguard Worker        # again ourselves.
218*cda5da8dSAndroid Build Coastguard Worker        impl.create_dict()
219*cda5da8dSAndroid Build Coastguard Worker        return self
220*cda5da8dSAndroid Build Coastguard Worker
221*cda5da8dSAndroid Build Coastguard Worker    def __getattribute__(self, name):
222*cda5da8dSAndroid Build Coastguard Worker        with _patch(self):
223*cda5da8dSAndroid Build Coastguard Worker            return object.__getattribute__(self, name)
224*cda5da8dSAndroid Build Coastguard Worker
225*cda5da8dSAndroid Build Coastguard Worker    def __setattr__(self, name, value):
226*cda5da8dSAndroid Build Coastguard Worker        if name == '__dict__':
227*cda5da8dSAndroid Build Coastguard Worker            raise AttributeError(
228*cda5da8dSAndroid Build Coastguard Worker                "%r object attribute '__dict__' is read-only"
229*cda5da8dSAndroid Build Coastguard Worker                % self.__class__.__name__)
230*cda5da8dSAndroid Build Coastguard Worker        with _patch(self):
231*cda5da8dSAndroid Build Coastguard Worker            return object.__setattr__(self, name, value)
232*cda5da8dSAndroid Build Coastguard Worker
233*cda5da8dSAndroid Build Coastguard Worker    def __delattr__(self, name):
234*cda5da8dSAndroid Build Coastguard Worker        if name == '__dict__':
235*cda5da8dSAndroid Build Coastguard Worker            raise AttributeError(
236*cda5da8dSAndroid Build Coastguard Worker                "%r object attribute '__dict__' is read-only"
237*cda5da8dSAndroid Build Coastguard Worker                % self.__class__.__name__)
238*cda5da8dSAndroid Build Coastguard Worker        with _patch(self):
239*cda5da8dSAndroid Build Coastguard Worker            return object.__delattr__(self, name)
240*cda5da8dSAndroid Build Coastguard Worker
241*cda5da8dSAndroid Build Coastguard Worker
242*cda5da8dSAndroid Build Coastguard Workerfrom threading import current_thread, RLock
243