diff --git a/src/app.cpp b/src/app.cpp index 05051c3..89e19c0 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1594,6 +1594,11 @@ void App::update() pumpConsolidation(); // Background auto-consolidation (settings toggle): gently trickle-merge notes when the wallet is idle. pumpAutoConsolidation(); + // While a USER-started consolidation is running, pause the heavy routine wallet scans (balance / + // addresses / history / recent-tx) so its merges get more of the node's cs_main budget and finish + // sooner — the end-of-run refresh repaints. An auto (background) run stays gentle and is left alone, and + // explicit needs (the user's own send, a never-loaded list) still go through. Idempotent per frame. + scan_governor_.suppressRoutineWalletScans(consolidation_.active() && !auto_consolidate_run_); // A Settings bootstrap that finished while its dialog wasn't drawn (clicked away, minimized): process it // here so the connect and reconcile gates release and the post-bootstrap rescan is armed. diff --git a/src/services/wallet_scan_governor.cpp b/src/services/wallet_scan_governor.cpp index 61f8265..fd087ab 100644 --- a/src/services/wallet_scan_governor.cpp +++ b/src/services/wallet_scan_governor.cpp @@ -171,6 +171,9 @@ ScanTicket WalletScanGovernor::tryAdmit(ScanStream stream, bool visible, const S if (st.waitingSince < 0.0 || now - st.lastAskAt > cfg_.waitLapseSec) st.waitingSince = now; // a new wait st.lastAskAt = now; st.askVisible = visible; + // Paused (e.g. a user-started consolidation owns the cs_main budget): keep the stream waiting, admit + // nothing. It resumes immediately once unpaused; explicit needs still go through beginExplicit. + if (suppressed_[static_cast(stream)]) return {}; pruneLeases(now); if (!live_.empty()) return {}; // one wallet scan in the daemon at a time if (bestWaiter(now) != stream) return {}; diff --git a/src/services/wallet_scan_governor.h b/src/services/wallet_scan_governor.h index f28742d..f642ae7 100644 --- a/src/services/wallet_scan_governor.h +++ b/src/services/wallet_scan_governor.h @@ -159,6 +159,21 @@ public: void invalidate(ScanStream stream); // forget its basis: the next routine scan is needed void reset(); // a new connection: forget everything; old tickets complete as no-ops + // Pause/resume a stream's ROUTINE scans (tryAdmit returns empty while paused; the stream keeps waiting + // and resumes the moment it is unpaused). Explicit needs (beginExplicit) are NEVER paused. + void suppressStream(ScanStream stream, bool paused) { suppressed_[static_cast(stream)] = paused; } + bool streamSuppressed(ScanStream stream) const { return suppressed_[static_cast(stream)]; } + // Convenience: pause/resume the heavy wallet-display scans (balance, addresses, history, recent-tx) as a + // group — used to hand a user-started consolidation more of the cs_main budget. Chat scans and Adhoc + // (the consolidation's own merge calls) are left running. + void suppressRoutineWalletScans(bool paused) + { + suppressStream(ScanStream::Balance, paused); + suppressStream(ScanStream::Addresses, paused); + suppressStream(ScanStream::History, paused); + suppressStream(ScanStream::RecentTx, paused); + } + bool inFlight(ScanStream stream) const; bool anyInFlight() const { return !live_.empty(); } double quietUntil() const { return quietUntil_; } @@ -208,6 +223,7 @@ private: Config cfg_; std::array(ScanStream::Count)> streams_{}; + std::array(ScanStream::Count)> suppressed_{}; // routine scans paused per stream std::vector live_; double quietUntil_ = 0.0; int tipLag_ = 0; diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 9d4ea1c..e40a21c 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -18030,6 +18030,23 @@ void testWalletScanGovernor() EXPECT_TRUE(static_cast(gov.tryAdmit(ScanStream::Balance, true, sig, 372.0))); EXPECT_FALSE(gov.waiting(ScanStream::Balance, 373.0)); // admitted: no longer waiting } + { // Suppression: a paused routine stream is never admitted (but keeps waiting); explicit needs bypass + // it; unpausing admits it again. Used to give a user consolidation the cs_main budget. + G gov; + gov.suppressRoutineWalletScans(true); + EXPECT_TRUE(gov.streamSuppressed(ScanStream::Balance)); + EXPECT_FALSE(static_cast(gov.tryAdmit(ScanStream::Balance, true, sig, 0.0))); // paused: not admitted + EXPECT_TRUE(gov.waiting(ScanStream::Balance, 0.1)); // ...but still waiting + auto ex = gov.beginExplicit(ScanStream::Balance, sig, 0.2); // explicit bypasses it + EXPECT_TRUE(static_cast(ex)); + gov.complete(ex, 0.0, ScanOutcome::Skipped, 0.3); + gov.suppressRoutineWalletScans(false); + EXPECT_FALSE(gov.streamSuppressed(ScanStream::Balance)); + EXPECT_TRUE(static_cast(gov.tryAdmit(ScanStream::Balance, true, sig, 0.4))); // resumes once unpaused + // Chat scans and Adhoc (the merges themselves) are never in the suppressed group. + EXPECT_FALSE(gov.streamSuppressed(ScanStream::ChatNotes)); + EXPECT_FALSE(gov.streamSuppressed(ScanStream::Adhoc)); + } } // The between-blocks probe's rotation: every z-address once (oldest-scanned first) before it is done.