mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
removed subproc threadability
This commit is contained in:
parent
537a29a0ef
commit
438f0c348b
3 changed files with 16 additions and 359 deletions
|
@ -13,7 +13,7 @@ import argparse
|
|||
import collections.abc as cabc
|
||||
|
||||
from xonsh.platform import ON_WINDOWS, ON_POSIX, pathbasename
|
||||
from xonsh.tools import executables_in
|
||||
from xonsh.tools import executables_in, print_warning
|
||||
from xonsh.lazyasd import lazyobject
|
||||
|
||||
|
||||
|
@ -30,7 +30,7 @@ class CommandsCache(cabc.Mapping):
|
|||
self._path_checksum = None
|
||||
self._alias_checksum = None
|
||||
self._path_mtime = -1
|
||||
self.threadable_predictors = default_threadable_predictors()
|
||||
self._threadable_predictors = {}
|
||||
|
||||
def __contains__(self, key):
|
||||
_ = self.all_commands
|
||||
|
@ -55,6 +55,13 @@ class CommandsCache(cabc.Mapping):
|
|||
"""Returns whether the cache is populated or not."""
|
||||
return len(self._cmds_cache) == 0
|
||||
|
||||
@property
|
||||
def threadable_predictors(self):
|
||||
print_warning("CommandsCache.threadable_predictors has been deprecated "
|
||||
"because it is no longer needed. Please remove downstream "
|
||||
"references to it. These operations will have no effect.")
|
||||
return self._threadable_predictors
|
||||
|
||||
@staticmethod
|
||||
def get_possible_names(name):
|
||||
"""Generates the possible `PATHEXT` extension variants of a given executable
|
||||
|
@ -212,311 +219,3 @@ class CommandsCache(cabc.Mapping):
|
|||
return (
|
||||
val == (name, True) and self.locate_binary(name, ignore_alias=True) is None
|
||||
)
|
||||
|
||||
def predict_threadable(self, cmd):
|
||||
"""Predicts whether a command list is able to be run on a background
|
||||
thread, rather than the main thread.
|
||||
"""
|
||||
predictor = self.get_predictor_threadable(cmd[0])
|
||||
return predictor(cmd[1:])
|
||||
|
||||
def get_predictor_threadable(self, cmd0):
|
||||
"""Return the predictor whether a command list is able to be run on a
|
||||
background thread, rather than the main thread.
|
||||
"""
|
||||
name = self.cached_name(cmd0)
|
||||
predictors = self.threadable_predictors
|
||||
if ON_WINDOWS:
|
||||
# On all names (keys) are stored in upper case so instead
|
||||
# we get the original cmd or alias name
|
||||
path, _ = self.lazyget(name, (None, None))
|
||||
if path is None:
|
||||
return predict_true
|
||||
else:
|
||||
name = pathbasename(path)
|
||||
if name not in predictors:
|
||||
pre, ext = os.path.splitext(name)
|
||||
if pre in predictors:
|
||||
predictors[name] = predictors[pre]
|
||||
if name not in predictors:
|
||||
predictors[name] = self.default_predictor(name, cmd0)
|
||||
predictor = predictors[name]
|
||||
return predictor
|
||||
|
||||
#
|
||||
# Background Predictors (as methods)
|
||||
#
|
||||
|
||||
def default_predictor(self, name, cmd0):
|
||||
"""Default predictor, using predictor from original command if the
|
||||
command is an alias, elseif build a predictor based on binary analysis
|
||||
on POSIX, else return predict_true.
|
||||
"""
|
||||
# alias stuff
|
||||
if not os.path.isabs(cmd0) and os.sep not in cmd0:
|
||||
alss = getattr(builtins, "aliases", dict())
|
||||
if cmd0 in alss:
|
||||
return self.default_predictor_alias(cmd0)
|
||||
|
||||
# other default stuff
|
||||
if ON_POSIX:
|
||||
return self.default_predictor_readbin(
|
||||
name, cmd0, timeout=0.1, failure=predict_true
|
||||
)
|
||||
else:
|
||||
return predict_true
|
||||
|
||||
def default_predictor_alias(self, cmd0):
|
||||
alias_recursion_limit = (
|
||||
10 # this limit is se to handle infinite loops in aliases definition
|
||||
)
|
||||
first_args = [] # contains in reverse order args passed to the aliased command
|
||||
alss = getattr(builtins, "aliases", dict())
|
||||
while cmd0 in alss:
|
||||
alias_name = alss[cmd0]
|
||||
if isinstance(alias_name, (str, bytes)) or not isinstance(
|
||||
alias_name, cabc.Sequence
|
||||
):
|
||||
return predict_true
|
||||
for arg in alias_name[:0:-1]:
|
||||
first_args.insert(0, arg)
|
||||
if cmd0 == alias_name[0]:
|
||||
# it is a self-alias stop recursion immediatly
|
||||
return predict_true
|
||||
cmd0 = alias_name[0]
|
||||
alias_recursion_limit -= 1
|
||||
if alias_recursion_limit == 0:
|
||||
return predict_true
|
||||
predictor_cmd0 = self.get_predictor_threadable(cmd0)
|
||||
return lambda cmd1: predictor_cmd0(first_args[::-1] + cmd1)
|
||||
|
||||
def default_predictor_readbin(self, name, cmd0, timeout, failure):
|
||||
"""Make a default predictor by
|
||||
analyzing the content of the binary. Should only works on POSIX.
|
||||
Return failure if the analysis fails.
|
||||
"""
|
||||
fname = cmd0 if os.path.isabs(cmd0) else None
|
||||
fname = cmd0 if fname is None and os.sep in cmd0 else fname
|
||||
fname = self.lazy_locate_binary(name) if fname is None else fname
|
||||
|
||||
if fname is None:
|
||||
return failure
|
||||
if not os.path.isfile(fname):
|
||||
return failure
|
||||
|
||||
try:
|
||||
fd = os.open(fname, os.O_RDONLY | os.O_NONBLOCK)
|
||||
except Exception:
|
||||
return failure # opening error
|
||||
|
||||
search_for = {
|
||||
(b"ncurses",): [False],
|
||||
(b"libgpm",): [False],
|
||||
(b"isatty", b"tcgetattr", b"tcsetattr"): [False, False, False],
|
||||
}
|
||||
tstart = time.time()
|
||||
block = b""
|
||||
while time.time() < tstart + timeout:
|
||||
previous_block = block
|
||||
try:
|
||||
block = os.read(fd, 2048)
|
||||
except Exception:
|
||||
# should not occur, except e.g. if a file is deleted a a dir is
|
||||
# created with the same name between os.path.isfile and os.open
|
||||
os.close(fd)
|
||||
return failure
|
||||
if len(block) == 0:
|
||||
os.close(fd)
|
||||
return predict_true # no keys of search_for found
|
||||
analyzed_block = previous_block + block
|
||||
for k, v in search_for.items():
|
||||
for i in range(len(k)):
|
||||
if v[i]:
|
||||
continue
|
||||
if k[i] in analyzed_block:
|
||||
v[i] = True
|
||||
if all(v):
|
||||
os.close(fd)
|
||||
return predict_false # use one key of search_for
|
||||
os.close(fd)
|
||||
return failure # timeout
|
||||
|
||||
|
||||
#
|
||||
# Background Predictors
|
||||
#
|
||||
|
||||
|
||||
def predict_true(args):
|
||||
"""Always say the process is threadable."""
|
||||
return True
|
||||
|
||||
|
||||
def predict_false(args):
|
||||
"""Never say the process is threadable."""
|
||||
return False
|
||||
|
||||
|
||||
@lazyobject
|
||||
def SHELL_PREDICTOR_PARSER():
|
||||
p = argparse.ArgumentParser("shell", add_help=False)
|
||||
p.add_argument("-c", nargs="?", default=None)
|
||||
p.add_argument("filename", nargs="?", default=None)
|
||||
return p
|
||||
|
||||
|
||||
def predict_shell(args):
|
||||
"""Predict the backgroundability of the normal shell interface, which
|
||||
comes down to whether it is being run in subproc mode.
|
||||
"""
|
||||
ns, _ = SHELL_PREDICTOR_PARSER.parse_known_args(args)
|
||||
if ns.c is None and ns.filename is None:
|
||||
pred = False
|
||||
else:
|
||||
pred = True
|
||||
return pred
|
||||
|
||||
|
||||
@lazyobject
|
||||
def HELP_VER_PREDICTOR_PARSER():
|
||||
p = argparse.ArgumentParser("cmd", add_help=False)
|
||||
p.add_argument("-h", "--help", dest="help", nargs="?", action="store", default=None)
|
||||
p.add_argument(
|
||||
"-v", "-V", "--version", dest="version", nargs="?", action="store", default=None
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def predict_help_ver(args):
|
||||
"""Predict the backgroundability of commands that have help & version
|
||||
switches: -h, --help, -v, -V, --version. If either of these options is
|
||||
present, the command is assumed to print to stdout normally and is therefore
|
||||
threadable. Otherwise, the command is assumed to not be threadable.
|
||||
This is useful for commands, like top, that normally enter alternate mode
|
||||
but may not in certain circumstances.
|
||||
"""
|
||||
ns, _ = HELP_VER_PREDICTOR_PARSER.parse_known_args(args)
|
||||
pred = ns.help is not None or ns.version is not None
|
||||
return pred
|
||||
|
||||
|
||||
@lazyobject
|
||||
def HG_PREDICTOR_PARSER():
|
||||
p = argparse.ArgumentParser("hg", add_help=False)
|
||||
p.add_argument("command")
|
||||
p.add_argument(
|
||||
"-i", "--interactive", action="store_true", default=False, dest="interactive"
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def predict_hg(args):
|
||||
"""Predict if mercurial is about to be run in interactive mode.
|
||||
If it is interactive, predict False. If it isn't, predict True.
|
||||
Also predict False for certain commands, such as split.
|
||||
"""
|
||||
ns, _ = HG_PREDICTOR_PARSER.parse_known_args(args)
|
||||
if ns.command == "split":
|
||||
return False
|
||||
else:
|
||||
return not ns.interactive
|
||||
|
||||
|
||||
def predict_env(args):
|
||||
"""Predict if env is launching a threadable command or not.
|
||||
The launched command is extracted from env args, and the predictor of
|
||||
lauched command is used."""
|
||||
|
||||
for i in range(len(args)):
|
||||
if args[i] and args[i][0] != "-" and "=" not in args[i]:
|
||||
# args[i] is the command and the following is its arguments
|
||||
# so args[i:] is used to predict if the command is threadable
|
||||
return builtins.__xonsh__.commands_cache.predict_threadable(args[i:])
|
||||
return True
|
||||
|
||||
|
||||
def default_threadable_predictors():
|
||||
"""Generates a new defaultdict for known threadable predictors.
|
||||
The default is to predict true.
|
||||
"""
|
||||
# alphabetical, for what it is worth.
|
||||
predictors = {
|
||||
"asciinema": predict_help_ver,
|
||||
"aurman": predict_false,
|
||||
"awk": predict_true,
|
||||
"bash": predict_shell,
|
||||
"cat": predict_false,
|
||||
"clear": predict_false,
|
||||
"cls": predict_false,
|
||||
"cmd": predict_shell,
|
||||
"cryptop": predict_false,
|
||||
"cryptsetup": predict_true,
|
||||
"csh": predict_shell,
|
||||
"curl": predict_true,
|
||||
"elvish": predict_shell,
|
||||
"emacsclient": predict_false,
|
||||
"env": predict_env,
|
||||
"ex": predict_false,
|
||||
"fish": predict_shell,
|
||||
"gawk": predict_true,
|
||||
"ghci": predict_help_ver,
|
||||
"git": predict_true,
|
||||
"gvim": predict_help_ver,
|
||||
"hg": predict_hg,
|
||||
"htop": predict_help_ver,
|
||||
"ipython": predict_shell,
|
||||
"julia": predict_shell,
|
||||
"ksh": predict_shell,
|
||||
"less": predict_help_ver,
|
||||
"ls": predict_true,
|
||||
"man": predict_help_ver,
|
||||
"mc": predict_false,
|
||||
"more": predict_help_ver,
|
||||
"mutt": predict_help_ver,
|
||||
"mvim": predict_help_ver,
|
||||
"nano": predict_help_ver,
|
||||
"nmcli": predict_true,
|
||||
"nvim": predict_false,
|
||||
"percol": predict_false,
|
||||
"ponysay": predict_help_ver,
|
||||
"psql": predict_false,
|
||||
"push": predict_shell,
|
||||
"pv": predict_false,
|
||||
"python": predict_shell,
|
||||
"python2": predict_shell,
|
||||
"python3": predict_shell,
|
||||
"ranger": predict_help_ver,
|
||||
"repo": predict_help_ver,
|
||||
"rview": predict_false,
|
||||
"rvim": predict_false,
|
||||
"rwt": predict_shell,
|
||||
"scp": predict_false,
|
||||
"sh": predict_shell,
|
||||
"ssh": predict_false,
|
||||
"startx": predict_false,
|
||||
"sudo": predict_help_ver,
|
||||
"sudoedit": predict_help_ver,
|
||||
"systemctl": predict_true,
|
||||
"tcsh": predict_shell,
|
||||
"telnet": predict_false,
|
||||
"top": predict_help_ver,
|
||||
"tput": predict_false,
|
||||
"udisksctl": predict_true,
|
||||
"unzip": predict_true,
|
||||
"vi": predict_false,
|
||||
"view": predict_false,
|
||||
"vim": predict_false,
|
||||
"vimpager": predict_help_ver,
|
||||
"weechat": predict_help_ver,
|
||||
"wget": predict_true,
|
||||
"xclip": predict_help_ver,
|
||||
"xdg-open": predict_false,
|
||||
"xo": predict_help_ver,
|
||||
"xon.sh": predict_shell,
|
||||
"xonsh": predict_shell,
|
||||
"yes": predict_false,
|
||||
"zip": predict_true,
|
||||
"zipinfo": predict_true,
|
||||
"zsh": predict_shell,
|
||||
}
|
||||
return predictors
|
||||
|
|
|
@ -1336,35 +1336,6 @@ def DEFAULT_VARS():
|
|||
"not always happen.",
|
||||
doc_configurable=False,
|
||||
),
|
||||
"THREAD_SUBPROCS": Var(
|
||||
is_bool_or_none,
|
||||
to_bool_or_none,
|
||||
bool_or_none_to_str,
|
||||
True if not ON_CYGWIN else False,
|
||||
"Whether or not to try to run subrocess mode in a Python thread, "
|
||||
"when applicable. There are various trade-offs, which normally "
|
||||
"affects only interactive sessions.\n\nWhen True:\n\n"
|
||||
"* Xonsh is able capture & store the stdin, stdout, and stderr \n"
|
||||
" of threadable subprocesses.\n"
|
||||
"* However, stopping threaded subprocs with ^Z (i.e. ``SIGTSTP``)\n"
|
||||
" is disabled as it causes deadlocked terminals.\n"
|
||||
" ``SIGTSTP`` may still be issued and only the physical pressing\n"
|
||||
" of ``Ctrl+Z`` is ignored.\n"
|
||||
"* Threadable commands are run with ``PopenThread`` and threadable \n"
|
||||
" aliases are run with ``ProcProxyThread``.\n\n"
|
||||
"When False:\n\n"
|
||||
"* Xonsh may not be able to capture stdin, stdout, and stderr streams \n"
|
||||
" unless explicitly asked to do so.\n"
|
||||
"* Stopping the thread with ``Ctrl+Z`` yields to job control.\n"
|
||||
"* Threadable commands are run with ``Popen`` and threadable \n"
|
||||
" alias are run with ``ProcProxy``.\n\n"
|
||||
"The desired effect is often up to the command, user, or use case.\n\n"
|
||||
"None values are for internal use only and are used to turn off "
|
||||
"threading when loading xonshrc files. This is done because Bash "
|
||||
"was automatically placing new xonsh instances in the background "
|
||||
"at startup when threadable subprocs were used. Please see "
|
||||
"https://github.com/xonsh/xonsh/pull/3705 for more information.\n",
|
||||
),
|
||||
"TITLE": Var(
|
||||
is_string,
|
||||
ensure_string,
|
||||
|
@ -1459,8 +1430,7 @@ def DEFAULT_VARS():
|
|||
default_xonshrc,
|
||||
"A list of the locations of run control files, if they exist. User "
|
||||
"defined run control file will supersede values set in system-wide "
|
||||
"control file if there is a naming collision. $THREAD_SUBPROCS=None "
|
||||
"when reading in run control files.",
|
||||
"control file if there is a naming collision.",
|
||||
doc_default=(
|
||||
"On Linux & Mac OSX: ``['/etc/xonshrc', '~/.config/xonsh/rc.xsh', '~/.xonshrc']``\n"
|
||||
"\nOn Windows: "
|
||||
|
@ -2195,8 +2165,6 @@ def xonshrc_context(rcfiles=None, execer=None, ctx=None, env=None, login=True):
|
|||
ctx = {} if ctx is None else ctx
|
||||
if rcfiles is None:
|
||||
return env
|
||||
orig_thread = env.get("THREAD_SUBPROCS")
|
||||
env["THREAD_SUBPROCS"] = None
|
||||
env["XONSHRC"] = tuple(rcfiles)
|
||||
for rcfile in rcfiles:
|
||||
if not os.path.isfile(rcfile):
|
||||
|
@ -2205,8 +2173,6 @@ def xonshrc_context(rcfiles=None, execer=None, ctx=None, env=None, login=True):
|
|||
_, ext = os.path.splitext(rcfile)
|
||||
status = xonsh_script_run_control(rcfile, ctx, env, execer=execer, login=login)
|
||||
loaded.append(status)
|
||||
if env["THREAD_SUBPROCS"] is None:
|
||||
env["THREAD_SUBPROCS"] = orig_thread
|
||||
return ctx
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import xonsh.lazyimps as xli
|
|||
import xonsh.jobs as xj
|
||||
|
||||
from xonsh.procs.readers import ConsoleParallelReader
|
||||
from xonsh.procs.posix import PopenThread
|
||||
from xonsh.procs.proxies import ProcProxy, ProcProxyThread
|
||||
from xonsh.procs.pipelines import (
|
||||
pause_call_resume,
|
||||
|
@ -358,7 +357,7 @@ class SubprocSpec:
|
|||
self.binary_loc = None
|
||||
self.is_proxy = False
|
||||
self.background = False
|
||||
self.threadable = True
|
||||
self.threadable = False
|
||||
self.pipeline_index = None
|
||||
self.last_in_pipeline = False
|
||||
self.captured_stdout = None
|
||||
|
@ -649,7 +648,7 @@ class SubprocSpec:
|
|||
return
|
||||
self.is_proxy = True
|
||||
env = builtins.__xonsh__.env
|
||||
thable = env.get("THREAD_SUBPROCS") and getattr(
|
||||
thable = getattr(
|
||||
alias, "__xonsh_threadable__", True
|
||||
)
|
||||
cls = ProcProxyThread if thable else ProcProxy
|
||||
|
@ -712,17 +711,9 @@ def _update_last_spec(last):
|
|||
if callable_alias:
|
||||
pass
|
||||
else:
|
||||
cmds_cache = builtins.__xonsh__.commands_cache
|
||||
thable = (
|
||||
env.get("THREAD_SUBPROCS")
|
||||
and cmds_cache.predict_threadable(last.args)
|
||||
and cmds_cache.predict_threadable(last.cmd)
|
||||
)
|
||||
if captured and thable:
|
||||
last.cls = PopenThread
|
||||
elif not thable:
|
||||
# foreground processes should use Popen
|
||||
last.threadable = False
|
||||
if captured:
|
||||
# update this when we have PTY server/client
|
||||
last.cls = subprocess.Popen
|
||||
if captured == "object" or captured == "hiddenobject":
|
||||
# CommandPipeline objects should not pipe stdout, stderr
|
||||
return
|
||||
|
@ -870,6 +861,7 @@ def run_subproc(cmds, captured=False, envs=None):
|
|||
# sure that the shell doesn't hang. This `pause_call_resume` invocation
|
||||
# does this
|
||||
pause_call_resume(proc, int)
|
||||
pause_call_resume(proc, int)
|
||||
# create command or return if backgrounding.
|
||||
if background:
|
||||
return
|
||||
|
|
Loading…
Add table
Reference in a new issue