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:
@@ -623,6 +623,7 @@ set(APP_HEADERS
|
||||
src/services/connect_scan_gate.h
|
||||
src/services/scan_policy.h
|
||||
src/services/chat_scan_policy.h
|
||||
src/services/mining_thread_commit.h
|
||||
src/services/wallet_security_controller.h
|
||||
src/services/wallet_security_workflow.h
|
||||
src/services/wallet_security_workflow_executor.h
|
||||
|
||||
+7
-1
@@ -264,7 +264,9 @@ void App::applyPoolAutoBalance(const util::PoolStatsService::Snapshot& snap)
|
||||
|
||||
// Restart so the new pool (and its per-pool algo) takes effect immediately.
|
||||
if (xmrig_manager_ && xmrig_manager_->isRunning()) {
|
||||
const int threads = xmrig_manager_->getRequestedThreads();
|
||||
// A thread-count retarget may still be queued on the control thread: keep its count, not the old one.
|
||||
const int queued = pool_threads_target_.load(std::memory_order_relaxed);
|
||||
const int threads = queued > 0 ? queued : xmrig_manager_->getRequestedThreads();
|
||||
stopPoolMining();
|
||||
startPoolMining(threads);
|
||||
}
|
||||
@@ -994,6 +996,9 @@ void App::update()
|
||||
checkAutoLock();
|
||||
}
|
||||
|
||||
// Apply a settled mining thread-count pick: one settings save + at most one miner change per gesture.
|
||||
tickMiningThreadCommit();
|
||||
|
||||
// Mine-when-idle check (cheap on most frames: it gates itself to 1 Hz)
|
||||
checkIdleMining();
|
||||
|
||||
@@ -1131,6 +1136,7 @@ void App::update()
|
||||
state_.pool_mining.hashrate_60s = 0.0;
|
||||
state_.pool_mining.hashrate_15m = 0.0;
|
||||
pool_starting_.store(false, std::memory_order_relaxed);
|
||||
pool_threads_target_.store(0, std::memory_order_relaxed); // that miner is gone
|
||||
const std::string err = xmrig_manager_->getLastError();
|
||||
ui::Notifications::instance().error(err.empty() ? TR("appx_miner_stopped_unexpectedly")
|
||||
: (std::string(TR("appx_miner_stopped_prefix")) + err));
|
||||
|
||||
@@ -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
|
||||
|
||||
+36
-1
@@ -3369,6 +3369,28 @@ void App::startPoolMining(int threads)
|
||||
if (state_.mining.generate) stopMining();
|
||||
// (the "stop the already-running miner first" step is done inside the control job below, in FIFO order)
|
||||
|
||||
postPoolMinerJob(threads, /*retargetOnly=*/false);
|
||||
}
|
||||
|
||||
// Change the thread count of the pool miner that is running, or queued to start (Mining tab picks, idle
|
||||
// thread scaling). Never starts a miner: the job decides on the mining-control thread (postPoolMinerJob).
|
||||
// Nothing is posted when there is no miner, or when the newest start/retarget already asks for `threads`.
|
||||
void App::retargetPoolMining(int threads)
|
||||
{
|
||||
if (!supportsPoolMining() || !xmrig_manager_) return;
|
||||
threads = std::clamp(threads, 1, std::max(1, (int)std::thread::hardware_concurrency()));
|
||||
const int target = pool_threads_target_.load(std::memory_order_relaxed);
|
||||
if (target <= 0 && !xmrig_manager_->isRunning()) return; // no miner, none queued
|
||||
if (target == threads) return; // the newest queued job already does this
|
||||
postPoolMinerJob(threads, /*retargetOnly=*/true);
|
||||
}
|
||||
|
||||
// Builds the xmrig config from settings + wallet state on this (main) thread and posts the blocking
|
||||
// stop(if running)+start to the mining-control thread. retargetOnly (a thread-count change): the job does
|
||||
// nothing unless a miner is running when it runs, at another count; a config that no longer validates
|
||||
// stops the miner, as the old stop-then-start did.
|
||||
void App::postPoolMinerJob(int threads, bool retargetOnly)
|
||||
{
|
||||
daemon::XmrigManager::Config cfg;
|
||||
cfg.pool_url = settings_->getPoolUrl();
|
||||
cfg.worker_name = settings_->getPoolWorker();
|
||||
@@ -3381,6 +3403,7 @@ void App::startPoolMining(int threads)
|
||||
ui::Notifications::instance().error(util::isTransparentAddress(cfg.worker_name)
|
||||
? TR("mining_payout_needs_shielded")
|
||||
: "Pool payout address is not a valid DragonX address — mining not started.");
|
||||
if (retargetOnly) stopPoolMining(); // never keep mining on a config that no longer validates
|
||||
return;
|
||||
}
|
||||
// The algo follows the pool: official pools use their own algo (pool.dragonx.cc
|
||||
@@ -3411,6 +3434,7 @@ void App::startPoolMining(int threads)
|
||||
if (cfg.wallet_address.empty()) {
|
||||
DEBUG_LOGF("[ERROR] Pool mining: No wallet address available\n");
|
||||
ui::Notifications::instance().error("No wallet address available — generate a Z address in the Receive tab");
|
||||
if (retargetOnly) stopPoolMining();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3418,10 +3442,20 @@ void App::startPoolMining(int threads)
|
||||
// never blocks on stop()'s SIGTERM->SIGKILL->join; marshal the spawn result back to the UI. (M-03/L-06/
|
||||
// L-08/L-09/L-13). cfg was fully built above on this (main) thread.
|
||||
daemon::XmrigManager::Config cfgCopy = cfg;
|
||||
postMiningControl([this, cfgCopy]() {
|
||||
const int target = cfg.threads;
|
||||
pool_threads_target_.store(target, std::memory_order_relaxed);
|
||||
postMiningControl([this, cfgCopy, target, retargetOnly]() {
|
||||
// A retarget is decided HERE, after every earlier start/stop ran: nothing happens unless a miner is
|
||||
// running now and at another count. So it never starts a miner the user stopped, and a restart's
|
||||
// stop window (isRunning() false on the UI thread) can't hide the running one.
|
||||
if (retargetOnly) {
|
||||
if (!xmrig_manager_->isRunning()) { clearPoolThreadsTarget(target); return; }
|
||||
if (xmrig_manager_->getRequestedThreads() == target) return;
|
||||
}
|
||||
if (xmrig_manager_->isRunning()) xmrig_manager_->stop(3000);
|
||||
const bool ok = xmrig_manager_->start(cfgCopy);
|
||||
const std::string err = ok ? std::string() : xmrig_manager_->getLastError();
|
||||
if (!ok) clearPoolThreadsTarget(target);
|
||||
if (!worker_) return;
|
||||
worker_->post([this, ok, err]() -> rpc::RPCWorker::MainCb {
|
||||
return [this, ok, err]() {
|
||||
@@ -3450,6 +3484,7 @@ void App::startPoolMining(int threads)
|
||||
void App::stopPoolMining()
|
||||
{
|
||||
if (!xmrig_manager_) return;
|
||||
pool_threads_target_.store(0, std::memory_order_relaxed); // no start/retarget is current any more
|
||||
// Off the render thread — stop()'s SIGTERM->SIGKILL->join can block up to ~3s. (M-03/L-06/L-08/L-09)
|
||||
postMiningControl([this]() {
|
||||
if (xmrig_manager_->isRunning()) xmrig_manager_->stop(3000);
|
||||
|
||||
+70
-7
@@ -819,8 +819,7 @@ void App::checkIdleMining() {
|
||||
if (!idle_scaled_to_idle_) {
|
||||
idle_scaled_to_idle_ = true;
|
||||
if (isPool) {
|
||||
stopPoolMining();
|
||||
startPoolMining(idleThreads);
|
||||
retargetPoolMining(idleThreads);
|
||||
} else {
|
||||
startMining(idleThreads);
|
||||
}
|
||||
@@ -831,8 +830,7 @@ void App::checkIdleMining() {
|
||||
if (idle_scaled_to_idle_) {
|
||||
idle_scaled_to_idle_ = false;
|
||||
if (isPool) {
|
||||
stopPoolMining();
|
||||
startPoolMining(activeThreads);
|
||||
retargetPoolMining(activeThreads);
|
||||
} else {
|
||||
startMining(activeThreads);
|
||||
}
|
||||
@@ -840,13 +838,16 @@ void App::checkIdleMining() {
|
||||
} else {
|
||||
// Mining just started while user is active — ensure active
|
||||
// thread count is applied (grid selection may differ).
|
||||
// Pool: compare with the count last REQUESTED (a restart still queued on the control thread
|
||||
// included), not the xmrig API's last report, which lags a restart by seconds.
|
||||
int currentThreads = isPool
|
||||
? xmrig_manager_->getStats().threads_active
|
||||
? services::effectivePoolThreads(pool_threads_target_.load(std::memory_order_relaxed),
|
||||
xmrig_manager_->getRequestedThreads(),
|
||||
xmrig_manager_->getStats().threads_active)
|
||||
: state_.mining.genproclimit;
|
||||
if (currentThreads > 0 && currentThreads != activeThreads) {
|
||||
if (isPool) {
|
||||
stopPoolMining();
|
||||
startPoolMining(activeThreads);
|
||||
retargetPoolMining(activeThreads);
|
||||
} else {
|
||||
startMining(activeThreads);
|
||||
}
|
||||
@@ -889,6 +890,68 @@ void App::checkIdleMining() {
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Mining thread-count picks — coalesced (one save + at most one miner change per gesture)
|
||||
// ===========================================================================
|
||||
|
||||
namespace {
|
||||
double miningSteadySeconds()
|
||||
{
|
||||
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void App::selectMiningThreads(int threads)
|
||||
{
|
||||
if (!settings_) return;
|
||||
threads = std::clamp(threads, 1, std::max(1, (int)std::thread::hardware_concurrency()));
|
||||
if (settings_->getPoolThreads() != threads) { // in memory now; saved once when the pick settles
|
||||
settings_->setPoolThreads(threads);
|
||||
mining_threads_save_pending_ = true;
|
||||
}
|
||||
mining_thread_commit_.select(threads, miningSteadySeconds());
|
||||
}
|
||||
|
||||
void App::tickMiningThreadCommit()
|
||||
{
|
||||
if (shutting_down_ || !settings_) return; // beginShutdown() saves settings itself
|
||||
const double now = miningSteadySeconds();
|
||||
|
||||
// The grid holds the commit while the button is down. The release is noticed HERE, not by the tab, so
|
||||
// a drag that ends while the Mining tab isn't drawn (page switch, focus loss) still commits.
|
||||
bool held = false;
|
||||
if (mining_thread_drag_) {
|
||||
if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) held = true;
|
||||
else { mining_thread_drag_ = false; mining_thread_commit_.release(now); }
|
||||
}
|
||||
|
||||
// The benchmark owns the miner's thread count while it runs (its cancel/finish restore it): keep the
|
||||
// pick in settings, drop the miner change.
|
||||
if (ui::IsMiningBenchmarkActive()) {
|
||||
mining_thread_commit_.cancel();
|
||||
mining_solo_threads_pending_ = 0;
|
||||
if (mining_threads_save_pending_) { mining_threads_save_pending_ = false; settings_->save(); }
|
||||
return;
|
||||
}
|
||||
|
||||
int threads = 0;
|
||||
if (mining_thread_commit_.take(now, held, threads)) {
|
||||
if (mining_threads_save_pending_) { mining_threads_save_pending_ = false; settings_->save(); }
|
||||
retargetPoolMining(threads); // restarts xmrig once, only if it is running
|
||||
mining_solo_threads_pending_ = threads; // setgenerate, once no other toggle is in flight
|
||||
} else if (mining_thread_commit_.pending() && !held) {
|
||||
ui::material::RequestProgressFrame(); // keep frames coming until the debounce deadline
|
||||
}
|
||||
|
||||
// Solo: startMining() drops a call while another setgenerate is in flight, so wait for it (its result
|
||||
// wakes the loop). Applies only while solo mining is on, as before.
|
||||
if (mining_solo_threads_pending_ > 0 && !mining_toggle_in_progress_.load(std::memory_order_relaxed)) {
|
||||
const int t = mining_solo_threads_pending_;
|
||||
mining_solo_threads_pending_ = 0;
|
||||
if (state_.mining.generate) startMining(t);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Restart Setup Wizard (from Settings)
|
||||
// ===========================================================================
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#pragma once
|
||||
|
||||
// Coalesces mining thread-count picks. The Mining tab reports every intermediate value of a gesture (a tile
|
||||
// drag crosses up to ~24 tiles; the -/+ stepper is clicked repeatedly); only the LAST one should reach the
|
||||
// settings file and the miner, once, when the input settles: on drag release, or kDebounceSec after the
|
||||
// last click. Pure: time is passed in; nothing here touches settings, the miner or ImGui.
|
||||
|
||||
namespace dragonx {
|
||||
namespace services {
|
||||
|
||||
class MiningThreadCommit {
|
||||
public:
|
||||
static constexpr double kDebounceSec = 0.5;
|
||||
|
||||
// The user picked `threads` at `now` (seconds, any monotonic clock). Latest wins.
|
||||
void select(int threads, double now)
|
||||
{
|
||||
target_ = threads;
|
||||
pending_ = true;
|
||||
due_ = now + kDebounceSec;
|
||||
}
|
||||
|
||||
// The drag that produced the pending pick ended: it is due now, not after the debounce.
|
||||
void release(double now)
|
||||
{
|
||||
if (pending_ && due_ > now) due_ = now;
|
||||
}
|
||||
|
||||
void cancel() { pending_ = false; }
|
||||
bool pending() const { return pending_; }
|
||||
|
||||
// Once per frame. `held`: a drag is still in progress (nothing commits until it ends). Returns true
|
||||
// exactly once per settled pick and sets `out` to it.
|
||||
bool take(double now, bool held, int& out)
|
||||
{
|
||||
if (!pending_ || held || now < due_) return false;
|
||||
pending_ = false;
|
||||
out = target_;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
int target_ = 0;
|
||||
bool pending_ = false;
|
||||
double due_ = 0.0;
|
||||
};
|
||||
|
||||
// The pool thread count a wanted count is compared with: the newest start/retarget posted (it includes a
|
||||
// restart still queued on the mining-control thread), else the count the running xmrig was started with,
|
||||
// else what its HTTP API last reported (that lags a restart by seconds, so it is only a last resort).
|
||||
inline int effectivePoolThreads(int queuedTarget, int requested, int apiActive)
|
||||
{
|
||||
if (queuedTarget > 0) return queuedTarget;
|
||||
if (requested > 0) return requested;
|
||||
return apiActive;
|
||||
}
|
||||
|
||||
} // namespace services
|
||||
} // namespace dragonx
|
||||
@@ -143,14 +143,8 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
auto applyThreads = [&](int tc) {
|
||||
tc = std::clamp(tc, 1, max_threads);
|
||||
if (tc == s_selected_threads) return;
|
||||
s_selected_threads = tc;
|
||||
app->settings()->setPoolThreads(tc);
|
||||
app->settings()->save();
|
||||
if (mining.generate) app->startMining(tc);
|
||||
if (s_pool_mode && state.pool_mining.xmrig_running) {
|
||||
app->stopPoolMining();
|
||||
app->startPoolMining(tc);
|
||||
}
|
||||
s_selected_threads = tc; // shown at once; the App applies it ~0.5 s after the last change
|
||||
app->selectMiningThreads(tc);
|
||||
};
|
||||
ImFont* stepFont = Type().iconSmall();
|
||||
// Match the -/+ button height to the InputInt's actual frame height (GetFrameHeight ==
|
||||
@@ -707,11 +701,14 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
|
||||
// Drag-to-select logic (disabled during benchmark; ignored while a dropdown popup is open)
|
||||
if (!benchActive && !anyPopupOpen && ImGui::IsMouseClicked(0) && hovered_thread > 0) {
|
||||
// Begin drag
|
||||
// Begin drag. A click on the tile that is already selected changes nothing (it used to save
|
||||
// settings and restart the miner).
|
||||
s_drag_active = true;
|
||||
s_drag_anchor_thread = hovered_thread;
|
||||
s_selected_threads = hovered_thread;
|
||||
threads_changed = true;
|
||||
if (hovered_thread != s_selected_threads) {
|
||||
s_selected_threads = hovered_thread;
|
||||
threads_changed = true;
|
||||
}
|
||||
}
|
||||
if (s_drag_active) {
|
||||
if (ImGui::IsMouseDown(0)) {
|
||||
@@ -783,17 +780,11 @@ void RenderMiningControls(App* app, const WalletState& state, const MiningInfo&
|
||||
dl->AddText(capFont, capFont->LegacySize, txtPos, txtCol, buf);
|
||||
}
|
||||
|
||||
if (threads_changed) {
|
||||
app->settings()->setPoolThreads(s_selected_threads);
|
||||
app->settings()->save();
|
||||
if (mining.generate) {
|
||||
app->startMining(s_selected_threads);
|
||||
}
|
||||
if (s_pool_mode && state.pool_mining.xmrig_running) {
|
||||
app->stopPoolMining();
|
||||
app->startPoolMining(s_selected_threads);
|
||||
}
|
||||
}
|
||||
// Report every change (the header, RAM estimate and tile fill follow s_selected_threads live), but
|
||||
// let the App apply it: it holds the commit while the mouse is down, so a drag across 16 tiles is
|
||||
// ONE settings save and at most ONE miner change, on release.
|
||||
if (threads_changed) app->selectMiningThreads(s_selected_threads);
|
||||
app->setMiningThreadDragActive(s_drag_active);
|
||||
|
||||
curY += gridH + secGap;
|
||||
}
|
||||
|
||||
@@ -140,16 +140,19 @@ static void RenderMiningTabContent(App* app)
|
||||
|
||||
// Sync thread grid with actual count when idle thread scaling adjusts threads
|
||||
// Skip during benchmark — the benchmark controls thread counts directly
|
||||
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active && !IsMiningBenchmarkActive()) {
|
||||
// ...nor while a thread-count pick is still on its way to the miner (the grid would snap back to the old count).
|
||||
if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active &&
|
||||
!app->miningThreadCommitPending() && !IsMiningBenchmarkActive()) {
|
||||
if (s_pool_mode && state.pool_mining.xmrig_running) {
|
||||
// Use the requested thread count (available immediately) rather
|
||||
// than threads_active from the xmrig API which lags during restarts.
|
||||
int reqThreads = app->getXmrigRequestedThreads();
|
||||
// Use the requested thread count (available immediately) rather than threads_active from the
|
||||
// xmrig API, which lags during restarts; the App's target also covers a restart still queued.
|
||||
const int queued = app->poolThreadsTarget();
|
||||
int reqThreads = queued > 0 ? queued : app->getXmrigRequestedThreads();
|
||||
if (reqThreads > 0)
|
||||
s_selected_threads = ClampMiningThreads(reqThreads, max_threads);
|
||||
else if (state.pool_mining.threads_active > 0)
|
||||
s_selected_threads = ClampMiningThreads(state.pool_mining.threads_active, max_threads);
|
||||
} else if (mining.generate && mining.genproclimit > 0) {
|
||||
} else if (mining.generate && mining.genproclimit > 0 && !app->isMiningToggleInProgress()) {
|
||||
s_selected_threads = ClampMiningThreads(mining.genproclimit, max_threads);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "services/scan_policy.h"
|
||||
#include "services/connect_scan_gate.h"
|
||||
#include "services/chat_scan_policy.h"
|
||||
#include "services/mining_thread_commit.h"
|
||||
#include "services/network_refresh_service.h"
|
||||
#include "services/refresh_scheduler.h"
|
||||
#include "services/note_consolidation.h"
|
||||
@@ -5686,6 +5687,68 @@ void testConsoleInputModel()
|
||||
EXPECT_EQ(errorLines[0].text, std::string("Error: bad rpc"));
|
||||
}
|
||||
|
||||
void testMiningThreadCommit()
|
||||
{
|
||||
using dragonx::services::MiningThreadCommit;
|
||||
using dragonx::services::effectivePoolThreads;
|
||||
int out = 0;
|
||||
|
||||
// Stepper: three clicks inside the debounce window -> one commit, with the last value.
|
||||
{
|
||||
MiningThreadCommit c;
|
||||
c.select(5, 10.0); c.select(6, 10.1); c.select(7, 10.2);
|
||||
EXPECT_TRUE(c.pending());
|
||||
EXPECT_FALSE(c.take(10.5, false, out)); // 0.3 s after the last click: not yet
|
||||
EXPECT_TRUE(c.take(10.75, false, out));
|
||||
EXPECT_EQ(out, 7);
|
||||
EXPECT_FALSE(c.pending());
|
||||
EXPECT_FALSE(c.take(11.0, false, out)); // consumed exactly once
|
||||
}
|
||||
// Drag: nothing commits while held, however long; the release commits the last tile at once.
|
||||
{
|
||||
MiningThreadCommit c;
|
||||
for (int i = 1; i <= 16; ++i) {
|
||||
c.select(i, 20.0 + i * 0.1);
|
||||
EXPECT_FALSE(c.take(20.0 + i * 0.1 + 0.9, true, out));
|
||||
}
|
||||
EXPECT_FALSE(c.take(40.0, true, out)); // still held 20 s later
|
||||
c.release(40.1);
|
||||
EXPECT_TRUE(c.take(40.1, false, out));
|
||||
EXPECT_EQ(out, 16);
|
||||
}
|
||||
// Plain click in real frame order: the tab's render selects (button down); on the next frame the App's
|
||||
// tick sees the button up, calls release() and then take(): it commits on that frame, not 0.5 s later.
|
||||
{
|
||||
MiningThreadCommit c;
|
||||
c.select(4, 30.0);
|
||||
c.release(30.016);
|
||||
EXPECT_TRUE(c.take(30.016, false, out));
|
||||
EXPECT_EQ(out, 4);
|
||||
}
|
||||
// release() with nothing pending changes nothing.
|
||||
{
|
||||
MiningThreadCommit c;
|
||||
c.release(1.0);
|
||||
EXPECT_FALSE(c.pending());
|
||||
EXPECT_FALSE(c.take(2.0, false, out));
|
||||
}
|
||||
// A new pick during the wait pushes the deadline out; cancel() drops it.
|
||||
{
|
||||
MiningThreadCommit c;
|
||||
c.select(2, 50.0);
|
||||
c.select(9, 50.4);
|
||||
EXPECT_FALSE(c.take(50.6, false, out));
|
||||
c.cancel();
|
||||
EXPECT_FALSE(c.pending());
|
||||
EXPECT_FALSE(c.take(60.0, false, out));
|
||||
}
|
||||
// Which pool count idle scaling compares with.
|
||||
EXPECT_EQ(effectivePoolThreads(8, 4, 2), 8); // a queued restart's target beats the stale values
|
||||
EXPECT_EQ(effectivePoolThreads(0, 4, 2), 4);
|
||||
EXPECT_EQ(effectivePoolThreads(0, 0, 2), 2);
|
||||
EXPECT_EQ(effectivePoolThreads(0, 0, 0), 0);
|
||||
}
|
||||
|
||||
void testMiningBenchmarkModel()
|
||||
{
|
||||
dragonx::ui::ThreadBenchmark benchmark;
|
||||
@@ -12660,6 +12723,7 @@ int main()
|
||||
testRendererHelpers();
|
||||
testConsoleInputModel();
|
||||
testMiningBenchmarkModel();
|
||||
testMiningThreadCommit();
|
||||
testBalanceAddressListModel();
|
||||
testExplorerBlockCache();
|
||||
testTransactionHistoryCache();
|
||||
|
||||
Reference in New Issue
Block a user