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 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 08:23:30 -05:00
co-authored by Claude Opus 4.8
parent 5376681af5
commit 6a24355c79
2 changed files with 16 additions and 5 deletions
+1 -1
View File
@@ -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
+15 -4
View File
@@ -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<int>& 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<std::mutex> 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<std::mutex> lk(results_mutex_);
results_.push_back({result_str, is_error});
};
});
} else {