Skip to content

Commit 5e8e371

Browse files
authored
bpo-27448: Work around a gc.disable race condition in subprocess. (#1932)
* bpo-27448: Work around a gc.disable race condition in subprocess. This works around a gc.isenabled/gc.disable race condition in the 2.7 subprocess module by using a lock for the critical section. It'll prevent multiple simultaneous subprocess launches from winding up with gc remaining disabled but it can't fix the ultimate problem: gc enable and disable is a global setting and a hack. Users are *strongly encouraged* to use subprocess32 from PyPI instead of the 2.7 standard library subprocess module. Mixing threads with subprocess is a recipie for disaster otherwise even with "fixes" to ameliorate common issues like this. * Add a blurb!
1 parent 9721e51 commit 5e8e371

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

Lib/subprocess.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ class pywintypes:
7171
else:
7272
import select
7373
_has_poll = hasattr(select, 'poll')
74+
try:
75+
import threading
76+
except ImportError:
77+
threading = None
7478
import fcntl
7579
import pickle
7680

@@ -878,6 +882,21 @@ def _close_fds(self, but):
878882
pass
879883

880884

885+
# Used as a bandaid workaround for https://bugs.python.org/issue27448
886+
# to prevent multiple simultaneous subprocess launches from interfering
887+
# with one another and leaving gc disabled.
888+
if threading:
889+
_disabling_gc_lock = threading.Lock()
890+
else:
891+
class _noop_context_manager(object):
892+
# A dummy context manager that does nothing for the rare
893+
# user of a --without-threads build.
894+
def __enter__(self): pass
895+
def __exit__(self, *args): pass
896+
897+
_disabling_gc_lock = _noop_context_manager()
898+
899+
881900
def _execute_child(self, args, executable, preexec_fn, close_fds,
882901
cwd, env, universal_newlines,
883902
startupinfo, creationflags, shell, to_close,
@@ -909,10 +928,12 @@ def _close_in_parent(fd):
909928
errpipe_read, errpipe_write = self.pipe_cloexec()
910929
try:
911930
try:
912-
gc_was_enabled = gc.isenabled()
913-
# Disable gc to avoid bug where gc -> file_dealloc ->
914-
# write to stderr -> hang. http://bugs.python.org/issue1336
915-
gc.disable()
931+
with self._disabling_gc_lock:
932+
gc_was_enabled = gc.isenabled()
933+
# Disable gc to avoid bug where gc -> file_dealloc ->
934+
# write to stderr -> hang.
935+
# https://bugs.python.org/issue1336
936+
gc.disable()
916937
try:
917938
self.pid = os.fork()
918939
except:
@@ -986,9 +1007,10 @@ def _dup2(a, b):
9861007
exc_value.child_traceback = ''.join(exc_lines)
9871008
os.write(errpipe_write, pickle.dumps(exc_value))
9881009

989-
# This exitcode won't be reported to applications, so it
990-
# really doesn't matter what we return.
991-
os._exit(255)
1010+
finally:
1011+
# This exitcode won't be reported to applications, so it
1012+
# really doesn't matter what we return.
1013+
os._exit(255)
9921014

9931015
# Parent
9941016
if gc_was_enabled:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Work around a `gc.disable()` race condition in the `subprocess` module that
2+
could leave garbage collection disabled when multiple threads are spawning
3+
subprocesses at once. Users are *strongly encouraged* to use the
4+
`subprocess32` module from PyPI on Python 2.7 instead, it is much more
5+
reliable.

0 commit comments

Comments
 (0)