proc_daemon/
pool.rs

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