Skip to content

Commit

Permalink
fix: the SIGTERM handler is now opt-in. #1310
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed May 18, 2022
1 parent cde33ba commit 803a549
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 9 deletions.
7 changes: 7 additions & 0 deletions CHANGES.rst
Expand Up @@ -20,9 +20,16 @@ development at the same time, such as 4.5.x and 5.0.
Unreleased
----------

- A new setting, :ref:`config_run_sigterm`, controls whether a SIGTERM signal
handler is used. In 6.3, the signal handler was always installed, to capture
data at unusual process ends. Unfortunately, this introduced other problems
(see `issue 1310`_). Now the signal handler is only used if you opt-in by
setting ``[run] sigterm = true``.

- On Python 3.11, the ``[toml]`` extra no longer installs tomli, instead using
tomllib from the standard library. Thanks `Shantanu <pull 1359_>`_.

.. _issue 1310: https://github.com/nedbat/coveragepy/issues/1310
.. _pull 1359: https://github.com/nedbat/coveragepy/pull/1359


Expand Down
2 changes: 2 additions & 0 deletions coverage/config.py
Expand Up @@ -190,6 +190,7 @@ def __init__(self):
self.relative_files = False
self.run_include = None
self.run_omit = None
self.sigterm = False
self.source = None
self.source_pkgs = []
self.timid = False
Expand Down Expand Up @@ -364,6 +365,7 @@ def copy(self):
('relative_files', 'run:relative_files', 'boolean'),
('run_include', 'run:include', 'list'),
('run_omit', 'run:omit', 'list'),
('sigterm', 'run:sigterm', 'boolean'),
('source', 'run:source', 'list'),
('source_pkgs', 'run:source_pkgs', 'list'),
('timid', 'run:timid', 'boolean'),
Expand Down
13 changes: 7 additions & 6 deletions coverage/control.py
Expand Up @@ -536,12 +536,13 @@ def _init_for_start(self):

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

def _init_data(self, suffix):
"""Create a data file if we don't have one yet."""
Expand Down
18 changes: 18 additions & 0 deletions doc/config.rst
Expand Up @@ -268,6 +268,24 @@ need to know the source origin.
.. versionadded:: 5.0


.. _config_run_sigterm:

[run] sigterm
.............

(boolean, default False) if true, register a SIGTERM signal handler to capture
data when the process ends due to a SIGTERM signal. This includes
:meth:`Process.terminate <python:multiprocessing.Process.terminate>`, and other
ways to terminate a process. This can help when collecting data in usual
situations, but can also introduce problems (see `issue 1310`_).

Only on Linux and Mac.

.. _issue 1310: https://github.com/nedbat/coveragepy/issues/1310

.. versionadded:: 6.4 (in 6.3 this was always enabled)


.. _config_run_source:

[run] source
Expand Down
13 changes: 10 additions & 3 deletions tests/test_concurrency.py
Expand Up @@ -696,7 +696,8 @@ def random_load(): # pragma: nested
class SigtermTest(CoverageTest):
"""Tests of our handling of SIGTERM."""

def test_sigterm_saves_data(self):
@pytest.mark.parametrize("sigterm", [False, True])
def test_sigterm_saves_data(self, sigterm):
# A terminated process should save its coverage data.
self.make_file("clobbered.py", """\
import multiprocessing
Expand Down Expand Up @@ -724,7 +725,8 @@ def subproc(x):
[run]
parallel = True
concurrency = multiprocessing
""")
""" + ("sigterm = true" if sigterm else "")
)
out = self.run_command("coverage run clobbered.py")
# Under the Python tracer on Linux, we get the "Trace function changed"
# message. Does that matter?
Expand All @@ -735,7 +737,11 @@ def subproc(x):
assert out == "START\nNOT THREE\nEND\n"
self.run_command("coverage combine")
out = self.run_command("coverage report -m")
assert self.squeezed_lines(out)[2] == "clobbered.py 17 1 94% 6"
if sigterm:
expected = "clobbered.py 17 1 94% 6"
else:
expected = "clobbered.py 17 5 71% 5-10"
assert self.squeezed_lines(out)[2] == expected

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

0 comments on commit 803a549

Please sign in to comment.