Skip to content

Commit 59fd1bf

Browse files
committed
The _posixsubprocess module is now required on POSIX.
Remove the pure Python POSIX subprocess implementation. If non-CPython VMs (are there any for 3.x yet?) were somehow depending on this, they already have the exact same set of problems with Python code being executed after os.fork() that _posixsubprocess was written to deal with. They should implement an equivalent outside of Python.
1 parent c80504f commit 59fd1bf

File tree

2 files changed

+28
-182
lines changed

2 files changed

+28
-182
lines changed

Lib/subprocess.py

Lines changed: 28 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -397,39 +397,14 @@ class pywintypes:
397397
else:
398398
import select
399399
_has_poll = hasattr(select, 'poll')
400-
import fcntl
401-
import pickle
402-
403-
try:
404-
import _posixsubprocess
405-
except ImportError:
406-
_posixsubprocess = None
407-
warnings.warn("The _posixsubprocess module is not being used. "
408-
"Child process reliability may suffer if your "
409-
"program uses threads.", RuntimeWarning)
400+
import _posixsubprocess
401+
_create_pipe = _posixsubprocess.cloexec_pipe
410402

411403
# When select or poll has indicated that the file is writable,
412404
# we can write up to _PIPE_BUF bytes without risk of blocking.
413405
# POSIX defines PIPE_BUF as >= 512.
414406
_PIPE_BUF = getattr(select, 'PIPE_BUF', 512)
415407

416-
_FD_CLOEXEC = getattr(fcntl, 'FD_CLOEXEC', 1)
417-
418-
def _set_cloexec(fd, cloexec):
419-
old = fcntl.fcntl(fd, fcntl.F_GETFD)
420-
if cloexec:
421-
fcntl.fcntl(fd, fcntl.F_SETFD, old | _FD_CLOEXEC)
422-
else:
423-
fcntl.fcntl(fd, fcntl.F_SETFD, old & ~_FD_CLOEXEC)
424-
425-
if _posixsubprocess:
426-
_create_pipe = _posixsubprocess.cloexec_pipe
427-
else:
428-
def _create_pipe():
429-
fds = os.pipe()
430-
_set_cloexec(fds[0], True)
431-
_set_cloexec(fds[1], True)
432-
return fds
433408

