1use bytes::BytesMut;
21use std::mem::ManuallyDrop;
22use std::sync::atomic::{AtomicU64, Ordering};
23use std::sync::{Arc, Mutex};
24use tokio::sync::{OwnedSemaphorePermit, Semaphore};
25
26const SMALL_MAX: usize = 64 * 1024;
28const MEDIUM_MAX: usize = 512 * 1024;
29const LARGE_MAX: usize = 4 * 1024 * 1024;
30
31#[derive(Clone)]
60pub struct BytesPool {
61 small_pool: Arc<PoolTier>,
63 medium_pool: Arc<PoolTier>,
65 large_pool: Arc<PoolTier>,
67 xlarge_pool: Arc<PoolTier>,
69 metrics: Arc<BytesPoolMetrics>,
71}
72
73struct PoolTier {
75 buffer_size: usize,
77 max_buffers: usize,
79 semaphore: Arc<Semaphore>,
81 name: &'static str,
83 available_buffers: Mutex<Vec<BytesMut>>,
85 metrics: Mutex<Option<Arc<BytesPoolMetrics>>>,
87 tier_total_acquires: AtomicU64,
89 tier_pool_hits: AtomicU64,
91 tier_current_allocated_bytes: AtomicU64,
93}
94
95#[derive(Debug, Default)]
99pub struct BytesPoolMetrics {
100 pub total_acquires: AtomicU64,
102 pub pool_hits: AtomicU64,
104 pub pool_misses: AtomicU64,
106 pub total_bytes_allocated: AtomicU64,
108 pub current_allocated_bytes: AtomicU64,
110 pub available_buffers: AtomicU64,
112}
113
114pub struct PooledBuffer {
118 pub buffer: ManuallyDrop<BytesMut>,
120 tier: Option<Arc<PoolTier>>,
122 _permit: Option<OwnedSemaphorePermit>,
124}
125
126pub struct BytesPoolConfig {
130 pub small_size: usize,
131 pub small_max: usize,
132 pub medium_size: usize,
133 pub medium_max: usize,
134 pub large_size: usize,
135 pub large_max: usize,
136 pub xlarge_size: usize,
137 pub xlarge_max: usize,
138}
139
140impl Default for BytesPoolConfig {
141 fn default() -> Self {
142 Self {
143 small_size: 4 * 1024,
144 small_max: 1000,
145 medium_size: 64 * 1024,
146 medium_max: 500,
147 large_size: 512 * 1024,
148 large_max: 100,
149 xlarge_size: 4 * 1024 * 1024,
150 xlarge_max: 25,
151 }
152 }
153}
154
155impl BytesPool {
156 pub fn new_tiered() -> Self {
171 Self::with_config(BytesPoolConfig::default())
172 }
173
174 pub fn with_config(config: BytesPoolConfig) -> Self {
187 let metrics = Arc::new(BytesPoolMetrics::default());
188 let small_pool = Arc::new(PoolTier::new(config.small_size, config.small_max, "small"));
189 let medium_pool = Arc::new(PoolTier::new(config.medium_size, config.medium_max, "medium"));
190 let large_pool = Arc::new(PoolTier::new(config.large_size, config.large_max, "large"));
191 let xlarge_pool = Arc::new(PoolTier::new(config.xlarge_size, config.xlarge_max, "xlarge"));
192
193 small_pool.set_metrics(Arc::clone(&metrics));
195 medium_pool.set_metrics(Arc::clone(&metrics));
196 large_pool.set_metrics(Arc::clone(&metrics));
197 xlarge_pool.set_metrics(Arc::clone(&metrics));
198
199 Self {
200 small_pool,
201 medium_pool,
202 large_pool,
203 xlarge_pool,
204 metrics,
205 }
206 }
207
208 pub async fn acquire_buffer(&self, size: usize) -> PooledBuffer {
227 let tier = self.select_tier(size);
228 let mut buffer = tier.acquire_buffer(size, &self.metrics).await;
229 if buffer._permit.is_some() {
230 buffer.tier = Some(Arc::clone(tier));
231 }
232 buffer
233 }
234
235 pub fn try_acquire_buffer(&self, size: usize) -> Option<PooledBuffer> {
254 let tier = self.select_tier(size);
255 let mut buffer = tier.try_acquire_buffer(size, &self.metrics)?;
256 buffer.tier = Some(Arc::clone(tier));
258 Some(buffer)
259 }
260
261 fn select_tier(&self, size: usize) -> &Arc<PoolTier> {
263 if size <= SMALL_MAX {
264 &self.small_pool
265 } else if size <= MEDIUM_MAX {
266 &self.medium_pool
267 } else if size <= LARGE_MAX {
268 &self.large_pool
269 } else {
270 &self.xlarge_pool
271 }
272 }
273
274 pub fn metrics(&self) -> &BytesPoolMetrics {
276 &self.metrics
277 }
278
279 pub fn hit_rate(&self) -> f64 {
281 let hits = self.metrics.pool_hits.load(Ordering::Relaxed);
282 let total = self.metrics.total_acquires.load(Ordering::Relaxed);
283 if total == 0 { 0.0 } else { hits as f64 / total as f64 }
284 }
285
286 pub fn available_buffers(&self) -> u64 {
288 self.metrics.available_buffers.load(Ordering::Relaxed)
289 }
290}
291
292impl PoolTier {
293 fn new(buffer_size: usize, max_buffers: usize, name: &'static str) -> Self {
294 Self {
295 buffer_size,
296 max_buffers,
297 semaphore: Arc::new(Semaphore::new(max_buffers)),
298 name,
299 available_buffers: Mutex::new(Vec::new()),
300 metrics: Mutex::new(None),
301 tier_total_acquires: AtomicU64::new(0),
302 tier_pool_hits: AtomicU64::new(0),
303 tier_current_allocated_bytes: AtomicU64::new(0),
304 }
305 }
306
307 fn set_metrics(&self, metrics: Arc<BytesPoolMetrics>) {
308 *self.metrics.lock().unwrap_or_else(|e| e.into_inner()) = Some(metrics);
309 }
310
311 fn take_or_allocate_buffer(&self, size: usize, pool_metrics: &BytesPoolMetrics) -> (BytesMut, bool) {
312 let buffer_opt = {
313 let mut available = self.available_buffers.lock().unwrap_or_else(|e| e.into_inner());
314 available.pop()
315 };
316 let was_reused = buffer_opt.is_some();
317 if was_reused {
318 pool_metrics.available_buffers.fetch_sub(1, Ordering::Relaxed);
323 }
324
325 let buffer = if let Some(mut buf) = buffer_opt {
326 let previous_capacity = buf.capacity();
327 buf.clear();
328 if previous_capacity < size {
329 buf.reserve(size - previous_capacity);
330 }
331 let current_capacity = buf.capacity();
332 if current_capacity > previous_capacity {
333 let delta = (current_capacity - previous_capacity) as u64;
334 pool_metrics.total_bytes_allocated.fetch_add(delta, Ordering::Relaxed);
335 pool_metrics.current_allocated_bytes.fetch_add(delta, Ordering::Relaxed);
336 self.tier_current_allocated_bytes.fetch_add(delta, Ordering::Relaxed);
337 }
338 buf
339 } else {
340 let buf = BytesMut::with_capacity(size.max(self.buffer_size));
341 let allocated_bytes = buf.capacity() as u64;
342 pool_metrics
343 .total_bytes_allocated
344 .fetch_add(allocated_bytes, Ordering::Relaxed);
345 pool_metrics
346 .current_allocated_bytes
347 .fetch_add(allocated_bytes, Ordering::Relaxed);
348 self.tier_current_allocated_bytes
349 .fetch_add(allocated_bytes, Ordering::Relaxed);
350 buf
351 };
352
353 (buffer, was_reused)
354 }
355
356 fn record_acquire_metrics(&self, pool_metrics: &BytesPoolMetrics, buffer_capacity: usize, was_reused: bool) {
357 rustfs_io_metrics::record_bytes_pool_acquire(self.name, buffer_capacity, was_reused);
358
359 if was_reused {
360 pool_metrics.pool_hits.fetch_add(1, Ordering::Relaxed);
361 self.tier_pool_hits.fetch_add(1, Ordering::Relaxed);
362 } else {
363 pool_metrics.pool_misses.fetch_add(1, Ordering::Relaxed);
364 }
365
366 let tier_total_acquires = self.tier_total_acquires.load(Ordering::Relaxed);
367 let tier_pool_hits = self.tier_pool_hits.load(Ordering::Relaxed);
368 let tier_hit_rate = if tier_total_acquires == 0 {
369 0.0
370 } else {
371 tier_pool_hits as f64 / tier_total_acquires as f64
372 };
373 rustfs_io_metrics::record_bytes_pool_hit_rate(self.name, tier_hit_rate);
374 rustfs_io_metrics::record_bytes_pool_allocated(self.name, self.tier_current_allocated_bytes.load(Ordering::Relaxed));
375 }
376
377 async fn acquire_buffer(&self, size: usize, pool_metrics: &BytesPoolMetrics) -> PooledBuffer {
378 let permit = match Arc::clone(&self.semaphore).acquire_owned().await {
380 Ok(p) => p,
381 Err(_) => {
382 let buffer = BytesMut::with_capacity(size.max(self.buffer_size));
383 return PooledBuffer {
384 buffer: ManuallyDrop::new(buffer),
385 tier: None,
386 _permit: None,
387 };
388 }
389 };
390
391 let _metrics_lock = self.metrics.lock().unwrap_or_else(|e| e.into_inner());
393 let _metrics = _metrics_lock.as_ref().expect("operation should succeed");
394
395 pool_metrics.total_acquires.fetch_add(1, Ordering::Relaxed);
397 self.tier_total_acquires.fetch_add(1, Ordering::Relaxed);
398
399 let (buffer, was_reused) = self.take_or_allocate_buffer(size, pool_metrics);
400 let buffer_capacity = buffer.capacity();
401 self.record_acquire_metrics(pool_metrics, buffer_capacity, was_reused);
402
403 PooledBuffer {
404 buffer: ManuallyDrop::new(buffer),
405 tier: None, _permit: Some(permit),
407 }
408 }
409
410 fn try_acquire_buffer(&self, size: usize, pool_metrics: &BytesPoolMetrics) -> Option<PooledBuffer> {
411 let permit = Arc::clone(&self.semaphore).try_acquire_owned().ok()?;
413
414 let _metrics_lock = self.metrics.lock().unwrap_or_else(|e| e.into_inner());
416 let _metrics = _metrics_lock.as_ref().expect("operation should succeed");
417
418 pool_metrics.total_acquires.fetch_add(1, Ordering::Relaxed);
420 self.tier_total_acquires.fetch_add(1, Ordering::Relaxed);
421
422 let (buffer, was_reused) = self.take_or_allocate_buffer(size, pool_metrics);
423 let buffer_capacity = buffer.capacity();
424 self.record_acquire_metrics(pool_metrics, buffer_capacity, was_reused);
425
426 Some(PooledBuffer {
427 buffer: ManuallyDrop::new(buffer),
428 tier: None,
429 _permit: Some(permit),
430 })
431 }
432
433 fn return_buffer(&self, buffer: BytesMut) {
435 let mut buffer = Some(buffer);
436
437 if let Ok(mut available) = self.available_buffers.try_lock()
438 && available.len() < self.max_buffers
439 {
440 available.push(buffer.take().expect("buffer should be present until returned"));
441 if let Ok(metrics) = self.metrics.try_lock()
442 && let Some(metrics) = metrics.as_ref()
443 {
444 metrics.available_buffers.fetch_add(1, Ordering::Relaxed);
445 }
446 }
447
448 if let Some(buffer) = buffer {
449 let released_bytes = buffer.capacity() as u64;
450 self.tier_current_allocated_bytes
451 .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
452 Some(current.saturating_sub(released_bytes))
453 })
454 .ok();
455 if let Ok(metrics) = self.metrics.try_lock()
456 && let Some(metrics) = metrics.as_ref()
457 {
458 metrics
459 .current_allocated_bytes
460 .try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
461 Some(current.saturating_sub(released_bytes))
462 })
463 .ok();
464 }
465 }
466 rustfs_io_metrics::record_bytes_pool_allocated(self.name, self.tier_current_allocated_bytes.load(Ordering::Relaxed));
467 }
468}
469
470impl Drop for PooledBuffer {
471 #[allow(unsafe_code)]
474 fn drop(&mut self) {
475 let buffer = unsafe { ManuallyDrop::take(&mut self.buffer) };
476 if let Some(ref tier) = self.tier {
477 tier.return_buffer(buffer);
478 }
479 }
481}
482
483impl AsRef<[u8]> for PooledBuffer {
484 fn as_ref(&self) -> &[u8] {
485 self.buffer.as_ref()
486 }
487}
488
489impl AsMut<[u8]> for PooledBuffer {
490 fn as_mut(&mut self) -> &mut [u8] {
491 self.buffer.as_mut()
492 }
493}
494
495impl std::ops::Deref for PooledBuffer {
496 type Target = BytesMut;
497
498 fn deref(&self) -> &Self::Target {
499 &self.buffer
500 }
501}
502
503impl std::ops::DerefMut for PooledBuffer {
504 fn deref_mut(&mut self) -> &mut Self::Target {
505 &mut self.buffer
506 }
507}
508
509impl std::fmt::Debug for BytesPool {
510 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
511 f.debug_struct("BytesPool")
512 .field("small_pool", &self.small_pool)
513 .field("medium_pool", &self.medium_pool)
514 .field("large_pool", &self.large_pool)
515 .field("xlarge_pool", &self.xlarge_pool)
516 .field("metrics", &self.metrics)
517 .finish()
518 }
519}
520
521impl std::fmt::Debug for PoolTier {
522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 f.debug_struct("PoolTier")
524 .field("name", &self.name)
525 .field("buffer_size", &self.buffer_size)
526 .field("max_buffers", &self.max_buffers)
527 .field("available_permits", &self.semaphore.available_permits())
528 .field(
529 "available_buffers",
530 &self.available_buffers.lock().unwrap_or_else(|e| e.into_inner()).len(),
531 )
532 .finish()
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[tokio::test]
541 async fn test_new_tiered() {
542 let pool = BytesPool::new_tiered();
543 assert_eq!(pool.small_pool.buffer_size, 4 * 1024);
544 assert_eq!(pool.small_pool.max_buffers, 1000);
545 }
546
547 #[tokio::test]
548 async fn test_acquire_buffer() {
549 let pool = BytesPool::new_tiered();
550 let buffer = pool.acquire_buffer(2048).await;
551 assert!(buffer.capacity() >= 2048);
552 }
553
554 #[tokio::test]
555 async fn available_buffers_gauge_decrements_on_reuse() {
556 let pool = BytesPool::new_tiered();
561 let buf = pool.acquire_buffer(2048).await;
562 drop(buf); assert_eq!(pool.available_buffers(), 1, "returned buffer should be counted");
564 let buf2 = pool.acquire_buffer(2048).await; assert_eq!(pool.available_buffers(), 0, "reused buffer must decrement the gauge");
566 drop(buf2);
567 assert_eq!(pool.available_buffers(), 1, "gauge tracks the real pool size");
568 }
569
570 #[tokio::test]
571 async fn test_acquire_buffer_after_shutdown_is_unpooled() {
572 let pool = BytesPool::new_tiered();
573 pool.small_pool.semaphore.close();
574
575 let buffer = pool.acquire_buffer(2048).await;
576
577 assert!(buffer.tier.is_none());
578 assert!(buffer._permit.is_none());
579 assert!(buffer.capacity() >= pool.small_pool.buffer_size);
580 drop(buffer);
581 assert_eq!(pool.available_buffers(), 0);
582 }
583
584 #[tokio::test]
585 async fn test_tier_selection() {
586 let pool = BytesPool::new_tiered();
587
588 let buf1 = pool.acquire_buffer(1024).await;
590 assert_eq!(buf1.capacity(), 4 * 1024);
591
592 let buf2 = pool.acquire_buffer(100 * 1024).await;
594 assert_eq!(buf2.capacity(), 100 * 1024); let buf3 = pool.acquire_buffer(1024 * 1024).await;
598 assert_eq!(buf3.capacity(), 1024 * 1024); let buf4 = pool.acquire_buffer(8 * 1024 * 1024).await;
602 assert_eq!(buf4.capacity(), 8 * 1024 * 1024); }
604
605 #[tokio::test]
606 async fn test_try_acquire_buffer() {
607 let pool = BytesPool::with_config(BytesPoolConfig {
608 small_size: 1024,
609 small_max: 1,
610 ..Default::default()
611 });
612
613 let buffer1 = pool.try_acquire_buffer(512);
615 assert!(buffer1.is_some());
616
617 let buffer2 = pool.try_acquire_buffer(512);
619 assert!(buffer2.is_none());
620 }
621
622 #[tokio::test]
623 async fn test_metrics() {
624 let pool = BytesPool::new_tiered();
625 let _buffer = pool.acquire_buffer(1024).await;
626 drop(_buffer);
627
628 let metrics = pool.metrics();
629 assert!(metrics.total_acquires.load(Ordering::Relaxed) > 0);
630 }
631
632 #[tokio::test]
633 async fn test_hit_rate() {
634 let pool = BytesPool::new_tiered();
635 assert_eq!(pool.hit_rate(), 0.0); let _buffer = pool.acquire_buffer(1024).await;
638 drop(_buffer);
639
640 assert_eq!(pool.hit_rate(), 0.0);
642 }
643
644 #[tokio::test]
645 async fn test_available_buffers() {
646 let pool = BytesPool::new_tiered();
647 assert_eq!(pool.available_buffers(), 0);
648
649 let _buffer = pool.acquire_buffer(1024).await;
650 drop(_buffer);
651
652 assert_eq!(pool.available_buffers(), 1);
654 }
655
656 #[tokio::test]
657 async fn test_buffer_reuse() {
658 let pool = BytesPool::with_config(BytesPoolConfig {
660 small_size: 1024,
661 small_max: 2,
662 ..Default::default()
663 });
664
665 let initial_acquires = pool.metrics().total_acquires.load(Ordering::Relaxed);
667 let initial_hits = pool.metrics().pool_hits.load(Ordering::Relaxed);
668 assert_eq!(initial_acquires, 0);
669
670 let buffer1 = pool.acquire_buffer(512).await;
672 let initial_bytes_allocated = pool.metrics().total_bytes_allocated.load(Ordering::Relaxed);
673 assert!(initial_bytes_allocated >= 1024);
674
675 drop(buffer1);
677
678 let _buffer2 = pool.acquire_buffer(512).await;
680 let bytes_after_reuse = pool.metrics().total_bytes_allocated.load(Ordering::Relaxed);
681
682 assert_eq!(initial_bytes_allocated, bytes_after_reuse);
684
685 let total_acquires = pool.metrics().total_acquires.load(Ordering::Relaxed) - initial_acquires;
687 assert_eq!(total_acquires, 2);
688
689 let delta_hits = pool.metrics().pool_hits.load(Ordering::Relaxed) - initial_hits;
691 assert_eq!(delta_hits, 1);
692 }
693
694 #[tokio::test]
695 async fn test_tier_allocated_bytes_tracks_real_allocations() {
696 let pool = BytesPool::with_config(BytesPoolConfig {
697 small_size: 1024,
698 small_max: 2,
699 ..Default::default()
700 });
701
702 let buf1 = pool.acquire_buffer(512).await;
704 assert_eq!(pool.small_pool.tier_current_allocated_bytes.load(Ordering::Relaxed), 1024);
705
706 drop(buf1);
708 let buf2 = pool.acquire_buffer(512).await;
709 assert_eq!(pool.small_pool.tier_current_allocated_bytes.load(Ordering::Relaxed), 1024);
710
711 let _buf3 = pool.acquire_buffer(512).await;
713 assert_eq!(pool.small_pool.tier_current_allocated_bytes.load(Ordering::Relaxed), 2048);
714
715 drop(buf2);
716 }
717
718 #[tokio::test]
719 async fn test_tier_hit_rate_counters_track_reuse() {
720 let pool = BytesPool::with_config(BytesPoolConfig {
721 small_size: 1024,
722 small_max: 2,
723 ..Default::default()
724 });
725
726 let buf1 = pool.acquire_buffer(512).await;
728 drop(buf1);
729
730 let _buf2 = pool.acquire_buffer(512).await;
732
733 assert_eq!(pool.small_pool.tier_total_acquires.load(Ordering::Relaxed), 2);
734 assert_eq!(pool.small_pool.tier_pool_hits.load(Ordering::Relaxed), 1);
735 }
736}