mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
[3.11] gh-109593: Fix reentrancy issue in multiprocessing resource_tracker (GH-109629) (#109897)
gh-109593: Fix reentrancy issue in multiprocessing resource_tracker (GH-109629)
---------
(cherry picked from commit 0eb98837b6
)
Co-authored-by: Antoine Pitrou <antoine@python.org>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
This commit is contained in:
parent
bda6949e86
commit
f764abb375
7 changed files with 95 additions and 2 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue