From 255c0ad1dd719a782b818d7b47844de65f49c71c Mon Sep 17 00:00:00 2001 From: Thomas Oettli Date: Fri, 23 Jan 2026 22:26:46 +0100 Subject: [PATCH] Shutdown daemon gracefully --- src/ddns_service/server.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/ddns_service/server.py b/src/ddns_service/server.py index 5161c52..02e2b35 100644 --- a/src/ddns_service/server.py +++ b/src/ddns_service/server.py @@ -8,6 +8,7 @@ import json import logging import signal import ssl +import threading from concurrent.futures import ThreadPoolExecutor from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import parse_qs, urlparse @@ -93,12 +94,36 @@ class DDNSServer(ThreadingHTTPServer): self.pool_size = app.config["daemon"]["thread_pool_size"] self.request_timeout = app.config["daemon"]["request_timeout"] self.executor = ThreadPoolExecutor(max_workers=self.pool_size) + self.active_requests = 0 + self.requests_lock = threading.Lock() + self.requests_done = threading.Condition(self.requests_lock) super().__init__(address, DDNSRequestHandler) def process_request(self, request, client_address): """Submit request to thread pool.""" + with self.requests_lock: + self.active_requests += 1 request.settimeout(self.request_timeout) - self.executor.submit(self.process_request_thread, request, client_address) + self.executor.submit(self._handle_request_wrapper, request, client_address) + + def _handle_request_wrapper(self, request, client_address): + """Wrap request handling to track active requests.""" + try: + self.process_request_thread(request, client_address) + finally: + with self.requests_lock: + self.active_requests -= 1 + if self.active_requests == 0: + self.requests_done.notify_all() + + def wait_for_requests(self, timeout=5): + """Wait for active requests to complete.""" + with self.requests_lock: + if self.active_requests > 0: + logging.info(f"Waiting for {self.active_requests} active request(s)") + self.requests_done.wait(timeout=timeout) + if self.active_requests > 0: + logging.warning(f"Shutdown timeout, {self.active_requests} request(s) still active") def server_close(self): """Shutdown thread pool and close server.""" @@ -503,6 +528,9 @@ def run_daemon(app): while not app.is_shutting_down(): server.handle_request() + # Graceful shutdown - wait for active requests + server.wait_for_requests(5) + # Cleanup expired_cleanup_thread.stop() ratelimit_cleanup_thread.stop()