Mercurial > traipse_dev
comparison plugins/cherrypy/_cpthreadinglocal.py @ 0:4385a7d0efd1 grumpy-goblin
Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author | sirebral |
---|---|
date | Tue, 14 Jul 2009 16:41:58 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4385a7d0efd1 |
---|---|
1 # This is a backport of Python-2.4's threading.local() implementation | |
2 | |
3 """Thread-local objects | |
4 | |
5 (Note that this module provides a Python version of thread | |
6 threading.local class. Depending on the version of Python you're | |
7 using, there may be a faster one available. You should always import | |
8 the local class from threading.) | |
9 | |
10 Thread-local objects support the management of thread-local data. | |
11 If you have data that you want to be local to a thread, simply create | |
12 a thread-local object and use its attributes: | |
13 | |
14 >>> mydata = local() | |
15 >>> mydata.number = 42 | |
16 >>> mydata.number | |
17 42 | |
18 | |
19 You can also access the local-object's dictionary: | |
20 | |
21 >>> mydata.__dict__ | |
22 {'number': 42} | |
23 >>> mydata.__dict__.setdefault('widgets', []) | |
24 [] | |
25 >>> mydata.widgets | |
26 [] | |
27 | |
28 What's important about thread-local objects is that their data are | |
29 local to a thread. If we access the data in a different thread: | |
30 | |
31 >>> log = [] | |
32 >>> def f(): | |
33 ... items = mydata.__dict__.items() | |
34 ... items.sort() | |
35 ... log.append(items) | |
36 ... mydata.number = 11 | |
37 ... log.append(mydata.number) | |
38 | |
39 >>> import threading | |
40 >>> thread = threading.Thread(target=f) | |
41 >>> thread.start() | |
42 >>> thread.join() | |
43 >>> log | |
44 [[], 11] | |
45 | |
46 we get different data. Furthermore, changes made in the other thread | |
47 don't affect data seen in this thread: | |
48 | |
49 >>> mydata.number | |
50 42 | |
51 | |
52 Of course, values you get from a local object, including a __dict__ | |
53 attribute, are for whatever thread was current at the time the | |
54 attribute was read. For that reason, you generally don't want to save | |
55 these values across threads, as they apply only to the thread they | |
56 came from. | |
57 | |
58 You can create custom local objects by subclassing the local class: | |
59 | |
60 >>> class MyLocal(local): | |
61 ... number = 2 | |
62 ... initialized = False | |
63 ... def __init__(self, **kw): | |
64 ... if self.initialized: | |
65 ... raise SystemError('__init__ called too many times') | |
66 ... self.initialized = True | |
67 ... self.__dict__.update(kw) | |
68 ... def squared(self): | |
69 ... return self.number ** 2 | |
70 | |
71 This can be useful to support default values, methods and | |
72 initialization. Note that if you define an __init__ method, it will be | |
73 called each time the local object is used in a separate thread. This | |
74 is necessary to initialize each thread's dictionary. | |
75 | |
76 Now if we create a local object: | |
77 | |
78 >>> mydata = MyLocal(color='red') | |
79 | |
80 Now we have a default number: | |
81 | |
82 >>> mydata.number | |
83 2 | |
84 | |
85 an initial color: | |
86 | |
87 >>> mydata.color | |
88 'red' | |
89 >>> del mydata.color | |
90 | |
91 And a method that operates on the data: | |
92 | |
93 >>> mydata.squared() | |
94 4 | |
95 | |
96 As before, we can access the data in a separate thread: | |
97 | |
98 >>> log = [] | |
99 >>> thread = threading.Thread(target=f) | |
100 >>> thread.start() | |
101 >>> thread.join() | |
102 >>> log | |
103 [[('color', 'red'), ('initialized', True)], 11] | |
104 | |
105 without affecting this thread's data: | |
106 | |
107 >>> mydata.number | |
108 2 | |
109 >>> mydata.color | |
110 Traceback (most recent call last): | |
111 ... | |
112 AttributeError: 'MyLocal' object has no attribute 'color' | |
113 | |
114 Note that subclasses can define slots, but they are not thread | |
115 local. They are shared across threads: | |
116 | |
117 >>> class MyLocal(local): | |
118 ... __slots__ = 'number' | |
119 | |
120 >>> mydata = MyLocal() | |
121 >>> mydata.number = 42 | |
122 >>> mydata.color = 'red' | |
123 | |
124 So, the separate thread: | |
125 | |
126 >>> thread = threading.Thread(target=f) | |
127 >>> thread.start() | |
128 >>> thread.join() | |
129 | |
130 affects what we see: | |
131 | |
132 >>> mydata.number | |
133 11 | |
134 | |
135 >>> del mydata | |
136 """ | |
137 | |
138 # Threading import is at end | |
139 | |
140 class _localbase(object): | |
141 __slots__ = '_local__key', '_local__args', '_local__lock' | |
142 | |
143 def __new__(cls, *args, **kw): | |
144 self = object.__new__(cls) | |
145 key = '_local__key', 'thread.local.' + str(id(self)) | |
146 object.__setattr__(self, '_local__key', key) | |
147 object.__setattr__(self, '_local__args', (args, kw)) | |
148 object.__setattr__(self, '_local__lock', RLock()) | |
149 | |
150 if args or kw and (cls.__init__ is object.__init__): | |
151 raise TypeError("Initialization arguments are not supported") | |
152 | |
153 # We need to create the thread dict in anticipation of | |
154 # __init__ being called, to make sire we don't cal it | |
155 # again ourselves. | |
156 dict = object.__getattribute__(self, '__dict__') | |
157 currentThread().__dict__[key] = dict | |
158 | |
159 return self | |
160 | |
161 def _patch(self): | |
162 key = object.__getattribute__(self, '_local__key') | |
163 d = currentThread().__dict__.get(key) | |
164 if d is None: | |
165 d = {} | |
166 currentThread().__dict__[key] = d | |
167 object.__setattr__(self, '__dict__', d) | |
168 | |
169 # we have a new instance dict, so call out __init__ if we have | |
170 # one | |
171 cls = type(self) | |
172 if cls.__init__ is not object.__init__: | |
173 args, kw = object.__getattribute__(self, '_local__args') | |
174 cls.__init__(self, *args, **kw) | |
175 else: | |
176 object.__setattr__(self, '__dict__', d) | |
177 | |
178 class local(_localbase): | |
179 | |
180 def __getattribute__(self, name): | |
181 lock = object.__getattribute__(self, '_local__lock') | |
182 lock.acquire() | |
183 try: | |
184 _patch(self) | |
185 return object.__getattribute__(self, name) | |
186 finally: | |
187 lock.release() | |
188 | |
189 def __setattr__(self, name, value): | |
190 lock = object.__getattribute__(self, '_local__lock') | |
191 lock.acquire() | |
192 try: | |
193 _patch(self) | |
194 return object.__setattr__(self, name, value) | |
195 finally: | |
196 lock.release() | |
197 | |
198 def __delattr__(self, name): | |
199 lock = object.__getattribute__(self, '_local__lock') | |
200 lock.acquire() | |
201 try: | |
202 _patch(self) | |
203 return object.__delattr__(self, name) | |
204 finally: | |
205 lock.release() | |
206 | |
207 | |
208 def __del__(): | |
209 threading_enumerate = enumerate | |
210 __getattribute__ = object.__getattribute__ | |
211 | |
212 def __del__(self): | |
213 key = __getattribute__(self, '_local__key') | |
214 | |
215 try: | |
216 threads = list(threading_enumerate()) | |
217 except: | |
218 # if enumerate fails, as it seems to do during | |
219 # shutdown, we'll skip cleanup under the assumption | |
220 # that there is nothing to clean up | |
221 return | |
222 | |
223 for thread in threads: | |
224 try: | |
225 __dict__ = thread.__dict__ | |
226 except AttributeError: | |
227 # Thread is dying, rest in peace | |
228 continue | |
229 | |
230 if key in __dict__: | |
231 try: | |
232 del __dict__[key] | |
233 except KeyError: | |
234 pass # didn't have anything in this thread | |
235 | |
236 return __del__ | |
237 __del__ = __del__() | |
238 | |
239 from threading import currentThread, enumerate, RLock |