-
Notifications
You must be signed in to change notification settings - Fork 0
feat(api): align stats/daily + whale/tx with the legacy indexer contract #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,8 @@ struct IndexerConfig { | |
| indexer_backfill_loop_secs: u64, | ||
| #[serde(default = "default_analytics_flush_secs")] | ||
| indexer_analytics_flush_secs: u64, | ||
| #[serde(default = "default_stats_refresh_secs")] | ||
| indexer_stats_refresh_secs: u64, | ||
| } | ||
|
|
||
| fn default_network() -> String { | ||
|
|
@@ -67,6 +69,9 @@ fn default_clickhouse_table() -> String { | |
| fn default_analytics_flush_secs() -> u64 { | ||
| 15 | ||
| } | ||
| fn default_stats_refresh_secs() -> u64 { | ||
| 300 | ||
| } | ||
|
|
||
| #[tokio::main] | ||
| async fn main() -> anyhow::Result<()> { | ||
|
|
@@ -221,10 +226,45 @@ async fn main() -> anyhow::Result<()> { | |
| }) | ||
| }; | ||
|
|
||
| // Stats MV refresh loop. `stats_daily_mv` (migration 0002) has no | ||
| // auto-refresh; without this it stays empty and `/stats/daily` returns | ||
| // nothing. The first tick fires immediately and does a plain (blocking) | ||
| // refresh — Postgres rejects `REFRESH ... CONCURRENTLY` on a | ||
| // never-populated MV — then every subsequent tick uses CONCURRENTLY so | ||
| // reads are never locked out. | ||
| let stats_refresh_handle = { | ||
| let pool = pool.clone(); | ||
| let cancel = cancel.clone(); | ||
| let interval = Duration::from_secs(cfg.indexer_stats_refresh_secs); | ||
| tokio::spawn(async move { | ||
| let mut populated = false; | ||
| let mut tick = tokio::time::interval(interval); | ||
| loop { | ||
| tokio::select! { | ||
| _ = cancel.cancelled() => return Ok::<(), anyhow::Error>(()), | ||
| _ = tick.tick() => { | ||
| let res = if populated { | ||
| indexer_db::stats::refresh(&pool).await | ||
| } else { | ||
| indexer_db::stats::refresh_full(&pool).await | ||
| }; | ||
| match res { | ||
| Ok(()) => populated = true, | ||
| Err(e) => { | ||
| tracing::warn!(error = %e, "stats_daily_mv refresh failed"); | ||
| } | ||
|
Comment on lines
+246
to
+255
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a timeout around each MV refresh call to keep shutdown responsive. The refresh future is awaited directly; if the DB call hangs, this task cannot observe cancellation and Proposed fix _ = tick.tick() => {
- let res = if populated {
- indexer_db::stats::refresh(&pool).await
- } else {
- indexer_db::stats::refresh_full(&pool).await
- };
+ let res = tokio::time::timeout(Duration::from_secs(30), async {
+ if populated {
+ indexer_db::stats::refresh(&pool).await
+ } else {
+ indexer_db::stats::refresh_full(&pool).await
+ }
+ }).await;
match res {
- Ok(()) => populated = true,
- Err(e) => {
+ Ok(Ok(())) => populated = true,
+ Ok(Err(e)) => {
tracing::warn!(error = %e, "stats_daily_mv refresh failed");
}
+ Err(_) => {
+ tracing::warn!("stats_daily_mv refresh timed out");
+ }
}
}Also applies to: 267-267 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| } | ||
| } | ||
| }) | ||
| }; | ||
|
|
||
| shutdown_signal().await; | ||
| tracing::info!("indexer: shutdown signal received; cancelling workers"); | ||
| cancel.cancel(); | ||
|
|
||
| let _ = stats_refresh_handle.await?; | ||
| let _ = backfill_handle.await?; | ||
| let _ = coinblast_handle.await?; | ||
| if let Some(t) = tail_handle { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Doestokio::time::intervalallow a zero Duration, or does it panic/error?💡 Result:
The
tokio::time::intervalfunction panics if provided with a zeroDuration[1][2][3]. This behavior is explicitly documented in the official Tokio API documentation [1][4]. The implementation enforces this requirement using an assertion, ensuring that the period must be greater than zero [5][6][7]. This also applies totokio::time::interval_at[4][5].Citations:
🏁 Script executed:
Repository: Sentriscloud/indexer-rs
Length of output: 231
🏁 Script executed:
Repository: Sentriscloud/indexer-rs
Length of output: 11879
🏁 Script executed:
Repository: Sentriscloud/indexer-rs
Length of output: 6702
Guard
INDEXER_STATS_REFRESH_SECS> 0 and make stats refresh shutdown-cancel responsivecfg.indexer_stats_refresh_secs == 0will panic when constructingtokio::time::interval(Duration::from_secs(cfg.indexer_stats_refresh_secs)); fail fast on startup.Proposed fix
indexer_db::stats::refresh(_full)directly (no timeout) and doesn’t observecancel.cancelled()until that DB await completes;mainthen awaitsstats_refresh_handleaftercancel.cancel().🤖 Prompt for AI Agents