perf(mining): one save, at most one restart per thread-count gesture

Every thread-count change on the Mining tab saved settings.json and,
with a miner running, stopped and restarted it: xmrig re-inits its
RandomX dataset and reconnects to the pool, seconds of lost hashing
each time. A drag across the grid did this once per tile crossed (up to
~24), with a "Starting pool miner" toast each; every -/+ click or Enter
did it again.

The tab now hands each pick to the App, which puts the newest in
settings at once and applies it when the input settles: on drag
release, or 0.5 s after the last click, even if the Mining tab was left
in the meantime. The header, RAM estimate and tile fill still follow
the drag live.

- Pool: the restart is decided on the mining-control thread, after every
  earlier start or stop has run. It never starts a miner the user
  stopped, is a no-op when xmrig already runs at that count, and is no
  longer silently skipped when the pick lands in another restart's stop
  window (which left xmrig on the old count).
- Solo: one setgenerate per gesture. A pick made while one is in flight
  now waits for it instead of being dropped (another stale count).
- Clicking the tile that is already selected does nothing; it used to
  save settings and restart the miner.
- With idle thread scaling on, the grid no longer snaps back to the old
  count while a pick is on its way to the miner. A pool auto-balance
  restart keeps a count still queued.
- Idle thread scaling changes the pool count through the same retarget.
  Its correction after a start compares with the count last requested,
  not the xmrig API's lagging report, so it comes about 1 s in instead
  of after the first API report (3-5 s).
- A retarget whose pool config no longer validates shows the same error
  and stops the miner, as the old stop-then-start did.
- While a benchmark runs a pick is saved, but the miner change is
  dropped: the benchmark owns the count until it ends.

A crash within 0.5 s of a pick, or mid-drag, loses that pick; every
clean exit saves it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 21:09:48 -05:00
co-authored by Claude Opus 5.5
parent c9cb50f4dd
commit 6c02e67c6d
9 changed files with 296 additions and 36 deletions
+34
View File
@@ -32,6 +32,7 @@
#include "services/wallet_scan_governor.h"
#include "services/chat_scan_policy.h"
#include "services/note_consolidation.h"
#include "services/mining_thread_commit.h"
#include "services/network_refresh_service.h"
#include "services/wallet_security_controller.h"
#include "services/wallet_security_workflow.h"
@@ -347,9 +348,23 @@ public:
// Pool mining (xmrig)
void startPoolMining(int threads);
void stopPoolMining();
// Change the thread count of the pool miner that is running (or queued to start). Never starts one.
void retargetPoolMining(int threads);
int getXmrigRequestedThreads() const {
return xmrig_manager_ ? xmrig_manager_->getRequestedThreads() : 0;
}
// Thread count of the newest pool start/retarget posted and not stopped since (0 = none). Leads
// getXmrigRequestedThreads(), which only changes once the mining-control thread runs that job.
int poolThreadsTarget() const { return pool_threads_target_.load(std::memory_order_relaxed); }
// A thread-count pick from the Mining tab (grid tile, -/+, Enter). The settings value changes at once;
// the save and the miner change happen ONCE, when the input settles (drag released, or ~0.5 s after
// the last click). Latest wins.
void selectMiningThreads(int threads);
void setMiningThreadDragActive(bool active) { mining_thread_drag_ = active; }
// A pick has not reached the miner yet (the grid must not snap back to the old count meanwhile).
bool miningThreadCommitPending() const {
return mining_thread_commit_.pending() || mining_solo_threads_pending_ > 0;
}
// True while the pool miner process is live — used to refuse replacing the binary under it.
bool isPoolMinerRunning() const {
return xmrig_manager_ && xmrig_manager_->isRunning();
@@ -1107,6 +1122,15 @@ private:
bool mining_ctl_stop_ = false;
void postMiningControl(std::function<void()> job); // enqueue a blocking xmrig op onto the FIFO thread
void stopMiningControlThread(); // signal + join the control thread (shutdown)
// 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).
void postPoolMinerJob(int threads, bool retargetOnly);
// Coalesced thread-count picks (main thread only; see services/mining_thread_commit.h).
services::MiningThreadCommit mining_thread_commit_;
bool mining_thread_drag_ = false; // the thread grid is mid-drag (set by the Mining tab)
bool mining_threads_save_pending_ = false; // settings hold a picked count that is not on disk yet
int mining_solo_threads_pending_ = 0; // settled pick waiting for an in-flight setgenerate
void tickMiningThreadCommit(); // per frame, from update()
// Auto-balance runtime state (pool mining, full-node only). The service fetches
// pool hashrates off-thread; the RNG drives the weighted-random pick.
util::PoolStatsService pool_stats_service_;
@@ -1589,6 +1613,16 @@ private:
// poll — drives the "connecting…" → "connected" feedback for pool mining (which has a connect delay).
std::atomic<bool> pool_starting_{false};
// Thread count of the newest pool start/retarget job posted (0 = none / stopped). A hint for the main
// thread: set when a job is posted, cleared by stopPoolMining(), by a start that fails and by a retarget
// that finds no miner. The control thread re-checks the real state, so a stale value can only skip a
// job the newest posted one already covers, or post a no-op one.
std::atomic<int> pool_threads_target_{0};
void clearPoolThreadsTarget(int mine) { // only if it still holds this job's value
int expected = mine;
pool_threads_target_.compare_exchange_strong(expected, 0, std::memory_order_relaxed);
}
// Auto-shield guard (prevents concurrent auto-shield operations)
std::atomic<bool> auto_shield_pending_{false};
// v1.3.0+ daemons auto-shield coinbase themselves; probe z_autoshieldstatus once per connection and