common_batcher/
pending_worker.rs1use tokio::time::Instant;
16
17use crate::flush_policy::{FlushPolicy, FlushTrigger};
18use crate::flush_timer::FlushTimer;
19use crate::pending_batch::PendingBatch;
20
21pub struct PendingWorker<T, P> {
27 batch: PendingBatch<T>,
28 flush_policy: P,
29 flush_timer: FlushTimer,
30}
31
32impl<T, P> PendingWorker<T, P> {
33 pub fn new(flush_policy: P) -> Self {
35 Self {
36 batch: PendingBatch::new(),
37 flush_policy,
38 flush_timer: FlushTimer::new(),
39 }
40 }
41
42 pub fn is_empty(&self) -> bool {
44 self.batch.is_empty()
45 }
46
47 pub fn total_rows(&self) -> usize {
49 self.batch.total_rows()
50 }
51
52 pub async fn wait_flush(&mut self) {
58 self.flush_timer.wait().await;
59 }
60
61 pub fn take_pending(&mut self) -> Option<Vec<T>> {
66 self.flush_timer.set_deadline(None);
67 if self.batch.is_empty() {
68 None
69 } else {
70 Some(self.batch.take())
71 }
72 }
73}
74
75impl<T, P: FlushPolicy> PendingWorker<T, P> {
76 pub fn submit(&mut self, item: T, total_rows: usize) {
86 self.batch.push(item, total_rows, Instant::now());
87 self.refresh_deadline();
88 }
89
90 pub fn take_ready(&mut self, trigger: FlushTrigger) -> Option<Vec<T>> {
95 if !self.batch.is_empty()
96 && self
97 .flush_policy
98 .should_flush(&self.batch, Instant::now(), trigger)
99 {
100 self.take_pending()
101 } else {
102 self.refresh_deadline();
103 None
104 }
105 }
106
107 fn refresh_deadline(&mut self) {
108 let deadline = if self.batch.is_empty() {
109 None
110 } else {
111 self.flush_policy.deadline(&self.batch)
112 };
113 self.flush_timer.set_deadline(deadline);
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use std::future::Future;
120 use std::task::Poll;
121 use std::time::Duration;
122
123 use super::*;
124 use crate::flush_policy::timing::TimingFlushPolicy;
125
126 fn worker(rows: usize) -> PendingWorker<i32, TimingFlushPolicy> {
127 PendingWorker::new(TimingFlushPolicy::try_new(Duration::from_millis(10), rows).unwrap())
128 }
129
130 async fn assert_wait_pending<T, P>(worker: &mut PendingWorker<T, P>) {
131 let wait = worker.wait_flush();
132 tokio::pin!(wait);
133 assert!(std::future::poll_fn(|cx| Poll::Ready(wait.as_mut().poll(cx).is_pending())).await);
134 }
135
136 #[tokio::test(start_paused = true)]
137 async fn test_first_deadline_and_cancelled_wait() {
138 let mut worker = worker(100);
139 let start = Instant::now();
140 worker.submit(1, 1);
141 assert_wait_pending(&mut worker).await;
142 tokio::time::advance(Duration::from_millis(5)).await;
143 worker.submit(2, 1);
144 assert!(worker.take_ready(FlushTrigger::Submission).is_none());
145 assert_wait_pending(&mut worker).await;
146 worker.wait_flush().await;
147 assert_eq!(start + Duration::from_millis(10), Instant::now());
148 assert_eq!(Some(vec![1, 2]), worker.take_ready(FlushTrigger::Deadline));
149 assert!(worker.is_empty());
150 assert_eq!(0, worker.total_rows());
151 assert_wait_pending(&mut worker).await;
152 }
153
154 #[tokio::test(start_paused = true)]
155 async fn test_submission_does_not_consume_expired_deadline() {
156 let mut worker = worker(100);
157 worker.submit(1, 1);
158 tokio::time::advance(Duration::from_millis(10)).await;
159 worker.submit(2, 1);
160 assert!(worker.take_ready(FlushTrigger::Submission).is_none());
161 worker.wait_flush().await;
162 assert_eq!(Some(vec![1, 2]), worker.take_ready(FlushTrigger::Deadline));
163 }
164
165 #[tokio::test(start_paused = true)]
166 async fn test_size_flush_and_rearm() {
167 let mut worker = worker(2);
168 worker.submit(1, 3);
169 assert_eq!(3, worker.total_rows());
170 assert_eq!(Some(vec![1]), worker.take_ready(FlushTrigger::Submission));
171 tokio::time::advance(Duration::from_secs(1)).await;
172 assert_wait_pending(&mut worker).await;
173 let start = Instant::now();
174 worker.submit(2, 1);
175 worker.wait_flush().await;
176 assert_eq!(start + Duration::from_millis(10), Instant::now());
177 assert_eq!(Some(vec![2]), worker.take_ready(FlushTrigger::Deadline));
178 }
179
180 #[tokio::test(start_paused = true)]
181 async fn test_take_pending_without_waiting() {
182 let mut worker = worker(100);
183 assert_eq!(None, worker.take_pending());
184 worker.submit(1, 0);
185 assert!(!worker.is_empty());
186 let now = Instant::now();
187 assert_eq!(Some(vec![1]), worker.take_pending());
188 assert_eq!(now, Instant::now());
189 tokio::time::advance(Duration::from_secs(1)).await;
190 assert_wait_pending(&mut worker).await;
191 }
192
193 #[tokio::test(start_paused = true)]
194 async fn test_row_threshold_ablation() {
195 for (rows, early) in [(2, true), (100, false)] {
196 let mut worker = worker(rows);
197 let start = Instant::now();
198 worker.submit(1, 1);
199 tokio::time::advance(Duration::from_millis(5)).await;
200 worker.submit(2, 1);
201 let batch = worker.take_ready(FlushTrigger::Submission);
202 if early {
203 assert_eq!(Some(vec![1, 2]), batch);
204 assert_eq!(start + Duration::from_millis(5), Instant::now());
205 } else {
206 assert!(batch.is_none());
207 worker.wait_flush().await;
208 assert_eq!(start + Duration::from_millis(10), Instant::now());
209 assert_eq!(Some(vec![1, 2]), worker.take_ready(FlushTrigger::Deadline));
210 }
211 }
212 }
213
214 #[tokio::test]
215 async fn test_non_timing_policy() {
216 struct TwoSubmissionsPolicy;
217 impl FlushPolicy for TwoSubmissionsPolicy {
218 fn deadline<T>(&self, _: &PendingBatch<T>) -> Option<Instant> {
219 None
220 }
221 fn should_flush<T>(
222 &self,
223 batch: &PendingBatch<T>,
224 _: Instant,
225 _: FlushTrigger,
226 ) -> bool {
227 batch.len() >= 2
228 }
229 }
230 let mut worker = PendingWorker::new(TwoSubmissionsPolicy);
231 worker.submit(1, 0);
232 assert_wait_pending(&mut worker).await;
233 assert!(worker.take_ready(FlushTrigger::Submission).is_none());
234 worker.submit(2, 0);
235 assert_eq!(
236 Some(vec![1, 2]),
237 worker.take_ready(FlushTrigger::Submission)
238 );
239 }
240
241 #[tokio::test(start_paused = true)]
242 async fn test_caller_owns_event_loop() {
243 let mut worker = worker(100);
244 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
245 tx.send(1).await.unwrap();
246 tokio::select! {
247 _ = worker.wait_flush() => panic!("empty worker must not wake"),
248 item = rx.recv() => worker.submit(item.unwrap(), 1),
249 }
250 assert!(worker.take_ready(FlushTrigger::Submission).is_none());
251 let start = Instant::now();
252 tokio::select! {
253 _ = worker.wait_flush() => {
254 assert_eq!(Some(vec![1]), worker.take_ready(FlushTrigger::Deadline));
255 }
256 _ = rx.recv() => panic!("sender is still open without another submission"),
257 }
258 assert_eq!(start + Duration::from_millis(10), Instant::now());
259 }
260}