@@ -470,6 +470,10 @@ class pywintypes:
470
470
warnings .warn ("The _posixsubprocess module is not being used. "
471
471
"Child process reliability may suffer if your "
472
472
"program uses threads." , RuntimeWarning )
473
+ try :
474
+ import threading
475
+ except ImportError :
476
+ import dummy_threading as threading
473
477
474
478
# When select or poll has indicated that the file is writable,
475
479
# we can write up to _PIPE_BUF bytes without risk of blocking.
@@ -715,6 +719,12 @@ def __init__(self, args, bufsize=0, executable=None,
715
719
pass_fds = ()):
716
720
"""Create new Popen instance."""
717
721
_cleanup ()
722
+ # Held while anything is calling waitpid before returncode has been
723
+ # updated to prevent clobbering returncode if wait() or poll() are
724
+ # called from multiple threads at once. After acquiring the lock,
725
+ # code must re-check self.returncode to see if another thread just
726
+ # finished a waitpid() call.
727
+ self ._waitpid_lock = threading .Lock ()
718
728
719
729
self ._child_created = False
720
730
self ._input = None
@@ -1568,6 +1578,7 @@ def _dup2(a, b):
1568
1578
def _handle_exitstatus (self , sts , _WIFSIGNALED = os .WIFSIGNALED ,
1569
1579
_WTERMSIG = os .WTERMSIG , _WIFEXITED = os .WIFEXITED ,
1570
1580
_WEXITSTATUS = os .WEXITSTATUS ):
1581
+ """All callers to this function MUST hold self._waitpid_lock."""
1571
1582
# This method is called (indirectly) by __del__, so it cannot
1572
1583
# refer to anything outside of its local scope."""
1573
1584
if _WIFSIGNALED (sts ):
@@ -1589,24 +1600,34 @@ def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid,
1589
1600
1590
1601
"""
1591
1602
if self .returncode is None :
1603
+ if not self ._waitpid_lock .acquire (False ):
1604
+ # Something else is busy calling waitpid. Don't allow two
1605
+ # at once. We know nothing yet.
1606
+ return None
1592
1607
try :
1593
- pid , sts = _waitpid (self .pid , _WNOHANG )
1594
- if pid == self .pid :
1595
- self ._handle_exitstatus (sts )
1596
- except _os_error , e :
1597
- if _deadstate is not None :
1598
- self .returncode = _deadstate
1599
- elif e .errno == _ECHILD :
1600
- # This happens if SIGCLD is set to be ignored or
1601
- # waiting for child processes has otherwise been
1602
- # disabled for our process. This child is dead, we
1603
- # can't get the status.
1604
- # http://bugs.python.org/issue15756
1605
- self .returncode = 0
1608
+ try :
1609
+ if self .returncode is not None :
1610
+ return self .returncode # Another thread waited.
1611
+ pid , sts = _waitpid (self .pid , _WNOHANG )
1612
+ if pid == self .pid :
1613
+ self ._handle_exitstatus (sts )
1614
+ except _os_error , e :
1615
+ if _deadstate is not None :
1616
+ self .returncode = _deadstate
1617
+ elif e .errno == _ECHILD :
1618
+ # This happens if SIGCLD is set to be ignored or
1619
+ # waiting for child processes has otherwise been
1620
+ # disabled for our process. This child is dead, we
1621
+ # can't get the status.
1622
+ # http://bugs.python.org/issue15756
1623
+ self .returncode = 0
1624
+ finally :
1625
+ self ._waitpid_lock .release ()
1606
1626
return self .returncode
1607
1627
1608
1628
1609
1629
def _try_wait (self , wait_flags ):
1630
+ """All callers to this function MUST hold self._waitpid_lock."""
1610
1631
try :
1611
1632
(pid , sts ) = _eintr_retry_call (os .waitpid , self .pid , wait_flags )
1612
1633
except OSError , e :
@@ -1639,19 +1660,36 @@ def wait(self, timeout=None, endtime=None):
1639
1660
# cribbed from Lib/threading.py in Thread.wait() at r71065.
1640
1661
delay = 0.0005 # 500 us -> initial delay of 1 ms
1641
1662
while True :
1642
- (pid , sts ) = self ._try_wait (os .WNOHANG )
1643
- assert pid == self .pid or pid == 0
1644
- if pid == self .pid :
1645
- self ._handle_exitstatus (sts )
1646
- break
1663
+ if self ._waitpid_lock .acquire (False ):
1664
+ try :
1665
+ if self .returncode is not None :
1666
+ break # Another thread waited.
1667
+ (pid , sts ) = self ._try_wait (os .WNOHANG )
1668
+ assert pid == self .pid or pid == 0
1669
+ if pid == self .pid :
1670
+ self ._handle_exitstatus (sts )
1671
+ break
1672
+ finally :
1673
+ self ._waitpid_lock .release ()
1647
1674
remaining = self ._remaining_time (endtime )
1648
1675
if remaining <= 0 :
1649
1676
raise TimeoutExpired (self .args , timeout )
1650
1677
delay = min (delay * 2 , remaining , .05 )
1651
1678
time .sleep (delay )
1652
- elif self .returncode is None :
1653
- (pid , sts ) = self ._try_wait (0 )
1654
- self ._handle_exitstatus (sts )
1679
+ else :
1680
+ while self .returncode is None :
1681
+ self ._waitpid_lock .acquire ()
1682
+ try :
1683
+ if self .returncode is not None :
1684
+ break # Another thread waited.
1685
+ (pid , sts ) = self ._try_wait (0 )
1686
+ # Check the pid and loop as waitpid has been known to
1687
+ # return 0 even without WNOHANG in odd situations.
1688
+ # http://bugs.python.org/issue14396.
1689
+ if pid == self .pid :
1690
+ self ._handle_exitstatus (sts )
1691
+ finally :
1692
+ self ._waitpid_lock .release ()
1655
1693
return self .returncode
1656
1694
1657
1695
0 commit comments