Skip to main content

solana_streamer_sdk/streaming/common/
order_buffer.rs

1use crate::streaming::event_parser::DexEvent;
2use std::collections::{BTreeMap, HashMap};
3use tokio::time::Instant;
4
5#[derive(Default)]
6pub struct SlotBuffer {
7    slots: BTreeMap<u64, Vec<(u64, DexEvent)>>,
8    current_slot: u64,
9    last_flush_time: Option<Instant>,
10    streaming_watermarks: HashMap<u64, u64>,
11}
12
13impl SlotBuffer {
14    #[inline]
15    pub fn new() -> Self {
16        Self {
17            slots: BTreeMap::new(),
18            current_slot: 0,
19            last_flush_time: Some(Instant::now()),
20            streaming_watermarks: HashMap::new(),
21        }
22    }
23
24    #[inline]
25    pub fn push(&mut self, slot: u64, tx_index: u64, event: DexEvent) {
26        if self.slots.is_empty() {
27            self.last_flush_time = Some(Instant::now());
28        }
29        self.slots.entry(slot).or_default().push((tx_index, event));
30        if slot > self.current_slot {
31            self.current_slot = slot;
32        }
33    }
34
35    #[inline]
36    pub fn is_empty(&self) -> bool {
37        self.slots.is_empty()
38    }
39
40    pub fn flush_before(&mut self, current_slot: u64) -> Vec<DexEvent> {
41        let keep_slots = self.slots.split_off(&current_slot);
42        let flush_slots = std::mem::replace(&mut self.slots, keep_slots);
43
44        let mut result = Vec::with_capacity(flush_slots.values().map(Vec::len).sum());
45        for (_slot, mut events) in flush_slots {
46            // Stable sort: events of one transaction share a tx_index and must keep parser order.
47            events.sort_by_key(|(idx, _)| *idx);
48            result.extend(events.into_iter().map(|(_, event)| event));
49        }
50
51        if !result.is_empty() {
52            self.last_flush_time = Some(Instant::now());
53        }
54        result
55    }
56
57    pub fn flush_all(&mut self) -> Vec<DexEvent> {
58        let all_slots = std::mem::take(&mut self.slots);
59        let mut result = Vec::with_capacity(all_slots.values().map(Vec::len).sum());
60
61        for (_slot, mut events) in all_slots {
62            // Stable sort: events of one transaction share a tx_index and must keep parser order.
63            events.sort_by_key(|(idx, _)| *idx);
64            result.extend(events.into_iter().map(|(_, event)| event));
65        }
66
67        if !result.is_empty() {
68            self.last_flush_time = Some(Instant::now());
69        }
70        result
71    }
72
73    #[inline]
74    pub fn should_timeout(&self, timeout_ms: u64) -> bool {
75        self.last_flush_time
76            .map(|t| !self.slots.is_empty() && t.elapsed().as_millis() as u64 > timeout_ms)
77            .unwrap_or(false)
78    }
79
80    /// Push every event that belongs to a single `(slot, tx_index)` at once and return the
81    /// events that are now ready to deliver in order.
82    ///
83    /// All events parsed out of one transaction share that transaction's `tx_index`, so they
84    /// must be admitted as a group. Admitting them one-by-one would advance the per-`tx_index`
85    /// watermark past `tx_index` after the first event, and every remaining event of the same
86    /// transaction would then look "already delivered" and be dropped silently.
87    pub fn push_streaming(
88        &mut self,
89        slot: u64,
90        tx_index: u64,
91        events: Vec<DexEvent>,
92    ) -> Vec<DexEvent> {
93        let mut result = Vec::new();
94        if events.is_empty() {
95            return result;
96        }
97
98        if slot > self.current_slot && self.current_slot > 0 {
99            let keep_slots = self.slots.split_off(&slot);
100            let flush_slots = std::mem::replace(&mut self.slots, keep_slots);
101            result.reserve(flush_slots.values().map(Vec::len).sum());
102            for (old_slot, mut buffered) in flush_slots {
103                // Stable sort: events of one transaction share a tx_index and must keep parser order.
104                buffered.sort_by_key(|(idx, _)| *idx);
105                result.extend(buffered.into_iter().map(|(_, event)| event));
106                self.streaming_watermarks.remove(&old_slot);
107            }
108        }
109
110        if slot > self.current_slot {
111            self.current_slot = slot;
112        }
113
114        let next_expected = *self.streaming_watermarks.get(&slot).unwrap_or(&0);
115
116        if tx_index == next_expected {
117            result.reserve(events.len());
118            result.extend(events);
119            let mut watermark = next_expected + 1;
120
121            let remove_empty_slot = if let Some(buffered) = self.slots.get_mut(&slot) {
122                // Stable sort: events of one transaction share a tx_index and must keep parser order.
123                buffered.sort_by_key(|(idx, _)| *idx);
124                // Drain whole transactions whose tx_index is contiguous with the watermark.
125                // A single tx_index may hold several events, so advance the watermark per
126                // distinct index rather than per event.
127                let mut ready_count = 0;
128                while ready_count < buffered.len() && buffered[ready_count].0 == watermark {
129                    let idx = watermark;
130                    while ready_count < buffered.len() && buffered[ready_count].0 == idx {
131                        ready_count += 1;
132                    }
133                    watermark = idx + 1;
134                }
135                result.reserve(ready_count);
136                for (_, event) in buffered.drain(..ready_count) {
137                    result.push(event);
138                }
139                buffered.is_empty()
140            } else {
141                false
142            };
143            if remove_empty_slot {
144                self.slots.remove(&slot);
145            }
146            self.streaming_watermarks.insert(slot, watermark);
147        } else if tx_index > next_expected {
148            if self.slots.is_empty() {
149                self.last_flush_time = Some(Instant::now());
150            }
151            let buffered = self.slots.entry(slot).or_default();
152            buffered.reserve(events.len());
153            for event in events {
154                buffered.push((tx_index, event));
155            }
156        }
157
158        if !result.is_empty() {
159            self.last_flush_time = Some(Instant::now());
160        }
161        result
162    }
163
164    pub fn flush_streaming_timeout(&mut self) -> Vec<DexEvent> {
165        let flush_slots = std::mem::take(&mut self.slots);
166        let mut result = Vec::with_capacity(flush_slots.values().map(Vec::len).sum());
167        for (slot, mut events) in flush_slots {
168            // Stable sort: events of one transaction share a tx_index and must keep parser order.
169            events.sort_by_key(|(idx, _)| *idx);
170            result.extend(events.into_iter().map(|(_, event)| event));
171            self.streaming_watermarks.remove(&slot);
172        }
173        if !result.is_empty() {
174            self.last_flush_time = Some(Instant::now());
175        }
176        result
177    }
178}
179
180pub struct MicroBatchBuffer {
181    events: Vec<(u64, u64, DexEvent)>,
182    window_start_us: i64,
183}
184
185impl MicroBatchBuffer {
186    #[inline]
187    pub fn new() -> Self {
188        Self { events: Vec::with_capacity(64), window_start_us: 0 }
189    }
190
191    #[inline]
192    pub fn push(
193        &mut self,
194        slot: u64,
195        tx_index: u64,
196        event: DexEvent,
197        now_us: i64,
198        window_us: u64,
199    ) -> bool {
200        if self.events.is_empty() {
201            self.window_start_us = now_us;
202        }
203        self.events.push((slot, tx_index, event));
204        (now_us - self.window_start_us) as u64 >= window_us
205    }
206
207    #[inline]
208    pub fn flush(&mut self) -> Vec<DexEvent> {
209        if self.events.is_empty() {
210            return Vec::new();
211        }
212
213        // Stable sort: events of one transaction share (slot, tx_index) and must keep parser order.
214        self.events.sort_by_key(|(slot, tx_index, _)| (*slot, *tx_index));
215        let mut result = Vec::with_capacity(self.events.len());
216        result.extend(self.events.drain(..).map(|(_, _, event)| event));
217        self.window_start_us = 0;
218        result
219    }
220
221    #[inline]
222    pub fn should_flush(&self, now_us: i64, window_us: u64) -> bool {
223        !self.events.is_empty() && (now_us - self.window_start_us) as u64 >= window_us
224    }
225
226    #[inline]
227    pub fn is_empty(&self) -> bool {
228        self.events.is_empty()
229    }
230}
231
232impl Default for MicroBatchBuffer {
233    fn default() -> Self {
234        Self::new()
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241    use crate::streaming::event_parser::protocols::BlockMetaEvent;
242
243    fn event(id: u64) -> DexEvent {
244        DexEvent::BlockMetaEvent(BlockMetaEvent::new(id, id.to_string(), 0, 0))
245    }
246
247    fn ids(events: Vec<DexEvent>) -> Vec<u64> {
248        events
249            .into_iter()
250            .map(|event| match event {
251                DexEvent::BlockMetaEvent(event) => event.slot,
252                _ => unreachable!("test only creates block meta events"),
253            })
254            .collect()
255    }
256
257    #[test]
258    fn flush_before_keeps_newer_slots_and_sorts_flushed_events() {
259        let mut buffer = SlotBuffer::new();
260        buffer.push(7, 2, event(72));
261        buffer.push(5, 1, event(51));
262        buffer.push(5, 0, event(50));
263
264        assert_eq!(ids(buffer.flush_before(6)), vec![50, 51]);
265        assert_eq!(ids(buffer.flush_all()), vec![72]);
266    }
267
268    #[test]
269    fn streaming_order_drains_only_contiguous_ready_prefix() {
270        let mut buffer = SlotBuffer::new();
271
272        assert!(buffer.push_streaming(10, 3, vec![event(103)]).is_empty());
273        assert!(buffer.push_streaming(10, 1, vec![event(101)]).is_empty());
274        assert_eq!(ids(buffer.push_streaming(10, 0, vec![event(100)])), vec![100, 101]);
275        assert_eq!(ids(buffer.push_streaming(10, 2, vec![event(102)])), vec![102, 103]);
276        assert!(buffer.is_empty());
277    }
278
279    #[test]
280    fn streaming_order_keeps_every_event_of_a_multi_event_transaction() {
281        let mut buffer = SlotBuffer::new();
282
283        // One transaction (tx_index 0) parsed into three events must all be delivered.
284        assert_eq!(
285            ids(buffer.push_streaming(10, 0, vec![event(100), event(101), event(102)])),
286            vec![100, 101, 102]
287        );
288        // A later transaction in the same slot still delivers in order.
289        assert_eq!(ids(buffer.push_streaming(10, 1, vec![event(110)])), vec![110]);
290        assert!(buffer.is_empty());
291    }
292
293    #[test]
294    fn streaming_order_releases_buffered_multi_event_transactions_in_order() {
295        let mut buffer = SlotBuffer::new();
296
297        // tx_index 1 arrives first (two events) and must wait for tx_index 0.
298        assert!(buffer.push_streaming(10, 1, vec![event(110), event(111)]).is_empty());
299        // tx_index 0 (two events) arrives and releases itself plus the buffered tx_index 1.
300        assert_eq!(
301            ids(buffer.push_streaming(10, 0, vec![event(100), event(101)])),
302            vec![100, 101, 110, 111]
303        );
304        assert!(buffer.is_empty());
305    }
306
307    #[test]
308    fn micro_batch_flush_sorts_and_reuses_allocation() {
309        let mut buffer = MicroBatchBuffer::new();
310        let initial_capacity = buffer.events.capacity();
311
312        assert!(!buffer.push(2, 1, event(21), 0, 100));
313        assert!(!buffer.push(1, 0, event(10), 10, 100));
314
315        assert_eq!(ids(buffer.flush()), vec![10, 21]);
316        assert!(buffer.events.capacity() >= initial_capacity);
317
318        assert!(!buffer.push(3, 0, event(30), 200, 100));
319        assert_eq!(ids(buffer.flush()), vec![30]);
320    }
321}