-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: add IntoChunksExt trait for chunking
#125
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
base: main
Are you sure you want to change the base?
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 |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| use itertools::Itertools; | ||
|
|
||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| /// A partially-applied chunking operation over an iterator. | ||
| /// | ||
| /// Produced by [`IntoChunksExt::into_chunks`]; call [`take_chunks`] to | ||
| /// finish. | ||
| /// | ||
| /// [`take_chunks`]: Chunked::take_chunks | ||
| pub struct Chunked<I> { | ||
| iter: I, | ||
| chunk_size: usize, | ||
| } | ||
|
|
||
| impl<'a, T, I> Chunked<I> | ||
| where | ||
| I: Iterator<Item = &'a T>, | ||
| T: Clone + 'a, | ||
| { | ||
| /// Collects at most `max_chunks` chunks, discarding any remaining items. | ||
| pub fn take_chunks(self, max_chunks: usize) -> Vec<Vec<T>> { | ||
| let chunked = self.iter.chunks(self.chunk_size); | ||
| chunked | ||
| .into_iter() | ||
| .take(max_chunks) | ||
| .map(|chunk| chunk.cloned().collect()) | ||
| .collect() | ||
| } | ||
| } | ||
|
|
||
| /// Extends iterators of references with staged chunked collection. | ||
| /// | ||
| /// # Example | ||
| /// | ||
| /// ```ignore | ||
| /// let data = vec![1, 2, 3, 4, 5, 6, 7]; | ||
| /// let chunks = data.iter().into_chunks(3).take_chunks(2); | ||
| /// assert_eq!(chunks, vec![vec![1, 2, 3], vec![4, 5, 6]]); | ||
| /// ``` | ||
| pub trait IntoChunksExt<'a, T: 'a>: Sized { | ||
| /// Begins a chunked collection with the given chunk size. | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// Panics if `chunk_size` is zero. | ||
| fn into_chunks(self, chunk_size: usize) -> Chunked<Self>; | ||
| } | ||
|
|
||
| impl<'a, T: 'a, I> IntoChunksExt<'a, T> for I | ||
| where | ||
| I: Iterator<Item = &'a T>, | ||
| { | ||
| fn into_chunks(self, chunk_size: usize) -> Chunked<Self> { | ||
| assert!(chunk_size > 0, "chunk_size must be greater than zero"); | ||
| Chunked { | ||
| iter: self, | ||
| chunk_size, | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+42
to
+62
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn should_split_into_chunks_of_given_size() { | ||
| let data: Vec<i32> = (1..=7).collect(); | ||
| let chunks = data.iter().into_chunks(3).take_chunks(10); | ||
| assert_eq!(chunks, vec![vec![1, 2, 3], vec![4, 5, 6], vec![7]]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn should_limit_to_max_chunks() { | ||
| let data: Vec<i32> = (1..=9).collect(); | ||
| let chunks = data.iter().into_chunks(3).take_chunks(2); | ||
| assert_eq!(chunks, vec![vec![1, 2, 3], vec![4, 5, 6]]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn should_return_empty_for_empty_input() { | ||
| let data: Vec<i32> = vec![]; | ||
| let chunks = data.iter().into_chunks(3).take_chunks(5); | ||
| assert!(chunks.is_empty()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn should_return_empty_when_max_chunks_is_zero() { | ||
| let data: Vec<i32> = (1..=9).collect(); | ||
| let chunks = data.iter().into_chunks(3).take_chunks(0); | ||
| assert!(chunks.is_empty()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn should_handle_chunk_size_larger_than_input() { | ||
| let data: Vec<i32> = vec![1, 2, 3]; | ||
| let chunks = data.iter().into_chunks(10).take_chunks(5); | ||
| assert_eq!(chunks, vec![vec![1, 2, 3]]); | ||
| } | ||
|
|
||
| #[test] | ||
| #[should_panic(expected = "chunk_size must be greater than zero")] | ||
| fn should_panic_on_zero_chunk_size() { | ||
| let data: Vec<i32> = vec![1, 2, 3]; | ||
| let _ = data.iter().into_chunks(0); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| pub mod chunks; | ||
| pub mod insertion_ordered_map; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,6 @@ use solana_address::Address; | |
| use canlog::log; | ||
| use cksol_types_internal::log::Priority; | ||
|
|
||
| use itertools::Itertools; | ||
| use sol_rpc_types::Slot; | ||
| use solana_hash::Hash; | ||
|
|
||
|
|
@@ -26,6 +25,7 @@ use crate::{ | |
| mutate_state, read_state, | ||
| }, | ||
| transaction::{get_recent_slot_and_blockhash, submit_transaction}, | ||
| utils::chunks::IntoChunksExt, | ||
| }; | ||
|
|
||
| pub const WITHDRAWAL_PROCESSING_DELAY: Duration = Duration::from_mins(1); | ||
|
|
@@ -102,9 +102,10 @@ pub async fn process_pending_withdrawals<R: CanisterRuntime>(runtime: R) { | |
| } | ||
| }; | ||
|
|
||
| let (affordable_requests, num_pending_withdrawals) = read_state(|state| { | ||
| let (batches, more_to_process, needs_consolidation) = read_state(|state| { | ||
| let mut available_balance = state.balance(); | ||
| let pending = state.pending_withdrawal_requests(); | ||
| let num_pending = pending.len(); | ||
|
|
||
| let affordable: Vec<_> = pending | ||
| .values() | ||
|
|
@@ -119,31 +120,27 @@ pub async fn process_pending_withdrawals<R: CanisterRuntime>(runtime: R) { | |
| .map(|t| t.request.clone()) | ||
| .collect(); | ||
|
|
||
| (affordable, pending.len()) | ||
| let more_to_process = affordable.len() > MAX_CONCURRENT_RPC_CALLS * MAX_WITHDRAWALS_PER_TX; | ||
| let needs_consolidation = affordable.len() < num_pending; | ||
| let batches = affordable | ||
| .iter() | ||
| .into_chunks(MAX_WITHDRAWALS_PER_TX) | ||
| .take_chunks(MAX_CONCURRENT_RPC_CALLS); | ||
| (batches, more_to_process, needs_consolidation) | ||
|
Comment on lines
+125
to
+129
|
||
| }); | ||
|
|
||
| if affordable_requests.len() < num_pending_withdrawals { | ||
| if needs_consolidation { | ||
| log!( | ||
| Priority::Info, | ||
| "Insufficient minter balance for some withdrawal requests, scheduling consolidation" | ||
| ); | ||
| runtime.set_timer(Duration::ZERO, consolidate_deposits); | ||
| } | ||
|
|
||
| let more_to_process = | ||
| affordable_requests.len() > MAX_CONCURRENT_RPC_CALLS * MAX_WITHDRAWALS_PER_TX; | ||
| let reschedule = scopeguard::guard(runtime.clone(), |runtime| { | ||
| runtime.set_timer(Duration::ZERO, process_pending_withdrawals); | ||
| }); | ||
|
|
||
| let batches: Vec<Vec<_>> = affordable_requests | ||
| .into_iter() | ||
| .chunks(MAX_WITHDRAWALS_PER_TX) | ||
| .into_iter() | ||
| .take(MAX_CONCURRENT_RPC_CALLS) | ||
| .map(Iterator::collect) | ||
| .collect(); | ||
|
|
||
| if batches.is_empty() { | ||
| // Nothing to process | ||
| scopeguard::ScopeGuard::into_inner(reschedule); | ||
|
|
||
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.
The current
Chunked/IntoChunksExtdesign only supportsIterator<Item = &T>and builds chunks by cloning (chunk.cloned().collect()). This forces callers with owned data (e.g.,Vec<(Account, (Lamport, Vec<...>))>) to iterate by reference and clone just to batch. Consider adding an owning variant/impl that chunksIterator<Item = T>by moving items, so batch construction can remain allocation-efficient.