fix(daemon): let POSIX stop() see the daemon exit instead of always SIGKILLing
POSIX stop() set should_stop_, which made the monitor thread return without reaping the child, and then polled the state-based isRunning(), which stays true while the state is Stopping. So every Linux/macOS stop waited out its whole grace period, sent SIGTERM, waited 10 s more and SIGKILLed, even when the node had exited seconds earlier. Closing the GUI with an idle node took ~30 s; a node still flushing at 30 s was killed. - stop() joins the monitor first, then polls waitpid(pid, WNOHANG) on a cached pid, so it is the only reaper and sees the exit at once. - markStopRequested() (set by App::stopEmbeddedDaemon before the RPC stop, and by stop() itself; cleared by start()) makes an exit that lands before stop() runs a clean stop instead of a crash, on POSIX and in the Windows monitor. crash_count_ drives auto-restart, and a crash recorded before the request keeps its count. - The SIGTERM -> SIGKILL escalation stays for now, but only fires for a daemon that has really not exited. Replacing it with a wait policy and an explicit user choice is follow-up work. Verified against the fake daemon: an idle node that exits 3.4 s after stop used to be SIGTERMed at +20 s and SIGKILLed at +30 s (GUI closed in 30.6 s); it is now reaped as "exited cleanly" ~50 ms after it exits (GUI closes in 4.0 s). New POSIX tests cover the clean exit, the early requested exit and a graceful SIGTERM, each time-bounded so the old code fails fast. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,7 +59,16 @@ public:
|
||||
* @param wait_ms Maximum milliseconds to wait for graceful shutdown
|
||||
*/
|
||||
void stop(int wait_ms = 5000);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Record that we are about to ask the daemon to stop (e.g. over RPC)
|
||||
*
|
||||
* Call it before sending the stop. If the daemon then exits before stop() runs, the monitor
|
||||
* records a clean stop instead of a crash (crash_count_ drives auto-restart). stop() sets it
|
||||
* too; start() clears it.
|
||||
*/
|
||||
void markStopRequested() { stop_requested_ = true; }
|
||||
|
||||
/**
|
||||
* @brief Check if daemon process is running
|
||||
*/
|
||||
@@ -306,10 +315,15 @@ private:
|
||||
#else
|
||||
pid_t process_pid_ = 0;
|
||||
int stdout_fd_ = -1;
|
||||
|
||||
// Waits up to timeout_ms for `pid` to exit, draining its output; true once reaped. Only valid
|
||||
// after monitor_thread_ is joined, so that stop() is the sole reaper.
|
||||
bool reapWithin(pid_t pid, int timeout_ms, int& status);
|
||||
#endif
|
||||
|
||||
|
||||
std::thread monitor_thread_;
|
||||
std::atomic<bool> should_stop_{false};
|
||||
std::atomic<bool> stop_requested_{false}; // a stop was asked for: the next exit is not a crash
|
||||
std::set<std::string> debug_categories_;
|
||||
int max_connections_ = 0; // 0 = daemon default
|
||||
bool stratum_enabled_ = false; // -stratum: host a RandomX pool (v1.3.0+; older daemons ignore it)
|
||||
|
||||
Reference in New Issue
Block a user