diff --git a/test/conftest.py b/test/conftest.py index e3f14bf..6afbf9b 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -21,18 +21,28 @@ def feeder(): @pytest.fixture(autouse=True) def _assert_no_j1939_thread_leak(): - """Fail any test that leaves a j1939.* background thread alive.""" + """Fail any test that leaves a j1939.* background thread alive. + + The poll window here (3.5s) is deliberately kept slightly above + ElectronicControlUnit.stop()'s own default dispatch_join_timeout (3.0s): + stop() only logs a warning (rather than failing) if the dispatch thread + doesn't exit within that timeout, e.g. while a slow subscriber callback + unwinds. If this fixture's window were shorter than stop()'s own + tolerance, a thread that stop() itself considers "still fine, just slow" + could trip a false-positive leak failure here. See #81. + """ before = {t.ident for t in threading.enumerate() if t.name.startswith('j1939.')} yield - # Give freshly-stopped threads a brief moment to actually exit. + # Give freshly-stopped threads a chance to actually exit. import time - for _ in range(20): + deadline = time.monotonic() + 3.5 + while True: leaked = [t for t in threading.enumerate() if t.name.startswith('j1939.') and t.ident not in before and t.is_alive()] - if not leaked: + if not leaked or time.monotonic() >= deadline: break time.sleep(0.01) assert not leaked, (