chronix_analytics/compute/
buffer_pool.rs1use parking_lot::Mutex;
4
5const DEFAULT_MAX_POOL_BYTES: usize = 64 * 1024 * 1024;
32
33pub struct BufferPool<T> {
36 pool: Mutex<Vec<Vec<T>>>,
37 max_size: usize,
38 max_pool_bytes: usize,
39 current_pool_bytes: Mutex<usize>,
40}
41
42impl<T> BufferPool<T> {
43 pub fn new(max_size: usize) -> Self {
46 Self {
47 pool: Mutex::new(Vec::with_capacity(max_size)),
48 max_size,
49 max_pool_bytes: DEFAULT_MAX_POOL_BYTES,
50 current_pool_bytes: Mutex::new(0),
51 }
52 }
53
54 pub fn with_max_bytes(max_size: usize, max_pool_bytes: usize) -> Self {
56 Self {
57 pool: Mutex::new(Vec::with_capacity(max_size)),
58 max_size,
59 max_pool_bytes,
60 current_pool_bytes: Mutex::new(0),
61 }
62 }
63
64 pub fn pooled_bytes(&self) -> usize {
66 *self.current_pool_bytes.lock()
67 }
68
69 pub fn get(&self, min_capacity: usize) -> Vec<T> {
75 let mut pool = self.pool.lock();
76 if let Some(mut buf) = pool.pop() {
77 let buf_bytes = std::mem::size_of::<T>() * buf.capacity();
78 *self.current_pool_bytes.lock() -= buf_bytes;
79 if buf.capacity() < min_capacity {
80 buf.reserve(min_capacity - buf.len());
81 }
82 buf
83 } else {
84 Vec::with_capacity(min_capacity)
85 }
86 }
87
88 pub fn put(&self, mut buf: Vec<T>) {
92 buf.clear();
93 let buf_bytes = std::mem::size_of::<T>() * buf.capacity();
94 let mut pool = self.pool.lock();
95 let mut bytes = self.current_pool_bytes.lock();
96 if pool.len() < self.max_size && *bytes + buf_bytes <= self.max_pool_bytes {
97 *bytes += buf_bytes;
98 pool.push(buf);
99 }
100 }
102
103 pub fn available(&self) -> usize {
105 self.pool.lock().len()
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn get_and_put() {
115 let pool = BufferPool::<f64>::new(2);
116 let mut buf = pool.get(64);
117 buf.extend_from_slice(&[1.0, 2.0, 3.0]);
118 assert_eq!(buf.len(), 3);
119 let cap = buf.capacity();
120
121 pool.put(buf);
122 assert_eq!(pool.available(), 1);
123
124 let buf2 = pool.get(1);
125 assert!(buf2.capacity() >= cap);
126 assert!(buf2.is_empty());
127 assert_eq!(pool.available(), 0);
128 }
129
130 #[test]
131 fn pool_max_size_respected() {
132 let pool = BufferPool::<f64>::new(1);
133 pool.put(vec![1.0, 2.0]);
134 pool.put(vec![3.0, 4.0]); assert_eq!(pool.available(), 1);
136 }
137
138 #[test]
139 fn min_capacity_honored() {
140 let pool = BufferPool::<f64>::new(2);
141 pool.put(Vec::with_capacity(4));
142 let buf = pool.get(100);
143 assert!(buf.capacity() >= 100);
144 }
145
146 #[test]
147 fn byte_budget_rejects_oversized_buffer() {
148 let pool = BufferPool::<f64>::with_max_bytes(4, 64);
150 pool.put(Vec::<f64>::with_capacity(100));
152 assert_eq!(pool.available(), 0, "oversized buffer should be dropped");
153 assert_eq!(pool.pooled_bytes(), 0);
154 }
155
156 #[test]
157 fn byte_budget_accepts_small_buffer() {
158 let pool = BufferPool::<f64>::with_max_bytes(4, 1024);
159 pool.put(Vec::<f64>::with_capacity(8)); assert_eq!(pool.available(), 1);
161 assert_eq!(pool.pooled_bytes(), 64);
162 }
163
164 #[test]
165 fn byte_budget_decremented_on_get() {
166 let pool = BufferPool::<f64>::with_max_bytes(4, 1024);
167 pool.put(Vec::<f64>::with_capacity(8));
168 assert_eq!(pool.pooled_bytes(), 64);
169
170 let _buf = pool.get(1);
171 assert_eq!(pool.pooled_bytes(), 0);
172 }
173}