Skip to content

Commit e5d1a43

Browse files
committed
Fix CTRL+C not working on Windows
Seems sending CTRL_C event on Windows to a new process group does not work in general. It registers on cmd.exe and powershell.exe on its own, however is ignored on powershell when ran within Windows Terminal or Wezterm. Sending CTRL_BREAK seems to work, but on the later brings down the shell itself. However, Windows works differently with signals than Linux. On Linux only the root process gets the signal. On Windows when a signal is sent to a process all processes within that process group will get an instance of that signal. This means that on Windows we don't need to forward signals. All child automatically gets it. The only thing we do is escalate the unanswered interrupt to process kill (there's no SIGTERM on Windows). Signed-off-by: Bernát Gábor <[email protected]>
1 parent d6265f6 commit e5d1a43

File tree

3 files changed

+11
-14
lines changed

3 files changed

+11
-14
lines changed

docs/changelog/2159.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ``CTRL+C`` is not stopping the process on Windows - by :user:`gaborbernat`.

src/tox/execute/local_sub_process/__init__.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,16 @@
2323
from asyncio.windows_utils import Popen
2424
from signal import CTRL_C_EVENT as SIG_INTERRUPT
2525
from signal import SIGTERM
26-
from subprocess import CREATE_NEW_PROCESS_GROUP
2726

2827
from .read_via_thread_windows import ReadViaThreadWindows as ReadViaThread
2928

30-
CREATION_FLAGS = CREATE_NEW_PROCESS_GROUP # a custom flag needed for Windows signal send ability (CTRL+C)
3129
else: # pragma: win32 no cover
3230
from signal import SIGINT as SIG_INTERRUPT
3331
from signal import SIGKILL, SIGTERM
3432
from subprocess import Popen
3533

3634
from .read_via_thread_unix import ReadViaThreadUnix as ReadViaThread
3735

38-
CREATION_FLAGS = 0
39-
4036

4137
IS_WIN = sys.platform == "win32"
4238

@@ -67,9 +63,10 @@ def interrupt(self) -> None:
6763
msg = "requested interrupt of %d from %d, activate in %.2f"
6864
logging.warning(msg, to_pid, host_pid, self.options.suicide_timeout)
6965
if self.wait(self.options.suicide_timeout) is None: # still alive -> INT
70-
msg = "send signal %s to %d from %d with timeout %.2f"
71-
logging.warning(msg, f"SIGINT({SIG_INTERRUPT})", to_pid, host_pid, self.options.interrupt_timeout)
72-
self._process.send_signal(SIG_INTERRUPT)
66+
if sys.platform != "win32": # on Windows everyone in the same process group, so they got the message
67+
msg = "send signal %s to %d from %d with timeout %.2f"
68+
logging.warning(msg, f"SIGINT({SIG_INTERRUPT})", to_pid, host_pid, self.options.interrupt_timeout)
69+
self._process.send_signal(SIG_INTERRUPT)
7370
if self.wait(self.options.interrupt_timeout) is None: # still alive -> TERM # pragma: no branch
7471
terminate_output = self.options.terminate_timeout
7572
logging.warning(msg, f"SIGTERM({SIGTERM})", to_pid, host_pid, terminate_output)
@@ -198,7 +195,6 @@ def __enter__(self) -> ExecuteStatus:
198195
stdin={StdinSource.USER: None, StdinSource.OFF: DEVNULL, StdinSource.API: PIPE}[self.request.stdin],
199196
cwd=str(self.request.cwd),
200197
env=self.request.env,
201-
creationflags=CREATION_FLAGS,
202198
)
203199
except OSError as exception:
204200
return LocalSubprocessExecuteFailedStatus(self.options, self._out, self._err, exception.errno)

src/tox/session/cmd/run/common.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,13 @@ def execute(state: State, max_workers: Optional[int], has_spinner: bool, live: b
191191
previous, has_previous = None, False
192192
try:
193193
spinner = ToxSpinner(has_spinner, state, len(to_run_list))
194+
thread = Thread(
195+
target=_queue_and_wait,
196+
name="tox-interrupt",
197+
args=(state, to_run_list, results, future_to_env, interrupt, done, max_workers, spinner, live),
198+
)
199+
thread.start()
194200
try:
195-
thread = Thread(
196-
target=_queue_and_wait,
197-
name="tox-interrupt",
198-
args=(state, to_run_list, results, future_to_env, interrupt, done, max_workers, spinner, live),
199-
)
200-
thread.start()
201201
thread.join()
202202
except KeyboardInterrupt:
203203
previous, has_previous = signal(SIGINT, Handlers.SIG_IGN), True

0 commit comments

Comments
 (0)