Skip to content

Commit f6243ac

Browse files
authored
bpo-35537: subprocess can use posix_spawn with pipes (GH-11575)
* subprocess.Popen can now also use os.posix_spawn() with pipes, but only if pipe file descriptors are greater than 2. * Fix Popen._posix_spawn(): set '_child_created' attribute to True. * Add Popen._close_pipe_fds() helper function to factorize the code.
1 parent ab67281 commit f6243ac

File tree

2 files changed

+66
-30
lines changed

2 files changed

+66
-30
lines changed

Doc/whatsnew/3.8.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ Optimizations
280280
and Linux (using glibc 2.24 or newer) if all these conditions are met:
281281

282282
* *close_fds* is false;
283-
* *preexec_fn*, *pass_fds*, *cwd*, *stdin*, *stdout*, *stderr* and
284-
*start_new_session* parameters are not set;
283+
* *preexec_fn*, *pass_fds*, *cwd* and *start_new_session* parameters
284+
are not set;
285285
* the *executable* path contains a directory.
286286

287287
* :func:`shutil.copyfile`, :func:`shutil.copy`, :func:`shutil.copy2`,

Lib/subprocess.py

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,34 @@ def wait(self, timeout=None):
10661066
pass
10671067
raise # resume the KeyboardInterrupt
10681068

1069+
def _close_pipe_fds(self,
1070+
p2cread, p2cwrite,
1071+
c2pread, c2pwrite,
1072+
errread, errwrite):
1073+
# self._devnull is not always defined.
1074+
devnull_fd = getattr(self, '_devnull', None)
1075+
1076+
if _mswindows:
1077+
if p2cread != -1:
1078+
p2cread.Close()
1079+
if c2pwrite != -1:
1080+
c2pwrite.Close()
1081+
if errwrite != -1:
1082+
errwrite.Close()
1083+
else:
1084+
if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
1085+
os.close(p2cread)
1086+
if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
1087+
os.close(c2pwrite)
1088+
if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
1089+
os.close(errwrite)
1090+
1091+
if devnull_fd is not None:
1092+
os.close(devnull_fd)
1093+
1094+
# Prevent a double close of these handles/fds from __init__ on error.
1095+
self._closed_child_pipe_fds = True
1096+
10691097

10701098
if _mswindows:
10711099
#
@@ -1244,17 +1272,9 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
12441272
# output pipe are maintained in this process or else the
12451273
# pipe will not close when the child process exits and the
12461274
# ReadFile will hang.
1247-
if p2cread != -1:
1248-
p2cread.Close()
1249-
if c2pwrite != -1:
1250-
c2pwrite.Close()
1251-
if errwrite != -1:
1252-
errwrite.Close()
1253-
if hasattr(self, '_devnull'):
1254-
os.close(self._devnull)
1255-
# Prevent a double close of these handles/fds from __init__
1256-
# on error.
1257-
self._closed_child_pipe_fds = True
1275+
self._close_pipe_fds(p2cread, p2cwrite,
1276+
c2pread, c2pwrite,
1277+
errread, errwrite)
12581278

12591279
# Retain the process handle, but close the thread handle
12601280
self._child_created = True
@@ -1441,7 +1461,10 @@ def _get_handles(self, stdin, stdout, stderr):
14411461
errread, errwrite)
14421462

14431463

1444-
def _posix_spawn(self, args, executable, env, restore_signals):
1464+
def _posix_spawn(self, args, executable, env, restore_signals,
1465+
p2cread, p2cwrite,
1466+
c2pread, c2pwrite,
1467+
errread, errwrite):
14451468
"""Execute program using os.posix_spawn()."""
14461469
if env is None:
14471470
env = os.environ
@@ -1456,7 +1479,26 @@ def _posix_spawn(self, args, executable, env, restore_signals):
14561479
sigset.append(signum)
14571480
kwargs['setsigdef'] = sigset
14581481

1482+
file_actions = []
1483+
for fd in (p2cwrite, c2pread, errread):
1484+
if fd != -1:
1485+
file_actions.append((os.POSIX_SPAWN_CLOSE, fd))
1486+
for fd, fd2 in (
1487+
(p2cread, 0),
1488+
(c2pwrite, 1),
1489+
(errwrite, 2),
1490+
):
1491+
if fd != -1:
1492+
file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2))
1493+
if file_actions:
1494+
kwargs['file_actions'] = file_actions
1495+
14591496
self.pid = os.posix_spawn(executable, args, env, **kwargs)
1497+
self._child_created = True
1498+
1499+
self._close_pipe_fds(p2cread, p2cwrite,
1500+
c2pread, c2pwrite,
1501+
errread, errwrite)
14601502

14611503
def _execute_child(self, args, executable, preexec_fn, close_fds,
14621504
pass_fds, cwd, env,
@@ -1489,11 +1531,14 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
14891531
and not close_fds
14901532
and not pass_fds
14911533
and cwd is None
1492-
and p2cread == p2cwrite == -1
1493-
and c2pread == c2pwrite == -1
1494-
and errread == errwrite == -1
1534+
and (p2cread == -1 or p2cread > 2)
1535+
and (c2pwrite == -1 or c2pwrite > 2)
1536+
and (errwrite == -1 or errwrite > 2)
14951537
and not start_new_session):
1496-
self._posix_spawn(args, executable, env, restore_signals)
1538+
self._posix_spawn(args, executable, env, restore_signals,
1539+
p2cread, p2cwrite,
1540+
c2pread, c2pwrite,
1541+
errread, errwrite)
14971542
return
14981543

14991544
orig_executable = executable
@@ -1548,18 +1593,9 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
15481593
# be sure the FD is closed no matter what
15491594
os.close(errpipe_write)
15501595

1551-
# self._devnull is not always defined.
1552-
devnull_fd = getattr(self, '_devnull', None)
1553-
if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
1554-
os.close(p2cread)
1555-
if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
1556-
os.close(c2pwrite)
1557-
if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
1558-
os.close(errwrite)
1559-
if devnull_fd is not None:
1560-
os.close(devnull_fd)
1561-
# Prevent a double close of these fds from __init__ on error.
1562-
self._closed_child_pipe_fds = True
1596+
self._close_pipe_fds(p2cread, p2cwrite,
1597+
c2pread, c2pwrite,
1598+
errread, errwrite)
15631599

15641600
# Wait for exec to fail or succeed; possibly raising an
15651601
# exception (limited in size)

0 commit comments

Comments
 (0)