Skip to content

Commit 8471da5

Browse files
committed
Add comments, add informative warning
1 parent f16a447 commit 8471da5

File tree

1 file changed

+29
-3
lines changed

1 file changed

+29
-3
lines changed

Lib/multiprocessing/resource_tracker.py

+29-3
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,31 @@
5151
})
5252

5353

54+
class ReentrantCallError(RuntimeError):
55+
pass
56+
57+
5458
class ResourceTracker(object):
5559

5660
def __init__(self):
5761
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.
6377
if self._lock._recursion_count() > 1:
64-
return
78+
return self._reentrant_call_error()
6579
if self._fd is None:
6680
# not running
6781
return
@@ -84,7 +98,8 @@ def ensure_running(self):
8498
the resource created by its parent.'''
8599
with self._lock:
86100
if self._lock._recursion_count() > 1:
87-
return
101+
# The code below is certainly not reentrant-safe, so bail out
102+
return self._reentrant_call_error()
88103
if self._fd is not None:
89104
# resource tracker was launched before, is it still running?
90105
if self._check_alive():
@@ -163,7 +178,17 @@ def unregister(self, name, rtype):
163178
self._send('UNREGISTER', name, rtype)
164179

165180
def _send(self, cmd, name, rtype):
166-
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.")
167192
msg = '{0}:{1}:{2}\n'.format(cmd, name, rtype).encode('ascii')
168193
if len(msg) > 512:
169194
# posix guarantees that writes to a pipe of less than PIPE_BUF
@@ -180,6 +205,7 @@ def _send(self, cmd, name, rtype):
180205
unregister = _resource_tracker.unregister
181206
getfd = _resource_tracker.getfd
182207

208+
183209
def main(fd):
184210
'''Run resource tracker.'''
185211
# protect the process from ^C and "killall python" etc

0 commit comments

Comments
 (0)