Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions Lib/multiprocessing/popen_fork.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import atexit
import os
import signal
import threading

from . import util

Expand All @@ -13,6 +14,13 @@
class Popen(object):
method = 'fork'

# Lock to protect os.waitpid() calls from concurrent threads.
# Without this lock, a thread can reap a child process that
# another thread is also trying to wait on, causing the second
# thread's waitpid() to raise OSError(ECHILD) and leaving
# the Process.exitcode stuck at None. See gh-148318.
_waitpid_lock = threading.Lock()

def __init__(self, process_obj):
util._flush_std_streams()
self.returncode = None
Expand All @@ -24,15 +32,20 @@ def duplicate_for_child(self, fd):

def poll(self, flag=os.WNOHANG):
if self.returncode is None:
try:
pid, sts = os.waitpid(self.pid, flag)
except OSError:
# Child process not yet created. See #1731717
# e.errno == errno.ECHILD == 10
return None
if pid == self.pid:
self.returncode = os.waitstatus_to_exitcode(sts)
return self.returncode
with self._waitpid_lock:
# Another thread may have set returncode while we
# waited for the lock.
if self.returncode is not None:
return self.returncode
try:
pid, sts = os.waitpid(self.pid, flag)
except OSError:
# Child process not yet created. See #1731717
# e.errno == errno.ECHILD == 10
return None
if pid == self.pid:
self.returncode = os.waitstatus_to_exitcode(sts)
return self.returncode

def wait(self, timeout=None):
if self.returncode is None:
Expand Down