Skip to content

fix: prevent hung archiver fetches from stopping nodelist refresh - #28

Open
jairajdev wants to merge 1 commit into
mainfrom
fix/nodelist-refresh-timeout
Open

fix: prevent hung archiver fetches from stopping nodelist refresh#28
jairajdev wants to merge 1 commit into
mainfrom
fix/nodelist-refresh-timeout

Conversation

@jairajdev

Copy link
Copy Markdown
Contributor

A hung archiver HTTP request could block the nodelist refresh loop indefinitely, leaving the proxy serving a stale node list for days.

  • Add reqwest client timeouts to nodelist refresh and archiver discovery using max_http_timeout_ms
  • Log fetch, read, and parse failures to stderr so PM2 error logs capture archiver issues
  • Run nodelist refresh and archiver discovery in spawned tasks so the interval ticker is not blocked
  • Continue to the next archiver when a fetch fails or times out instead of stalling the loop

A hung archiver HTTP request could block the nodelist refresh loop indefinitely, leaving the proxy serving a stale node list for days.

- Add reqwest client timeouts to nodelist refresh and archiver discovery using max_http_timeout_ms
- Log fetch, read, and parse failures to stderr so PM2 error logs capture archiver issues
- Run nodelist refresh and archiver discovery in spawned tasks so the interval ticker is not blocked
- Continue to the next archiver when a fetch fails or times out instead of stalling the loop
Comment thread src/main.rs
ticker.tick().await;
_liberdus.update_active_nodelist().await;
let liberdus = Arc::clone(&_liberdus);
tokio::spawn(async move {

@kgmyatthu kgmyatthu Jul 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spawning unnecessary task, as this scope is already inside a tokio multi threaded task, This will resut in O(n) task grow from original O(1) task each iteration of loop.

Just do this.

Suggested change
tokio::spawn(async move {
_liberdus.update_active_nodelist().await;

Main file doesn't need to be touch at all for this PR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep the main file untouched.

Comment thread src/main.rs
ticker.tick().await;
Arc::clone(&_archivers).discover().await;
let archivers = Arc::clone(&_archivers);
tokio::spawn(async move {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same review as spawning nodelist update

@kgmyatthu kgmyatthu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix cargo fmt.

Comment thread src/archivers.rs

tokio::spawn(async move {
let resp = match reqwest::get(url).await {
let client = match reqwest::Client::builder()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building a http client for every task spawn in for loop is extremely expensive and cost thread processing time.

Move this line out of the for loop and Atomic reference clone it if you're gonna go builder pattern.

Otherwise just use request::get(url)

Comment thread src/archivers.rs
}
};
let body: Result<SignedArchiverListResponse, _> =
serde_json::from_str(&resp.text().await.unwrap());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I genuinely think using unwrap(); in my previous implementation was the actual problem and none of the new code is necessary if we could just gracefully handle the unwrap();

What I think happened is unwrap() introduce a panic that poison the background recurring tokio task loop and the loop break off.

@jairajdev So all you have to do is use a match arm in unpacking that resp.text() instead of unwrap(). That would just solve the entire problem without all this code changes. Because one iterative failure only stale for about 60sec and the next iteration of discovery will kick in due to infinite loop in the main. The reason why I think unwrap(); is the culprit is that it break the loop and diminishes entire background thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants