rust_loguru/
record_pool.rs

1use parking_lot::Mutex;
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::sync::Arc;
4
5use crate::level::LogLevel;
6use crate::record::Record;
7
8/// A thread-safe pool of reusable Record objects
9pub struct RecordPool {
10    /// The pool of available records
11    pool: Arc<Mutex<Vec<Record>>>,
12    /// The maximum capacity of the pool
13    capacity: usize,
14    /// The current size of the pool
15    size: Arc<AtomicUsize>,
16}
17
18impl RecordPool {
19    /// Create a new record pool with the specified capacity
20    pub fn new(capacity: usize) -> Self {
21        Self {
22            pool: Arc::new(Mutex::new(Vec::with_capacity(capacity))),
23            capacity,
24            size: Arc::new(AtomicUsize::new(0)),
25        }
26    }
27
28    /// Acquire a record from the pool or create a new one
29    pub fn acquire(&self) -> PooledRecord {
30        let record = {
31            let mut guard = self.pool.lock();
32            if let Some(record) = guard.pop() {
33                self.size.fetch_sub(1, Ordering::Relaxed);
34                record
35            } else {
36                Record::empty()
37            }
38        };
39
40        PooledRecord {
41            record: Some(record),
42            pool: self.pool.clone(),
43            size: self.size.clone(),
44            capacity: self.capacity,
45        }
46    }
47
48    /// Get the current size of the pool
49    pub fn size(&self) -> usize {
50        self.size.load(Ordering::Relaxed)
51    }
52
53    /// Get the capacity of the pool
54    pub fn capacity(&self) -> usize {
55        self.capacity
56    }
57
58    /// Clear the pool, dropping all records
59    pub fn clear(&self) {
60        let mut guard = self.pool.lock();
61        guard.clear();
62        self.size.store(0, Ordering::Relaxed);
63    }
64}
65
66impl Default for RecordPool {
67    fn default() -> Self {
68        Self::new(128)
69    }
70}
71
72/// A record acquired from a pool that will be returned when dropped
73pub struct PooledRecord {
74    /// The record, wrapped in Option for take() in drop
75    record: Option<Record>,
76    /// Reference to the pool
77    pool: Arc<Mutex<Vec<Record>>>,
78    /// Reference to the pool size counter
79    size: Arc<AtomicUsize>,
80    /// Pool capacity
81    capacity: usize,
82}
83
84impl PooledRecord {
85    /// Get a mutable reference to the record
86    pub fn get_mut(&mut self) -> &mut Record {
87        self.record.as_mut().unwrap()
88    }
89
90    /// Get a reference to the record
91    pub fn get(&self) -> &Record {
92        self.record.as_ref().unwrap()
93    }
94
95    /// Take ownership of the record, preventing it from being returned to the pool
96    pub fn into_record(mut self) -> Record {
97        self.record.take().unwrap()
98    }
99
100    /// Set the log level
101    pub fn set_level(&mut self, level: LogLevel) {
102        if let Some(record) = self.record.as_mut() {
103            *record = Record::new(
104                level,
105                record.message().to_string(),
106                Some(record.module().to_string()),
107                Some(record.file().to_string()),
108                Some(record.line()),
109            );
110        }
111    }
112
113    /// Set the message
114    pub fn set_message(&mut self, message: impl Into<String>) {
115        if let Some(record) = self.record.as_mut() {
116            *record = Record::new(
117                record.level(),
118                message,
119                Some(record.module().to_string()),
120                Some(record.file().to_string()),
121                Some(record.line()),
122            );
123        }
124    }
125}
126
127impl Drop for PooledRecord {
128    fn drop(&mut self) {
129        // Only return the record to the pool if we still have it
130        if let Some(record) = self.record.take() {
131            let current_size = self.size.load(Ordering::Relaxed);
132            if current_size < self.capacity {
133                let mut guard = self.pool.lock();
134                guard.push(record);
135                self.size.fetch_add(1, Ordering::Relaxed);
136            }
137        }
138    }
139}
140
141impl std::ops::Deref for PooledRecord {
142    type Target = Record;
143
144    fn deref(&self) -> &Self::Target {
145        self.get()
146    }
147}
148
149impl std::ops::DerefMut for PooledRecord {
150    fn deref_mut(&mut self) -> &mut Self::Target {
151        self.get_mut()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_record_pool_basic() {
161        let pool = RecordPool::new(2);
162        assert_eq!(pool.size(), 0);
163
164        let mut record = pool.acquire();
165        record.set_level(LogLevel::Info);
166        record.set_message("test");
167        drop(record);
168
169        assert_eq!(pool.size(), 1);
170    }
171
172    #[test]
173    fn test_record_pool_capacity() {
174        let pool = RecordPool::new(1);
175
176        let record1 = pool.acquire();
177        let record2 = pool.acquire();
178
179        drop(record1);
180        assert_eq!(pool.size(), 1);
181
182        drop(record2);
183        assert_eq!(pool.size(), 1); // Still 1, not 2
184    }
185
186    #[test]
187    fn test_record_pool_clear() {
188        let pool = RecordPool::new(2);
189
190        let record1 = pool.acquire();
191        let record2 = pool.acquire();
192
193        drop(record1);
194        drop(record2);
195
196        assert_eq!(pool.size(), 2);
197        pool.clear();
198        assert_eq!(pool.size(), 0);
199    }
200
201    #[test]
202    fn test_pooled_record_into_record() {
203        let pool = RecordPool::new(1);
204
205        let record = pool.acquire();
206        let _owned = record.into_record();
207
208        assert_eq!(pool.size(), 0); // Record was not returned to pool
209    }
210
211    #[test]
212    fn test_pooled_record() {
213        let pool = RecordPool::new(1);
214        let mut record = pool.acquire();
215
216        record.set_level(LogLevel::Info);
217        record.set_message("test");
218
219        let owned = record.into_record();
220        assert_eq!(owned.level(), LogLevel::Info);
221        assert_eq!(owned.message(), "test");
222    }
223}