5151 })
5252
5353
54+ class ReentrantCallError (RuntimeError ):
55+ pass
56+
57+
5458class ResourceTracker (object ):
5559
5660 def __init__ (self ):
57- self ._lock = threading .Lock ()
61+ self ._lock = threading .RLock ()
5862 self ._fd = None
5963 self ._pid = None
6064
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+
6173 def _stop (self ):
6274 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 ()
6379 if self ._fd is None :
6480 # not running
6581 return
@@ -81,6 +97,9 @@ def ensure_running(self):
8197 This can be run from any process. Usually a child process will use
8298 the resource created by its parent.'''
8399 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 ()
84103 if self ._fd is not None :
85104 # resource tracker was launched before, is it still running?
86105 if self ._check_alive ():
@@ -159,7 +178,17 @@ def unregister(self, name, rtype):
159178 self ._send ('UNREGISTER' , name , rtype )
160179
161180 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." )
163192 msg = '{0}:{1}:{2}\n ' .format (cmd , name , rtype ).encode ('ascii' )
164193 if len (msg ) > 512 :
165194 # posix guarantees that writes to a pipe of less than PIPE_BUF
@@ -176,6 +205,7 @@ def _send(self, cmd, name, rtype):
176205unregister = _resource_tracker .unregister
177206getfd = _resource_tracker .getfd
178207
208+
179209def main (fd ):
180210 '''Run resource tracker.'''
181211 # protect the process from ^C and "killall python" etc
0 commit comments