Files
ObsidianDragon/src/services/refresh_scheduler.h
T
DanSandClaude Opus 5.5 f86cc4d6b7 perf(net): hold wallet scans at connect, in the background and on tab entry
- Connect gate: after the node starts answering, wallet scans wait until the
  lock state is known and the node knows its tip (or the wallet has fewer
  than 500 transactions), at most 20 s. A node already behind latches the
  sync profile from the connect's getinfo. Meanwhile a list-only pass keeps
  the address list and the cached history up, and the session's first
  balance and per-address balances are read once even while catching up,
  so the wallet shows its last-synced values instead of 0.
- Background: minimized, hidden, or covered without focus (3 s debounce,
  any input cancels) polls only the chain state every 15 s. Wallet scans,
  the mining poll, price, nudges and auto-lock interaction tracking pause;
  auto-lock now fires while minimized. Leaving a held profile marks every
  timer due again.
- Tab entry marks the timers due instead of forcing scans; a stale list or
  a never-loaded history still loads on entry.
- The scan entry points own the hard gates, failed enumerations keep the
  previous lists behind a retry hold, and failed balance calls never
  overwrite known balances with 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:06:34 -05:00

113 lines
4.5 KiB
C++

#pragma once
#include "ui/sidebar.h"
#include <array>
namespace dragonx {
namespace services {
class RefreshScheduler {
public:
enum class Timer {
Core,
Transactions,
Addresses,
Peers,
Price,
Fast,
TxAge,
Opid
};
struct Intervals {
float core;
float transactions;
float addresses;
float peers;
};
static constexpr float kCoreDefault = 5.0f;
static constexpr float kAddressDefault = 15.0f;
static constexpr float kTransactionDefault = 10.0f;
static constexpr float kPeerDefault = 10.0f;
static constexpr float kPrice = 60.0f;
static constexpr float kFast = 1.0f;
static constexpr float kTxMaxAge = 15.0f;
static constexpr float kOpidPoll = 2.0f;
// Low-impact polling profile applied while the daemon is SYNCING, regardless of the active tab.
// Only a slow progress poll runs (core, 10s); transactions/addresses/peers are disabled (0).
// Frequent getpeerinfo, per-block transaction scans, and balance polls all contend for the
// daemon's cs_main lock and measurably slow block connection during sync — this is exactly why
// the lightweight Console tab syncs faster than the Peers tab. Reverts to the per-tab profile
// once sync completes. (Tx/address/balance data is incomplete mid-sync anyway.)
static constexpr Intervals kSyncProfile{10.0f, 0.0f, 0.0f, 0.0f};
// While the window is in the background (minimized, hidden, or covered without focus): only the chain
// state, slower. It keeps sync detection, the lost-connection check and the balance's own rare poll
// (auto-shield) alive; the wallet scans and peers wait for the window to come back.
static constexpr Intervals kBackgroundProfile{15.0f, 0.0f, 0.0f, 0.0f};
// How long the sync profile holds after the node last read "behind" (see the Core callback). Must
// outlast two Core polls in every held profile, or it could lapse between two "behind" readings.
static constexpr float kSyncSettleSeconds = 30.0f;
static Intervals intervalsForPage(ui::NavPage page);
// The profile in force: background, else syncing, else the page's.
static Intervals selectIntervals(ui::NavPage page, bool syncing, bool background);
void applyPage(ui::NavPage page);
void setIntervals(Intervals intervals);
const Intervals& intervals() const { return intervals_; }
void tick(float deltaSeconds);
bool isDue(Timer timer) const;
bool consumeDue(Timer timer);
void reset(Timer timer);
void markDue(Timer timer);
void setTimer(Timer timer, float seconds);
// Hold a timer for exactly `seconds`, whatever the page's interval (which only measures elapsed
// time): not due until then, due as soon as it runs out. consumeDue/reset/markDue/setTimer end it.
// For retry backoff: the old "setTimer(interval - delay)" collapsed to an immediate retry wherever
// the page interval was shorter than the delay (every frame on Overview/Send).
void deferFor(Timer timer, float seconds);
// Due again `seconds` from now (a scan the governor held back asks again soon). Unlike deferFor, never
// due on a 0-interval timer (the sync profile's), so a held-back scan can't fire mid-sync.
void retrySoon(Timer timer, float seconds);
float timer(Timer timer) const;
float interval(Timer timer) const;
void markImmediateRefresh();
void markWalletMutationRefresh();
void resetTxAge();
bool shouldRefreshTransactions(int lastTxBlockHeight,
int currentBlockHeight,
bool transactionsDirty) const;
private:
struct Timers {
float core = 0.0f;
float transactions = 0.0f;
float addresses = 0.0f;
float peers = 0.0f;
float price = 0.0f;
float fast = 0.0f;
float txAge = 0.0f;
float opid = 0.0f;
};
float& timerRef(Timer timer);
const float& timerRef(Timer timer) const;
static constexpr int kTimerCount = 8;
float& holdRef(Timer timer) { return holds_[static_cast<int>(timer)]; }
float hold(Timer timer) const { return holds_[static_cast<int>(timer)]; }
Intervals intervals_{kCoreDefault, kTransactionDefault, kAddressDefault, kPeerDefault};
Timers timers_;
// deferFor() holds: > 0 counting down (not due), 0 ran out (due), < 0 none.
std::array<float, kTimerCount> holds_{{-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f}};
};
} // namespace services
} // namespace dragonx