summaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/Lib/subprocess.py
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/tools/python3/Lib/subprocess.py')
-rw-r--r--contrib/tools/python3/Lib/subprocess.py140
1 files changed, 85 insertions, 55 deletions
diff --git a/contrib/tools/python3/Lib/subprocess.py b/contrib/tools/python3/Lib/subprocess.py
index 3ec39ca3e61..3a8c7434d37 100644
--- a/contrib/tools/python3/Lib/subprocess.py
+++ b/contrib/tools/python3/Lib/subprocess.py
@@ -43,8 +43,10 @@ getstatusoutput(...): Runs a command in the shell, waits for it to complete,
import builtins
import errno
import io
+import locale
import os
import time
+import signal
import sys
import threading
import warnings
@@ -72,8 +74,8 @@ except ModuleNotFoundError:
else:
_mswindows = True
-# wasm32-emscripten and wasm32-wasi do not support processes
-_can_fork_exec = sys.platform not in {"emscripten", "wasi"}
+# some platforms do not support subprocesses
+_can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watchos"}
if _mswindows:
import _winapi
@@ -81,6 +83,7 @@ if _mswindows:
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
STD_ERROR_HANDLE, SW_HIDE,
STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW,
+ STARTF_FORCEONFEEDBACK, STARTF_FORCEOFFFEEDBACK,
ABOVE_NORMAL_PRIORITY_CLASS, BELOW_NORMAL_PRIORITY_CLASS,
HIGH_PRIORITY_CLASS, IDLE_PRIORITY_CLASS,
NORMAL_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS,
@@ -91,6 +94,7 @@ if _mswindows:
"STD_INPUT_HANDLE", "STD_OUTPUT_HANDLE",
"STD_ERROR_HANDLE", "SW_HIDE",
"STARTF_USESTDHANDLES", "STARTF_USESHOWWINDOW",
+ "STARTF_FORCEONFEEDBACK", "STARTF_FORCEOFFFEEDBACK",
"STARTUPINFO",
"ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS",
"HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS",
@@ -101,18 +105,22 @@ else:
if _can_fork_exec:
from _posixsubprocess import fork_exec as _fork_exec
# used in methods that are called by __del__
- _waitpid = os.waitpid
- _waitstatus_to_exitcode = os.waitstatus_to_exitcode
- _WIFSTOPPED = os.WIFSTOPPED
- _WSTOPSIG = os.WSTOPSIG
- _WNOHANG = os.WNOHANG
+ class _del_safe:
+ waitpid = os.waitpid
+ waitstatus_to_exitcode = os.waitstatus_to_exitcode
+ WIFSTOPPED = os.WIFSTOPPED
+ WSTOPSIG = os.WSTOPSIG
+ WNOHANG = os.WNOHANG
+ ECHILD = errno.ECHILD
else:
- _fork_exec = None
- _waitpid = None
- _waitstatus_to_exitcode = None
- _WIFSTOPPED = None
- _WSTOPSIG = None
- _WNOHANG = None
+ class _del_safe:
+ waitpid = None
+ waitstatus_to_exitcode = None
+ WIFSTOPPED = None
+ WSTOPSIG = None
+ WNOHANG = None
+ ECHILD = errno.ECHILD
+
import select
import selectors
@@ -136,8 +144,6 @@ class CalledProcessError(SubprocessError):
def __str__(self):
if self.returncode and self.returncode < 0:
- # Lazy import to improve module import time
- import signal
try:
return "Command '%s' died with %r." % (
self.cmd, signal.Signals(-self.returncode))
@@ -346,7 +352,7 @@ def _args_from_interpreter_flags():
if dev_mode:
args.extend(('-X', 'dev'))
for opt in ('faulthandler', 'tracemalloc', 'importtime',
- 'frozen_modules', 'showrefcount', 'utf8'):
+ 'frozen_modules', 'showrefcount', 'utf8', 'gil'):
if opt in xoptions:
value = xoptions[opt]
if value is True:
@@ -375,8 +381,6 @@ def _text_encoding():
if sys.flags.utf8_mode:
return "utf-8"
else:
- # Lazy import to improve module import time
- import locale
return locale.getencoding()
@@ -711,6 +715,9 @@ def _use_posix_spawn():
# os.posix_spawn() is not available
return False
+ if ((_env := os.environ.get('_PYTHON_SUBPROCESS_USE_POSIX_SPAWN')) in ('0', '1')):
+ return bool(int(_env))
+
if sys.platform in ('darwin', 'sunos5'):
# posix_spawn() is a syscall on both macOS and Solaris,
# and properly reports errors
@@ -746,6 +753,7 @@ def _use_posix_spawn():
# guarantee the given libc/syscall API will be used.
_USE_POSIX_SPAWN = _use_posix_spawn()
_USE_VFORK = True
+_HAVE_POSIX_SPAWN_CLOSEFROM = hasattr(os, 'POSIX_SPAWN_CLOSEFROM')
class Popen:
@@ -836,6 +844,9 @@ class Popen:
if not isinstance(bufsize, int):
raise TypeError("bufsize must be an integer")
+ if stdout is STDOUT:
+ raise ValueError("STDOUT can only be used for stderr")
+
if pipesize is None:
pipesize = -1 # Restore default
if not isinstance(pipesize, int):
@@ -1226,8 +1237,11 @@ class Popen:
finally:
self._communication_started = True
-
- sts = self.wait(timeout=self._remaining_time(endtime))
+ try:
+ sts = self.wait(timeout=self._remaining_time(endtime))
+ except TimeoutExpired as exc:
+ exc.timeout = timeout
+ raise
return (stdout, stderr)
@@ -1602,6 +1616,10 @@ class Popen:
fh.close()
+ def _writerthread(self, input):
+ self._stdin_write(input)
+
+
def _communicate(self, input, endtime, orig_timeout):
# Start reader threads feeding into a list hanging off of this
# object, unless they've already been started.
@@ -1620,8 +1638,23 @@ class Popen:
self.stderr_thread.daemon = True
self.stderr_thread.start()
- if self.stdin:
- self._stdin_write(input)
+ # Start writer thread to send input to stdin, unless already
+ # started. The thread writes input and closes stdin when done,
+ # or continues in the background on timeout.
+ if self.stdin and not hasattr(self, "_stdin_thread"):
+ self._stdin_thread = \
+ threading.Thread(target=self._writerthread,
+ args=(input,))
+ self._stdin_thread.daemon = True
+ self._stdin_thread.start()
+
+ # Wait for the writer thread, or time out. If we time out, the
+ # thread remains writing and the fd left open in case the user
+ # calls communicate again.
+ if hasattr(self, "_stdin_thread"):
+ self._stdin_thread.join(self._remaining_time(endtime))
+ if self._stdin_thread.is_alive():
+ raise TimeoutExpired(self.args, orig_timeout)
# Wait for the reader threads, or time out. If we time out, the
# threads remain reading and the fds left open in case the user
@@ -1657,9 +1690,6 @@ class Popen:
# Don't signal a process that we know has already died.
if self.returncode is not None:
return
-
- # Lazy import to improve module import time
- import signal
if sig == signal.SIGTERM:
self.terminate()
elif sig == signal.CTRL_C_EVENT:
@@ -1754,19 +1784,13 @@ class Popen:
errread, errwrite)
- def _posix_spawn(self, args, executable, env, restore_signals,
+ def _posix_spawn(self, args, executable, env, restore_signals, close_fds,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite):
"""Execute program using os.posix_spawn()."""
- if env is None:
- env = os.environ
-
kwargs = {}
if restore_signals:
- # Lazy import to improve module import time
- import signal
-
# See _Py_RestoreSignals() in Python/pylifecycle.c
sigset = []
for signame in ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ'):
@@ -1786,6 +1810,10 @@ class Popen:
):
if fd != -1:
file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2))
+
+ if close_fds:
+ file_actions.append((os.POSIX_SPAWN_CLOSEFROM, 3))
+
if file_actions:
kwargs['file_actions'] = file_actions
@@ -1833,7 +1861,7 @@ class Popen:
if (_USE_POSIX_SPAWN
and os.path.dirname(executable)
and preexec_fn is None
- and not close_fds
+ and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM)
and not pass_fds
and cwd is None
and (p2cread == -1 or p2cread > 2)
@@ -1845,7 +1873,7 @@ class Popen:
and gids is None
and uid is None
and umask < 0):
- self._posix_spawn(args, executable, env, restore_signals,
+ self._posix_spawn(args, executable, env, restore_signals, close_fds,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite)
@@ -1966,20 +1994,16 @@ class Popen:
raise child_exception_type(err_msg)
- def _handle_exitstatus(self, sts,
- _waitstatus_to_exitcode=_waitstatus_to_exitcode,
- _WIFSTOPPED=_WIFSTOPPED,
- _WSTOPSIG=_WSTOPSIG):
+ def _handle_exitstatus(self, sts, _del_safe=_del_safe):
"""All callers to this function MUST hold self._waitpid_lock."""
# This method is called (indirectly) by __del__, so it cannot
# refer to anything outside of its local scope.
- if _WIFSTOPPED(sts):
- self.returncode = -_WSTOPSIG(sts)
+ if _del_safe.WIFSTOPPED(sts):
+ self.returncode = -_del_safe.WSTOPSIG(sts)
else:
- self.returncode = _waitstatus_to_exitcode(sts)
+ self.returncode = _del_safe.waitstatus_to_exitcode(sts)
- def _internal_poll(self, _deadstate=None, _waitpid=_waitpid,
- _WNOHANG=_WNOHANG, _ECHILD=errno.ECHILD):
+ def _internal_poll(self, _deadstate=None, _del_safe=_del_safe):
"""Check if child process has terminated. Returns returncode
attribute.
@@ -1995,13 +2019,13 @@ class Popen:
try:
if self.returncode is not None:
return self.returncode # Another thread waited.
- pid, sts = _waitpid(self.pid, _WNOHANG)
+ pid, sts = _del_safe.waitpid(self.pid, _del_safe.WNOHANG)
if pid == self.pid:
self._handle_exitstatus(sts)
except OSError as e:
if _deadstate is not None:
self.returncode = _deadstate
- elif e.errno == _ECHILD:
+ elif e.errno == _del_safe.ECHILD:
# This happens if SIGCLD is set to be ignored or
# waiting for child processes has otherwise been
# disabled for our process. This child is dead, we
@@ -2075,6 +2099,10 @@ class Popen:
self.stdin.flush()
except BrokenPipeError:
pass # communicate() must ignore BrokenPipeError.
+ except ValueError:
+ # ignore ValueError: I/O operation on closed file.
+ if not self.stdin.closed:
+ raise
if not input:
try:
self.stdin.close()
@@ -2100,10 +2128,13 @@ class Popen:
self._save_input(input)
if self._input:
- input_view = memoryview(self._input)
+ if not isinstance(self._input, memoryview):
+ input_view = memoryview(self._input)
+ else:
+ input_view = self._input.cast("b") # byte input required
with _PopenSelector() as selector:
- if self.stdin and input:
+ if self.stdin and not self.stdin.closed and self._input:
selector.register(self.stdin, selectors.EVENT_WRITE)
if self.stdout and not self.stdout.closed:
selector.register(self.stdout, selectors.EVENT_READ)
@@ -2112,7 +2143,7 @@ class Popen:
while selector.get_map():
timeout = self._remaining_time(endtime)
- if timeout is not None and timeout < 0:
+ if timeout is not None and timeout <= 0:
self._check_timeout(endtime, orig_timeout,
stdout, stderr,
skip_check_and_raise=True)
@@ -2136,7 +2167,7 @@ class Popen:
selector.unregister(key.fileobj)
key.fileobj.close()
else:
- if self._input_offset >= len(self._input):
+ if self._input_offset >= len(input_view):
selector.unregister(key.fileobj)
key.fileobj.close()
elif key.fileobj in (self.stdout, self.stderr):
@@ -2145,8 +2176,11 @@ class Popen:
selector.unregister(key.fileobj)
key.fileobj.close()
self._fileobj2output[key.fileobj].append(data)
-
- self.wait(timeout=self._remaining_time(endtime))
+ try:
+ self.wait(timeout=self._remaining_time(endtime))
+ except TimeoutExpired as exc:
+ exc.timeout = orig_timeout
+ raise
# All data exchanged. Translate lists into strings.
if stdout is not None:
@@ -2216,13 +2250,9 @@ class Popen:
def terminate(self):
"""Terminate the process with SIGTERM
"""
- # Lazy import to improve module import time
- import signal
self.send_signal(signal.SIGTERM)
def kill(self):
"""Kill the process with SIGKILL
"""
- # Lazy import to improve module import time
- import signal
self.send_signal(signal.SIGKILL)