diff options
author | Bernát Gábor <bgabor8@bloomberg.net> | 2021-02-15 07:44:21 +0000 |
---|---|---|
committer | Bernát Gábor <gaborjbernat@gmail.com> | 2021-02-15 07:52:20 +0000 |
commit | 098f047fae45362ef3f66589d31b5e40cd185185 (patch) | |
tree | 90e2dc4f8193fa9480b74c8a6cb12253c99da723 | |
parent | d13f172b37678422bfcdaaa9610b41ac633d5590 (diff) | |
download | tox-git-098f047fae45362ef3f66589d31b5e40cd185185.tar.gz |
Fix tox is not exiting because package env thread holding it up
Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net>
-rw-r--r-- | docs/changelog/1915.bugfix.rst | 2 | ||||
-rw-r--r-- | docs/changelog/1915.feature.rst | 3 | ||||
-rw-r--r-- | src/tox/execute/api.py | 25 | ||||
-rw-r--r-- | src/tox/execute/local_sub_process/__init__.py | 6 | ||||
-rw-r--r-- | src/tox/run.py | 7 | ||||
-rw-r--r-- | src/tox/session/cmd/run/common.py | 12 | ||||
-rw-r--r-- | src/tox/session/state.py | 3 | ||||
-rw-r--r-- | src/tox/tox_env/python/virtual_env/package/api.py | 21 | ||||
-rw-r--r-- | tests/execute/local_subprocess/test_local_subprocess.py | 5 | ||||
-rw-r--r-- | tox.ini | 4 |
10 files changed, 63 insertions, 25 deletions
diff --git a/docs/changelog/1915.bugfix.rst b/docs/changelog/1915.bugfix.rst new file mode 100644 index 00000000..d3e137a9 --- /dev/null +++ b/docs/changelog/1915.bugfix.rst @@ -0,0 +1,2 @@ +Fix a bug that caused tox to never finish when pulling configuration from a tox run environment that was never executed +- by :user:`gaborbernat`. diff --git a/docs/changelog/1915.feature.rst b/docs/changelog/1915.feature.rst new file mode 100644 index 00000000..562c9dfe --- /dev/null +++ b/docs/changelog/1915.feature.rst @@ -0,0 +1,3 @@ +The ``_TOX_SHOW_THREAD`` environment variable can be used to print alive threads when tox exists (useful to debug +when tox hangs because of some non-finished thread) and also now prints the pid of the local subprocess when reporting +the outcome of a execution - by :user:`gaborbernat`. diff --git a/src/tox/execute/api.py b/src/tox/execute/api.py index 338494e9..aa996ef2 100644 --- a/src/tox/execute/api.py +++ b/src/tox/execute/api.py @@ -7,7 +7,7 @@ import time from abc import ABC, abstractmethod from contextlib import contextmanager from types import TracebackType -from typing import Callable, Iterator, NoReturn, Optional, Sequence, Tuple, Type +from typing import Any, Callable, Dict, Iterator, NoReturn, Optional, Sequence, Tuple, Type from colorama import Fore @@ -57,6 +57,10 @@ class ExecuteStatus(ABC): def err(self) -> bytearray: return self._err.content + @property + def metadata(self) -> Dict[str, Any]: + return {} + class Execute(ABC): """Abstract API for execution of a tox environment""" @@ -79,7 +83,9 @@ class Execute(ABC): exit_code = status.exit_code finally: end = time.monotonic() - status.outcome = Outcome(request, show, exit_code, out_sync.text, err_sync.text, start, end, instance.cmd) + status.outcome = Outcome( + request, show, exit_code, out_sync.text, err_sync.text, start, end, instance.cmd, status.metadata + ) @abstractmethod def build_instance( @@ -138,6 +144,7 @@ class Outcome: start: float, end: float, cmd: Sequence[str], + metadata: Dict[str, Any], ): self.request = request self.show_on_standard = show_on_standard @@ -147,6 +154,7 @@ class Outcome: self.start = start self.end = end self.cmd = cmd + self.metadata = metadata def __bool__(self) -> bool: return self.exit_code == self.OK @@ -179,7 +187,18 @@ class Outcome: def log_run_done(self, lvl: int) -> None: req = self.request - LOGGER.log(lvl, "exit %s (%.2f seconds) %s> %s", self.exit_code, self.elapsed, req.cwd, req.shell_cmd) + metadata = "" + if self.metadata: + metadata = f" {', '.join(f'{k}={v}' for k, v in self.metadata.items())}" + LOGGER.log( + lvl, + "exit %s (%.2f seconds) %s> %s%s", + self.exit_code, + self.elapsed, + req.cwd, + req.shell_cmd, + metadata, + ) @property def elapsed(self) -> float: diff --git a/src/tox/execute/local_sub_process/__init__.py b/src/tox/execute/local_sub_process/__init__.py index ebb5b495..ed4f6690 100644 --- a/src/tox/execute/local_sub_process/__init__.py +++ b/src/tox/execute/local_sub_process/__init__.py @@ -6,7 +6,7 @@ import sys import time from subprocess import DEVNULL, PIPE, TimeoutExpired from types import TracebackType -from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, Type +from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type from ..api import Execute, ExecuteInstance, ExecuteStatus from ..request import ExecuteRequest, StdinSource @@ -134,6 +134,10 @@ class LocalSubprocessExecuteStatus(ExecuteStatus): def __repr__(self) -> str: return f"{self.__class__.__name__}(pid={self._process.pid}, returncode={self._process.returncode!r})" + @property + def metadata(self) -> Dict[str, Any]: + return {"pid": self._process.pid} if self._process.pid else {} + class LocalSubprocessExecuteFailedStatus(ExecuteStatus): def __init__(self, out: SyncWrite, err: SyncWrite, exit_code: Optional[int]) -> None: diff --git a/src/tox/run.py b/src/tox/run.py index aaeeb427..eb2222ee 100644 --- a/src/tox/run.py +++ b/src/tox/run.py @@ -1,5 +1,6 @@ """Main entry point for tox.""" import logging +import os import sys import time from itertools import chain @@ -27,6 +28,12 @@ def run(args: Optional[Sequence[str]] = None) -> None: raise except KeyboardInterrupt: result = -2 + finally: + if "_TOX_SHOW_THREAD" in os.environ: # pragma: no cover + import threading # pragma: no cover + + for thread in threading.enumerate(): # pragma: no cover + print(thread) # pragma: no cover raise SystemExit(result) diff --git a/src/tox/session/cmd/run/common.py b/src/tox/session/cmd/run/common.py index d39b0279..9869cbcb 100644 --- a/src/tox/session/cmd/run/common.py +++ b/src/tox/session/cmd/run/common.py @@ -202,11 +202,6 @@ def execute(state: State, max_workers: Optional[int], has_spinner: bool, live: b exit_code = report(state.options.start, ordered_results, state.options.is_colored) if has_previous: signal(SIGINT, previous) - if "_TOX_SHOW_THREAD" in os.environ: # pragma: no cover - import threading # pragma: no cover - - for thread in threading.enumerate(): # pragma: no cover - print(thread) # pragma: no cover return exit_code @@ -299,7 +294,12 @@ def _queue_and_wait( finally: executor.shutdown(wait=True) finally: - done.set() + try: + # call teardown - configuration only environments for example could not be finished + for _, tox_env in state.run_envs(): + tox_env.teardown() + finally: + done.set() def _handle_one_run_done(result: ToxEnvRunResult, spinner: ToxSpinner, state: State, live: bool) -> None: diff --git a/src/tox/session/state.py b/src/tox/session/state.py index 6ca22e65..d43813c8 100644 --- a/src/tox/session/state.py +++ b/src/tox/session/state.py @@ -126,6 +126,9 @@ class State: self._pkg_env[name] = packager, pkg_tox_env return pkg_tox_env + def run_envs(self) -> Iterator[Tuple[str, RunToxEnv]]: + yield from self._run_env.items() + @impl def tox_add_option(parser: "ToxParser") -> None: diff --git a/src/tox/tox_env/python/virtual_env/package/api.py b/src/tox/tox_env/python/virtual_env/package/api.py index e8c2e22f..60359df9 100644 --- a/src/tox/tox_env/python/virtual_env/package/api.py +++ b/src/tox/tox_env/python/virtual_env/package/api.py @@ -279,16 +279,17 @@ class Pep517VirtualEnvPackage(VirtualEnv, PythonPackage, Frontend): return env def teardown(self) -> None: - self.ref_count.decrement() - if self.ref_count.value == 0 and self._backend_executor is not None and self._teardown_done is False: - self._teardown_done = True - try: - if self.backend_executor.is_alive: - self._send("_exit") # try first on amicable shutdown - except SystemExit: # if already has been interrupted ignore - pass - finally: - self._backend_executor.close() + if not self._teardown_done: + self.ref_count.decrement() + if self.ref_count.value == 0 and self._backend_executor is not None and self._teardown_done is False: + self._teardown_done = True + try: + if self.backend_executor.is_alive: + self._send("_exit") # try first on amicable shutdown + except SystemExit: # if already has been interrupted ignore + pass + finally: + self._backend_executor.close() @contextmanager def _send_msg( diff --git a/tests/execute/local_subprocess/test_local_subprocess.py b/tests/execute/local_subprocess/test_local_subprocess.py index cb9e69f8..0f58ee1b 100644 --- a/tests/execute/local_subprocess/test_local_subprocess.py +++ b/tests/execute/local_subprocess/test_local_subprocess.py @@ -181,13 +181,14 @@ def test_local_execute_basic_fail(capsys: CaptureFixture, caplog: LogCaptureFixt assert len(caplog.records) == 1 record = caplog.records[0] assert record.levelno == logging.CRITICAL - assert record.msg == "exit %s (%.2f seconds) %s> %s" - _code, _duration, _cwd, _cmd = record.args + assert record.msg == "exit %s (%.2f seconds) %s> %s%s" + _code, _duration, _cwd, _cmd, _metadata = record.args assert _code == 3 assert _cwd == cwd assert _cmd == request.shell_cmd assert isinstance(_duration, float) assert _duration > 0 + assert _metadata.startswith(" pid=") def test_command_does_not_exist(caplog: LogCaptureFixture, os_env: Dict[str, str]) -> None: @@ -118,9 +118,8 @@ commands = [testenv:release] description = do a release, required posarg of the version number -passenv = - * basepython = python3.8 +skip_install = true deps = gitpython>=3.1 packaging>=20.9 @@ -133,7 +132,6 @@ description = dev environment with all deps at {envdir} usedevelop = true deps = {[testenv:release]deps} - setuptools_scm>=3 extras = docs testing |