Skip to main content

rustfs_io_core/
pool.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Tiered buffer pool for zero-copy buffer management.
16//!
17//! Migrated from rustfs-ecstore to provide unified buffer pooling
18//! across rustfs and rustfs-ecstore without cyclic dependencies.
19
20use bytes::BytesMut;
21use std::mem::ManuallyDrop;
22use std::sync::atomic::{AtomicU64, Ordering};
23use std::sync::{Arc, Mutex};
24use tokio::sync::{OwnedSemaphorePermit, Semaphore};
25
26// Tier size thresholds
27const SMALL_MAX: usize = 64 * 1024;
28const MEDIUM_MAX: usize = 512 * 1024;
29const LARGE_MAX: usize = 4 * 1024 * 1024;
30
31/// Tiered buffer pool for zero-copy buffer management.
32///
33/// This pool provides 4 tiers of buffers for different size ranges:
34/// - Small: 4KB - 64KB
35/// - Medium: 64KB - 512KB
36/// - Large: 512KB - 4MB
37/// - XLarge: > 4MB
38///
39/// Buffers are automatically reused when returned to the pool.
40///
41/// # Example
42///
43/// ```ignore
44/// let pool = BytesPool::new_tiered();
45///
46/// // Acquire a buffer (automatically selects tier based on size)
47/// let mut buffer = pool.acquire_buffer(8192).await;
48///
49/// // Use the buffer...
50/// buffer.put_slice(b"hello world");
51///
52/// // Return to pool (automatic when dropped)
53/// drop(buffer);
54///
55/// // Next acquisition will reuse the buffer
56/// let mut buffer2 = pool.acquire_buffer(8192).await;
57/// assert!(pool.hit_rate() > 0.0); // Buffer was reused!
58/// ```
59#[derive(Clone)]
60pub struct BytesPool {
61    /// Small object pool (4KB - 64KB)
62    small_pool: Arc<PoolTier>,
63    /// Medium object pool (64KB - 512KB)
64    medium_pool: Arc<PoolTier>,
65    /// Large object pool (512KB - 4MB)
66    large_pool: Arc<PoolTier>,
67    /// Extra large pool (> 4MB)
68    xlarge_pool: Arc<PoolTier>,
69    /// Pool metrics
70    metrics: Arc<BytesPoolMetrics>,
71}
72
73/// Single pool tier with concurrent access control and buffer reuse.
74struct PoolTier {
75    /// Buffer size for this tier
76    buffer_size: usize,
77    /// Maximum concurrent buffers
78    max_buffers: usize,
79    /// Semaphore for concurrency control
80    semaphore: Arc<Semaphore>,
81    /// Pool name for metrics
82    name: &'static str,
83    /// Queue of available buffers for reuse
84    available_buffers: Mutex<Vec<BytesMut>>,
85    /// Metrics for tracking this tier
86    metrics: Mutex<Option<Arc<BytesPoolMetrics>>>,
87    /// Total acquisitions for this tier
88    tier_total_acquires: AtomicU64,
89    /// Total hits for this tier
90    tier_pool_hits: AtomicU64,
91    /// Current allocated bytes for this tier
92    tier_current_allocated_bytes: AtomicU64,
93}
94
95/// Pool metrics for monitoring and optimization.
96///
97/// Tracks acquisition patterns and memory usage.
98#[derive(Debug, Default)]
99pub struct BytesPoolMetrics {
100    /// Total buffer acquisitions
101    pub total_acquires: AtomicU64,
102    /// Pool hits (buffer reused)
103    pub pool_hits: AtomicU64,
104    /// Pool misses (new allocation)
105    pub pool_misses: AtomicU64,
106    /// Total bytes allocated
107    pub total_bytes_allocated: AtomicU64,
108    /// Current allocated bytes
109    pub current_allocated_bytes: AtomicU64,
110    /// Current available buffers in pool
111    pub available_buffers: AtomicU64,
112}
113
114/// A buffer managed by the BytesPool.
115///
116/// When dropped, the buffer is automatically returned to the pool for reuse.
117pub struct PooledBuffer {
118    /// The underlying buffer (ManuallyDrop to allow taking on drop)
119    pub buffer: ManuallyDrop<BytesMut>,
120    /// Reference to pool tier for returning buffer
121    tier: Option<Arc<PoolTier>>,
122    /// The semaphore permit (must be dropped last to release slot)
123    _permit: Option<OwnedSemaphorePermit>,
124}
125
126/// BytesPool configuration.
127///
128/// Allows customization of buffer sizes and limits for each tier.
129pub 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    /// Create new tiered pool with default configuration.
157    ///
158    /// # Tier Configuration
159    ///
160    /// - Small: 4KB buffers, max 1000 concurrent
161    /// - Medium: 64KB buffers, max 500 concurrent
162    /// - Large: 512KB buffers, max 100 concurrent
163    /// - XLarge: 4MB buffers, max 25 concurrent
164    ///
165    /// # Example
166    ///
167    /// ```ignore
168    /// let pool = BytesPool::new_tiered();
169    /// ```
170    pub fn new_tiered() -> Self {
171        Self::with_config(BytesPoolConfig::default())
172    }
173
174    /// Create pool with custom configuration.
175    ///
176    /// # Example
177    ///
178    /// ```ignore
179    /// let config = BytesPoolConfig {
180    ///     small_size: 8 * 1024,  // 8KB small buffers
181    ///     small_max: 2000,
182    ///     ..Default::default()
183    /// };
184    /// let pool = BytesPool::with_config(config);
185    /// ```
186    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        // Set metrics reference in all tiers
194        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    /// Acquire buffer with automatic tier selection.
209    ///
210    /// Selects the appropriate tier based on requested size and blocks
211    /// until a buffer is available. Reuses returned buffers when available.
212    ///
213    /// # Arguments
214    ///
215    /// * `size` - Minimum capacity for the buffer
216    ///
217    /// # Returns
218    ///
219    /// A PooledBuffer that releases the permit and returns buffer to pool when dropped.
220    ///
221    /// # Example
222    ///
223    /// ```ignore
224    /// let mut buffer = pool.acquire_buffer(8192).await;
225    /// ```
226    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    /// Try to acquire buffer without blocking.
236    ///
237    /// # Arguments
238    ///
239    /// * `size` - Minimum capacity for the buffer
240    ///
241    /// # Returns
242    ///
243    /// * `Some(buffer)` - If a buffer was available
244    /// * `None` - If the pool is at capacity
245    ///
246    /// # Example
247    ///
248    /// ```ignore
249    /// if let Some(mut buffer) = pool.try_acquire_buffer(8192) {
250    ///     // Use buffer...
251    /// }
252    /// ```
253    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        // Set tier reference for return on drop
257        buffer.tier = Some(Arc::clone(tier));
258        Some(buffer)
259    }
260
261    /// Select appropriate tier based on size.
262    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    /// Get pool metrics.
275    pub fn metrics(&self) -> &BytesPoolMetrics {
276        &self.metrics
277    }
278
279    /// Get pool hit rate (0.0 - 1.0).
280    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    /// Get the number of available buffers in the pool.
287    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            // A reused buffer leaves the available pool. `return_buffer` does a
319            // matching `fetch_add(1)` on push, so without this the
320            // `available_buffers` gauge only ever grows and never reflects the real
321            // pool size (backlog#806).
322            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        // Acquire semaphore permit (owned for storage in PooledBuffer)
379        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        // Use the pool's shared metrics for recording
392        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        // Record acquisition
396        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, // Will be set after creating Arc<PoolTier>
406            _permit: Some(permit),
407        }
408    }
409
410    fn try_acquire_buffer(&self, size: usize, pool_metrics: &BytesPoolMetrics) -> Option<PooledBuffer> {
411        // Try to acquire permit without blocking
412        let permit = Arc::clone(&self.semaphore).try_acquire_owned().ok()?;
413
414        // Use the pool's shared metrics for recording
415        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        // Record acquisition
419        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    /// Return a buffer to the pool for reuse without ever blocking the caller.
434    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    // SAFETY: Drop has exclusive access to `self`; taking the `ManuallyDrop`
472    // buffer moves it exactly once into the pool when a tier still owns it.
473    #[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        // The permit is automatically dropped here, releasing the semaphore slot
480    }
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        // Regression (backlog#806): acquiring a pooled (reused) buffer must
557        // decrement the available_buffers gauge, mirroring return_buffer's
558        // increment, so the gauge reflects the real pool size instead of only
559        // growing.
560        let pool = BytesPool::new_tiered();
561        let buf = pool.acquire_buffer(2048).await;
562        drop(buf); // returns to the pool -> gauge += 1
563        assert_eq!(pool.available_buffers(), 1, "returned buffer should be counted");
564        let buf2 = pool.acquire_buffer(2048).await; // reuses the pooled buffer -> gauge -= 1
565        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        // Small buffer (4KB - 64KB)
589        let buf1 = pool.acquire_buffer(1024).await;
590        assert_eq!(buf1.capacity(), 4 * 1024);
591
592        // Medium buffer (64KB - 512KB) - capacity is max(requested, tier_size)
593        let buf2 = pool.acquire_buffer(100 * 1024).await;
594        assert_eq!(buf2.capacity(), 100 * 1024); // Requested size
595
596        // Large buffer (512KB - 4MB)
597        let buf3 = pool.acquire_buffer(1024 * 1024).await;
598        assert_eq!(buf3.capacity(), 1024 * 1024); // Requested size
599
600        // XLarge buffer (> 4MB)
601        let buf4 = pool.acquire_buffer(8 * 1024 * 1024).await;
602        assert_eq!(buf4.capacity(), 8 * 1024 * 1024); // Requested size
603    }
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        // First acquisition should succeed
614        let buffer1 = pool.try_acquire_buffer(512);
615        assert!(buffer1.is_some());
616
617        // Second should fail (pool at capacity)
618        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); // No acquisitions yet
636
637        let _buffer = pool.acquire_buffer(1024).await;
638        drop(_buffer);
639
640        // First acquire is a miss (no buffers available yet)
641        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        // After drop, buffer should be returned to pool
653        assert_eq!(pool.available_buffers(), 1);
654    }
655
656    #[tokio::test]
657    async fn test_buffer_reuse() {
658        // This test verifies that buffers are reused when returned to the pool
659        let pool = BytesPool::with_config(BytesPoolConfig {
660            small_size: 1024,
661            small_max: 2,
662            ..Default::default()
663        });
664
665        // Record initial state
666        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        // First acquisition - should allocate new (miss)
671        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        // Return buffer (by dropping)
676        drop(buffer1);
677
678        // Second acquisition - should reuse (hit)
679        let _buffer2 = pool.acquire_buffer(512).await;
680        let bytes_after_reuse = pool.metrics().total_bytes_allocated.load(Ordering::Relaxed);
681
682        // Bytes allocated should be the same (buffer was reused)
683        assert_eq!(initial_bytes_allocated, bytes_after_reuse);
684
685        // Total acquires should be 2
686        let total_acquires = pool.metrics().total_acquires.load(Ordering::Relaxed) - initial_acquires;
687        assert_eq!(total_acquires, 2);
688
689        // Pool hits should be 1
690        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        // First acquire allocates one small-tier buffer.
703        let buf1 = pool.acquire_buffer(512).await;
704        assert_eq!(pool.small_pool.tier_current_allocated_bytes.load(Ordering::Relaxed), 1024);
705
706        // Return and reuse should not increase allocated bytes.
707        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        // A second in-flight buffer forces one more allocation.
712        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        // First acquire is miss.
727        let buf1 = pool.acquire_buffer(512).await;
728        drop(buf1);
729
730        // Second acquire reuses previous buffer and counts as hit.
731        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}