fix: make module stop() callbacks signal-only so modules do not deadlock each other on shutdown
stop_wmodules() stopped and joined one module at a time, and three modules' stop() callbacks were not lightweight signals: each blocked for up to 10 s waiting for its own run loop to finish tearing down. agent-info's run loop can be parked inside Syscollector::pause(), which only releases once syscollector is told to stop, so agent-info burned its whole timeout before syscollector had even been signalled. It then logged "Timeout waiting for AgentInfo run loop to exit; skipping database teardown" and left its DBSync connection open. Those waits were redundant. syscollector and SCA already run their teardown on the module thread just before it returns, so joining that thread carries the same guarantee, under one shared budget instead of a per-module timeout. agent-info was the asymmetric one and now matches: the teardown runs from wm_agent_info_main on every exit path, including the early return during the handshake wait, where agent_info_task_registry_init() may already have opened agent_info.db. It releases the module's connections rather than destroying the instance, because the dispatcher and /control threads reach the module through pointers they only null-check. stop_wmodules() now signals every module and only then joins them all against a single deadline, mirroring the POSIX SIGTERM handler. Signalling everything up front makes it far more likely for a stop to arrive before a module has finished starting, so the places that silently dropped it are fixed too: AgentInfoImpl::start() no longer clears the stop and consults the injected global-shutdown predicate rather than m_stopped alone (agent_info_stop() null-checks the instance, so a stop arriving before it exists never reaches stop() at all, and with no shouldContinue supplied the predicate is the run loop's only exit condition), and SCA no longer resets g_shutting_down over a stop already requested. syscollector drops it too, in wm_sys_stop()'s !flags.running early return, and there the fix is not to start the module at all: win_module_thread() declines to run its routine when wm_shutdown_requested is already set on return from startup_gate_wait_for_ready(), which the gate does on shutdown. Without that check, 8 of 16 stops issued 1-2 s after start on Windows 11 burned the whole 20 s join budget and the successor process logged "sqlite: database is locked" -- the symptoms of #38370 and #38372. It is a stopgap: the gate itself should report why it returned so that every caller can abort, which is #38428. Also stop reporting a join timeout without checking the thread: once the shared budget was spent WaitForSingleObject was skipped entirely and the error was logged for modules that had already exited cleanly. Three more shutdown-window defects found while reviewing this change. wm_sys_stop() now records the stop before its !flags.running early return, and wm_sys_main() refuses to initialise under a stop already received: both Syscollector::init() and ::start() clear m_stopping, so such a stop was erased and the module scanned on through the shutdown. It also reads syscollector_stop_ptr into a local before calling through it, since the module thread clears that global as it exits. The join pass skips the calling thread, as the POSIX handler does with pthread_self(): merror_exit() from a module thread reaches stop_wmodules() through WinSetError(), and waiting on its own handle burned the whole budget on a timeout that never happened. And the budget now starts before the signal pass, because quiesce() waits for an in-flight flush with no timeout of its own and that time was off the clock, leaving the total unbounded against WaitToKillServiceTimeout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
N
nbertoldo committed
b7b8fafda0dd5f5ad8e442d774f21ce313092534
Parent: 649266c