feat(daemon): recognise our detached node, tail POSIX debug.log, more RPC threads

S12 - our node across sessions:
- The node the wallet launches is recorded in <datadir>/obsidiandragon-
  dragonxd.pid (pid + process start time + exe). A later session that
  finds it still running (after "Leave running" / keep_daemon_running)
  stops it gracefully on quit and waits for that exact process (no time
  limit, "Leave running" offered); never signals or kills it. A reused
  pid or a reboot never matches; stale records are deleted.
- stop_external_daemon is now honoured when this session only connected
  (no controller); it was ignored.

S13 - the node's own log on every platform:
- No -printtoconsole on Linux/macOS: the node writes its timestamped
  debug.log (which outlives the GUI) and we tail it next to the stderr
  pipe. Tail path from the datadir or the isolated override (Windows had
  %APPDATA% hard-coded, wrong for migrate-to-seed starts).
- After the node shrinks debug.log at startup, resume at this run's
  banner instead of re-reading the old run's tail.
- A node we didn't launch (left running, or found running) feeds the
  busy tracker from its debug.log, tied to the live process (our record,
  or POSIX hushd.pid) and only from lines it wrote. A GUI attaching to a
  node mid-rebuild now says "Node busy: rebuilding note witnesses" and
  asks before stopping it.
- A start that only finds a running node no longer resets the tracker;
  a found busy node shows the busy overlay at once.

S14: launch our node with -rpcthreads=16 unless DRAGONX.conf sets it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 12:06:52 -05:00
co-authored by Claude Opus 5.5
parent e20b70d73f
commit a3361020d1
7 changed files with 696 additions and 85 deletions
+14
View File
@@ -203,6 +203,10 @@ public:
// The node is rebuilding witnesses (our own node's log says so) or has been too busy to answer for a
// while: stopping it now either waits up to an hour or makes it start over. UI thread only.
bool nodeBusyForStop() const;
// May quitting (or a default-policy stop) stop a node this session didn't launch? With stop_external_daemon
// on, or when it is the node an earlier session of ours launched and left running (its ownership record
// matches a live process). That one is stopped gracefully and waited for, never killed.
bool mayStopExternalNode() const;
// For an operation that would stop the node (restart, rescan, switch, reinstall, bootstrap, ...): when
// nodeBusyForStop(), tell the user to try again later and return true; the caller then does nothing.
bool refuseIfNodeBusy();
@@ -1198,6 +1202,16 @@ private:
rpc::ConnectRetryBackoff canary_backoff_; // a canary refused as queue-full waits before the next
std::chrono::steady_clock::time_point canary_retry_at_{};
void pumpRpcBusyCanary();
// A node this session didn't launch (left running by an earlier session, or found running): feed the busy
// tracker from its <datadir>/debug.log, while that log is tied to the live process (see
// daemon::externalLogFeedOffset). Our own running node's output feeds the tracker directly. UI thread.
void pumpExternalDaemonLog();
long externalNodePid() const; // that node: our ownership record, or (POSIX) its hushd.pid; 0 if none
bool busyTrackerIsLive() const; // the tracker describes a node that is running now
bool ext_log_tied_ = false;
long ext_log_pid_ = 0;
std::uintmax_t ext_log_offset_ = 0;
double ext_log_next_at_ = 0.0;
void abortRpcBusyCanary();
void feedDaemonBusy(const std::string& chunk);
void resetDaemonBusy();