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();
|
pumpConsolidation();
|
||||||
// Background auto-consolidation (settings toggle): gently trickle-merge notes when the wallet is idle.
|
// Background auto-consolidation (settings toggle): gently trickle-merge notes when the wallet is idle.
|
||||||
pumpAutoConsolidation();
|
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
|
// 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.
|
// 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
|
if (st.waitingSince < 0.0 || now - st.lastAskAt > cfg_.waitLapseSec) st.waitingSince = now; // a new wait
|
||||||
st.lastAskAt = now;
|
st.lastAskAt = now;
|
||||||
st.askVisible = visible;
|
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);
|
pruneLeases(now);
|
||||||
if (!live_.empty()) return {}; // one wallet scan in the daemon at a time
|
if (!live_.empty()) return {}; // one wallet scan in the daemon at a time
|
||||||
if (bestWaiter(now) != stream) return {};
|
if (bestWaiter(now) != stream) return {};
|
||||||
|
|||||||
@@ -159,6 +159,21 @@ public:
|
|||||||
void invalidate(ScanStream stream); // forget its basis: the next routine scan is needed
|
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
|
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 inFlight(ScanStream stream) const;
|
||||||
bool anyInFlight() const { return !live_.empty(); }
|
bool anyInFlight() const { return !live_.empty(); }
|
||||||
double quietUntil() const { return quietUntil_; }
|
double quietUntil() const { return quietUntil_; }
|
||||||
@@ -208,6 +223,7 @@ private:
|
|||||||
|
|
||||||
Config cfg_;
|
Config cfg_;
|
||||||
std::array<Stream, static_cast<std::size_t>(ScanStream::Count)> streams_{};
|
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_;
|
std::vector<Live> live_;
|
||||||
double quietUntil_ = 0.0;
|
double quietUntil_ = 0.0;
|
||||||
int tipLag_ = 0;
|
int tipLag_ = 0;
|
||||||
|
|||||||
@@ -18030,6 +18030,23 @@ void testWalletScanGovernor()
|
|||||||
EXPECT_TRUE(static_cast<bool>(gov.tryAdmit(ScanStream::Balance, true, sig, 372.0)));
|
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
|
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.
|
// The between-blocks probe's rotation: every z-address once (oldest-scanned first) before it is done.
|
||||||
|
|||||||
Reference in New Issue
Block a user