From 772873104658182a5b76f5c0eed76047c62c6651 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Wed, 28 Oct 2026 23:31:07 +0100 Subject: With more workers blockingly waiting on udp sockets, we need to keep track of all sockets to shut them down on exit --- tests/integration/test_shutdown.py | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/integration/test_shutdown.py (limited to 'tests/integration') diff --git a/tests/integration/test_shutdown.py b/tests/integration/test_shutdown.py new file mode 100644 index 0000000..063d779 --- /dev/null +++ b/tests/integration/test_shutdown.py @@ -0,0 +1,58 @@ +"""That every thread the tracker starts also ends when it is asked to stop. + +The rest of the suite stops a tracker too, so a thread left behind already +shows up in teardown as the complaint that the tracker had to be killed. +These ask for the arrangements that park a thread in a blocking read in the +first place, and they time the exit, because a reader woken only by its own +receive timeout passes a correctness check while taking a second over it. +""" + +import time +import unittest + +from harness import Tracker, TrackerTestCase + +# Generous enough for a platform where shutting the socket down does not wake +# the reader and the receive timeout has to expire, tight enough that a reader +# which is never woken at all fails instead of hanging the suite +PATIENCE = 3.0 + + +def time_the_exit(extra_config): + tracker = Tracker(extra_config) + try: + # Serve one request of each kind first, so the threads that are about + # to be joined have actually run + tracker.announce(b"shutdown-probe\0\0\0\0\0\0") + sock, connection_id = tracker.udp_connect() + tracker.udp_announce(sock, connection_id, b"shutdown-probe\0\0\0\0\0\0") + sock.close() + started = time.time() + complaints = tracker.stop() + return time.time() - started, complaints + finally: + tracker._cleanup() + + +class UdpWorkers(TrackerTestCase): + config = "listen.udp.workers 8\n" + + def test_announce_reaches_one_of_eight_workers(self): + """Keeps the workers from being merely started and never used.""" + sock, connection_id = self.tracker.udp_connect() + answer = self.tracker.udp_announce(sock, connection_id, self.hash_for("eight-workers")) + sock.close() + self.assertEqual(answer["seeders"], 1) + + +class ShutdownEndsEveryThread(unittest.TestCase): + def test_eight_udp_workers_are_joined(self): + took, complaints = time_the_exit("listen.udp.workers 8\n") + self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) + self.assertLess(took, PATIENCE, "stopping eight udp workers took %.1f s" % took) + + def test_udp_in_the_event_loop_still_stops(self): + """Worker count zero leaves no thread to join, which must also work.""" + took, complaints = time_the_exit("listen.udp.workers 0\n") + self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) + self.assertLess(took, PATIENCE, "stopping without workers took %.1f s" % took) -- cgit v1.3