Skip to content

Commit bf68b59

Browse files
committed
dynassign: make update() atomic
Fixes the worst-case scenario mentioned in the original resolution of #10.
1 parent 67a392d commit bf68b59

File tree

1 file changed

+15
-5
lines changed

1 file changed

+15
-5
lines changed

unpythonic/dynassign.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
_L = threading.local()
1414

1515
_mainthread_stack = []
16+
_mainthread_lock = threading.RLock()
1617
def _getstack():
1718
if threading.current_thread() is threading.main_thread():
1819
return _mainthread_stack
@@ -22,7 +23,8 @@ def _getstack():
2223
#
2324
# TODO: preferable to use the parent thread's current stack, but difficult to get.
2425
# Could monkey-patch threading.Thread.__init__ to record this information in self...
25-
_L._stack = _mainthread_stack.copy()
26+
with _mainthread_lock:
27+
_L._stack = _mainthread_stack.copy()
2628
return _L._stack
2729

2830
def _getobservers():
@@ -168,10 +170,18 @@ def update(self, **bindings):
168170
caution applies. Use carefully, if at all.
169171
"""
170172
# validate, and resolve scopes (let AttributeError propagate)
171-
scopes = {k: self._resolve(k) for k in bindings}
172-
for k, v in bindings.items():
173-
scope = scopes[k]
174-
scope[k] = v
173+
def doit():
174+
scopes = {k: self._resolve(k) for k in bindings}
175+
for k, v in bindings.items():
176+
scope = scopes[k]
177+
scope[k] = v
178+
# If we're the main thread, any new threads spawned will copy our scope stack
179+
# in whatever state it happens to be in. Make the update atomic.
180+
if threading.current_thread() is threading.main_thread():
181+
with _mainthread_lock:
182+
doit()
183+
else:
184+
doit()
175185

176186
# membership test (in, not in)
177187
def __contains__(self, name):

0 commit comments

Comments
 (0)