Skip to main content

proc_daemon/
pool.rs

1use parking_lot::Mutex;
2use std::collections::VecDeque;
3use std::fmt::{self, Debug};
4use std::sync::Arc;
5
6/// A thread-safe object pool that pre-allocates and reuses objects to avoid runtime allocations
7/// on hot paths.
8pub struct ObjectPool<T> {
9    /// The collection of available objects
10    available: Mutex<VecDeque<T>>,
11
12    /// Factory function for creating new objects when the pool is empty
13    create_fn: Arc<dyn Fn() -> T + Send + Sync>,
14
15    /// Maximum size of the pool
16    max_size: usize,
17}
18
19impl<T: std::fmt::Debug> std::fmt::Debug for ObjectPool<T> {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("ObjectPool")
22            .field("available", &self.available)
23            .field("max_size", &self.max_size)
24            .field("create_fn", &"<function>")
25            .finish()
26    }
27}
28
29// Base implementation for all ObjectPools regardless of T
30impl<T> ObjectPool<T> {
31    /// Return an object to the pool.
32    ///
33    /// If the pool is full, the object will be dropped.
34    fn return_object(&self, object: T) {
35        let mut available = self.available.lock();
36        if available.len() < self.max_size {
37            available.push_back(object);
38        }
39        // If the pool is full, the object will be dropped
40    }
41}
42
43// Additional implementation for types that are Send + 'static
44impl<T: Send + 'static> ObjectPool<T> {
45    /// Create a new object pool with a specified capacity and factory function.
46    ///
47    /// # Arguments
48    ///
49    /// * `initial_size` - Number of objects to pre-allocate
50    /// * `max_size` - Maximum number of objects to keep in the pool
51    /// * `create_fn` - Function to create new objects when needed
52    ///
53    /// # Returns
54    ///
55    /// A new `ObjectPool<T>` pre-filled with `initial_size` objects
56    #[must_use]
57    pub fn new<F>(initial_size: usize, max_size: usize, create_fn: F) -> Self
58    where
59        F: Fn() -> T + Send + Sync + 'static,
60    {
61        let create_fn = Arc::new(create_fn);
62        let factory = Arc::clone(&create_fn);
63
64        // Pre-allocate objects
65        let mut available = VecDeque::with_capacity(max_size);
66        for _ in 0..initial_size {
67            available.push_back((factory)());
68        }
69
70        Self {
71            available: Mutex::new(available),
72            create_fn,
73            max_size,
74        }
75    }
76
77    /// Get an object from the pool or create a new one if the pool is empty.
78    ///
79    /// # Returns
80    ///
81    /// A `PooledObject<T>` that will return to the pool when dropped
82    /// Get an object from the pool. If the pool is empty, creates a new object.
83    pub fn get(&self) -> PooledObject<'_, T> {
84        let object = {
85            let mut available = self.available.lock();
86            available.pop_front().unwrap_or_else(|| (self.create_fn)())
87        };
88
89        PooledObject {
90            object: Some(object),
91            pool: self,
92        }
93    }
94}
95
96/// A smart pointer for objects borrowed from an `ObjectPool`.
97///
98/// When dropped, the object is returned to the pool if the pool isn't full.
99pub struct PooledObject<'a, T> {
100    /// The object borrowed from the pool (None if already returned)
101    object: Option<T>,
102
103    /// Reference to the pool this object belongs to
104    pool: &'a ObjectPool<T>,
105}
106
107impl<T> PooledObject<'_, T> {
108    /// Consume the pooled object and return it to the pool early.
109    /// This is useful when you're done with the object but it's not going out of scope yet.
110    pub fn return_to_pool(mut self) {
111        if let Some(object) = self.object.take() {
112            self.pool.return_object(object);
113        }
114    }
115}
116
117impl<T> Drop for PooledObject<'_, T> {
118    fn drop(&mut self) {
119        if let Some(object) = self.object.take() {
120            self.pool.return_object(object);
121        }
122    }
123}
124
125impl<T> std::ops::Deref for PooledObject<'_, T> {
126    type Target = T;
127
128    fn deref(&self) -> &Self::Target {
129        self.object
130            .as_ref()
131            .expect("Object already returned to pool")
132    }
133}
134
135impl<T> std::ops::DerefMut for PooledObject<'_, T> {
136    fn deref_mut(&mut self) -> &mut Self::Target {
137        self.object
138            .as_mut()
139            .expect("Object already returned to pool")
140    }
141}
142
143impl<T: Debug> Debug for PooledObject<'_, T> {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        f.debug_struct("PooledObject")
146            .field("object", &self.object)
147            .finish()
148    }
149}
150
151/// A pool for reusing String objects to avoid allocations in hot paths.
152#[derive(Debug)]
153pub struct StringPool {
154    /// The internal object pool for strings
155    inner: ObjectPool<String>,
156}
157
158impl StringPool {
159    /// Create a new string pool with default settings.
160    #[must_use]
161    pub fn new(initial_size: usize, max_size: usize, initial_capacity: usize) -> Self {
162        Self {
163            inner: ObjectPool::new(initial_size, max_size, move || {
164                String::with_capacity(initial_capacity)
165            }),
166        }
167    }
168
169    /// Get a string from the pool or create a new one if the pool is empty.
170    pub fn get(&self) -> PooledString<'_> {
171        let mut string = self.inner.get();
172        string.clear(); // Ensure the string is empty
173        PooledString(string)
174    }
175
176    /// Get a string from the pool and initialize it with the provided value.
177    pub fn get_with_value<S: AsRef<str>>(&self, value: S) -> PooledString<'_> {
178        let mut string = self.inner.get();
179        string.clear(); // Ensure the string is empty
180        string.push_str(value.as_ref());
181        PooledString(string)
182    }
183}
184
185/// A smart pointer for strings borrowed from a `StringPool`.
186pub struct PooledString<'a>(PooledObject<'a, String>);
187
188impl PooledString<'_> {
189    /// Consume the pooled string and return it to the pool early.
190    pub fn return_to_pool(self) {
191        self.0.return_to_pool();
192    }
193}
194
195impl std::ops::Deref for PooledString<'_> {
196    type Target = String;
197
198    fn deref(&self) -> &Self::Target {
199        &self.0
200    }
201}
202
203impl std::ops::DerefMut for PooledString<'_> {
204    fn deref_mut(&mut self) -> &mut Self::Target {
205        &mut self.0
206    }
207}
208
209impl Debug for PooledString<'_> {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        Debug::fmt(&**self, f)
212    }
213}
214
215/// Implement Display for `PooledString` to allow it to be used in string formatting and logs
216impl fmt::Display for PooledString<'_> {
217    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218        fmt::Display::fmt(&**self, f)
219    }
220}
221
222/// Pool of reusable `Vec<T>` objects to avoid allocations on hot paths.
223pub struct VecPool<T> {
224    /// Inner object pool
225    inner: ObjectPool<Vec<T>>,
226}
227
228impl<T: Send + 'static> VecPool<T> {
229    /// Create a new vector pool with default settings.
230    #[must_use]
231    pub fn new(initial_size: usize, max_size: usize, initial_capacity: usize) -> Self {
232        Self {
233            inner: ObjectPool::new(initial_size, max_size, move || {
234                Vec::with_capacity(initial_capacity)
235            }),
236        }
237    }
238
239    /// Get a vector from the pool.
240    pub fn get(&self) -> PooledVec<'_, T> {
241        let mut vec = self.inner.get();
242        vec.clear(); // Ensure it's empty
243        PooledVec(vec)
244    }
245}
246
247/// A smart pointer for vectors borrowed from a `VecPool`.
248pub struct PooledVec<'a, T>(PooledObject<'a, Vec<T>>);
249
250impl<T> PooledVec<'_, T> {
251    /// Consume the pooled vector and return it to the pool early.
252    pub fn return_to_pool(self) {
253        self.0.return_to_pool();
254    }
255}
256
257impl<T> std::ops::Deref for PooledVec<'_, T> {
258    type Target = Vec<T>;
259
260    fn deref(&self) -> &Self::Target {
261        &self.0
262    }
263}
264
265impl<T> std::ops::DerefMut for PooledVec<'_, T> {
266    fn deref_mut(&mut self) -> &mut Self::Target {
267        &mut self.0
268    }
269}
270
271impl<T: Debug> Debug for PooledVec<'_, T> {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        Debug::fmt(&**self, f)
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn test_object_pool() {
283        let pool = ObjectPool::new(5, 10, || String::from("test"));
284
285        // Get an object from the pool
286        let mut obj1 = pool.get();
287        assert_eq!(*obj1, "test");
288
289        // Modify the object
290        obj1.push_str("-modified");
291        assert_eq!(*obj1, "test-modified");
292
293        // Return the object to the pool
294        drop(obj1);
295
296        // Get another object from the pool
297        // The implementation creates a new object with the factory function
298        // when popping from the pool, so it will be "test" again
299        let mut obj2 = pool.get();
300        assert_eq!(*obj2, "test");
301
302        // We can modify this object independently
303        obj2.push_str("-new");
304        assert_eq!(*obj2, "test-new");
305    }
306
307    #[test]
308    fn test_string_pool() {
309        let pool = StringPool::new(5, 10, 32);
310
311        // Get a string from the pool
312        let mut str1 = pool.get();
313        str1.push_str("hello");
314        assert_eq!(*str1, "hello");
315
316        // Return the string to the pool
317        drop(str1);
318
319        // Get another string from the pool (should be empty)
320        let str2 = pool.get();
321        assert_eq!(*str2, "");
322
323        // Get a pre-filled string
324        let str3 = pool.get_with_value("world");
325        assert_eq!(*str3, "world");
326    }
327
328    #[test]
329    fn test_vec_pool() {
330        let pool = VecPool::new(5, 10, 32);
331
332        // Get a vec from the pool
333        let mut vec1 = pool.get();
334        vec1.push(1);
335        vec1.push(2);
336        assert_eq!(*vec1, vec![1, 2]);
337
338        // Return the vec to the pool
339        drop(vec1);
340
341        // Get another vec from the pool (should be empty)
342        let vec2 = pool.get();
343        assert_eq!(*vec2, Vec::<i32>::new());
344    }
345}