-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
bpo-35537: subprocess can use posix_spawn with pipes #11575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1066,6 +1066,34 @@ def wait(self, timeout=None): | |
pass | ||
raise # resume the KeyboardInterrupt | ||
|
||
def _close_pipe_fds(self, | ||
p2cread, p2cwrite, | ||
c2pread, c2pwrite, | ||
errread, errwrite): | ||
# self._devnull is not always defined. | ||
devnull_fd = getattr(self, '_devnull', None) | ||
|
||
if _mswindows: | ||
if p2cread != -1: | ||
p2cread.Close() | ||
if c2pwrite != -1: | ||
c2pwrite.Close() | ||
if errwrite != -1: | ||
errwrite.Close() | ||
else: | ||
if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: | ||
os.close(p2cread) | ||
if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd: | ||
os.close(c2pwrite) | ||
if errwrite != -1 and errread != -1 and errwrite != devnull_fd: | ||
os.close(errwrite) | ||
|
||
if devnull_fd is not None: | ||
os.close(devnull_fd) | ||
|
||
# Prevent a double close of these handles/fds from __init__ on error. | ||
self._closed_child_pipe_fds = True | ||
|
||
|
||
if _mswindows: | ||
# | ||
|
@@ -1244,17 +1272,9 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, | |
# output pipe are maintained in this process or else the | ||
# pipe will not close when the child process exits and the | ||
# ReadFile will hang. | ||
if p2cread != -1: | ||
p2cread.Close() | ||
if c2pwrite != -1: | ||
c2pwrite.Close() | ||
if errwrite != -1: | ||
errwrite.Close() | ||
if hasattr(self, '_devnull'): | ||
os.close(self._devnull) | ||
# Prevent a double close of these handles/fds from __init__ | ||
# on error. | ||
self._closed_child_pipe_fds = True | ||
self._close_pipe_fds(p2cread, p2cwrite, | ||
c2pread, c2pwrite, | ||
errread, errwrite) | ||
|
||
# Retain the process handle, but close the thread handle | ||
self._child_created = True | ||
|
@@ -1441,7 +1461,10 @@ def _get_handles(self, stdin, stdout, stderr): | |
errread, errwrite) | ||
|
||
|
||
def _posix_spawn(self, args, executable, env, restore_signals): | ||
def _posix_spawn(self, args, executable, env, restore_signals, | ||
p2cread, p2cwrite, | ||
c2pread, c2pwrite, | ||
errread, errwrite): | ||
"""Execute program using os.posix_spawn().""" | ||
if env is None: | ||
env = os.environ | ||
|
@@ -1456,7 +1479,26 @@ def _posix_spawn(self, args, executable, env, restore_signals): | |
sigset.append(signum) | ||
kwargs['setsigdef'] = sigset | ||
|
||
file_actions = [] | ||
for fd in (p2cwrite, c2pread, errread): | ||
if fd != -1: | ||
file_actions.append((os.POSIX_SPAWN_CLOSE, fd)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically, there is no need to explicitly close parent pipe ends because they have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically, there is no need, but _posixsubprocess does that. In case of doubt, I prefer to mimick _posixsubprocess behavior.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we decide to skip the close(), I would prefer to also modify _posixsubprocess. But here I only want to use posix_spawn() in more cases, so I prefer to not modify "unrelated" changes :-) |
||
for fd, fd2 in ( | ||
(p2cread, 0), | ||
(c2pwrite, 1), | ||
(errwrite, 2), | ||
): | ||
if fd != -1: | ||
file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2)) | ||
if file_actions: | ||
kwargs['file_actions'] = file_actions | ||
|
||
self.pid = os.posix_spawn(executable, args, env, **kwargs) | ||
self._child_created = True | ||
|
||
self._close_pipe_fds(p2cread, p2cwrite, | ||
c2pread, c2pwrite, | ||
errread, errwrite) | ||
|
||
def _execute_child(self, args, executable, preexec_fn, close_fds, | ||
pass_fds, cwd, env, | ||
|
@@ -1489,11 +1531,14 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, | |
and not close_fds | ||
and not pass_fds | ||
and cwd is None | ||
and p2cread == p2cwrite == -1 | ||
and c2pread == c2pwrite == -1 | ||
and errread == errwrite == -1 | ||
and (p2cread == -1 or p2cread > 2) | ||
and (c2pwrite == -1 or c2pwrite > 2) | ||
and (errwrite == -1 or errwrite > 2) | ||
vstinner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
and not start_new_session): | ||
self._posix_spawn(args, executable, env, restore_signals) | ||
self._posix_spawn(args, executable, env, restore_signals, | ||
p2cread, p2cwrite, | ||
c2pread, c2pwrite, | ||
errread, errwrite) | ||
return | ||
|
||
orig_executable = executable | ||
|
@@ -1548,18 +1593,9 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, | |
# be sure the FD is closed no matter what | ||
os.close(errpipe_write) | ||
|
||
# self._devnull is not always defined. | ||
devnull_fd = getattr(self, '_devnull', None) | ||
if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd: | ||
os.close(p2cread) | ||
if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd: | ||
os.close(c2pwrite) | ||
if errwrite != -1 and errread != -1 and errwrite != devnull_fd: | ||
os.close(errwrite) | ||
if devnull_fd is not None: | ||
os.close(devnull_fd) | ||
# Prevent a double close of these fds from __init__ on error. | ||
self._closed_child_pipe_fds = True | ||
self._close_pipe_fds(p2cread, p2cwrite, | ||
c2pread, c2pwrite, | ||
errread, errwrite) | ||
|
||
# Wait for exec to fail or succeed; possibly raising an | ||
# exception (limited in size) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of premature failure on
X.Close()
oros.close(X)
the remaining pipes/fds will remain "open". Perhaps it makes sense to usecontextlib.ExitStack
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a regression caused by my PR? Or a general remark on existing code? My PR just moves code, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, it doesn't look like a regression introduced by you (was already there).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least, I factorized the code so it should help to enhance the code ;-) Maybe open an issue if you want to work on that? I don't see an obvious pattern to ensure that all file descriptors are properly closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, makes sense. I bet there are many other places which can benefit from
ExitStack
in a similar manner.