Commit 009d7743 authored by Jason Madden's avatar Jason Madden

Better cross-version defaults to the Popen constructor. Implement pass_fds for python 3.

parent 23f7453b
...@@ -252,25 +252,39 @@ else: ...@@ -252,25 +252,39 @@ else:
class Popen(object): class Popen(object):
def __init__(self, args, bufsize=0, executable=None, def __init__(self, args, bufsize=None, executable=None,
stdin=None, stdout=None, stderr=None, stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=False, shell=False, preexec_fn=None, close_fds=None, shell=False,
cwd=None, env=None, universal_newlines=False, cwd=None, env=None, universal_newlines=False,
startupinfo=None, creationflags=0, threadpool=None): startupinfo=None, creationflags=0, threadpool=None,
**kwargs):
"""Create new Popen instance.""" """Create new Popen instance."""
# XXX: On Python 3, we don't implement these keyword arguments:
# (see patched_tests_setup) if not PY3 and kwargs:
# - pass_fds, raise TypeError("Got unexpected keyword arguments", kwargs)
# - start_new_session, pass_fds = kwargs.pop('pass_fds', ())
# - restore_signals start_new_session = kwargs.pop('start_new_session', False)
restore_signals = kwargs.pop('restore_signals', True)
hub = get_hub() hub = get_hub()
if bufsize is None and PY3: if bufsize is None:
bufsize = -1 # restore default # bufsize has different defaults on Py3 and Py2
if PY3:
bufsize = -1
else:
bufsize = 0
if not isinstance(bufsize, integer_types): if not isinstance(bufsize, integer_types):
raise TypeError("bufsize must be an integer") raise TypeError("bufsize must be an integer")
if close_fds is None:
# close_fds has different defaults on Py3/Py2
if PY3:
close_fds = True
else:
close_fds = False
if mswindows: if mswindows:
if preexec_fn is not None: if preexec_fn is not None:
raise ValueError("preexec_fn is not supported on Windows " raise ValueError("preexec_fn is not supported on Windows "
...@@ -285,6 +299,10 @@ class Popen(object): ...@@ -285,6 +299,10 @@ class Popen(object):
self._waiting = False self._waiting = False
else: else:
# POSIX # POSIX
if pass_fds and not close_fds:
import warnings
warnings.warn("pass_fds overriding close_fds.", RuntimeWarning)
close_fds = True
if startupinfo is not None: if startupinfo is not None:
raise ValueError("startupinfo is only supported on Windows " raise ValueError("startupinfo is only supported on Windows "
"platforms") "platforms")
...@@ -370,11 +388,12 @@ class Popen(object): ...@@ -370,11 +388,12 @@ class Popen(object):
self._closed_child_pipe_fds = False self._closed_child_pipe_fds = False
try: try:
self._execute_child(args, executable, preexec_fn, close_fds, self._execute_child(args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines, pass_fds, cwd, env, universal_newlines,
startupinfo, creationflags, shell, startupinfo, creationflags, shell,
p2cread, p2cwrite, p2cread, p2cwrite,
c2pread, c2pwrite, c2pread, c2pwrite,
errread, errwrite) errread, errwrite,
restore_signals, start_new_session)
except: except:
# Cleanup if the child failed starting. # Cleanup if the child failed starting.
# (gevent: New in python3, but reported as gevent bug in #347. # (gevent: New in python3, but reported as gevent bug in #347.
...@@ -613,13 +632,16 @@ class Popen(object): ...@@ -613,13 +632,16 @@ class Popen(object):
return w9xpopen return w9xpopen
def _execute_child(self, args, executable, preexec_fn, close_fds, def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines, pass_fds, cwd, env, universal_newlines,
startupinfo, creationflags, shell, startupinfo, creationflags, shell,
p2cread, p2cwrite, p2cread, p2cwrite,
c2pread, c2pwrite, c2pread, c2pwrite,
errread, errwrite): errread, errwrite,
restore_signals, start_new_session):
"""Execute program (MS Windows version)""" """Execute program (MS Windows version)"""
assert not pass_fds, "pass_fds not supported on Windows."
if not isinstance(args, string_types): if not isinstance(args, string_types):
args = list2cmdline(args) args = list2cmdline(args)
...@@ -833,13 +855,36 @@ class Popen(object): ...@@ -833,13 +855,36 @@ class Popen(object):
self._set_cloexec_flag(w) self._set_cloexec_flag(w)
return r, w return r, w
def _close_fds(self, but): def _close_fds(self, keep):
# `keep` is a set of fds, so we
# use os.closerange from 3 to min(keep)
# and then from max(keep + 1) to MAXFD and
# loop through filling in the gaps.
# Under new python versions, we need to explicitly set
# passed fds to be inheritable or they will go away on exec
if hasattr(os, 'set_inheritable'):
set_inheritable = os.set_inheritable
else:
set_inheritable = lambda i, v: True
if hasattr(os, 'closerange'): if hasattr(os, 'closerange'):
os.closerange(3, but) keep = sorted(keep)
os.closerange(but + 1, MAXFD) min_keep = min(keep)
max_keep = max(keep)
os.closerange(3, min_keep)
os.closerange(max_keep + 1, MAXFD)
for i in xrange(min_keep, max_keep):
if i in keep:
set_inheritable(i, True)
continue
try:
os.close(i)
except:
pass
else: else:
for i in xrange(3, MAXFD): for i in xrange(3, MAXFD):
if i == but: if i in keep:
set_inheritable(i, True)
continue continue
try: try:
os.close(i) os.close(i)
...@@ -847,11 +892,12 @@ class Popen(object): ...@@ -847,11 +892,12 @@ class Popen(object):
pass pass
def _execute_child(self, args, executable, preexec_fn, close_fds, def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines, pass_fds, cwd, env, universal_newlines,
startupinfo, creationflags, shell, startupinfo, creationflags, shell,
p2cread, p2cwrite, p2cread, p2cwrite,
c2pread, c2pwrite, c2pread, c2pwrite,
errread, errwrite): errread, errwrite,
restore_signals, start_new_session):
"""Execute program (POSIX version)""" """Execute program (POSIX version)"""
if PY3 and isinstance(args, (str, bytes)): if PY3 and isinstance(args, (str, bytes)):
...@@ -936,16 +982,33 @@ class Popen(object): ...@@ -936,16 +982,33 @@ class Popen(object):
os.close(fd) os.close(fd)
closed.add(fd) closed.add(fd)
# Close all other fds, if asked for
if close_fds:
self._close_fds(but=errpipe_write)
if cwd is not None: if cwd is not None:
os.chdir(cwd) os.chdir(cwd)
if preexec_fn: if preexec_fn:
preexec_fn() preexec_fn()
# Close all other fds, if asked for. This must be done
# after preexec_fn runs.
if close_fds:
fds_to_keep = set(pass_fds)
fds_to_keep.add(errpipe_write)
self._close_fds(fds_to_keep)
elif hasattr(os, 'get_inheritable'):
# close_fds was false, and we're on
# Python 3.4 or newer, so "all file
# descriptors except standard streams
# are closed, and inheritable handles
# are only inherited if the close_fds
# parameter is False."
for i in xrange(3, MAXFD):
try:
if i == errpipe_write or os.get_inheritable(i):
continue
os.close(i)
except:
pass
if env is None: if env is None:
os.execvp(executable, args) os.execvp(executable, args)
else: else:
......
...@@ -359,7 +359,7 @@ class ProcessTestCase(BaseTestCase): ...@@ -359,7 +359,7 @@ class ProcessTestCase(BaseTestCase):
# is relative. # is relative.
python_dir, python_base = self._split_python_path() python_dir, python_base = self._split_python_path()
rel_python = os.path.join(os.curdir, python_base) rel_python = os.path.join(os.curdir, python_base)
with support.temp_cwd('test_cwd_with_relative_arg') as wrong_dir: # gevent: use distinct name, avoid Travis CI failure with support.temp_cwd('test_cwd_with_relative_arg', quiet=True) as wrong_dir: # gevent: use distinct name, avoid Travis CI failure
# Before calling with the correct cwd, confirm that the call fails # Before calling with the correct cwd, confirm that the call fails
# without cwd and with the wrong cwd. # without cwd and with the wrong cwd.
self.assertRaises(FileNotFoundError, subprocess.Popen, self.assertRaises(FileNotFoundError, subprocess.Popen,
...@@ -376,7 +376,7 @@ class ProcessTestCase(BaseTestCase): ...@@ -376,7 +376,7 @@ class ProcessTestCase(BaseTestCase):
python_dir, python_base = self._split_python_path() python_dir, python_base = self._split_python_path()
rel_python = os.path.join(os.curdir, python_base) rel_python = os.path.join(os.curdir, python_base)
doesntexist = "somethingyoudonthave" doesntexist = "somethingyoudonthave"
with support.temp_cwd('test_cwd_with_relative_executable') as wrong_dir: # gevent: use distinct name, avoid Travis CI failure with support.temp_cwd('test_cwd_with_relative_executable', quiet=True) as wrong_dir: # gevent: use distinct name, avoid Travis CI failure
# Before calling with the correct cwd, confirm that the call fails # Before calling with the correct cwd, confirm that the call fails
# without cwd and with the wrong cwd. # without cwd and with the wrong cwd.
self.assertRaises(FileNotFoundError, subprocess.Popen, self.assertRaises(FileNotFoundError, subprocess.Popen,
......
...@@ -218,11 +218,6 @@ if sys.version_info[:2] >= (3, 4): ...@@ -218,11 +218,6 @@ if sys.version_info[:2] >= (3, 4):
# Our monkey patch waits for the process with a watcher and so detects # Our monkey patch waits for the process with a watcher and so detects
# the exit before the normal polling mechanism would # the exit before the normal polling mechanism would
'test_subprocess.POSIXProcessTestCase.test_close_fds',
'test_subprocess.POSIXProcessTestCase.test_pass_fds',
'test_subprocess.POSIXProcessTestCase.test_pass_fds_inheritable',
# XXX: We don't implement the pass_fds option yet
'test_subprocess.POSIXProcessTestCase.test_restore_signals', 'test_subprocess.POSIXProcessTestCase.test_restore_signals',
# XXX: We don't implement the restore_signals option yet # XXX: We don't implement the restore_signals option yet
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment