Skip to content

Commit 6360176

Browse files
Add option to delay propegating SIGINT to child process (#1588)
Co-Authored-By: Stefan H. Holek <[email protected]> Co-authored-by: Stefan H. Holek <[email protected]>
1 parent a421aeb commit 6360176

File tree

8 files changed

+47
-19
lines changed

8 files changed

+47
-19
lines changed

CONTRIBUTORS

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -160,22 +160,6 @@ Global settings are defined under the ``tox`` section as:
160160
Name of the virtual environment used to create a source distribution from the
161161
source tree.
162162

163-
.. conf:: interrupt_timeout ^ float ^ 0.3
164-
165-
.. versionadded:: 3.15.0
166-
167-
When tox is interrupted, it propagates the signal to the child process,
168-
waits :conf:``interrupt_timeout`` seconds, and sends it a SIGTERM if it hasn't
169-
exited.
170-
171-
.. conf:: terminate_timeout ^ float ^ 0.2
172-
173-
.. versionadded:: 3.15.0
174-
175-
When tox is interrupted, it propagates the signal to the child process,
176-
waits :conf:``interrupt_timeout`` seconds, sends it a SIGTERM, waits
177-
:conf:``terminate_timeout`` seconds, and sends it a SIGKILL if it hasn't exited.
178-
179163
Jenkins override
180164
++++++++++++++++
181165

@@ -597,6 +581,33 @@ Complete list of settings that you can put into ``testenv*`` sections:
597581
via the ``-e`` tox will only run those three (even if ``coverage`` may specify as ``depends`` other targets too -
598582
such as ``py27, py35, py36, py37``).
599583

584+
.. conf:: suicide_timeout ^ float ^ 0.0
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+
593+
.. conf:: interrupt_timeout ^ float ^ 0.3
594+
595+
.. versionadded:: 3.15.0
596+
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.
600+
601+
.. conf:: terminate_timeout ^ float ^ 0.2
602+
603+
.. versionadded:: 3.15.0
604+
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.
610+
600611
Substitutions
601612
-------------
602613

src/tox/action.py

Lines changed: 3 additions & 1 deletion
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

Lines changed: 8 additions & 0 deletions
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.0
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/session/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import tox
2020
from tox import reporter
2121
from tox.action import Action
22-
from tox.config import INTERRUPT_TIMEOUT, TERMINATE_TIMEOUT, parseconfig
22+
from tox.config import INTERRUPT_TIMEOUT, SUICIDE_TIMEOUT, TERMINATE_TIMEOUT, parseconfig
2323
from tox.config.parallel import ENV_VAR_KEY_PRIVATE as PARALLEL_ENV_VAR_KEY_PRIVATE
2424
from tox.config.parallel import OFF_VALUE as PARALLEL_OFF
2525
from tox.logs.result import ResultLog
@@ -170,6 +170,7 @@ def newaction(self, name, msg, *args):
170170
self.resultlog.command_log,
171171
self.popen,
172172
sys.executable,
173+
SUICIDE_TIMEOUT,
173174
INTERRUPT_TIMEOUT,
174175
TERMINATE_TIMEOUT,
175176
)

src/tox/venv.py

Lines changed: 1 addition & 0 deletions
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

Lines changed: 4 additions & 1 deletion
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.0 == 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)