1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-03-31 12:15:34 -04:00

feat(threads): add join_non_daemon() to APRSDThreadList

Allows graceful shutdown by waiting for non-daemon threads to complete
while allowing daemon threads to be terminated immediately.
This commit is contained in:
Walter Boring 2026-03-24 11:57:05 -04:00
parent b7a37322e1
commit 43ba69e352
2 changed files with 60 additions and 0 deletions

View File

@ -184,3 +184,15 @@ class APRSDThreadList:
@wrapt.synchronized(lock)
def __len__(self):
return len(self.threads_list)
@wrapt.synchronized(lock)
def join_non_daemon(self, timeout: float = 5.0):
"""Wait for non-daemon threads to complete gracefully.
Args:
timeout: Maximum seconds to wait per thread.
"""
for th in self.threads_list:
if not th.daemon and th.is_alive():
LOG.info(f'Waiting for non-daemon thread {th.name} to finish')
th.join(timeout=timeout)

View File

@ -404,3 +404,51 @@ class TestAPRSDThreadList(unittest.TestCase):
# Should handle concurrent access without errors
self.assertGreaterEqual(len(thread_list), 0)
def test_join_non_daemon(self):
"""Test join_non_daemon() waits for non-daemon threads."""
class NonDaemonTestThread(APRSDThread):
daemon = False
def __init__(self, name):
super().__init__(name)
self.finished = False
def loop(self):
time.sleep(0.2)
self.finished = True
return False
thread_list = APRSDThreadList()
thread = NonDaemonTestThread('NonDaemonJoinTest')
thread_list.add(thread)
thread.start()
# Stop triggers the event, thread should finish its loop then exit
thread.stop()
thread_list.join_non_daemon(timeout=5.0)
self.assertTrue(thread.finished or not thread.is_alive())
def test_join_non_daemon_skips_daemon_threads(self):
"""Test join_non_daemon() does not wait for daemon threads."""
thread_list = APRSDThreadList()
# Clear existing threads
thread_list.threads_list = []
# Create a daemon thread that loops forever
thread = TestThread('DaemonSkipTest', should_loop=True)
thread_list.add(thread)
thread.start()
# This should return quickly since it's a daemon thread
start = time.time()
thread_list.join_non_daemon(timeout=0.1)
elapsed = time.time() - start
self.assertLess(elapsed, 0.5) # Should not wait for daemon
# Cleanup
thread.stop()
thread.join(timeout=1)