Skip to content

Commit 6c56a2b

Browse files
jheskethstefanholek
andcommitted
Add option to delay propegating SIGINT to child process
Fixes tox-dev#1497 Ctrl+C sends a SIGTERM to both Tox and the running process, causing the running process to receive the signal twice once tox passes it along. This can often cancel or interfer with the process's cleanup. Instead, add an option to allow a process to suicide before sending the SIGTERM. Co-Authored-By: Stefan H. Holek <[email protected]>
1 parent 58fec20 commit 6c56a2b

File tree

7 files changed

+35
-8
lines changed

7 files changed

+35
-8
lines changed

CONTRIBUTORS

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Johannes Christ
4747
Jon Dufresne
4848
Josh Smeaton
4949
Josh Snyder
50+
Joshua Hesketh
5051
Julian Krause
5152
Jurko Gospodnetić
5253
Krisztian Fekete

docs/changelog/1497.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add an option to allow a process to suicide before sending the SIGTERM. - by :user:`jhesketh`

docs/config.rst

+17-6
Original file line numberDiff line numberDiff line change
@@ -581,21 +581,32 @@ Complete list of settings that you can put into ``testenv*`` sections:
581581
via the ``-e`` tox will only run those three (even if ``coverage`` may specify as ``depends`` other targets too -
582582
such as ``py27, py35, py36, py37``).
583583

584+
.. conf:: suicide_timeout ^ float ^ 0.1
585+
586+
.. versionadded:: 3.15.2
587+
588+
When an interrupt is sent via Ctrl+C, the SIGINT is sent to all foreground
589+
processes. The :conf:``suicide_timeout`` gives the running process time to
590+
cleanup and exit before receiving (in some cases, a duplicate) SIGINT from
591+
tox.
592+
584593
.. conf:: interrupt_timeout ^ float ^ 0.3
585594

586595
.. versionadded:: 3.15.0
587596

588-
When tox is interrupted, it propagates the signal to the child process,
589-
waits :conf:``interrupt_timeout`` seconds, and sends it a SIGTERM if it hasn't
590-
exited.
597+
When tox is interrupted, it propagates the signal to the child process
598+
after :conf:``suicide_timeout`` seconds. If the process still hasn't exited
599+
after :conf:``interrupt_timeout`` seconds, its sends a SIGTERM.
591600

592601
.. conf:: terminate_timeout ^ float ^ 0.2
593602

594603
.. versionadded:: 3.15.0
595604

596-
When tox is interrupted, it propagates the signal to the child process,
597-
waits :conf:``interrupt_timeout`` seconds, sends it a SIGTERM, waits
598-
:conf:``terminate_timeout`` seconds, and sends it a SIGKILL if it hasn't exited.
605+
When tox is interrupted, after waiting :conf:``interrupt_timeout`` seconds,
606+
it propagates the signal to the child process, waits
607+
:conf:``interrupt_timeout`` seconds, sends it a SIGTERM, waits
608+
:conf:``terminate_timeout`` seconds, and sends it a SIGKILL if it hasn't
609+
exited.
599610

600611
Substitutions
601612
-------------

src/tox/action.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def __init__(
3232
command_log,
3333
popen,
3434
python,
35+
suicide_timeout,
3536
interrupt_timeout,
3637
terminate_timeout,
3738
):
@@ -45,6 +46,7 @@ def __init__(
4546
self.command_log = command_log
4647
self._timed_report = None
4748
self.python = python
49+
self.suicide_timeout = suicide_timeout
4850
self.interrupt_timeout = interrupt_timeout
4951
self.terminate_timeout = terminate_timeout
5052

@@ -188,7 +190,7 @@ def evaluate_cmd(self, input_file_handler, process, redirect):
188190
def handle_interrupt(self, process):
189191
"""A three level stop mechanism for children - INT -> TERM -> KILL"""
190192
msg = "from {} {{}} pid {}".format(os.getpid(), process.pid)
191-
if process.poll() is None:
193+
if self._wait(process, self.suicide_timeout) is None:
192194
self.info("KeyboardInterrupt", msg.format("SIGINT"))
193195
process.send_signal(signal.CTRL_C_EVENT if sys.platform == "win32" else signal.SIGINT)
194196
if self._wait(process, self.interrupt_timeout) is None:

src/tox/config/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
WITHIN_PROVISION = os.environ.get(str("TOX_PROVISION")) == "1"
5555

56+
SUICIDE_TIMEOUT = 0.1
5657
INTERRUPT_TIMEOUT = 0.3
5758
TERMINATE_TIMEOUT = 0.2
5859

@@ -827,6 +828,13 @@ def develop(testenv_config, value):
827828

828829
parser.add_testenv_attribute_obj(DepOption())
829830

831+
parser.add_testenv_attribute(
832+
name="suicide_timeout",
833+
type="float",
834+
default=SUICIDE_TIMEOUT,
835+
help="timeout to allow process to exit before sending SIGINT",
836+
)
837+
830838
parser.add_testenv_attribute(
831839
name="interrupt_timeout",
832840
type="float",

src/tox/venv.py

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def new_action(self, msg, *args):
130130
command_log,
131131
self.popen,
132132
self.envconfig.envpython,
133+
self.envconfig.suicide_timeout,
133134
self.envconfig.interrupt_timeout,
134135
self.envconfig.terminate_timeout,
135136
)

tests/unit/config/test_config.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,25 @@ def test_is_same_dep(self):
175175
assert DepOption._is_same_dep("pkg_hello-world3==1.0", "pkg_hello-world3<=2.0")
176176
assert not DepOption._is_same_dep("pkg_hello-world3==1.0", "otherpkg>=2.0")
177177

178-
def test_interrupt_terminate_timeout_set_manually(self, newconfig):
178+
def test_suicide_interrupt_terminate_timeout_set_manually(self, newconfig):
179179
config = newconfig(
180180
[],
181181
"""
182182
[testenv:dev]
183+
suicide_timeout = 30.0
183184
interrupt_timeout = 5.0
184185
terminate_timeout = 10.0
185186
186187
[testenv:other]
187188
""",
188189
)
189190
envconfig = config.envconfigs["other"]
191+
assert 0.1 == envconfig.suicide_timeout
190192
assert 0.3 == envconfig.interrupt_timeout
191193
assert 0.2 == envconfig.terminate_timeout
192194

193195
envconfig = config.envconfigs["dev"]
196+
assert 30.0 == envconfig.suicide_timeout
194197
assert 5.0 == envconfig.interrupt_timeout
195198
assert 10.0 == envconfig.terminate_timeout
196199

0 commit comments

Comments
 (0)