use parking_lot::Mutex;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct MemoryPoolConfig {
pub max_pool_size: usize,
pub max_buffer_capacity: usize,
pub buffer_ttl: Duration,
pub growth_factor: f64,
}
impl Default for MemoryPoolConfig {
fn default() -> Self {
Self {
max_pool_size: 1000,
max_buffer_capacity: 1024 * 1024, buffer_ttl: Duration::from_secs(300), growth_factor: 1.5,
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
struct BufferEntry<T> {
buffer: T,
last_used: SystemTime,
usage_count: u64,
}
impl<T> BufferEntry<T> {
fn new(buffer: T) -> Self {
Self {
buffer,
last_used: SystemTime::now(),
usage_count: 1,
}
}
fn touch(&mut self) {
self.last_used = SystemTime::now();
self.usage_count += 1;
}
fn is_expired(&self, ttl: Duration) -> bool {
self.last_used.elapsed().unwrap_or(Duration::ZERO) > ttl
}
}
#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub struct MemoryPoolStats {
pub total_requests: u64,
pub pool_hits: u64,
pub pool_misses: u64,
pub string_pool_size: usize,
pub byte_pool_size: usize,
pub string_pool_capacity: usize,
pub byte_pool_capacity: usize,
pub cleanup_operations: u64,
pub oversized_discards: u64,
pub expired_discards: u64,
}
impl MemoryPoolStats {
pub fn hit_ratio(&self) -> f64 {
if self.total_requests == 0 {
0.0
} else {
self.pool_hits as f64 / self.total_requests as f64
}
}
pub fn avg_string_buffer_size(&self) -> f64 {
if self.string_pool_size == 0 {
0.0
} else {
self.string_pool_capacity as f64 / self.string_pool_size as f64
}
}
pub fn avg_byte_buffer_size(&self) -> f64 {
if self.byte_pool_size == 0 {
0.0
} else {
self.byte_pool_capacity as f64 / self.byte_pool_size as f64
}
}
pub fn memory_efficiency(&self) -> f64 {
let hit_ratio = self.hit_ratio();
let total_capacity = self.string_pool_capacity + self.byte_pool_capacity;
let total_buffers = self.string_pool_size + self.byte_pool_size;
if total_buffers == 0 {
hit_ratio
} else {
let avg_buffer_size = total_capacity as f64 / total_buffers as f64;
let size_factor = (avg_buffer_size / 1024.0).min(1.0); hit_ratio * size_factor
}
}
}
#[allow(dead_code)]
pub struct TextMemoryPool {
string_pool: Arc<Mutex<Vec<BufferEntry<String>>>>,
byte_pool: Arc<Mutex<Vec<BufferEntry<Vec<u8>>>>>,
config: MemoryPoolConfig,
stats: Arc<Mutex<MemoryPoolStats>>,
}
impl TextMemoryPool {
pub fn new(config: MemoryPoolConfig) -> Self {
Self {
string_pool: Arc::new(Mutex::new(Vec::new())),
byte_pool: Arc::new(Mutex::new(Vec::new())),
config,
stats: Arc::new(Mutex::new(MemoryPoolStats::default())),
}
}
pub fn new_default() -> Self {
Self::new(MemoryPoolConfig::default())
}
pub fn get_string_buffer(&self, min_capacity: usize) -> PooledString {
self.record_request();
let mut pool = self.string_pool.lock();
for i in (0..pool.len()).rev() {
if pool[i].buffer.capacity() >= min_capacity {
let mut entry = pool.swap_remove(i);
entry.touch();
entry.buffer.clear();
if entry.buffer.capacity() < min_capacity {
let new_capacity = (min_capacity as f64 * self.config.growth_factor) as usize;
entry.buffer.reserve(new_capacity - entry.buffer.capacity());
}
self.record_hit();
return PooledString::new(
entry.buffer,
Arc::clone(&self.string_pool),
&self.config,
Arc::clone(&self.stats),
);
}
}
let capacity = (min_capacity as f64 * self.config.growth_factor) as usize;
let buffer = String::with_capacity(capacity);
self.record_miss();
PooledString::new(
buffer,
Arc::clone(&self.string_pool),
&self.config,
Arc::clone(&self.stats),
)
}
pub fn get_byte_buffer(&self, min_capacity: usize) -> PooledBytes {
self.record_request();
let mut pool = self.byte_pool.lock();
for i in (0..pool.len()).rev() {
if pool[i].buffer.capacity() >= min_capacity {
let mut entry = pool.swap_remove(i);
entry.touch();
entry.buffer.clear();
if entry.buffer.capacity() < min_capacity {
let new_capacity = (min_capacity as f64 * self.config.growth_factor) as usize;
entry.buffer.reserve(new_capacity - entry.buffer.capacity());
}
self.record_hit();
return PooledBytes::new(
entry.buffer,
Arc::clone(&self.byte_pool),
&self.config,
Arc::clone(&self.stats),
);
}
}
let capacity = (min_capacity as f64 * self.config.growth_factor) as usize;
let buffer = Vec::with_capacity(capacity);
self.record_miss();
PooledBytes::new(
buffer,
Arc::clone(&self.byte_pool),
&self.config,
Arc::clone(&self.stats),
)
}
pub fn prewarm(&self, string_count: usize, string_capacity: usize, byte_count: usize, byte_capacity: usize) {
{
let mut pool = self.string_pool.lock();
for _ in 0..string_count {
if pool.len() >= self.config.max_pool_size {
break;
}
let buffer = String::with_capacity(string_capacity);
pool.push(BufferEntry::new(buffer));
}
}
{
let mut pool = self.byte_pool.lock();
for _ in 0..byte_count {
if pool.len() >= self.config.max_pool_size {
break;
}
let buffer = Vec::with_capacity(byte_capacity);
pool.push(BufferEntry::new(buffer));
}
}
log::debug!(
"Pre-warmed pool with {} string buffers ({} bytes each) and {} byte buffers ({} bytes each)",
string_count,
string_capacity,
byte_count,
byte_capacity
);
}
pub fn cleanup(&self) {
let mut cleanup_count = 0;
{
let mut pool = self.string_pool.lock();
let original_len = pool.len();
pool.retain(|entry| {
let should_keep = !entry.is_expired(self.config.buffer_ttl)
&& entry.buffer.capacity() <= self.config.max_buffer_capacity;
if !should_keep {
cleanup_count += 1;
if entry.is_expired(self.config.buffer_ttl) {
self.record_expired_discard();
} else {
self.record_oversized_discard();
}
}
should_keep
});
log::trace!(
"Cleaned up {} of {} string buffers",
original_len - pool.len(),
original_len
);
}
{
let mut pool = self.byte_pool.lock();
let original_len = pool.len();
pool.retain(|entry| {
let should_keep = !entry.is_expired(self.config.buffer_ttl)
&& entry.buffer.capacity() <= self.config.max_buffer_capacity;
if !should_keep {
cleanup_count += 1;
if entry.is_expired(self.config.buffer_ttl) {
self.record_expired_discard();
} else {
self.record_oversized_discard();
}
}
should_keep
});
log::trace!(
"Cleaned up {} of {} byte buffers",
original_len - pool.len(),
original_len
);
}
if cleanup_count > 0 {
self.record_cleanup();
log::debug!("Memory pool cleanup removed {} buffers", cleanup_count);
}
}
pub fn get_stats(&self) -> MemoryPoolStats {
let mut stats = self.stats.lock().clone();
{
let string_pool = self.string_pool.lock();
stats.string_pool_size = string_pool.len();
stats.string_pool_capacity = string_pool.iter().map(|e| e.buffer.capacity()).sum();
}
{
let byte_pool = self.byte_pool.lock();
stats.byte_pool_size = byte_pool.len();
stats.byte_pool_capacity = byte_pool.iter().map(|e| e.buffer.capacity()).sum();
}
stats
}
pub fn reset_stats(&self) {
let mut stats = self.stats.lock();
*stats = MemoryPoolStats::default();
}
pub fn clear(&self) {
{
let mut pool = self.string_pool.lock();
pool.clear();
}
{
let mut pool = self.byte_pool.lock();
pool.clear();
}
self.reset_stats();
}
pub fn config(&self) -> &MemoryPoolConfig {
&self.config
}
fn record_request(&self) {
let mut stats = self.stats.lock();
stats.total_requests += 1;
}
fn record_hit(&self) {
let mut stats = self.stats.lock();
stats.pool_hits += 1;
}
fn record_miss(&self) {
let mut stats = self.stats.lock();
stats.pool_misses += 1;
}
fn record_cleanup(&self) {
let mut stats = self.stats.lock();
stats.cleanup_operations += 1;
}
fn record_oversized_discard(&self) {
let mut stats = self.stats.lock();
stats.oversized_discards += 1;
}
fn record_expired_discard(&self) {
let mut stats = self.stats.lock();
stats.expired_discards += 1;
}
}
#[allow(dead_code)]
pub struct PooledString {
buffer: Option<String>,
pool: Arc<Mutex<Vec<BufferEntry<String>>>>,
config: MemoryPoolConfig,
stats: Arc<Mutex<MemoryPoolStats>>,
}
impl PooledString {
fn new(
buffer: String,
pool: Arc<Mutex<Vec<BufferEntry<String>>>>,
config: &MemoryPoolConfig,
stats: Arc<Mutex<MemoryPoolStats>>,
) -> Self {
Self {
buffer: Some(buffer),
pool,
config: config.clone(),
stats,
}
}
pub fn buffer_mut(&mut self) -> &mut String {
self.buffer
.as_mut()
.expect("Buffer should always be present when not dropped")
}
pub fn buffer(&self) -> &String {
self.buffer
.as_ref()
.expect("Buffer should always be present when not dropped")
}
pub fn len(&self) -> usize {
self.buffer().len()
}
pub fn is_empty(&self) -> bool {
self.buffer().is_empty()
}
pub fn capacity(&self) -> usize {
self.buffer().capacity()
}
pub fn into_inner(mut self) -> String {
self.buffer
.take()
.expect("Buffer should always be present when not dropped")
}
}
impl Drop for PooledString {
fn drop(&mut self) {
if let Some(buffer) = self.buffer.take() {
let capacity = buffer.capacity();
if capacity <= self.config.max_buffer_capacity {
let mut pool = self.pool.lock();
if pool.len() < self.config.max_pool_size {
pool.push(BufferEntry::new(buffer));
log::trace!("Returned string buffer to pool (capacity: {})", capacity);
} else {
log::trace!("Discarded string buffer - pool full");
}
} else {
let mut stats = self.stats.lock();
stats.oversized_discards += 1;
log::trace!("Discarded oversized string buffer (capacity: {})", capacity);
}
}
}
}
#[allow(dead_code)]
pub struct PooledBytes {
buffer: Option<Vec<u8>>,
pool: Arc<Mutex<Vec<BufferEntry<Vec<u8>>>>>,
config: MemoryPoolConfig,
stats: Arc<Mutex<MemoryPoolStats>>,
}
impl PooledBytes {
fn new(
buffer: Vec<u8>,
pool: Arc<Mutex<Vec<BufferEntry<Vec<u8>>>>>,
config: &MemoryPoolConfig,
stats: Arc<Mutex<MemoryPoolStats>>,
) -> Self {
Self {
buffer: Some(buffer),
pool,
config: config.clone(),
stats,
}
}
pub fn buffer_mut(&mut self) -> &mut Vec<u8> {
self.buffer
.as_mut()
.expect("Buffer should always be present when not dropped")
}
pub fn buffer(&self) -> &Vec<u8> {
self.buffer
.as_ref()
.expect("Buffer should always be present when not dropped")
}
pub fn len(&self) -> usize {
self.buffer().len()
}
pub fn is_empty(&self) -> bool {
self.buffer().is_empty()
}
pub fn capacity(&self) -> usize {
self.buffer().capacity()
}
pub fn into_inner(mut self) -> Vec<u8> {
self.buffer
.take()
.expect("Buffer should always be present when not dropped")
}
}
impl Drop for PooledBytes {
fn drop(&mut self) {
if let Some(buffer) = self.buffer.take() {
let capacity = buffer.capacity();
if capacity <= self.config.max_buffer_capacity {
let mut pool = self.pool.lock();
if pool.len() < self.config.max_pool_size {
pool.push(BufferEntry::new(buffer));
log::trace!("Returned byte buffer to pool (capacity: {})", capacity);
} else {
log::trace!("Discarded byte buffer - pool full");
}
} else {
let mut stats = self.stats.lock();
stats.oversized_discards += 1;
log::trace!("Discarded oversized byte buffer (capacity: {})", capacity);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_memory_pool_basic_operations() {
let pool = TextMemoryPool::new_default();
let mut string_buf = pool.get_string_buffer(100);
string_buf.buffer_mut().push_str("Hello, world!");
assert_eq!(string_buf.buffer(), "Hello, world!");
assert!(string_buf.capacity() >= 100);
drop(string_buf);
let mut byte_buf = pool.get_byte_buffer(50);
byte_buf.buffer_mut().extend_from_slice(b"Hello, bytes!");
assert_eq!(byte_buf.buffer(), b"Hello, bytes!");
assert!(byte_buf.capacity() >= 50);
drop(byte_buf);
let stats = pool.get_stats();
assert_eq!(stats.total_requests, 2);
assert_eq!(stats.pool_misses, 2); }
#[test]
fn test_buffer_reuse() {
let pool = TextMemoryPool::new_default();
{
let mut buf = pool.get_string_buffer(100);
buf.buffer_mut().push_str("Test content");
assert_eq!(buf.len(), 12);
}
{
let buf = pool.get_string_buffer(50); assert!(buf.is_empty()); assert!(buf.capacity() >= 100); }
let stats = pool.get_stats();
assert_eq!(stats.total_requests, 2);
assert_eq!(stats.pool_hits, 1);
assert_eq!(stats.pool_misses, 1);
assert_eq!(stats.hit_ratio(), 0.5);
}
#[test]
fn test_prewarming() {
let pool = TextMemoryPool::new_default();
pool.prewarm(10, 256, 5, 512);
let stats = pool.get_stats();
assert_eq!(stats.string_pool_size, 10);
assert_eq!(stats.byte_pool_size, 5);
assert!(stats.string_pool_capacity >= 10 * 256);
assert!(stats.byte_pool_capacity >= 5 * 512);
}
#[test]
fn test_oversized_buffer_handling() {
let config = MemoryPoolConfig {
max_buffer_capacity: 100, ..Default::default()
};
let pool = TextMemoryPool::new(config);
{
let mut buf = pool.get_string_buffer(200); buf.buffer_mut().push_str("Large buffer content");
}
let stats = pool.get_stats();
assert_eq!(stats.string_pool_size, 0);
assert!(stats.oversized_discards > 0);
}
#[test]
fn test_buffer_expiration() {
let config = MemoryPoolConfig {
buffer_ttl: Duration::from_millis(10), ..Default::default()
};
let pool = TextMemoryPool::new(config);
{
let buf = pool.get_string_buffer(100);
drop(buf);
}
thread::sleep(Duration::from_millis(20));
pool.cleanup();
let stats = pool.get_stats();
assert_eq!(stats.string_pool_size, 0);
}
#[test]
fn test_memory_pool_stats() {
let pool = TextMemoryPool::new_default();
for _ in 0..10 {
let buf = pool.get_string_buffer(100);
drop(buf);
}
for _ in 0..5 {
let buf = pool.get_string_buffer(50);
drop(buf);
}
let stats = pool.get_stats();
assert_eq!(stats.total_requests, 15);
assert!(stats.pool_hits > 0);
assert!(stats.pool_misses > 0);
assert!(stats.hit_ratio() > 0.0);
assert!(stats.hit_ratio() <= 1.0);
}
#[test]
fn test_pool_size_limits() {
let config = MemoryPoolConfig {
max_pool_size: 2, ..Default::default()
};
let pool = TextMemoryPool::new(config);
for _ in 0..5 {
let buf = pool.get_string_buffer(100);
drop(buf);
}
let stats = pool.get_stats();
assert!(stats.string_pool_size <= 2); }
#[test]
fn test_into_inner() {
let pool = TextMemoryPool::new_default();
let mut buf = pool.get_string_buffer(100);
buf.buffer_mut().push_str("Test content");
let owned_string = buf.into_inner();
assert_eq!(owned_string, "Test content");
let stats = pool.get_stats();
assert_eq!(stats.string_pool_size, 0);
}
#[test]
fn test_growth_factor() {
let config = MemoryPoolConfig {
growth_factor: 2.0,
..Default::default()
};
let pool = TextMemoryPool::new(config);
let buf = pool.get_string_buffer(100);
assert!(buf.capacity() >= 200);
}
}