perf(app): run miner shutdown off the UI thread
At quit the UI thread drained and joined the mining-control thread and then called the miner's blocking stop with a 3 s budget. Both ran on the UI thread, so the window could stall for up to that long before the shutdown screen rendered. - Quit now only signals the mining-control thread (no join) on the UI thread. The blocking teardown — join the control thread, then stop the miner — moves to a background thread, so xmrig still has a single driver but the UI thread never blocks on it. - On the daemon-stop path that teardown runs first on the shutdown thread, so the miner is fully stopped before the daemon stop begins. On the disconnect-only quit paths it runs on its own thread, which the final shutdown joins before tearing xmrig down, so there is never a second concurrent join of the control thread. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+32
-10
@@ -175,16 +175,29 @@ void App::postMiningControl(std::function<void()> job)
|
|||||||
|
|
||||||
// Signal the mining-control thread to stop and join it. Called at shutdown BEFORE xmrig_manager_ is stopped
|
// Signal the mining-control thread to stop and join it. Called at shutdown BEFORE xmrig_manager_ is stopped
|
||||||
// or destroyed, so no control job runs concurrently with teardown. Idempotent.
|
// or destroyed, so no control job runs concurrently with teardown. Idempotent.
|
||||||
void App::stopMiningControlThread()
|
void App::signalMiningControlStop()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lk(mining_ctl_mutex_);
|
std::lock_guard<std::mutex> lk(mining_ctl_mutex_);
|
||||||
mining_ctl_stop_ = true;
|
mining_ctl_stop_ = true;
|
||||||
}
|
}
|
||||||
mining_ctl_cv_.notify_all();
|
mining_ctl_cv_.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::stopMiningControlThread()
|
||||||
|
{
|
||||||
|
signalMiningControlStop();
|
||||||
if (mining_ctl_thread_.joinable()) mining_ctl_thread_.join();
|
if (mining_ctl_thread_.joinable()) mining_ctl_thread_.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blocking miner teardown, run OFF the UI thread at quit. Joins the control thread first (waits out any in-flight
|
||||||
|
// start/stop job) so this is the sole driver of xmrig_manager_, then stops the miner.
|
||||||
|
void App::finishMinerShutdown()
|
||||||
|
{
|
||||||
|
if (mining_ctl_thread_.joinable()) mining_ctl_thread_.join();
|
||||||
|
if (xmrig_manager_ && xmrig_manager_->isRunning()) xmrig_manager_->stop(3000);
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// How often auto-balance re-evaluates the pool while active. Long, because switching
|
// How often auto-balance re-evaluates the pool while active. Long, because switching
|
||||||
// restarts the miner (drops in-flight shares + reconnect); the incumbent stickiness
|
// restarts the miner (drops in-flight shares + reconnect); the incumbent stickiness
|
||||||
@@ -6591,14 +6604,12 @@ void App::beginShutdown()
|
|||||||
http_worker_->requestStop();
|
http_worker_->requestStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drain + join the mining-control thread FIRST so no async start/stop job runs while we tear the miner
|
// Signal the mining-control thread now (no join: the UI thread must not block). The blocking
|
||||||
// down here (avoids two threads driving xmrig_manager_ during shutdown). (M-03 cluster)
|
// join-control-thread -> xmrig stop(3000) teardown runs on a background thread (finishMinerShutdown), so
|
||||||
stopMiningControlThread();
|
// xmrig_manager_ still has a single driver. (M-03 cluster)
|
||||||
|
signalMiningControlStop();
|
||||||
// Stop xmrig pool miner before stopping the daemon
|
|
||||||
if (xmrig_manager_ && xmrig_manager_->isRunning()) {
|
if (xmrig_manager_ && xmrig_manager_->isRunning()) {
|
||||||
shutdown_status_ = TR("appx_stopping_pool_miner");
|
shutdown_status_ = TR("appx_stopping_pool_miner");
|
||||||
xmrig_manager_->stop(3000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUG_LOGF("beginShutdown: starting (daemon_controller_=%s)\n",
|
DEBUG_LOGF("beginShutdown: starting (daemon_controller_=%s)\n",
|
||||||
@@ -6614,7 +6625,10 @@ void App::beginShutdown()
|
|||||||
if (settings_) {
|
if (settings_) {
|
||||||
settings_->save();
|
settings_->save();
|
||||||
}
|
}
|
||||||
shutdown_complete_ = true;
|
miner_stop_thread_ = std::thread([this]() {
|
||||||
|
finishMinerShutdown();
|
||||||
|
shutdown_complete_ = true;
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6628,7 +6642,10 @@ void App::beginShutdown()
|
|||||||
if (shutdownDecision.action == daemon::DaemonController::ShutdownAction::DisconnectOnly) {
|
if (shutdownDecision.action == daemon::DaemonController::ShutdownAction::DisconnectOnly) {
|
||||||
DEBUG_LOGF("beginShutdown: %s, skipping daemon stop\n", shutdownDecision.logReason);
|
DEBUG_LOGF("beginShutdown: %s, skipping daemon stop\n", shutdownDecision.logReason);
|
||||||
shutdown_status_ = shutdownDecision.status;
|
shutdown_status_ = shutdownDecision.status;
|
||||||
shutdown_complete_ = true;
|
miner_stop_thread_ = std::thread([this]() {
|
||||||
|
finishMinerShutdown();
|
||||||
|
shutdown_complete_ = true;
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
shutdown_status_ = shutdownDecision.status;
|
shutdown_status_ = shutdownDecision.status;
|
||||||
@@ -6641,6 +6658,7 @@ void App::beginShutdown()
|
|||||||
// keeps rendering the shutdown screen (like SilentDragonX's
|
// keeps rendering the shutdown screen (like SilentDragonX's
|
||||||
// modal "Please wait" dialog).
|
// modal "Please wait" dialog).
|
||||||
shutdown_thread_ = std::thread([this]() {
|
shutdown_thread_ = std::thread([this]() {
|
||||||
|
finishMinerShutdown(); // miner fully stopped before the daemon stop begins
|
||||||
DEBUG_LOGF("shutdown thread: calling stopEmbeddedDaemon()\n");
|
DEBUG_LOGF("shutdown thread: calling stopEmbeddedDaemon()\n");
|
||||||
shutdown_status_ = TR("appx_sending_stop_command_to_daemon");
|
shutdown_status_ = TR("appx_sending_stop_command_to_daemon");
|
||||||
// Deliver stop and wait for the node to exit: no time limit, never killed unasked. The shutdown
|
// Deliver stop and wait for the node to exit: no time limit, never killed unasked. The shutdown
|
||||||
@@ -7735,7 +7753,11 @@ void App::shutdown()
|
|||||||
|
|
||||||
// Ensure the mining-control thread is stopped + joined (idempotent; beginShutdown already did it on the
|
// Ensure the mining-control thread is stopped + joined (idempotent; beginShutdown already did it on the
|
||||||
// normal quit path, but shutdown() can also run without it). Must precede xmrig_manager_ teardown.
|
// normal quit path, but shutdown() can also run without it). Must precede xmrig_manager_ teardown.
|
||||||
stopMiningControlThread();
|
// The background miner teardown (disconnect-only quit paths) is joined first: it is the one driver.
|
||||||
|
if (miner_stop_thread_.joinable()) miner_stop_thread_.join();
|
||||||
|
// After beginShutdown() the control thread is joined by finishMinerShutdown() (on shutdown_thread_ or
|
||||||
|
// miner_stop_thread_); joining it here too would be two concurrent joins.
|
||||||
|
if (!shutting_down_) stopMiningControlThread();
|
||||||
abortRpcBusyCanary();
|
abortRpcBusyCanary();
|
||||||
if (canary_thread_.joinable()) canary_thread_.join();
|
if (canary_thread_.joinable()) canary_thread_.join();
|
||||||
|
|
||||||
|
|||||||
@@ -1196,7 +1196,9 @@ private:
|
|||||||
std::deque<std::function<void()>> mining_ctl_queue_;
|
std::deque<std::function<void()>> mining_ctl_queue_;
|
||||||
bool mining_ctl_stop_ = false;
|
bool mining_ctl_stop_ = false;
|
||||||
void postMiningControl(std::function<void()> job); // enqueue a blocking xmrig op onto the FIFO thread
|
void postMiningControl(std::function<void()> job); // enqueue a blocking xmrig op onto the FIFO thread
|
||||||
|
void signalMiningControlStop(); // signal only (no join): safe on the UI thread
|
||||||
void stopMiningControlThread(); // signal + join the control thread (shutdown)
|
void stopMiningControlThread(); // signal + join the control thread (shutdown)
|
||||||
|
void finishMinerShutdown(); // join control thread -> xmrig stop(3000); background only
|
||||||
// Builds the pool config on the main thread and posts stop(if running)+start to the control thread.
|
// Builds the pool config on the main thread and posts stop(if running)+start to the control thread.
|
||||||
// retargetOnly: a thread-count change, decided on the control thread (never starts a miner).
|
// retargetOnly: a thread-count change, decided on the control thread (never starts a miner).
|
||||||
void postPoolMinerJob(int threads, bool retargetOnly);
|
void postPoolMinerJob(int threads, bool retargetOnly);
|
||||||
@@ -1262,6 +1264,7 @@ private:
|
|||||||
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
|
bool address_list_dirty_ = false; // P8: dedup rebuildAddressList
|
||||||
GuardedStatus shutdown_status_; // thread-safe: written by the shutdown thread, read by the UI (M-05)
|
GuardedStatus shutdown_status_; // thread-safe: written by the shutdown thread, read by the UI (M-05)
|
||||||
std::thread shutdown_thread_;
|
std::thread shutdown_thread_;
|
||||||
|
std::thread miner_stop_thread_; // disconnect-only quit paths: miner teardown off the UI thread
|
||||||
// Confirm-before-stopping-daemon-mid-witness-rebuild guard (see beginShutdown / renderDaemonStopConfirm)
|
// Confirm-before-stopping-daemon-mid-witness-rebuild guard (see beginShutdown / renderDaemonStopConfirm)
|
||||||
bool pending_shutdown_confirm_ = false; // a quit is deferred, waiting to open the confirm modal
|
bool pending_shutdown_confirm_ = false; // a quit is deferred, waiting to open the confirm modal
|
||||||
bool daemon_stop_confirm_open_ = false; // the confirm modal is currently showing
|
bool daemon_stop_confirm_open_ = false; // the confirm modal is currently showing
|
||||||
|
|||||||
Reference in New Issue
Block a user