Skip to content

Commit 803a549

Browse files
committed
fix: the SIGTERM handler is now opt-in. #1310
1 parent cde33ba commit 803a549

File tree

5 files changed

+44
-9
lines changed

5 files changed

+44
-9
lines changed

CHANGES.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@ development at the same time, such as 4.5.x and 5.0.
2020
Unreleased
2121
----------
2222

23+
- A new setting, :ref:`config_run_sigterm`, controls whether a SIGTERM signal
24+
handler is used. In 6.3, the signal handler was always installed, to capture
25+
data at unusual process ends. Unfortunately, this introduced other problems
26+
(see `issue 1310`_). Now the signal handler is only used if you opt-in by
27+
setting ``[run] sigterm = true``.
28+
2329
- On Python 3.11, the ``[toml]`` extra no longer installs tomli, instead using
2430
tomllib from the standard library. Thanks `Shantanu <pull 1359_>`_.
2531

32+
.. _issue 1310: https://github.com/nedbat/coveragepy/issues/1310
2633
.. _pull 1359: https://github.com/nedbat/coveragepy/pull/1359
2734

2835

coverage/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def __init__(self):
190190
self.relative_files = False
191191
self.run_include = None
192192
self.run_omit = None
193+
self.sigterm = False
193194
self.source = None
194195
self.source_pkgs = []
195196
self.timid = False
@@ -364,6 +365,7 @@ def copy(self):
364365
('relative_files', 'run:relative_files', 'boolean'),
365366
('run_include', 'run:include', 'list'),
366367
('run_omit', 'run:omit', 'list'),
368+
('sigterm', 'run:sigterm', 'boolean'),
367369
('source', 'run:source', 'list'),
368370
('source_pkgs', 'run:source_pkgs', 'list'),
369371
('timid', 'run:timid', 'boolean'),

coverage/control.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -536,12 +536,13 @@ def _init_for_start(self):
536536

537537
# Register our clean-up handlers.
538538
atexit.register(self._atexit)
539-
is_main = (threading.current_thread() == threading.main_thread())
540-
if is_main and not env.WINDOWS:
541-
# The Python docs seem to imply that SIGTERM works uniformly even
542-
# on Windows, but that's not my experience, and this agrees:
543-
# https://stackoverflow.com/questions/35772001/x/35792192#35792192
544-
self._old_sigterm = signal.signal(signal.SIGTERM, self._on_sigterm)
539+
if self.config.sigterm:
540+
is_main = (threading.current_thread() == threading.main_thread())
541+
if is_main and not env.WINDOWS:
542+
# The Python docs seem to imply that SIGTERM works uniformly even
543+
# on Windows, but that's not my experience, and this agrees:
544+
# https://stackoverflow.com/questions/35772001/x/35792192#35792192
545+
self._old_sigterm = signal.signal(signal.SIGTERM, self._on_sigterm)
545546

546547
def _init_data(self, suffix):
547548
"""Create a data file if we don't have one yet."""

doc/config.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,24 @@ need to know the source origin.
268268
.. versionadded:: 5.0
269269

270270

271+
.. _config_run_sigterm:
272+
273+
[run] sigterm
274+
.............
275+
276+
(boolean, default False) if true, register a SIGTERM signal handler to capture
277+
data when the process ends due to a SIGTERM signal. This includes
278+
:meth:`Process.terminate <python:multiprocessing.Process.terminate>`, and other
279+
ways to terminate a process. This can help when collecting data in usual
280+
situations, but can also introduce problems (see `issue 1310`_).
281+
282+
Only on Linux and Mac.
283+
284+
.. _issue 1310: https://github.com/nedbat/coveragepy/issues/1310
285+
286+
.. versionadded:: 6.4 (in 6.3 this was always enabled)
287+
288+
271289
.. _config_run_source:
272290

273291
[run] source

tests/test_concurrency.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,8 @@ def random_load(): # pragma: nested
696696
class SigtermTest(CoverageTest):
697697
"""Tests of our handling of SIGTERM."""
698698

699-
def test_sigterm_saves_data(self):
699+
@pytest.mark.parametrize("sigterm", [False, True])
700+
def test_sigterm_saves_data(self, sigterm):
700701
# A terminated process should save its coverage data.
701702
self.make_file("clobbered.py", """\
702703
import multiprocessing
@@ -724,7 +725,8 @@ def subproc(x):
724725
[run]
725726
parallel = True
726727
concurrency = multiprocessing
727-
""")
728+
""" + ("sigterm = true" if sigterm else "")
729+
)
728730
out = self.run_command("coverage run clobbered.py")
729731
# Under the Python tracer on Linux, we get the "Trace function changed"
730732
# message. Does that matter?
@@ -735,7 +737,11 @@ def subproc(x):
735737
assert out == "START\nNOT THREE\nEND\n"
736738
self.run_command("coverage combine")
737739
out = self.run_command("coverage report -m")
738-
assert self.squeezed_lines(out)[2] == "clobbered.py 17 1 94% 6"
740+
if sigterm:
741+
expected = "clobbered.py 17 1 94% 6"
742+
else:
743+
expected = "clobbered.py 17 5 71% 5-10"
744+
assert self.squeezed_lines(out)[2] == expected
739745

740746
def test_sigterm_still_runs(self):
741747
# A terminated process still runs its own SIGTERM handler.
@@ -766,6 +772,7 @@ def on_sigterm(signum, frame):
766772
[run]
767773
parallel = True
768774
concurrency = multiprocessing
775+
sigterm = True
769776
""")
770777
out = self.run_command("coverage run handler.py")
771778
assert out == "START\nSIGTERM\nEND\n"

0 commit comments

Comments
 (0)