From: luccabb <32229669+luccabb@users.noreply.github.com> Date: Fri, 21 Mar 2025 10:15:35 +0000 (-0700) Subject: [3.12] gh-88887: Cleanup `multiprocessing.resource_tracker.ResourceTracker` upon... X-Git-Tag: v3.12.10~70 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3d1f8cae48566fba109abd071e26cbcceaf5fce3;p=thirdparty%2FPython%2Fcpython.git [3.12] gh-88887: Cleanup `multiprocessing.resource_tracker.ResourceTracker` upon deletion (GH-130429) (#131530) Co-authored-by: Victor Stinner Co-authored-by: Gregory P. Smith (cherry picked from commit f53e7de6a84a0f535efb75c3671283b801a1af0f) --- diff --git a/Lib/multiprocessing/resource_tracker.py b/Lib/multiprocessing/resource_tracker.py index 23fea295c357..e4da12930334 100644 --- a/Lib/multiprocessing/resource_tracker.py +++ b/Lib/multiprocessing/resource_tracker.py @@ -70,22 +70,46 @@ class ResourceTracker(object): 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 - - # closing the "alive" file descriptor stops main() - os.close(self._fd) - self._fd = None + def __del__(self): + # making sure child processess are cleaned before ResourceTracker + # gets destructed. + # see https://github.com/python/cpython/issues/88887 + self._stop(use_blocking_lock=False) + + def _stop(self, use_blocking_lock=True): + if use_blocking_lock: + with self._lock: + self._stop_locked() + else: + acquired = self._lock.acquire(blocking=False) + try: + self._stop_locked() + finally: + if acquired: + self._lock.release() + + def _stop_locked( + self, + close=os.close, + waitpid=os.waitpid, + waitstatus_to_exitcode=os.waitstatus_to_exitcode, + ): + # This shouldn't happen (it might when called by a finalizer) + # so we check for it anyway. + if self._lock._recursion_count() > 1: + return self._reentrant_call_error() + if self._fd is None: + # not running + return + if self._pid is None: + return + + # closing the "alive" file descriptor stops main() + close(self._fd) + self._fd = None - os.waitpid(self._pid, 0) - self._pid = None + waitpid(self._pid, 0) + self._pid = None def getfd(self): self.ensure_running() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-02-21-14-47-46.gh-issue-88887.V3U0CV.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-21-14-47-46.gh-issue-88887.V3U0CV.rst new file mode 100644 index 000000000000..1a6c9483e2a3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-02-21-14-47-46.gh-issue-88887.V3U0CV.rst @@ -0,0 +1 @@ +Fixing multiprocessing Resource Tracker process leaking, usually observed when running Python as PID 1.