@@ -405,6 +405,10 @@ class STARTUPINFO:
405
405
import _posixsubprocess
406
406
import select
407
407
import selectors
408
+ try :
409
+ import threading
410
+ except ImportError :
411
+ import dummy_threading as threading
408
412
409
413
# When select or poll has indicated that the file is writable,
410
414
# we can write up to _PIPE_BUF bytes without risk of blocking.
@@ -748,6 +752,12 @@ def __init__(self, args, bufsize=-1, executable=None,
748
752
pass_fds = ()):
749
753
"""Create new Popen instance."""
750
754
_cleanup ()
755
+ # Held while anything is calling waitpid before returncode has been
756
+ # updated to prevent clobbering returncode if wait() or poll() are
757
+ # called from multiple threads at once. After acquiring the lock,
758
+ # code must re-check self.returncode to see if another thread just
759
+ # finished a waitpid() call.
760
+ self ._waitpid_lock = threading .Lock ()
751
761
752
762
self ._input = None
753
763
self ._communication_started = False
@@ -1450,6 +1460,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
1450
1460
def _handle_exitstatus (self , sts , _WIFSIGNALED = os .WIFSIGNALED ,
1451
1461
_WTERMSIG = os .WTERMSIG , _WIFEXITED = os .WIFEXITED ,
1452
1462
_WEXITSTATUS = os .WEXITSTATUS ):
1463
+ """All callers to this function MUST hold self._waitpid_lock."""
1453
1464
# This method is called (indirectly) by __del__, so it cannot
1454
1465
# refer to anything outside of its local scope.
1455
1466
if _WIFSIGNALED (sts ):
@@ -1471,7 +1482,13 @@ def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
1471
1482
1472
1483
"""
1473
1484
if self .returncode is None :
1485
+ if not self ._waitpid_lock .acquire (False ):
1486
+ # Something else is busy calling waitpid. Don't allow two
1487
+ # at once. We know nothing yet.
1488
+ return None
1474
1489
try :
1490
+ if self .returncode is not None :
1491
+ return self .returncode # Another thread waited.
1475
1492
pid , sts = _waitpid (self .pid , _WNOHANG )
1476
1493
if pid == self .pid :
1477
1494
self ._handle_exitstatus (sts )
@@ -1485,10 +1502,13 @@ def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
1485
1502
# can't get the status.
1486
1503
# http://bugs.python.org/issue15756
1487
1504
self .returncode = 0
1505
+ finally :
1506
+ self ._waitpid_lock .release ()
1488
1507
return self .returncode
1489
1508
1490
1509
1491
1510
def _try_wait (self , wait_flags ):
1511
+ """All callers to this function MUST hold self._waitpid_lock."""
1492
1512
try :
1493
1513
(pid , sts ) = _eintr_retry_call (os .waitpid , self .pid , wait_flags )
1494
1514
except OSError as e :
@@ -1521,23 +1541,33 @@ def wait(self, timeout=None, endtime=None):
1521
1541
# cribbed from Lib/threading.py in Thread.wait() at r71065.
1522
1542
delay = 0.0005 # 500 us -> initial delay of 1 ms
1523
1543
while True :
1524
- (pid , sts ) = self ._try_wait (os .WNOHANG )
1525
- assert pid == self .pid or pid == 0
1526
- if pid == self .pid :
1527
- self ._handle_exitstatus (sts )
1528
- break
1544
+ if self ._waitpid_lock .acquire (False ):
1545
+ try :
1546
+ if self .returncode is not None :
1547
+ break # Another thread waited.
1548
+ (pid , sts ) = self ._try_wait (os .WNOHANG )
1549
+ assert pid == self .pid or pid == 0
1550
+ if pid == self .pid :
1551
+ self ._handle_exitstatus (sts )
1552
+ break
1553
+ finally :
1554
+ self ._waitpid_lock .release ()
1529
1555
remaining = self ._remaining_time (endtime )
1530
1556
if remaining <= 0 :
1531
1557
raise TimeoutExpired (self .args , timeout )
1532
1558
delay = min (delay * 2 , remaining , .05 )
1533
1559
time .sleep (delay )
1534
1560
else :
1535
1561
while self .returncode is None :
1536
- (pid , sts ) = self ._try_wait (0 )
1537
- # Check the pid and loop as waitpid has been known to return
1538
- # 0 even without WNOHANG in odd situations. issue14396.
1539
- if pid == self .pid :
1540
- self ._handle_exitstatus (sts )
1562
+ with self ._waitpid_lock :
1563
+ if self .returncode is not None :
1564
+ break # Another thread waited.
1565
+ (pid , sts ) = self ._try_wait (0 )
1566
+ # Check the pid and loop as waitpid has been known to
1567
+ # return 0 even without WNOHANG in odd situations.
1568
+ # http://bugs.python.org/issue14396.
1569
+ if pid == self .pid :
1570
+ self ._handle_exitstatus (sts )
1541
1571
return self .returncode
1542
1572
1543
1573
0 commit comments