use safer_ring::pool::BufferPool;
use std::env;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::time::sleep;
#[derive(Debug)]
struct DemoConfig {
pool_size: usize,
buffer_size: usize,
concurrent_ops: usize,
duration_secs: u64,
stress_test: bool,
threads: usize,
}
impl Default for DemoConfig {
fn default() -> Self {
Self {
pool_size: 32,
buffer_size: 4096,
concurrent_ops: 16,
duration_secs: 10,
stress_test: false,
threads: 4,
}
}
}
impl DemoConfig {
fn from_args() -> Self {
let args: Vec<String> = env::args().collect();
let mut config = DemoConfig::default();
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"--pool-size" => {
if i + 1 < args.len() {
config.pool_size = args[i + 1].parse().unwrap_or(config.pool_size);
i += 2;
} else {
i += 1;
}
}
"--buffer-size" => {
if i + 1 < args.len() {
config.buffer_size = args[i + 1].parse().unwrap_or(config.buffer_size);
i += 2;
} else {
i += 1;
}
}
"--duration" => {
if i + 1 < args.len() {
config.duration_secs = args[i + 1].parse().unwrap_or(config.duration_secs);
i += 2;
} else {
i += 1;
}
}
"--stress-test" => {
config.stress_test = true;
i += 1;
}
"--threads" => {
if i + 1 < args.len() {
config.threads = args[i + 1].parse().unwrap_or(config.threads);
i += 2;
} else {
i += 1;
}
}
_ => i += 1,
}
}
config
}
}
#[derive(Debug, Default)]
struct PoolDemoStats {
allocations: u64,
allocation_failures: u64,
total_bytes_processed: u64,
operations_completed: u64,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🏊 Safer-Ring Buffer Pool Demonstration");
println!("=======================================");
println!();
println!("📚 EDUCATIONAL NOTE: Buffer Pool vs Simple Buffer Reuse");
println!("========================================================");
println!("🔍 BufferPool is designed for high-throughput scenarios with many concurrent");
println!(" operations that need pre-allocated buffers. However, for many applications,");
println!(" simply reusing a single OwnedBuffer in a loop is simpler and sufficient:");
println!();
println!(" 💡 Simple Pattern (recommended for most use cases):");
println!(" let mut buffer = OwnedBuffer::new(size);");
println!(" loop {{");
println!(" let (result, returned_buffer) = ring.read_owned(fd, buffer).await?;");
println!(" buffer = returned_buffer; // Hot potato reuse!");
println!(" }}");
println!();
println!(" 🏊 Pool Pattern (for high-frequency, concurrent scenarios):");
println!(" let pool = BufferPool::new(pool_size, buffer_size);");
println!(" let buffer = pool.get_buffer().await?;");
println!(" // buffer is automatically returned to pool when dropped");
println!();
println!(" 📊 Use BufferPool when you have:");
println!(" ✓ High-frequency allocations (thousands per second)");
println!(" ✓ Many concurrent operations needing buffers simultaneously");
println!(" ✓ Unpredictable buffer lifetime patterns");
println!(" ✓ Need to avoid allocation spikes in latency-critical code");
println!();
println!(" 🎯 Use simple OwnedBuffer reuse when you have:");
println!(" ✓ Sequential or low-frequency operations");
println!(" ✓ Predictable buffer usage patterns");
println!(" ✓ Want to minimize complexity");
println!(" ✓ Don't need many buffers simultaneously");
println!();
let config = DemoConfig::from_args();
println!("📊 Configuration:");
println!(" Pool size: {} buffers", config.pool_size);
println!(" Buffer size: {} bytes", config.buffer_size);
println!(" Concurrent operations: {}", config.concurrent_ops);
println!(" Duration: {} seconds", config.duration_secs);
if config.stress_test {
println!(" Stress test: {} threads", config.threads);
}
println!();
if config.stress_test {
run_stress_test(&config).await?;
} else {
run_basic_demo(&config).await?;
}
Ok(())
}
async fn run_basic_demo(config: &DemoConfig) -> Result<(), Box<dyn std::error::Error>> {
println!("🚀 Starting basic buffer pool demo...");
let pool = BufferPool::new(config.pool_size, config.buffer_size);
println!("✅ Buffer pool created");
println!("📈 Pool configuration:");
println!(" Pool size: {} buffers", pool.capacity());
println!(" Buffer size: {} bytes", pool.buffer_size());
println!();
println!("🔄 Demonstrating basic operations...");
let mut buffers = Vec::new();
for i in 0..std::cmp::min(5, config.pool_size) {
if let Some(buffer) = pool.get() {
println!(
" 📦 Acquired buffer {} (size: {} bytes)",
i + 1,
buffer.len()
);
buffers.push(buffer);
}
}
println!("📊 Pool stats after acquiring:");
let stats = pool.stats();
println!(" Available buffers: {}", stats.available_buffers);
println!(" In-use buffers: {}", stats.in_use_buffers);
println!();
println!("⚡ Simulating buffer usage...");
for (i, buffer) in buffers.iter_mut().enumerate() {
let test_data = format!("Test data for buffer {}", i + 1);
let bytes = test_data.as_bytes();
let copy_len = std::cmp::min(bytes.len(), buffer.len());
let mut slice = buffer.as_mut_slice();
slice[..copy_len].copy_from_slice(&bytes[..copy_len]);
println!(" ✏️ Filled buffer {} with: {}", i + 1, test_data);
}
println!("🔄 Returning buffers to pool (on drop)...");
drop(buffers);
println!("📊 All buffers returned to pool");
let stats = pool.stats();
println!(" Available buffers: {}", stats.available_buffers);
println!(" In-use buffers: {}", stats.in_use_buffers);
println!();
println!("🔀 Demonstrating concurrent buffer usage...");
run_concurrent_demo_with_pool(config, pool).await?;
println!("✅ Basic demo completed!");
Ok(())
}
async fn run_concurrent_demo_with_pool(
config: &DemoConfig,
pool: BufferPool,
) -> Result<(), Box<dyn std::error::Error>> {
let stats = Arc::new(tokio::sync::Mutex::new(PoolDemoStats::default()));
let mut tasks = Vec::new();
let pool = Arc::new(pool);
for task_id in 0..config.concurrent_ops {
let stats_clone = Arc::clone(&stats);
let pool_clone = Arc::clone(&pool);
let task = tokio::spawn(async move {
let mut local_ops = 0u64;
let start_time = Instant::now();
while start_time.elapsed().as_secs() < 5 {
if let Some(mut buffer) = pool_clone.get() {
let work_data = format!("Task {task_id} operation {local_ops}");
let bytes = work_data.as_bytes();
let copy_len = std::cmp::min(bytes.len(), buffer.len());
let mut slice = buffer.as_mut_slice();
slice[..copy_len].copy_from_slice(&bytes[..copy_len]);
sleep(Duration::from_millis(10)).await;
{
let mut stats = stats_clone.lock().await;
stats.allocations += 1;
stats.total_bytes_processed += copy_len as u64;
stats.operations_completed += 1;
}
local_ops += 1;
} else {
let mut stats = stats_clone.lock().await;
stats.allocation_failures += 1;
sleep(Duration::from_millis(1)).await; }
}
println!(" 🏁 Task {task_id} completed {local_ops} operations");
});
tasks.push(task);
}
for task in tasks {
task.await?;
}
let final_stats = stats.lock().await;
println!("📊 Concurrent demo results:");
println!(" Successful allocations: {}", final_stats.allocations);
println!(" Failed allocations: {}", final_stats.allocation_failures);
println!(
" Operations completed: {}",
final_stats.operations_completed
);
println!(" Bytes processed: {}", final_stats.total_bytes_processed);
let success_rate = if final_stats.allocations + final_stats.allocation_failures > 0 {
(final_stats.allocations as f64)
/ ((final_stats.allocations + final_stats.allocation_failures) as f64)
* 100.0
} else {
0.0
};
println!(" Success rate: {success_rate:.2}%");
Ok(())
}
async fn run_stress_test(config: &DemoConfig) -> Result<(), Box<dyn std::error::Error>> {
println!("💪 Starting stress test...");
let pool = Arc::new(BufferPool::new(config.pool_size, config.buffer_size));
let stats = Arc::new(tokio::sync::Mutex::new(PoolDemoStats::default()));
let stats_reporter = Arc::clone(&stats);
let duration_secs = config.duration_secs;
let report_task = tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(1));
let start_time = Instant::now();
loop {
interval.tick().await;
let stats = stats_reporter.lock().await;
let elapsed = start_time.elapsed().as_secs_f64();
let ops_per_sec = if elapsed > 0.0 {
stats.operations_completed as f64 / elapsed
} else {
0.0
};
println!(
"📊 [{:6.1}s] Ops: {:8}, Rate: {:8.0}/s, Failures: {:6}, Bytes: {:10}",
elapsed,
stats.operations_completed,
ops_per_sec,
stats.allocation_failures,
stats.total_bytes_processed
);
if elapsed >= duration_secs as f64 {
break;
}
}
});
let mut tasks = Vec::new();
for thread_id in 0..config.threads {
let stats_clone = Arc::clone(&stats);
let pool_clone = Arc::clone(&pool);
let duration = config.duration_secs;
let task = tokio::spawn(async move {
let start_time = Instant::now();
let mut local_ops = 0u64;
while start_time.elapsed().as_secs() < duration {
for _ in 0..100 {
if let Some(mut buffer) = pool_clone.get() {
let pattern = (thread_id as u8).wrapping_mul(local_ops as u8);
let mut slice = buffer.as_mut_slice();
for byte in slice.iter_mut().take(64) {
*byte = pattern;
}
local_ops += 1;
} else {
let mut stats_lock = stats_clone.lock().await;
stats_lock.allocation_failures += 1;
drop(stats_lock);
tokio::task::yield_now().await;
}
}
let mut stats_lock = stats_clone.lock().await;
stats_lock.operations_completed += local_ops;
stats_lock.total_bytes_processed += 64 * local_ops;
stats_lock.allocations += local_ops;
local_ops = 0;
tokio::task::yield_now().await;
}
println!("🏁 Thread {thread_id} completed");
});
tasks.push(task);
}
for task in tasks {
task.await?;
}
report_task.abort();
let final_stats = stats.lock().await;
println!();
println!("🏆 Stress Test Results:");
println!("========================================");
println!("Operations completed: {}", final_stats.operations_completed);
println!("Allocation failures: {}", final_stats.allocation_failures);
println!(
"Total bytes processed: {}",
final_stats.total_bytes_processed
);
println!(
"Average ops/sec: {:.0}",
final_stats.operations_completed as f64 / config.duration_secs as f64
);
println!("Successful allocations: {}", final_stats.allocations);
println!(
"Success rate: {:.2}%",
if final_stats.allocations + final_stats.allocation_failures > 0 {
(final_stats.allocations as f64
/ (final_stats.allocations + final_stats.allocation_failures) as f64)
* 100.0
} else {
100.0
}
);
Ok(())
}