51
51
})
52
52
53
53
54
+ class ReentrantCallError (RuntimeError ):
55
+ pass
56
+
57
+
54
58
class ResourceTracker (object ):
55
59
56
60
def __init__ (self ):
57
- self ._lock = threading .Lock ()
61
+ self ._lock = threading .RLock ()
58
62
self ._fd = None
59
63
self ._pid = None
60
64
65
+ def _reentrant_call_error (self ):
66
+ # gh-109629: this happens if an explicit call to the ResourceTracker
67
+ # gets interrupted by a garbage collection, invoking a finalizer (*)
68
+ # that itself calls back into ResourceTracker.
69
+ # (*) for example the SemLock finalizer
70
+ raise ReentrantCallError (
71
+ "Reentrant call into the multiprocessing resource tracker" )
72
+
61
73
def _stop (self ):
62
74
with self ._lock :
75
+ # This should not happen (_stop() isn't called by a finalizer)
76
+ # but we check for it anyway.
77
+ if self ._lock ._recursion_count () > 1 :
78
+ return self ._reentrant_call_error ()
63
79
if self ._fd is None :
64
80
# not running
65
81
return
@@ -81,6 +97,9 @@ def ensure_running(self):
81
97
This can be run from any process. Usually a child process will use
82
98
the resource created by its parent.'''
83
99
with self ._lock :
100
+ if self ._lock ._recursion_count () > 1 :
101
+ # The code below is certainly not reentrant-safe, so bail out
102
+ return self ._reentrant_call_error ()
84
103
if self ._fd is not None :
85
104
# resource tracker was launched before, is it still running?
86
105
if self ._check_alive ():
@@ -159,7 +178,17 @@ def unregister(self, name, rtype):
159
178
self ._send ('UNREGISTER' , name , rtype )
160
179
161
180
def _send (self , cmd , name , rtype ):
162
- self .ensure_running ()
181
+ try :
182
+ self .ensure_running ()
183
+ except ReentrantCallError :
184
+ # The code below might or might not work, depending on whether
185
+ # the resource tracker was already running and still alive.
186
+ # Better warn the user.
187
+ # (XXX is warnings.warn itself reentrant-safe? :-)
188
+ warnings .warn (
189
+ f"ResourceTracker called reentrantly for resource cleanup, "
190
+ f"which is unsupported. "
191
+ f"The { rtype } object { name !r} might leak." )
163
192
msg = '{0}:{1}:{2}\n ' .format (cmd , name , rtype ).encode ('ascii' )
164
193
if len (msg ) > 512 :
165
194
# posix guarantees that writes to a pipe of less than PIPE_BUF
@@ -176,6 +205,7 @@ def _send(self, cmd, name, rtype):
176
205
unregister = _resource_tracker .unregister
177
206
getfd = _resource_tracker .getfd
178
207
208
+
179
209
def main (fd ):
180
210
'''Run resource tracker.'''
181
211
# protect the process from ^C and "killall python" etc
0 commit comments