diff options
author | Gregory P. Smith <greg@krypto.org> | 2014-04-23 07:38:22 (GMT) |
---|---|---|
committer | Gregory P. Smith <greg@krypto.org> | 2014-04-23 07:38:22 (GMT) |
commit | b2188630125e5b5815aa60181fdd93f036a89522 (patch) | |
tree | a356a51e8701e88b1e982f3cc48f16eb3301fe52 /Lib/subprocess.py | |
parent | b0c597f5ffcb696a394c5901fec0e489f854ae12 (diff) | |
parent | d65ba51e245ffdd155bc1e7b8884fc943048111f (diff) | |
download | cpython-b2188630125e5b5815aa60181fdd93f036a89522.zip cpython-b2188630125e5b5815aa60181fdd93f036a89522.tar.gz cpython-b2188630125e5b5815aa60181fdd93f036a89522.tar.bz2 |
subprocess's Popen.wait() is now thread safe so that multiple threads
may be calling wait() or poll() on a Popen instance at the same time
without losing the Popen.returncode value. Fixes issue #21291.
Diffstat (limited to 'Lib/subprocess.py')
-rw-r--r-- | Lib/subprocess.py | 50 |
1 files changed, 40 insertions, 10 deletions
diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 45b58e3..daa3e25 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -416,6 +416,10 @@ else: import _posixsubprocess import select import selectors + try: + import threading + except ImportError: + import dummy_threading as threading # When select or poll has indicated that the file is writable, # we can write up to _PIPE_BUF bytes without risk of blocking. @@ -759,6 +763,12 @@ class Popen(object): pass_fds=()): """Create new Popen instance.""" _cleanup() + # Held while anything is calling waitpid before returncode has been + # updated to prevent clobbering returncode if wait() or poll() are + # called from multiple threads at once. After acquiring the lock, + # code must re-check self.returncode to see if another thread just + # finished a waitpid() call. + self._waitpid_lock = threading.Lock() self._input = None self._communication_started = False @@ -1466,6 +1476,7 @@ class Popen(object): def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED, _WTERMSIG=os.WTERMSIG, _WIFEXITED=os.WIFEXITED, _WEXITSTATUS=os.WEXITSTATUS): + """All callers to this function MUST hold self._waitpid_lock.""" # This method is called (indirectly) by __del__, so it cannot # refer to anything outside of its local scope. if _WIFSIGNALED(sts): @@ -1487,7 +1498,13 @@ class Popen(object): """ if self.returncode is None: + if not self._waitpid_lock.acquire(False): + # Something else is busy calling waitpid. Don't allow two + # at once. We know nothing yet. + return None try: + if self.returncode is not None: + return self.returncode # Another thread waited. pid, sts = _waitpid(self.pid, _WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) @@ -1501,10 +1518,13 @@ class Popen(object): # can't get the status. # http://bugs.python.org/issue15756 self.returncode = 0 + finally: + self._waitpid_lock.release() return self.returncode def _try_wait(self, wait_flags): + """All callers to this function MUST hold self._waitpid_lock.""" try: (pid, sts) = _eintr_retry_call(os.waitpid, self.pid, wait_flags) except OSError as e: @@ -1537,11 +1557,17 @@ class Popen(object): # cribbed from Lib/threading.py in Thread.wait() at r71065. delay = 0.0005 # 500 us -> initial delay of 1 ms while True: - (pid, sts) = self._try_wait(os.WNOHANG) - assert pid == self.pid or pid == 0 - if pid == self.pid: - self._handle_exitstatus(sts) - break + if self._waitpid_lock.acquire(False): + try: + if self.returncode is not None: + break # Another thread waited. + (pid, sts) = self._try_wait(os.WNOHANG) + assert pid == self.pid or pid == 0 + if pid == self.pid: + self._handle_exitstatus(sts) + break + finally: + self._waitpid_lock.release() remaining = self._remaining_time(endtime) if remaining <= 0: raise TimeoutExpired(self.args, timeout) @@ -1549,11 +1575,15 @@ class Popen(object): time.sleep(delay) else: while self.returncode is None: - (pid, sts) = self._try_wait(0) - # Check the pid and loop as waitpid has been known to return - # 0 even without WNOHANG in odd situations. issue14396. - if pid == self.pid: - self._handle_exitstatus(sts) + with self._waitpid_lock: + if self.returncode is not None: + break # Another thread waited. + (pid, sts) = self._try_wait(0) + # Check the pid and loop as waitpid has been known to + # return 0 even without WNOHANG in odd situations. + # http://bugs.python.org/issue14396. + if pid == self.pid: + self._handle_exitstatus(sts) return self.returncode |