Skip to content

Commit 396c97b

Browse files
committed
fix: indexer getting stuck when syncing from genesis
1 parent affdfce commit 396c97b

1 file changed

Lines changed: 80 additions & 2 deletions

File tree

crates/node/src/indexer/real.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use near_account_id::AccountId;
1919
use near_async::ActorSystem;
2020
use near_indexer::Indexer;
2121
use near_mpc_contract_interface::types::ProtocolContractState;
22+
use std::future::Future;
2223
use std::path::PathBuf;
2324
use std::sync::Arc;
2425
#[cfg(feature = "network-hardship-simulation")]
@@ -110,15 +111,41 @@ pub fn spawn_real_indexer(
110111

111112
let indexer = Indexer::from_near_node(near_indexer_config, near_config, &near_node);
112113

113-
let stream = indexer.streamer();
114-
115114
let indexer_state = Arc::new(IndexerState::new(
116115
near_node.view_client,
117116
near_node.client,
118117
near_node.rpc_handler,
119118
mpc_indexer_config.mpc_contract_id.clone(),
120119
));
121120

121+
tracing::info!("Indexer waiting for node to finish syncing before streaming blocks.");
122+
123+
// Defer all indexing work until the node has finished syncing. On a
124+
// node that state-syncs from scratch, neard sits at genesis until
125+
// sync completes; starting the streamer then pins its cursor at
126+
// genesis (`LatestSynced`), below the node's block tail, so it can
127+
// never reach the chain tip. (#3623)
128+
//
129+
// The wait is raced against `shutdown_token` so a SIGTERM during the
130+
// (possibly long) initial state sync still tears the thread down
131+
// cleanly instead of blocking until sync finishes.
132+
if !await_sync_or_shutdown(
133+
indexer_state.client.wait_for_full_sync(),
134+
&shutdown_token,
135+
)
136+
.await
137+
{
138+
tracing::info!(
139+
"Indexer thread received shutdown signal before sync completed; exiting."
140+
);
141+
let _ = indexer_exit_sender.send(Ok(()));
142+
return;
143+
}
144+
145+
// The node is fully synced by this point, so `LatestSynced` resolves
146+
// to the chain tip rather than genesis.
147+
let stream = indexer.streamer();
148+
122149
let txn_sender_result = TransactionProcessorHandle::start_transaction_processor(
123150
my_near_account_id_clone,
124151
account_secret_key.clone(),
@@ -289,3 +316,54 @@ pub fn spawn_real_indexer(
289316
foreign_chain_policy_reader,
290317
}
291318
}
319+
320+
/// Waits for `sync` to resolve, racing it against `shutdown`.
321+
///
322+
/// Returns `true` if `sync` completed first, or `false` if `shutdown` was
323+
/// requested first. Racing the wait keeps a SIGTERM responsive during a long
324+
/// initial state sync.
325+
async fn await_sync_or_shutdown(
326+
sync: impl Future<Output = ()>,
327+
shutdown: &CancellationToken,
328+
) -> bool {
329+
tokio::select! {
330+
_ = sync => true,
331+
_ = shutdown.cancelled() => false,
332+
}
333+
}
334+
335+
#[cfg(test)]
336+
#[expect(non_snake_case)] // tests follow `<system_under_test>__should_<assertion>` convention
337+
mod tests {
338+
use super::await_sync_or_shutdown;
339+
use std::future::pending;
340+
use tokio_util::sync::CancellationToken;
341+
342+
#[tokio::test]
343+
async fn await_sync_or_shutdown__should_return_true_when_sync_completes_first() {
344+
// Given
345+
let shutdown = CancellationToken::new();
346+
347+
// When
348+
let synced = await_sync_or_shutdown(async {}, &shutdown).await;
349+
350+
// Then
351+
assert!(synced);
352+
}
353+
354+
/// The dominant production path: a SIGTERM arrives while the node is still
355+
/// syncing, so the wait must yield to shutdown rather than block on sync.
356+
#[tokio::test]
357+
async fn await_sync_or_shutdown__should_return_false_when_shutdown_during_sync() {
358+
// Given
359+
let shutdown = CancellationToken::new();
360+
let shutdown_clone = shutdown.clone();
361+
tokio::spawn(async move { shutdown_clone.cancel() });
362+
363+
// When
364+
let synced = await_sync_or_shutdown(pending::<()>(), &shutdown).await;
365+
366+
// Then
367+
assert!(!synced);
368+
}
369+
}

0 commit comments

Comments
 (0)