fix(daemon): never restart, delete or swap under a node that hasn't exited
Every operation that stops the node (restart, rescan, repair, delete chain data, reinstall, bootstrap, wallet switch/adopt/restore/rebuild, wizard restart, encrypt, decrypt) now: - refuses up front while the node is rebuilding witnesses or has not answered for 30s, with a "try again when it's done" warning, and sends no stop; - goes ahead only once the node's PROCESS has exited, not its RPC port. A stop that doesn't finish within 120s cancels the operation (warning; a cancelled rescan/repair disarms its flags); nothing is started over the old node, chain data isn't deleted, wallet.dat isn't swapped. Specifics: - stopDaemonForWalletSwitch() no longer reports "stopped" when the app is quitting (adopt could swap wallet.dat under a live node). - Decrypt waited a fixed 3s after "stop" before renaming wallet.dat; it now waits for the process to exit, and scrubs the key export if not. - Bootstrap waits for the process before cleanChainData and stops the node through the stop routine instead of the shared RPC client. - StopScope::Connected lets switch/reinstall/decrypt stop an adopted or external node through the same never-kill routine. - A bounded stop that can't reach the node (a foreign node refusing our credentials) gives up after 5s instead of 120s. - macOS: isDaemonProcessRunning() checks processes (libproc), not the RPC port. - rpcBusyWhileAlive() also reads the busy gate: since the gate, the first timeout usually lands on another lane and the 30s clock never started (also weakened the quit prompt for a silent rebuild). - Notifications render during the first-run wizard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -200,6 +200,12 @@ public:
|
||||
// The node is alive and its RPC has only timed out / been refused as busy for >= 30s (a rebuild that
|
||||
// logged no marker, e.g. an external node whose output we can't read).
|
||||
bool rpcBusyWhileAlive() const;
|
||||
// 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;
|
||||
// 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();
|
||||
// Record how an RPC to the node just went, for rpcBusyWhileAlive(): busy kinds (ReplyTimeout /
|
||||
// QueueFull) start the clock, an answer stops it, anything else leaves it alone.
|
||||
void noteDaemonRpcResult(bool answered, rpc::RpcFailureKind kind);
|
||||
@@ -497,12 +503,16 @@ public:
|
||||
Background, // restart/rescan/switch/...: up to 120s, and let go as soon as the app starts quitting
|
||||
Fallback, // the synchronous shutdown fallback on the UI thread: up to 20s
|
||||
};
|
||||
daemon::StopOutcome stopEmbeddedDaemon(StopMode mode = StopMode::Background);
|
||||
// Stop the node specifically for a wallet switch, which MUST free the RPC port to relaunch on
|
||||
// -wallet=<name>. Unlike stopEmbeddedDaemon() (whose DisconnectOnly policy leaves an adopted/external
|
||||
// daemon running), this sends a graceful RPC "stop" — using our own connection creds, so only our
|
||||
// daemon obeys it — even to an adopted daemon, then waits (bounded) for the port to actually release.
|
||||
// Returns true once the RPC port is free (or we're shutting down).
|
||||
// Which node it may stop. Policy: our own, or an external one only when the "stop external daemon"
|
||||
// setting says so. Connected: also the node we're connected to (adopted or external), with the
|
||||
// credentials we connected with — a wallet switch, a reinstall and decrypt must free the port, the binary
|
||||
// and wallet.dat. Only a node that accepts those credentials obeys.
|
||||
enum class StopScope { Policy, Connected };
|
||||
daemon::StopOutcome stopEmbeddedDaemon(StopMode mode = StopMode::Background, StopScope scope = StopScope::Policy);
|
||||
// Stop the node for a wallet switch / adopt / restore / rebuild, which swap wallet.dat or relaunch on
|
||||
// -wallet=<name>. Unlike stopEmbeddedDaemon()'s default policy (which leaves an adopted/external node
|
||||
// running) it stops the node we're connected to too (StopScope::Connected). True only once the process
|
||||
// has exited and the RPC port is free; false if it is still running, or the app started quitting.
|
||||
bool stopDaemonForWalletSwitch();
|
||||
bool isEmbeddedDaemonRunning() const;
|
||||
bool isUsingEmbeddedDaemon() const { return supportsEmbeddedDaemon() && use_embedded_daemon_; }
|
||||
@@ -1078,6 +1088,10 @@ private:
|
||||
// and resets it to -1. Atomic because the worker thread writes it and the UI thread reads/clears it.
|
||||
std::atomic<int> pending_delete_result_{-1};
|
||||
std::atomic<bool> pending_reinstall_node_busy_{false}; // reinstall gave up: the old node never exited
|
||||
// A worker stopped the node for an operation and it didn't exit: the operation was cancelled. Told (and,
|
||||
// for a rescan/repair, its armed state undone) on the UI thread. Bits: kStopFailed*.
|
||||
static constexpr int kStopFailedNotify = 1, kStopFailedUndoRescan = 2;
|
||||
std::atomic<int> pending_stop_failed_{0};
|
||||
|
||||
// Encryption state check timeout
|
||||
float encryption_check_timer_ = 0.0f;
|
||||
|
||||
Reference in New Issue
Block a user