feat(wallet): pause routine wallet scans during a user-run consolidation
A manual consolidation's merges compete with the GUI's routine wallet scans for the daemon's cs_main: the run won't fire the next merge while any scan is in flight, and because each merge mutates the wallet it re-triggers the balance / history scans, which then delay the following merge. The WalletScanGovernor already serializes all of these one-at-a-time under a cs_main budget, so the fix is to pause the heavy routine streams — not every RPC — while a user run owns the budget. WalletScanGovernor gains per-stream routine suppression (tryAdmit returns empty for a paused stream, which keeps waiting and resumes the instant it is unpaused; explicit needs via beginExplicit are never paused), plus a convenience that pauses the balance / addresses / history / recent-tx group together. App::update pauses that group while consolidation_.active() AND the run is user-started — an auto (background) run is left gentle and untouched, and the cheap sync / peer / mempool / operation-status polls keep running so sync and disconnects stay visible. The end-of-run forced balance refresh repaints once the run finishes. Note: a manual run is bounded (≤ maxBatches per click, ~minutes), so the balance and recent-tx views are only briefly stale — an incoming payment shows once the run finishes or is stopped. Unit-tested (testWalletScanGovernor): a paused stream is not admitted but keeps waiting, explicit bypasses it, unpausing re-admits, and chat/Adhoc are never in the paused group. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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<std::size_t>(stream)]) return {};
|
||||
pruneLeases(now);
|
||||
if (!live_.empty()) return {}; // one wallet scan in the daemon at a time
|
||||
if (bestWaiter(now) != stream) return {};
|
||||
|
||||
@@ -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<std::size_t>(stream)] = paused; }
|
||||
bool streamSuppressed(ScanStream stream) const { return suppressed_[static_cast<std::size_t>(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<Stream, static_cast<std::size_t>(ScanStream::Count)> streams_{};
|
||||
std::array<bool, static_cast<std::size_t>(ScanStream::Count)> suppressed_{}; // routine scans paused per stream
|
||||
std::vector<Live> live_;
|
||||
double quietUntil_ = 0.0;
|
||||
int tipLag_ = 0;
|
||||
|
||||
@@ -18030,6 +18030,23 @@ void testWalletScanGovernor()
|
||||
EXPECT_TRUE(static_cast<bool>(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<bool>(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<bool>(ex));
|
||||
gov.complete(ex, 0.0, ScanOutcome::Skipped, 0.3);
|
||||
gov.suppressRoutineWalletScans(false);
|
||||
EXPECT_FALSE(gov.streamSuppressed(ScanStream::Balance));
|
||||
EXPECT_TRUE(static_cast<bool>(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.
|
||||
|
||||
Reference in New Issue
Block a user