Work around race condition in subprocess.
There's a race condition in python's subprocess module, and gclient uses
it heavily while multithreaded. Avoid the race by locking around calls
to subprocess.Popen's constructor. Detailed explanation in the bug.
BUG=531561
Review URL: https://codereview.chromium.org/1343783004
git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@296685 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/subprocess2.py b/subprocess2.py
index 21e3487..a00592f 100644
--- a/subprocess2.py
+++ b/subprocess2.py
@@ -181,6 +181,14 @@
Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
exceptions generated by cygwin when it fails trying to emulate fork().
"""
+ # subprocess.Popen.__init__() is not threadsafe; there is a race between
+ # creating the exec-error pipe for the child and setting it to CLOEXEC during
+ # which another thread can fork and cause the pipe to be inherited by its
+ # descendents, which will cause the current Popen to hang until all those
+ # descendents exit. Protect this with a lock so that only one fork/exec can
+ # happen at a time.
+ popen_lock = threading.Lock()
+
def __init__(self, args, **kwargs):
# Make sure we hack subprocess if necessary.
hack_subprocess()
@@ -234,7 +242,8 @@
self.returncode = None
try:
- super(Popen, self).__init__(args, **kwargs)
+ with self.popen_lock:
+ super(Popen, self).__init__(args, **kwargs)
except OSError, e:
if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
# Convert fork() emulation failure into a CygwinRebaseError().