434409
__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput",
435410
"getoutput", "check_output", "CalledProcessError", "DEVNULL"]
@@ -1267,140 +1242,33 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
12671242
errpipe_read, errpipe_write = _create_pipe()
12681243
try:
12691244
try:
1270-
1271-
if _posixsubprocess:
1272-
# We must avoid complex work that could involve
1273-
# malloc or free in the child process to avoid
1274-
# potential deadlocks, thus we do all this here.
1275-
# and pass it to fork_exec()
1276-
1277-
if env:
1278-
env_list = [os.fsencode(k) + b'=' + os.fsencode(v)
1279-
for k, v in env.items()]
1280-
else:
1281-
env_list = None # Use execv instead of execve.
1282-
executable = os.fsencode(executable)
1283-
if os.path.dirname(executable):
1284-
executable_list = (executable,)
1285-
else:
1286-
# This matches the behavior of os._execvpe().
1287-
executable_list = tuple(
1288-
os.path.join(os.fsencode(dir), executable)
1289-
for dir in os.get_exec_path(env))
1290-
fds_to_keep = set(pass_fds)
1291-
fds_to_keep.add(errpipe_write)
1292-
self.pid = _posixsubprocess.fork_exec(
1293-
args, executable_list,
1294-
close_fds, sorted(fds_to_keep), cwd, env_list,
1295-
p2cread, p2cwrite, c2pread, c2pwrite,
1296-
errread, errwrite,
1297-
errpipe_read, errpipe_write,
1298-
restore_signals, start_new_session, preexec_fn)
1245+
# We must avoid complex work that could involve
1246+
# malloc or free in the child process to avoid
1247+
# potential deadlocks, thus we do all this here.
1248+
# and pass it to fork_exec()
1249+
1250+
if env:
1251+
env_list = [os.fsencode(k) + b'=' + os.fsencode(v)
1252+
for k, v in env.items()]
12991253
else:
1300-
# Pure Python implementation: It is not thread safe.
1301-
# This implementation may deadlock in the child if your
1302-
# parent process has any other threads running.
1303-
1304-
gc_was_enabled = gc.isenabled()
1305-
# Disable gc to avoid bug where gc -> file_dealloc ->
1306-
# write to stderr -> hang. See issue1336
1307-
gc.disable()
1308-
try:
1309-
self.pid = os.fork()
1310-
except:
1311-
if gc_was_enabled:
1312-
gc.enable()
1313-
raise
1314-
self._child_created = True
1315-
if self.pid == 0:
1316-
# Child
1317-
try:
1318-
# Close parent's pipe ends
1319-
if p2cwrite != -1:
1320-
os.close(p2cwrite)
1321-
if c2pread != -1:
1322-
os.close(c2pread)
1323-
if errread != -1:
1324-
os.close(errread)
1325-
os.close(errpipe_read)
1326-
1327-
# Dup fds for child
1328-
def _dup2(a, b):
1329-
# dup2() removes the CLOEXEC flag but
1330-
# we must do it ourselves if dup2()
1331-
# would be a no-op (issue #10806).
1332-
if a == b:
1333-
_set_cloexec(a, False)
1334-
elif a != -1:
1335-
os.dup2(a, b)
1336-
_dup2(p2cread, 0)
1337-
_dup2(c2pwrite, 1)
1338-
_dup2(errwrite, 2)
1339-
1340-
# Close pipe fds. Make sure we don't close the
1341-
# same fd more than once, or standard fds.
1342-
closed = set()
1343-
for fd in [p2cread, c2pwrite, errwrite]:
1344-
if fd > 2 and fd not in closed:
1345-
os.close(fd)
1346-
closed.add(fd)
1347-
1348-
# Close all other fds, if asked for
1349-
if close_fds:
1350-
fds_to_keep = set(pass_fds)
1351-
fds_to_keep.add(errpipe_write)
1352-
self._close_fds(fds_to_keep)
1353-
1354-
1355-
if cwd is not None:
1356-
os.chdir(cwd)
1357-
1358-
# This is a copy of Python/pythonrun.c
1359-
# _Py_RestoreSignals(). If that were exposed
1360-
# as a sys._py_restoresignals func it would be
1361-
# better.. but this pure python implementation
1362-
# isn't likely to be used much anymore.
1363-
if restore_signals:
1364-
signals = ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ')
1365-
for sig in signals:
1366-
if hasattr(signal, sig):
1367-
signal.signal(getattr(signal, sig),
1368-
signal.SIG_DFL)
1369-
1370-
if start_new_session and hasattr(os, 'setsid'):
1371-
os.setsid()
1372-
1373-
if preexec_fn:
1374-
preexec_fn()
1375-
1376-
if env is None:
1377-
os.execvp(executable, args)
1378-
else:
1379-
os.execvpe(executable, args, env)
1380-
1381-
except:
1382-
try:
1383-
exc_type, exc_value = sys.exc_info()[:2]
1384-
if isinstance(exc_value, OSError):
1385-
errno_num = exc_value.errno
1386-
else:
1387-
errno_num = 0
1388-
message = '%s:%x:%s' % (exc_type.__name__,
1389-
errno_num, exc_value)
1390-
message = message.encode(errors="surrogatepass")
1391-
os.write(errpipe_write, message)
1392-
except Exception:
1393-
# We MUST not allow anything odd happening
1394-
# above to prevent us from exiting below.
1395-
pass
1396-
1397-
# This exitcode won't be reported to applications
1398-
# so it really doesn't matter what we return.
1399-
os._exit(255)
1400-
1401-
# Parent
1402-
if gc_was_enabled:
1403-
gc.enable()
1254+
env_list = None # Use execv instead of execve.
1255+
executable = os.fsencode(executable)
1256+
if os.path.dirname(executable):
1257+
executable_list = (executable,)
1258+
else:
1259+
# This matches the behavior of os._execvpe().
1260+
executable_list = tuple(
1261+
os.path.join(os.fsencode(dir), executable)
1262+
for dir in os.get_exec_path(env))
1263+
fds_to_keep = set(pass_fds)
1264+
fds_to_keep.add(errpipe_write)
1265+
self.pid = _posixsubprocess.fork_exec(
1266+
args, executable_list,
1267+
close_fds, sorted(fds_to_keep), cwd, env_list,
1268+
p2cread, p2cwrite, c2pread, c2pwrite,
1269+
errread, errwrite,
1270+
errpipe_read, errpipe_write,
1271+
restore_signals, start_new_session, preexec_fn)
14041272
finally:
14051273
# be sure the FD is closed no matter what
14061274
os.close(errpipe_write)

Lib/test/test_subprocess.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,28 +1495,6 @@ def tearDown(self):
14951495
ProcessTestCase.tearDown(self)
14961496

14971497

1498-
@unittest.skipUnless(getattr(subprocess, '_posixsubprocess', False),
1499-
"_posixsubprocess extension module not found.")
1500-
class ProcessTestCasePOSIXPurePython(ProcessTestCase, POSIXProcessTestCase):
1501-
@classmethod
1502-
def setUpClass(cls):
1503-
global subprocess
1504-
assert subprocess._posixsubprocess
1505-
# Reimport subprocess while forcing _posixsubprocess to not exist.
1506-
with support.check_warnings(('.*_posixsubprocess .* not being used.*',
1507-
RuntimeWarning)):
1508-
subprocess = support.import_fresh_module(
1509-
'subprocess', blocked=['_posixsubprocess'])
1510-
assert not subprocess._posixsubprocess
1511-
1512-
@classmethod
1513-
def tearDownClass(cls):
1514-
global subprocess
1515-
# Reimport subprocess as it should be, restoring order to the universe.
1516-
subprocess = support.import_fresh_module('subprocess')
1517-
assert subprocess._posixsubprocess
1518-
1519-
15201498
class HelperFunctionTests(unittest.TestCase):
15211499
@unittest.skipIf(mswindows, "errno and EINTR make no sense on windows")
15221500
def test_eintr_retry_call(self):

0 commit comments

Comments
 (0)