pytest: Finalizers don't run on SIGTERM
I caught this issue in a Jenkins environment. This is the series of events:
- My tests create resources outside the test environment
- Jenkins kills the pytest process with SIGTERM. Finalizers won’t run and resources won’t be cleaned up.
That doesn’t seem right to me. A cleanup method should always cleanup, no matter what caused the exit. For example KeyboardInterrupt does run cleanup, so why shouldn’t SIGTERM?
Here is a sample program:
import pytest
@pytest.fixture
def myfixture(request):
print('fixture')
def finalizer():
print('finalizer')
request.addfinalizer(finalizer)
def test_fast_test(myfixture):
print('run fast test')
def test_slow_test(myfixture):
print('run slow test')
import time
time.sleep(100)
Steps to reproduce:
- Open two terminals.
- In the first terminal, run the tests withn pytest:
pytest -s finalizer_test.py
- In the second terminal, kill the pytest process with SIGTERM. For example using pkill command:
pkill pytest
This is the output:
finalizer_test.py fixture
run fast test
.finalizer
fixture
run slow test
Terminated
As you can see the second finalizer didn’t run.
Environment:
- Ubuntu 16.04
- Python 3.5.2
- pip packages:
$ pip list
Package Version
-------------- -------
atomicwrites 1.3.0
attrs 19.1.0
more-itertools 7.0.0
pathlib2 2.3.3
pip 19.1.1
pkg-resources 0.0.0
pluggy 0.11.0
py 1.8.0
pytest 4.4.2
setuptools 20.7.0
six 1.12.0
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 28 (18 by maintainers)
Commits related to this issue
- Run pytest finalizers on SIGTERM pytest finalizers don't run on SIGTERM; only SIGINT. When Buildkite terminates, it sends a SIGTERM. This intercept SIGTERM and sends SIGINT instead. https://github.... — committed to dagster-io/dagster by jmsanders 3 years ago
- Run pytest finalizers on SIGTERM (#4509) pytest finalizers don't run on SIGTERM; only SIGINT. When Buildkite terminates, it sends a SIGTERM. This intercept SIGTERM and sends SIGINT instead. ht... — committed to dagster-io/dagster by jmsanders 3 years ago
- use pytest fixture for signals https://github.com/pytest-dev/pytest/issues/5243 — committed to WLAN-Pi/wlanpi-hwtest by joshschmelzle 3 years ago
this also wouldn’t be terribly hard to do as a fixture or plugin without adding this to core:
@asottile pytest is a test runner that does resource management - any python service/tool that does resource management is typically expected to handle the term signal and clean up
so i believe pytest should do so as well
@asottile If finalizers are specifically the sticking point, then I suggest that the topic be switched to the essence of the desire, which is mine as well: if a fixture is in charge of a resource’s lifecycle, then how can resource leaks be avoided?
Why is this any of pytest’s concern? Fixtures provide the main mechanism by which tests that leverage the pytest framework set up their resources. It is even documented to be useful for this purpose:
Many of these resources are important to clean up. For example, if I create an object in a bucket in Object Storage for my CI job, then it is essential to know that that specific object (and bucket) resource will be cleaned up if that job is canceled mid-run; otherwise, over time, I will accumulate objects and buckets from canceled CI jobs. If I am using the pytest framework and I use a fixture to create that resource, then it is only natural to expect fixtures to have a mechanism, any mechanism at all, to clean up that resource or suddenly fixtures are no longer useful for resource lifecycles in many cases and the documentation is misleading.
SIGTERM is commonplace for canceling a process gracefully. No one disputes that because it’s been that way for a long time. Generally, there is a grace period after the SIGTERM during which the process can clean up and, after that, the SIGTERM is followed up by a SIGKILL. For reference, one can check out what Google does for Kubernetes.
So, since there are certain points that cannot be argued against:
That leaves only the final question which is: how should resource leaks be avoided by pytest users when a SIGTERM is sent?
@asottile I disagree with it not being the standard behaviour for other programs. I think it’s the opposite, that’s why I opened this ticket. A wrapper doesn’t seem like the right way to handle this. Any process should implement signal handling for SIGTERM to do cleanup imo.
SIGTERM is a signal to the process that it should exit. The point of telling the process it should exit instead of just killing it is to let the process do pre-exit execution such as cleanup. If I wanted to kill the process without cleanup I would send SIGKILL.
@blueyed The kernel closes file descriptors on process exit. I think it makes sense to only do cleanup and nothing else on SIGTERM. I could maybe pick up the work in the future but I don’t have time at the moment. So if you or anyone else want to do the work I’m all for it.
This is standard behaviour in python (and as far as I can tell in most programming languages) – I think it would be more suprising for pytest to implement this.
If you want something that can make this work for you, you could rewrite the signal to
SIGINT
using a wrapper and then you’d trigger the normal cleanup path for interrupt in python.One way you could do this is to use dumb-init to rewrite the signal using
--rewrite
Here’s a simple example where I turn
SIGTERM
intoSIGINT
:Closing then, thanks everyone and thanks @gsmethells for opening the follow up issues. 👍
I just reviewed the docs,
I believe pytest should raise a exception and do a graceful shutdown
A correct solution will need a way to sort out blocked teardowns
This may require quite some investigation
Probably needs a check for the main thread.
My fundamental reasoning is that python does not do this, we should not deviate from the standard language behaviour as this introduces a difference in the test environment which is not present in the runtime environment. Additionally, it represents some pretty-complex interaction and cross-platform concerns that we now have to maintain the complexity for.
I have yet to see a programming language which calls finalizers on
SIGTERM
, only onSIGINT
. If anything this is something a language should implement (or individual applications which wish to handleSIGTERM
) and not the concern of a testing framework.