summaryrefslogtreecommitdiffstats
path: root/Lib/multiprocessing
diff options
context:
space:
mode:
authorAntoine Pitrou <antoine@python.org>2023-09-26 11:57:25 (GMT)
committerGitHub <noreply@github.com>2023-09-26 11:57:25 (GMT)
commit0eb98837b60bc58e57ad3e2b35c6b0e9ab634678 (patch)
tree215ecf809bc7a23493ffd1419db302515c05d377 /Lib/multiprocessing
parent2897142d2ec0930a8991af964c798b68fb6dcadd (diff)
downloadcpython-0eb98837b60bc58e57ad3e2b35c6b0e9ab634678.zip
cpython-0eb98837b60bc58e57ad3e2b35c6b0e9ab634678.tar.gz
cpython-0eb98837b60bc58e57ad3e2b35c6b0e9ab634678.tar.bz2
gh-109593: Fix reentrancy issue in multiprocessing resource_tracker (#109629)
--------- Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Diffstat (limited to 'Lib/multiprocessing')
-rw-r--r--Lib/multiprocessing/resource_tracker.py34
1 files changed, 32 insertions, 2 deletions
diff --git a/Lib/multiprocessing/resource_tracker.py b/Lib/multiprocessing/resource_tracker.py
index 3783c1f..8e41f46 100644
--- a/Lib/multiprocessing/resource_tracker.py
+++ b/Lib/multiprocessing/resource_tracker.py
@@ -51,15 +51,31 @@ if os.name == 'posix':
})
+class ReentrantCallError(RuntimeError):
+ pass
+
+
class ResourceTracker(object):
def __init__(self):
- self._lock = threading.Lock()
+ self._lock = threading.RLock()
self._fd = None
self._pid = None
+ def _reentrant_call_error(self):
+ # gh-109629: this happens if an explicit call to the ResourceTracker
+ # gets interrupted by a garbage collection, invoking a finalizer (*)
+ # that itself calls back into ResourceTracker.
+ # (*) for example the SemLock finalizer
+ raise ReentrantCallError(
+ "Reentrant call into the multiprocessing resource tracker")
+
def _stop(self):
with self._lock:
+ # This should not happen (_stop() isn't called by a finalizer)
+ # but we check for it anyway.
+ if self._lock._recursion_count() > 1:
+ return self._reentrant_call_error()
if self._fd is None:
# not running
return
@@ -81,6 +97,9 @@ class ResourceTracker(object):
This can be run from any process. Usually a child process will use
the resource created by its parent.'''
with self._lock:
+ if self._lock._recursion_count() > 1:
+ # The code below is certainly not reentrant-safe, so bail out
+ return self._reentrant_call_error()
if self._fd is not None:
# resource tracker was launched before, is it still running?
if self._check_alive():
@@ -159,7 +178,17 @@ class ResourceTracker(object):
self._send('UNREGISTER', name, rtype)
def _send(self, cmd, name, rtype):
- self.ensure_running()
+ try:
+ self.ensure_running()
+ except ReentrantCallError:
+ # The code below might or might not work, depending on whether
+ # the resource tracker was already running and still alive.
+ # Better warn the user.
+ # (XXX is warnings.warn itself reentrant-safe? :-)
+ warnings.warn(
+ f"ResourceTracker called reentrantly for resource cleanup, "
+ f"which is unsupported. "
+ f"The {rtype} object {name!r} might leak.")
msg = '{0}:{1}:{2}\n'.format(cmd, name, rtype).encode('ascii')
if len(msg) > 512:
# posix guarantees that writes to a pipe of less than PIPE_BUF
@@ -176,6 +205,7 @@ register = _resource_tracker.register
unregister = _resource_tracker.unregister
getfd = _resource_tracker.getfd
+
def main(fd):
'''Run resource tracker.'''
# protect the process from ^C and "killall python" etc