linkrs_query/executor/streaming/chunk/
pool.rs1use linkrs_core::Value;
21use parking_lot::Mutex;
22use std::sync::LazyLock;
23
24pub const MAX_POOLED_ROWS: usize = 64;
25pub const MAX_POOLED_ROW_CAPACITY: usize = 1024;
26
27static ROW_POOL: LazyLock<Mutex<Vec<Vec<Value>>>> = LazyLock::new(|| Mutex::new(Vec::new()));
28
29pub struct RowBufferPool;
31
32impl RowBufferPool {
33 pub fn acquire_row() -> Vec<Value> {
35 ROW_POOL.lock().pop().unwrap_or_default()
36 }
37
38 pub fn release_row(row: &mut Vec<Value>) {
40 row.clear();
41 if row.capacity() > MAX_POOLED_ROW_CAPACITY {
42 return;
43 }
44 let mut pool = ROW_POOL.lock();
45 if pool.len() < MAX_POOLED_ROWS {
46 pool.push(std::mem::take(row));
47 }
48 }
49
50 pub fn acquire_rows(n: usize) -> Vec<Vec<Value>> {
52 let mut pool = ROW_POOL.lock();
53 let take = n.min(pool.len());
54 let mut out = Vec::with_capacity(n);
55 for _ in 0..take {
56 if let Some(row) = pool.pop() {
57 out.push(row);
58 }
59 }
60 out
61 }
62
63 pub fn release_rows(rows: &mut Vec<Vec<Value>>) {
65 let mut pool = ROW_POOL.lock();
66 for row in rows.iter_mut() {
67 row.clear();
68 if row.capacity() > MAX_POOLED_ROW_CAPACITY {
69 continue;
70 }
71 if pool.len() >= MAX_POOLED_ROWS {
72 break;
73 }
74 pool.push(std::mem::take(row));
75 }
76 rows.clear();
77 }
78
79 pub fn pool_len() -> usize {
81 ROW_POOL.lock().len()
82 }
83
84 #[cfg(test)]
86 pub fn clear_for_test() {
87 ROW_POOL.lock().clear();
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn acquire_release_reuses_capacity() {
97 RowBufferPool::clear_for_test();
98 let mut row = vec![Value::Int(1), Value::Int(2)];
99 RowBufferPool::release_row(&mut row);
100 assert!(row.is_empty());
101 assert_eq!(RowBufferPool::pool_len(), 1);
102 let reused = RowBufferPool::acquire_row();
103 assert_eq!(RowBufferPool::pool_len(), 0);
104 assert!(reused.capacity() >= 2);
105 }
106
107 #[test]
108 fn pool_drops_beyond_capacity() {
109 RowBufferPool::clear_for_test();
110 let mut rows: Vec<Vec<Value>> = (0..(MAX_POOLED_ROWS + 16))
111 .map(|_| Vec::<Value>::new())
112 .collect();
113 RowBufferPool::release_rows(&mut rows);
114 assert!(rows.is_empty());
115 assert_eq!(RowBufferPool::pool_len(), MAX_POOLED_ROWS);
116 RowBufferPool::clear_for_test();
117 }
118
119 #[test]
120 fn oversized_row_is_dropped() {
121 RowBufferPool::clear_for_test();
122 let mut row = Vec::with_capacity(MAX_POOLED_ROW_CAPACITY + 1);
123 row.push(Value::Int(1));
124 RowBufferPool::release_row(&mut row);
125 assert_eq!(RowBufferPool::pool_len(), 0);
126 }
127
128 #[test]
129 fn acquire_rows_partial_fill() {
130 RowBufferPool::clear_for_test();
131 let got = RowBufferPool::acquire_rows(4);
132 assert!(got.is_empty());
133 let mut rows = vec![vec![Value::Int(1)], vec![Value::Int(2)]];
134 RowBufferPool::release_rows(&mut rows);
135 let got = RowBufferPool::acquire_rows(4);
136 assert_eq!(got.len(), 2);
137 RowBufferPool::clear_for_test();
138 }
139}