Use _stop_event consistently across all threads as the way to signal that it should stop. Add thread joins with timeouts to allow the program to exit cleanly

This commit is contained in:
Ian Renton
2026-09-18 09:21:38 +01:00
parent d79c8f72c8
commit 29d8654234
28 changed files with 239 additions and 126 deletions
+52 -33
View File
@@ -1,5 +1,6 @@
import logging
import threading
import time
from core.config import config, create_provider_from_config
@@ -16,6 +17,7 @@ class DataProviders:
self.static_data_providers = []
self.sig_ref_data_providers = []
self.callsign_data_providers = []
self._startup_timers = []
def setup(self):
for entry in config["spot_providers"]:
@@ -43,41 +45,58 @@ class DataProviders:
def start(self):
# Start data providers before spot/alert providers so the lookup data is there already for incoming spots.
# Each category is fired off after a small delay to give the rest of Spothole chance to start up.
threading.Timer(5.0, lambda: self.start_providers(self.static_data_providers, "static data")).start()
threading.Timer(
10.0,
lambda: self.start_providers(self.callsign_data_providers, "callsign data"),
).start()
threading.Timer(15.0, lambda: self.start_providers(self.spot_providers, "spot")).start()
threading.Timer(20.0, lambda: self.start_providers(self.alert_providers, "alert")).start()
threading.Timer(
25.0,
lambda: self.start_providers(self.solar_condition_providers, "solar condition"),
).start()
threading.Timer(
30.0,
lambda: self.start_providers(self.sig_ref_data_providers, "SIG ref data"),
).start()
self._startup_timers = [
threading.Timer(5.0, lambda: self.start_providers(self.static_data_providers, "static data")),
threading.Timer(10.0, lambda: self.start_providers(self.callsign_data_providers, "callsign data")),
threading.Timer(15.0, lambda: self.start_providers(self.spot_providers, "spot")),
threading.Timer(20.0, lambda: self.start_providers(self.alert_providers, "alert")),
threading.Timer(
25.0,
lambda: self.start_providers(self.solar_condition_providers, "solar condition"),
),
threading.Timer(30.0, lambda: self.start_providers(self.sig_ref_data_providers, "SIG ref data")),
]
for t in self._startup_timers:
t.daemon = True
t.start()
def stop(self):
for sp in self.spot_providers:
if sp.enabled:
sp.stop()
for ap in self.alert_providers:
if ap.enabled:
ap.stop()
for scp in self.solar_condition_providers:
if scp.enabled:
scp.stop()
for srdp in self.sig_ref_data_providers:
if srdp.enabled:
srdp.stop()
for sdp in self.static_data_providers:
if sdp.enabled:
sdp.stop()
for cdp in self.callsign_data_providers:
if cdp.enabled:
cdp.stop()
# Cancel any startup timers that haven't fired yet
for t in self._startup_timers:
t.cancel()
# Stop all providers
all_providers = [
p
for p in (
self.spot_providers
+ self.alert_providers
+ self.solar_condition_providers
+ self.sig_ref_data_providers
+ self.static_data_providers
+ self.callsign_data_providers
)
if p.enabled
]
if not all_providers:
return
def stop_provider(p):
try:
p.stop()
except Exception:
logger.exception("Exception stopping provider")
threads = [threading.Thread(target=stop_provider, args=(p,), daemon=True) for p in all_providers]
for t in threads:
t.start()
deadline = time.monotonic() + 40
for t in threads:
t.join(timeout=max(0.0, deadline - time.monotonic()))
still_running = [t for t in threads if t.is_alive()]
if still_running:
logger.warning("Some threads did not stop in time!")
# Global object