perf(daemon): back off the monitor poll when idle
The daemon monitor thread drained output and then slept a fixed short interval on every tick, waking several times a second for the life of the node even long after startup, when the node emits almost nothing. Stopping it also meant waiting out the current sleep before it saw the stop flag. - The monitor now waits on a condition variable instead of a plain sleep, so a stop request wakes it immediately rather than after the interval. The stop flag is set under the monitor's mutex so the wake cannot be lost. - The poll interval stays fast for the first 30 s after the thread starts and for 3 s after any output, and otherwise doubles per idle tick up to a 500 ms cap. Any new output drops it straight back to fast, so responsiveness during startup, rescan and shutdown is unchanged while a quiet node is polled far less often. The interval choice is a pure function. Tests cover the interval function across the warmup window, the recent-data window, the reset on new output, the doubling, and the cap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
@@ -474,9 +476,23 @@ public:
|
||||
* reader can tell a new crash from an error it has already reported. */
|
||||
std::uint64_t getUnexpectedExitCount() const { return unexpected_exits_.load(); }
|
||||
|
||||
/** Monitor poll interval: stay `fast` for the first 30 s after the thread starts and for 3 s after any
|
||||
* output; otherwise double per idle tick up to `cap`. Any output resets to `fast`. Pure. */
|
||||
static std::chrono::milliseconds nextMonitorInterval(std::chrono::milliseconds cur, bool bytesRead,
|
||||
std::chrono::milliseconds sinceStart, std::chrono::milliseconds sinceLastData,
|
||||
std::chrono::milliseconds fast, std::chrono::milliseconds cap)
|
||||
{
|
||||
using ms = std::chrono::milliseconds;
|
||||
if (bytesRead || sinceStart < ms(30000) || sinceLastData < ms(3000)) return fast;
|
||||
const ms next = cur * 2;
|
||||
return next > cap ? cap : (next < fast ? fast : next);
|
||||
}
|
||||
|
||||
private:
|
||||
void setState(State s, const std::string& message = "");
|
||||
void monitorProcess();
|
||||
void requestMonitorStop(); // set should_stop_ under monitor_mutex_ and wake the monitor
|
||||
size_t outputTotalSnapshot() const; // output_total_ under output_mutex_ (monitor idle detection)
|
||||
void drainOutput(); // read any pending stdout into process_output_
|
||||
void appendOutput(const char* data, size_t len); // append + trim (caller holds output_mutex_)
|
||||
bool startProcess(const std::string& binary_path, const std::vector<std::string>& args);
|
||||
@@ -514,6 +530,8 @@ private:
|
||||
|
||||
std::thread monitor_thread_;
|
||||
std::atomic<bool> should_stop_{false};
|
||||
std::mutex monitor_mutex_; // pairs with monitor_cv_ so stop wakes the monitor at once
|
||||
std::condition_variable monitor_cv_;
|
||||
std::atomic<bool> stop_requested_{false}; // a stop was asked for: the next exit is not a crash
|
||||
// Serializes start/beginStop/stop/endStop over monitor_thread_, should_stop_ and stop_requested_ (two
|
||||
// threads joining one std::thread is undefined). Never taken by the monitor thread or the state callback.
|
||||
|
||||
Reference in New Issue
Block a user