Skip to content
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

fix(provider): Do not overflow LRU cache capacity in ChainStreamPoller #1052

Merged
merged 1 commit into from
Jul 15, 2024

Conversation

popzxc
Copy link
Contributor

@popzxc popzxc commented Jul 15, 2024

Motivation

I started to work on handling reorgs in ChainStreamPoller, but noticed a few adjacent problems that probably should be fixed first.
Here, I noticed that we don't check whether we've reached the LRU cache capacity. As a result, if a lot of blocks are created in a short time (e.g. via anvil_mine), some blocks may be skipped.

This PR fixes that, and also adds a few basic tests for ChainStreamPoller.

Solution

Only add new items to the cache if we don't reach capacity.

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

Self {
client: client.clone(),
poll_task: PollerBuilder::new(client, "eth_blockNumber", ()),
next_yield: NO_BLOCK_NUMBER,
next_yield,
known_blocks: LruCache::new(BLOCK_CACHE_SIZE),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, I'm not quite sure why an LRU cache is used here. Wouldn't a simple VecDeque suffice?

Copy link
Member

Choose a reason for hiding this comment

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

yeah, imo vecdeque would be more appropriate here actually

@@ -106,8 +112,77 @@ impl<T: Transport + Clone, N: Network> ChainStreamPoller<T, N> {
}
};
self.known_blocks.put(number, block);
if self.known_blocks.len() == BLOCK_CACHE_SIZE.get() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The implication here is that we will have to wait for the next value from poller_task while we technically know already that there are more blocks we can fetch. However, implementing it may be somewhat complicated and feels like a premature optimization.

Copy link
Member

Choose a reason for hiding this comment

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

hmm, imo like this loop's logic is flawed because this allows iterating next_yield..tip before we start yielding blocks again

I consider this a hotfix, but ideally we can solve this my improving this stream impl.

}
}
}
}
}

#[cfg(all(test, feature = "anvil-api"))] // Tests rely heavily on ability to mine blocks on demand.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Writing tests without anvil_mine would be more complex, so I hope it's fine given that tests are run with --all-features in CI.

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

this stream impl is a bit weird because this does things sequentially that should be done sequentially?

I'm fine with this hotfix for now, but we should rethink this and get rid of the lru, seems weird

@@ -106,8 +112,77 @@ impl<T: Transport + Clone, N: Network> ChainStreamPoller<T, N> {
}
};
self.known_blocks.put(number, block);
if self.known_blocks.len() == BLOCK_CACHE_SIZE.get() {
Copy link
Member

Choose a reason for hiding this comment

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

hmm, imo like this loop's logic is flawed because this allows iterating next_yield..tip before we start yielding blocks again

I consider this a hotfix, but ideally we can solve this my improving this stream impl.

Self {
client: client.clone(),
poll_task: PollerBuilder::new(client, "eth_blockNumber", ()),
next_yield: NO_BLOCK_NUMBER,
next_yield,
known_blocks: LruCache::new(BLOCK_CACHE_SIZE),
Copy link
Member

Choose a reason for hiding this comment

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

yeah, imo vecdeque would be more appropriate here actually

@mattsse mattsse merged commit c461d68 into alloy-rs:main Jul 15, 2024
22 checks passed
@popzxc popzxc deleted the stream-poller-lru-capacity branch July 15, 2024 11:23
j75689 pushed a commit to bnb-chain/alloy that referenced this pull request Aug 1, 2024
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