use anyhow::{anyhow, Result};
use parking_lot::{Mutex, RwLock};
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::VecDeque;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::{debug, info, trace, warn};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PoolType {
AstParsing,
StringIntern,
AnalysisCache,
FileContent,
GraphConstruction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AllocationStrategy {
Pooled,
Direct,
MemoryMapped,
}
#[derive(Debug, Clone)]
pub struct MemoryConfig {
pub max_total_memory: usize,
pub pool_limits: FxHashMap<PoolType, usize>,
pub small_allocation_threshold: usize, pub large_allocation_threshold: usize, pub max_cache_age: Duration,
pub cache_pressure_threshold: f64, pub enable_tracking: bool,
}
impl Default for MemoryConfig {
fn default() -> Self {
let mut pool_limits = FxHashMap::default();
pool_limits.insert(PoolType::AstParsing, 64 * 1024 * 1024); pool_limits.insert(PoolType::StringIntern, 16 * 1024 * 1024); pool_limits.insert(PoolType::AnalysisCache, 128 * 1024 * 1024); pool_limits.insert(PoolType::FileContent, 32 * 1024 * 1024); pool_limits.insert(PoolType::GraphConstruction, 32 * 1024 * 1024);
Self {
max_total_memory: 512 * 1024 * 1024, pool_limits,
small_allocation_threshold: 4 * 1024, large_allocation_threshold: 1024 * 1024, max_cache_age: Duration::from_secs(300), cache_pressure_threshold: 0.85,
enable_tracking: true,
}
}
}
pub struct PooledBuffer {
data: Vec<u8>,
pool_type: PoolType,
manager: Option<Arc<MemoryManager>>,
}
impl PooledBuffer {
fn new(data: Vec<u8>, pool_type: PoolType, manager: Arc<MemoryManager>) -> Self {
Self {
data,
pool_type,
manager: Some(manager),
}
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.data
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
&mut self.data
}
#[must_use]
pub fn capacity(&self) -> usize {
self.data.capacity()
}
pub fn resize(&mut self, new_size: usize) {
self.data.resize(new_size, 0);
}
}
impl Drop for PooledBuffer {
fn drop(&mut self) {
if let Some(manager) = self.manager.take() {
manager.return_buffer(self.pool_type, std::mem::take(&mut self.data));
}
}
}
#[derive(Debug)]
struct StringInterner {
strings: RwLock<FxHashSet<Arc<str>>>,
total_size: Mutex<usize>,
max_size: usize,
}
impl StringInterner {
fn new(max_size: usize) -> Self {
Self {
strings: RwLock::new(FxHashSet::default()),
total_size: Mutex::new(0),
max_size,
}
}
fn intern(&self, s: &str) -> Result<Arc<str>> {
{
let strings = self.strings.read();
if let Some(existing) = strings.get(s) {
return Ok(Arc::clone(existing));
}
}
{
let current_size = *self.total_size.lock();
if current_size + s.len() > self.max_size {
return Err(anyhow!("String interning pool exhausted"));
}
}
let mut strings = self.strings.write();
let mut total_size = self.total_size.lock();
if let Some(existing) = strings.get(s) {
return Ok(Arc::clone(existing));
}
let arc_str: Arc<str> = Arc::from(s);
*total_size += s.len();
strings.insert(Arc::clone(&arc_str));
Ok(arc_str)
}
fn memory_usage(&self) -> usize {
*self.total_size.lock()
}
fn clear(&self) {
self.strings.write().clear();
*self.total_size.lock() = 0;
}
}
#[derive(Debug)]
struct MemoryPool {
buffers: Mutex<VecDeque<Vec<u8>>>,
total_size: Mutex<usize>,
max_size: usize,
allocation_count: Mutex<u64>,
reuse_count: Mutex<u64>,
}
impl MemoryPool {
fn new(max_size: usize) -> Self {
Self {
buffers: Mutex::new(VecDeque::new()),
total_size: Mutex::new(0),
max_size,
allocation_count: Mutex::new(0),
reuse_count: Mutex::new(0),
}
}
fn get_buffer(&self, min_size: usize) -> Vec<u8> {
let mut buffers = self.buffers.lock();
let mut total_size = self.total_size.lock();
if let Some(mut buffer) = buffers.pop_front() {
if buffer.capacity() >= min_size {
buffer.clear();
buffer.resize(min_size, 0);
*self.reuse_count.lock() += 1;
return buffer;
}
*total_size -= buffer.capacity();
}
*self.allocation_count.lock() += 1;
let mut buffer = Vec::with_capacity(min_size.max(4096)); buffer.resize(min_size, 0);
*total_size += buffer.capacity();
buffer
}
fn return_buffer(&self, mut buffer: Vec<u8>) {
let mut buffers = self.buffers.lock();
let total_size = self.total_size.lock();
if *total_size + buffer.capacity() > self.max_size {
return;
}
buffer.clear();
buffers.push_back(buffer);
}
fn stats(&self) -> PoolStats {
let buffers = self.buffers.lock();
let total_size = *self.total_size.lock();
let allocation_count = *self.allocation_count.lock();
let reuse_count = *self.reuse_count.lock();
PoolStats {
buffer_count: buffers.len(),
total_size,
allocation_count,
reuse_count,
reuse_ratio: if allocation_count > 0 {
reuse_count as f64 / allocation_count as f64
} else {
0.0
},
}
}
fn clear(&self) {
self.buffers.lock().clear();
*self.total_size.lock() = 0;
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub buffer_count: usize,
pub total_size: usize,
pub allocation_count: u64,
pub reuse_count: u64,
pub reuse_ratio: f64,
}
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub total_allocated: usize,
pub pool_stats: FxHashMap<PoolType, PoolStats>,
pub string_intern_size: usize,
pub peak_usage: usize,
pub allocation_pressure: f64,
}
pub struct MemoryManager {
config: MemoryConfig,
pools: FxHashMap<PoolType, MemoryPool>,
string_interner: StringInterner,
total_allocated: Mutex<usize>,
peak_usage: Mutex<usize>,
last_cleanup: Mutex<Instant>,
}
impl MemoryManager {
pub fn new() -> Result<Arc<Self>> {
Self::with_config(MemoryConfig::default())
}
pub fn with_config(config: MemoryConfig) -> Result<Arc<Self>> {
let mut pools = FxHashMap::default();
for (&pool_type, &max_size) in &config.pool_limits {
pools.insert(pool_type, MemoryPool::new(max_size));
}
let string_interner = StringInterner::new(
config
.pool_limits
.get(&PoolType::StringIntern)
.copied()
.unwrap_or(16 * 1024 * 1024),
);
Ok(Arc::new(Self {
config,
pools,
string_interner,
total_allocated: Mutex::new(0),
peak_usage: Mutex::new(0),
last_cleanup: Mutex::new(Instant::now()),
}))
}
pub fn configure_pool(&self, pool_type: PoolType, _max_size: usize) -> Result<()> {
if let Some(_pool) = self.pools.get(&pool_type) {
warn!("Pool reconfiguration not supported in current implementation");
}
Ok(())
}
pub fn allocate_buffer(
self: &Arc<Self>,
pool_type: PoolType,
size: usize,
) -> Result<PooledBuffer> {
let strategy = self.determine_strategy(size);
match strategy {
AllocationStrategy::Pooled => {
if let Some(pool) = self.pools.get(&pool_type) {
let buffer = pool.get_buffer(size);
self.track_allocation(buffer.capacity());
Ok(PooledBuffer::new(buffer, pool_type, Arc::clone(self)))
} else {
Err(anyhow!("Pool type {pool_type:?} not configured"))
}
}
AllocationStrategy::Direct => {
let buffer = vec![0; size];
self.track_allocation(buffer.capacity());
Ok(PooledBuffer::new(buffer, pool_type, Arc::clone(self)))
}
AllocationStrategy::MemoryMapped => {
let buffer = vec![0; size];
self.track_allocation(buffer.capacity());
Ok(PooledBuffer::new(buffer, pool_type, Arc::clone(self)))
}
}
}
pub fn intern_string(&self, s: &str) -> Result<Arc<str>> {
self.string_interner.intern(s)
}
pub fn stats(&self) -> MemoryStats {
let total_allocated = *self.total_allocated.lock();
let peak_usage = *self.peak_usage.lock();
let mut pool_stats = FxHashMap::default();
for (&pool_type, pool) in &self.pools {
pool_stats.insert(pool_type, pool.stats());
}
let string_intern_size = self.string_interner.memory_usage();
let allocation_pressure = total_allocated as f64 / self.config.max_total_memory as f64;
MemoryStats {
total_allocated,
pool_stats,
string_intern_size,
peak_usage,
allocation_pressure,
}
}
pub fn cleanup(&self) -> Result<usize> {
let mut cleaned = 0;
let now = Instant::now();
let mut last_cleanup = self.last_cleanup.lock();
if now.duration_since(*last_cleanup) < Duration::from_secs(30) {
return Ok(0); }
*last_cleanup = now;
let stats = self.stats();
if stats.allocation_pressure < self.config.cache_pressure_threshold {
return Ok(0); }
if stats.allocation_pressure > 0.9 {
self.string_interner.clear();
cleaned += stats.string_intern_size;
info!(
"Cleared string interner: {} bytes",
stats.string_intern_size
);
}
for (pool_type, pool) in &self.pools {
let pool_stats = pool.stats();
if pool_stats.total_size > 0 {
pool.clear();
cleaned += pool_stats.total_size;
debug!(
"Cleared pool {:?}: {} bytes",
pool_type, pool_stats.total_size
);
}
}
if cleaned > 0 {
info!("Memory cleanup freed {} bytes", cleaned);
}
Ok(cleaned)
}
fn determine_strategy(&self, size: usize) -> AllocationStrategy {
if size < self.config.small_allocation_threshold {
AllocationStrategy::Pooled
} else if size > self.config.large_allocation_threshold {
AllocationStrategy::MemoryMapped
} else {
AllocationStrategy::Direct
}
}
fn track_allocation(&self, size: usize) {
let mut total = self.total_allocated.lock();
*total += size;
let mut peak = self.peak_usage.lock();
if *total > *peak {
*peak = *total;
}
if *total as f64 / self.config.max_total_memory as f64
> self.config.cache_pressure_threshold
{
trace!(
"Memory pressure detected: {:.1}%",
*total as f64 / self.config.max_total_memory as f64 * 100.0
);
}
}
fn return_buffer(&self, pool_type: PoolType, buffer: Vec<u8>) {
if let Some(pool) = self.pools.get(&pool_type) {
let capacity = buffer.capacity();
pool.return_buffer(buffer);
let mut total = self.total_allocated.lock();
*total = total.saturating_sub(capacity);
}
}
}
static GLOBAL_MEMORY_MANAGER: once_cell::sync::OnceCell<Arc<MemoryManager>> =
once_cell::sync::OnceCell::new();
pub fn global_memory_manager() -> Result<Arc<MemoryManager>> {
GLOBAL_MEMORY_MANAGER
.get()
.cloned()
.ok_or_else(|| anyhow!("Global memory manager not initialized"))
}
pub fn init_global_memory_manager() -> Result<()> {
let manager = MemoryManager::new()?;
GLOBAL_MEMORY_MANAGER
.set(manager)
.map_err(|_| anyhow!("Global memory manager already initialized"))?;
Ok(())
}
pub fn init_global_memory_manager_with_config(config: MemoryConfig) -> Result<()> {
let manager = MemoryManager::with_config(config)?;
GLOBAL_MEMORY_MANAGER
.set(manager)
.map_err(|_| anyhow!("Global memory manager already initialized"))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_memory_manager_creation() -> Result<()> {
let manager = MemoryManager::new()?;
let stats = manager.stats();
assert_eq!(stats.total_allocated, 0);
assert_eq!(stats.peak_usage, 0);
Ok(())
}
#[test]
fn test_buffer_allocation() -> Result<()> {
let manager = MemoryManager::new()?;
let buffer = manager.allocate_buffer(PoolType::AstParsing, 1024)?;
assert_eq!(buffer.as_slice().len(), 1024);
assert!(buffer.capacity() >= 1024);
Ok(())
}
#[test]
fn test_string_interning() -> Result<()> {
let manager = MemoryManager::new()?;
let str1 = manager.intern_string("test")?;
let str2 = manager.intern_string("test")?;
assert!(Arc::ptr_eq(&str1, &str2));
assert_eq!(str1.as_ref(), "test");
Ok(())
}
#[test]
fn test_memory_cleanup() -> Result<()> {
let manager = MemoryManager::new()?;
let _buffer1 = manager.allocate_buffer(PoolType::AstParsing, 1024)?;
let _buffer2 = manager.allocate_buffer(PoolType::FileContent, 2048)?;
let _interned = manager.intern_string("test_string")?;
let stats_before = manager.stats();
assert!(stats_before.total_allocated > 0);
let _cleaned = manager.cleanup()?;
Ok(())
}
#[test]
fn test_pool_buffer_reuse() -> Result<()> {
let manager = MemoryManager::new()?;
{
let _buffer = manager.allocate_buffer(PoolType::AstParsing, 1024)?;
}
let buffer2 = manager.allocate_buffer(PoolType::AstParsing, 1024)?;
assert_eq!(buffer2.as_slice().len(), 1024);
Ok(())
}
#[test]
fn test_allocation_strategy() -> Result<()> {
let manager = MemoryManager::new()?;
assert_eq!(manager.determine_strategy(1024), AllocationStrategy::Pooled);
assert_eq!(
manager.determine_strategy(2 * 1024 * 1024),
AllocationStrategy::MemoryMapped
);
assert_eq!(
manager.determine_strategy(512 * 1024),
AllocationStrategy::Direct
);
Ok(())
}
#[test]
fn test_concurrent_access() -> Result<()> {
let manager = MemoryManager::new()?;
let manager = Arc::clone(&manager);
let handles: Vec<_> = (0..4)
.map(|i| {
let manager = Arc::clone(&manager);
thread::spawn(move || -> Result<()> {
for j in 0..10 {
let _buffer =
manager.allocate_buffer(PoolType::AstParsing, 1024 + j * 100)?;
let _interned =
manager.intern_string(&format!("thread_{}_iter_{}", i, j))?;
thread::sleep(Duration::from_millis(1));
}
Ok(())
})
})
.collect();
for handle in handles {
handle.join().unwrap()?;
}
let stats = manager.stats();
assert!(stats.total_allocated > 0);
Ok(())
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}