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 {