From 33e33e60bea2874ac40997bfd4f4012a8c4a70a8 Mon Sep 17 00:00:00 2001 From: Daniel Holth Date: Thu, 2 Nov 2023 13:20:32 -0400 Subject: [PATCH] Restore CTRL-C handling by setting new flag in libmamba (#340) Co-authored-by: jaimergp Co-authored-by: Jannis Leidel --- conda_libmamba_solver/mamba_utils.py | 2 + news/339-pthread-sigmask | 19 +++++++++ pyproject.toml | 1 + tests/requirements.txt | 2 +- tests/test_workarounds.py | 61 ++++++++++++++++++++++++++-- 5 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 news/339-pthread-sigmask diff --git a/conda_libmamba_solver/mamba_utils.py b/conda_libmamba_solver/mamba_utils.py index eba7b395..0d879bed 100644 --- a/conda_libmamba_solver/mamba_utils.py +++ b/conda_libmamba_solver/mamba_utils.py @@ -85,6 +85,8 @@ def set_channel_priorities(index: Dict[str, "_ChannelRepoInfo"], has_priority: b def init_api_context() -> api.Context: + # This function has to be called BEFORE 1st initialization of the context + api.Context.use_default_signal_handler(False) api_ctx = api.Context() # Output params diff --git a/news/339-pthread-sigmask b/news/339-pthread-sigmask new file mode 100644 index 00000000..5fc9ee8a --- /dev/null +++ b/news/339-pthread-sigmask @@ -0,0 +1,19 @@ +### Enhancements + +* + +### Bug fixes + +* Do not use `libmamba`'s default signal handler so users can `Ctrl-C` from `conda`. (#337 via #340) + +### Deprecations + +* + +### Docs + +* + +### Other + +* diff --git a/pyproject.toml b/pyproject.toml index 6a4c55fe..5ac295be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ addopts = [ "--strict-markers", "--xdoctest-modules", "--xdoctest-style=google", + "--reruns=3", ] markers = [ diff --git a/tests/requirements.txt b/tests/requirements.txt index 6561555c..c8886926 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,6 @@ +conda-build conda-forge::pytest-xprocess conda-index -conda-build conda-lock # needed for many conda tests flask diff --git a/tests/test_workarounds.py b/tests/test_workarounds.py index b52bb891..df4c0697 100644 --- a/tests/test_workarounds.py +++ b/tests/test_workarounds.py @@ -1,9 +1,15 @@ # Copyright (C) 2022 Anaconda, Inc # Copyright (C) 2023 conda # SPDX-License-Identifier: BSD-3-Clause +import ctypes import json +import signal +import subprocess as sp import sys -from subprocess import PIPE, check_call, run +import time + +import pytest +from conda.common.compat import on_win def test_matchspec_star_version(): @@ -12,7 +18,7 @@ def test_matchspec_star_version(): We work around that with `.utils.safe_conda_build_form()`. Reported in https://github.com/conda/conda/issues/11347 """ - check_call( + sp.check_call( [ sys.executable, "-m", @@ -31,7 +37,7 @@ def test_matchspec_star_version(): def test_build_string_filters(): - process = run( + process = sp.run( [ sys.executable, "-m", @@ -44,7 +50,7 @@ def test_build_string_filters(): "numpy=*=*py38*", "--json", ], - stdout=PIPE, + stdout=sp.PIPE, text=True, ) print(process.stdout) @@ -56,3 +62,50 @@ def test_build_string_filters(): assert pkg["version"].startswith("3.8") if pkg["name"] == "numpy": assert "py38" in pkg["build_string"] + + +@pytest.mark.parametrize("stage", ["Collecting package metadata", "Solving environment"]) +def test_ctrl_c(stage): + p = sp.Popen( + [ + sys.executable, + "-m", + "conda", + "create", + "-p", + "UNUSED", + "--dry-run", + "--solver=libmamba", + "--override-channels", + "--channel=conda-forge", + "vaex", + ], + text=True, + stdout=sp.PIPE, + stderr=sp.PIPE, + ) + t0 = time.time() + while stage not in p.stdout.readline(): + time.sleep(0.1) + if time.time() - t0 > 30: + raise RuntimeError("Timeout") + + # works around Windows' awkward CTRL-C signal handling + # https://stackoverflow.com/a/64357453 + if on_win: + try: + kernel = ctypes.windll.kernel32 + kernel.FreeConsole() + kernel.AttachConsole(p.pid) + kernel.SetConsoleCtrlHandler(None, 1) + kernel.GenerateConsoleCtrlEvent(0, 0) + p.wait(timeout=30) + assert p.returncode != 0 + assert "KeyboardInterrupt" in p.stdout.read() + p.stderr.read() + finally: + kernel.SetConsoleCtrlHandler(None, 0) + else: + p.send_signal(signal.SIGINT) + p.wait(timeout=30) + assert p.returncode != 0 + assert "KeyboardInterrupt" in p.stdout.read() + p.stderr.read()