The RPC worker loop only caught std::exception. A task that threw anything else - a non-std exception propagating out of a posted job - escaped the loop and tore down the worker thread, so every later RPC silently went unanswered. - The worker loop now also has a catch-all that swallows non-std throws and keeps the thread running. Such a job still loses its main-thread callback, exactly as the std::exception path already did; the only change is that the worker survives. - RPCClient's connected_ and warming_up_ flags are now atomic. They are read from the UI thread while the worker updates them, so the plain bools were a data race. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
155 lines
3.7 KiB
C++
155 lines
3.7 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
//
|
|
// rpc_worker.cpp — Background work queue. Executes WorkFn on its own thread,
|
|
// returns MainCb callbacks drained each frame on the main thread.
|
|
|
|
#include "rpc_worker.h"
|
|
#include <cstdio>
|
|
#include "../util/logger.h"
|
|
|
|
namespace dragonx {
|
|
namespace rpc {
|
|
|
|
RPCWorker::RPCWorker() = default;
|
|
|
|
RPCWorker::~RPCWorker()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
void RPCWorker::start()
|
|
{
|
|
if (running_.load(std::memory_order_relaxed)) return;
|
|
|
|
running_.store(true, std::memory_order_release);
|
|
thread_ = std::thread(&RPCWorker::run, this);
|
|
}
|
|
|
|
void RPCWorker::stop()
|
|
{
|
|
if (!running_.load(std::memory_order_relaxed) && !thread_.joinable()) return;
|
|
|
|
// Signal stop if not already signaled
|
|
requestStop();
|
|
|
|
if (thread_.joinable()) {
|
|
thread_.join();
|
|
}
|
|
|
|
// Discard pending tasks
|
|
{
|
|
std::lock_guard<std::mutex> lk(taskMtx_);
|
|
tasks_.clear();
|
|
}
|
|
}
|
|
|
|
void RPCWorker::requestStop()
|
|
{
|
|
if (!running_.load(std::memory_order_relaxed)) return;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lk(taskMtx_);
|
|
running_.store(false, std::memory_order_release);
|
|
}
|
|
taskCv_.notify_one();
|
|
}
|
|
|
|
void RPCWorker::post(WorkFn work)
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lk(taskMtx_);
|
|
tasks_.push_back(std::move(work));
|
|
}
|
|
taskCv_.notify_one();
|
|
}
|
|
|
|
int RPCWorker::drainResults()
|
|
{
|
|
// Swap the result queue under the lock, then execute outside the lock
|
|
// to minimise contention with the worker thread.
|
|
std::deque<MainCb> batch;
|
|
{
|
|
std::lock_guard<std::mutex> lk(resultMtx_);
|
|
batch.swap(results_);
|
|
}
|
|
|
|
int count = 0;
|
|
for (auto& cb : batch) {
|
|
if (cb) {
|
|
try {
|
|
cb();
|
|
} catch (const std::exception& e) {
|
|
DEBUG_LOGF("[RPCWorker] Main-thread callback threw: %s\n", e.what());
|
|
} catch (...) {
|
|
DEBUG_LOGF("[RPCWorker] Main-thread callback threw unknown exception\n");
|
|
}
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool RPCWorker::hasPendingResults() const
|
|
{
|
|
std::lock_guard<std::mutex> lk(resultMtx_);
|
|
return !results_.empty();
|
|
}
|
|
|
|
std::size_t RPCWorker::pendingTaskCount() const
|
|
{
|
|
std::lock_guard<std::mutex> lk(taskMtx_);
|
|
return tasks_.size();
|
|
}
|
|
|
|
std::size_t RPCWorker::pendingResultCount() const
|
|
{
|
|
std::lock_guard<std::mutex> lk(resultMtx_);
|
|
return results_.size();
|
|
}
|
|
|
|
void RPCWorker::run()
|
|
{
|
|
while (true) {
|
|
WorkFn task;
|
|
|
|
// Wait for a task or stop signal
|
|
{
|
|
std::unique_lock<std::mutex> lk(taskMtx_);
|
|
taskCv_.wait(lk, [this] {
|
|
return !tasks_.empty() || !running_.load(std::memory_order_acquire);
|
|
});
|
|
|
|
if (!running_.load(std::memory_order_acquire) && tasks_.empty()) {
|
|
break;
|
|
}
|
|
|
|
if (!tasks_.empty()) {
|
|
task = std::move(tasks_.front());
|
|
tasks_.pop_front();
|
|
}
|
|
}
|
|
|
|
if (!task) continue;
|
|
|
|
// Execute the work function (blocking I/O happens here)
|
|
try {
|
|
MainCb result = task();
|
|
if (result) {
|
|
std::lock_guard<std::mutex> lk(resultMtx_);
|
|
results_.push_back(std::move(result));
|
|
}
|
|
} catch (const std::exception& e) {
|
|
DEBUG_LOGF("[RPCWorker] Task threw: %s\n", e.what());
|
|
} catch (...) {
|
|
// A raw post() job that throws still loses its MainCb (no worse than the
|
|
// std::exception path); this only keeps the worker thread alive.
|
|
DEBUG_LOGF("[RPCWorker] Task threw unknown exception\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace rpc
|
|
} // namespace dragonx
|