diff options
author | elfstrom <elfstrom@users.noreply.github.com> | 2023-10-02 15:24:19 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-02 15:24:19 (GMT) |
commit | c2cadb0ec2d192c78d9e01c3c0c1cae12ea57392 (patch) | |
tree | 26ef8d8315d7d24bd62705158a7c8da5b8bbfe6f /Lib/concurrent/futures | |
parent | 5e6e99646e46d485e018429f6000661609e4f1b5 (diff) | |
download | cpython-c2cadb0ec2d192c78d9e01c3c0c1cae12ea57392.zip cpython-c2cadb0ec2d192c78d9e01c3c0c1cae12ea57392.tar.gz cpython-c2cadb0ec2d192c78d9e01c3c0c1cae12ea57392.tar.bz2 |
[3.12] gh-105829: Fix concurrent.futures.ProcessPoolExecutor deadlock (GH-108513) (#109784)
This fixes issue GH-105829, https://github.com/python/cpython/issues/105829
(cherry picked from commit 405b06375a8a4cdb08ff53afade09a8b66ec23d5)
Diffstat (limited to 'Lib/concurrent/futures')
-rw-r--r-- | Lib/concurrent/futures/process.py | 18 |
1 files changed, 15 insertions, 3 deletions
diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index a8dab13..33df65a 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -71,6 +71,11 @@ class _ThreadWakeup: self._reader, self._writer = mp.Pipe(duplex=False) def close(self): + # Please note that we do not take the shutdown lock when + # calling clear() (to avoid deadlocking) so this method can + # only be called safely from the same thread as all calls to + # clear() even if you hold the shutdown lock. Otherwise we + # might try to read from the closed pipe. if not self._closed: self._closed = True self._writer.close() @@ -426,8 +431,12 @@ class _ExecutorManagerThread(threading.Thread): elif wakeup_reader in ready: is_broken = False - with self.shutdown_lock: - self.thread_wakeup.clear() + # No need to hold the _shutdown_lock here because: + # 1. we're the only thread to use the wakeup reader + # 2. we're also the only thread to call thread_wakeup.close() + # 3. we want to avoid a possible deadlock when both reader and writer + # would block (gh-105829) + self.thread_wakeup.clear() return result_item, is_broken, cause @@ -710,7 +719,10 @@ class ProcessPoolExecutor(_base.Executor): # as it could result in a deadlock if a worker process dies with the # _result_queue write lock still acquired. # - # _shutdown_lock must be locked to access _ThreadWakeup. + # _shutdown_lock must be locked to access _ThreadWakeup.close() and + # .wakeup(). Care must also be taken to not call clear or close from + # more than one thread since _ThreadWakeup.clear() is not protected by + # the _shutdown_lock self._executor_manager_thread_wakeup = _ThreadWakeup() # Create communication channels for the executor |