mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-27 03:52:47 +02:00
8310ccdee6
SIGTERM did not stop the daemon. Observed: the process stayed alive 18+ minutes with all three companion listen sockets still bound and the serial port still held, so a restart could not reopen the radio. Two defects compounded. Cleanup never ran. The signal handler cancelled run(), whose finally then awaited _shutdown() from inside an already-cancelled task -- so the first await raised CancelledError, run() returned, and asyncio.run() tore the loop down before a single shutdown step executed. Not one step logged. Running cleanup in a sibling task instead does not fix it either: run() returns as soon as the dispatcher stops and asyncio.run() cancels every leftover task on the way out, which showed up as 'frame server :5050' being cancelled mid-stop. The handler now unwinds run() cooperatively by stopping the dispatcher, so run_forever() returns on its own and cleanup runs in a task that is not being cancelled. Cancelling is kept only as a fallback for a failure before the dispatcher exists. Nothing bounded the steps. Frame servers, bridges, router and Glass had no timeout, so one stuck step stranded every step after it -- including releasing the radio. Each step is now bounded by SHUTDOWN_STEP_TIMEOUT_S and logged by name, so a hang is both survivable and diagnosable, and the sync steps (HTTP stop, sensor manager, GPS, radio cleanup) run off-loop so a blocking close cannot stall the sequence. The dispatcher is stopped first so RX ends before its radio is released. Even with cleanup fixed, a single non-daemon thread that never returns hangs SIGTERM forever: interpreter finalization joins them with no timeout, which is where the original 18-minute hang sat (main thread parked in Py_FinalizeEx -> wait_for_thread_shutdown). Report any that are still alive by name so the offender can be fixed at the source, excluding asyncio's own executor workers since asyncio.run() joins those under its own timeout. Then arm a daemon watchdog that forces the process down SHUTDOWN_EXIT_GRACE_S after cleanup finishes, so a future stray thread costs a delayed exit rather than a stuck service. Verified on hardware: SIGTERM and SIGINT both exit in 1s with zero warnings, all ports and the serial device released, and MQTT publishing its offline status before disconnecting.