From 6a24355c79ecf4b2f063c6c21457b84b89f5dd29 Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 13 Oct 2026 08:23:30 -0500 Subject: [PATCH] fix(console): release busy gate when the fast worker stops When the daemon disconnects, teardown stops the fast RPC worker without draining its queued main-thread callbacks. The console busy gate was released inside that callback, so a command whose callback was discarded left the input disabled for the rest of the session. - The busy gate now drops from an RAII guard at the end of the worker job itself, so it is released however the job ends: success, error, or a worker torn down undrained. - The result is published to the console before the gate drops, so a freed-up console always has the output queued. - An unknown, non-std::exception failure is now caught and reported instead of escaping the job. User-visible: the Console input can no longer stay stuck disabled after a reconnect. Co-Authored-By: Claude Opus 4.8 --- src/app_network.cpp | 2 +- src/ui/windows/console_command_executor.cpp | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/app_network.cpp b/src/app_network.cpp index 0306728..f3665d6 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -943,7 +943,7 @@ void App::onDisconnected(const std::string& reason) // curl_easy_perform unblocks and stop()'s join() returns promptly (no UI freeze). if (fast_rpc_) fast_rpc_->requestAbort(); if (fast_worker_) { - fast_worker_->stop(); + fast_worker_->stop(); // undrained MainCbs are lost; console busy gate is released worker-side // Drop the stopped worker so onConnected recreates and starts a fresh one. Keeping a // stopped-but-present worker would defeat onConnected's `if (!fast_worker_)` guard, leaving // the fast lane dead for the rest of the session — which silently stalls the opid poll diff --git a/src/ui/windows/console_command_executor.cpp b/src/ui/windows/console_command_executor.cpp index 83eda02..fcff020 100644 --- a/src/ui/windows/console_command_executor.cpp +++ b/src/ui/windows/console_command_executor.cpp @@ -121,6 +121,12 @@ void FullNodeConsoleExecutor::submit(const std::string& cmd) if (worker) { in_flight_.fetch_add(1); // gates busy() so the input is disabled until this returns worker->post([rpc, method, params, this]() -> rpc::RPCWorker::MainCb { + // Drops the busy gate when the worker job ends, however it ends. The gate used to drop in the + // MainCb, which is lost when teardown stops the fast worker undrained, leaving busy() stuck. + struct ReleaseGate { + std::atomic& n; + ~ReleaseGate() { n.fetch_sub(1); } + } gate{in_flight_}; std::string result_str; bool is_error = false; try { @@ -129,14 +135,19 @@ void FullNodeConsoleExecutor::submit(const std::string& cmd) } catch (const std::exception& e) { result_str = e.what(); is_error = true; + } catch (...) { + result_str = "RPC failed (unknown error)"; + is_error = true; } - return [this, result_str, is_error]() { - in_flight_.fetch_sub(1); + { + // Publish the result before the gate drops, so a non-busy console always has it queued. + std::lock_guard lk(results_mutex_); + results_.push_back({result_str, is_error}); + } + return [this]() { // A Console command may have changed the wallet (a send, an import, a new address): the // scans gated on change signals must look again. (Main thread: this is a drained callback.) if (app_) app_->noteWalletMutation(); - std::lock_guard lk(results_mutex_); - results_.push_back({result_str, is_error}); }; }); } else {