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().