Skip to main content

sozu_lib/
pool.rs

1/// experimental module to measure buffer pool usage
2///
3/// this allows us to track how many buffers are used through the
4/// buffers.count metric.
5///
6/// Right now, we wrap the `pool` crate, but we might write a different
7/// buffer pool in the future, so this module will still be useful to
8/// test the differences
9use std::{
10    cmp,
11    io::{self, Read, Write},
12    ops, ptr,
13    sync::atomic::{AtomicUsize, Ordering},
14};
15
16use crate::metrics::names;
17
18static BUFFER_COUNT: AtomicUsize = AtomicUsize::new(0);
19
20pub struct Pool {
21    pub inner: poule::Pool<BufferMetadata>,
22    pub buffer_size: usize,
23}
24
25impl Pool {
26    pub fn with_capacity(minimum: usize, maximum: usize, buffer_size: usize) -> Pool {
27        debug_assert!(
28            minimum <= maximum,
29            "pool minimum ({minimum}) must not exceed maximum ({maximum})"
30        );
31        let mut inner = poule::Pool::with_extra(maximum, buffer_size);
32        inner.grow_to(minimum);
33        let pool = Pool { inner, buffer_size };
34        // Post-condition: a fresh pool hands out nothing and respects its
35        // capacity ceiling. `grow_to(minimum)` may pre-allocate, but never
36        // beyond `maximum`, and nothing is checked out yet.
37        debug_assert_eq!(pool.inner.used(), 0, "a fresh pool has nothing checked out");
38        debug_assert!(
39            pool.inner.capacity() <= maximum,
40            "grown capacity must never exceed the configured maximum"
41        );
42        #[cfg(debug_assertions)]
43        pool.check_invariants();
44        pool
45    }
46
47    pub fn checkout(&mut self) -> Option<Checkout> {
48        // Pre-condition: the accounting invariant holds on entry.
49        #[cfg(debug_assertions)]
50        self.check_invariants();
51        // Snapshot used-count before any growth or checkout so the
52        // post-conditions can assert the exact delta. Read only inside
53        // `debug_assert!` → dead code in release, but it must still compile.
54        let used_before = self.inner.used();
55
56        if self.inner.used() == self.inner.capacity()
57            && self.inner.capacity() < self.inner.maximum_capacity()
58        {
59            self.inner.grow_to(std::cmp::min(
60                self.inner.capacity() * 2,
61                self.inner.maximum_capacity(),
62            ));
63            debug!(
64                "growing pool capacity from {} to {}",
65                self.inner.capacity(),
66                std::cmp::min(self.inner.capacity() * 2, self.inner.maximum_capacity())
67            );
68        }
69        let capacity = self.buffer_size;
70        let buffer_size = self.buffer_size;
71        let result = self
72            .inner
73            .checkout(|| {
74                trace!("initializing a buffer with capacity {}", capacity);
75                BufferMetadata::new()
76            })
77            .map(|c| {
78                let old_buffer_count = BUFFER_COUNT.fetch_add(1, Ordering::SeqCst);
79                gauge!(names::buffer::IN_USE, old_buffer_count + 1);
80                Checkout { inner: c }
81            });
82
83        match &result {
84            Some(checkout) => {
85                // A successful checkout consumes exactly one slot: the pool's
86                // used-count rose by one and never exceeded the live capacity.
87                debug_assert_eq!(
88                    self.inner.used(),
89                    used_before + 1,
90                    "a successful checkout must increment used-count by exactly 1"
91                );
92                debug_assert!(
93                    self.inner.used() <= self.inner.capacity(),
94                    "used-count must never exceed the pool capacity"
95                );
96                // The handed-out buffer must carry at least the configured size
97                // (poule rounds the per-entry extra up to `align_of::<Entry>`, so
98                // capacity equals buffer_size only when it is already aligned —
99                // the default 16393 rounds to 16400) and start empty
100                // (position == end == 0 from `BufferMetadata::new`).
101                debug_assert!(
102                    checkout.capacity() >= buffer_size,
103                    "a checked-out buffer must hold at least the configured buffer size"
104                );
105                debug_assert_eq!(
106                    checkout.available_data(),
107                    0,
108                    "a freshly checked-out buffer must hold no data"
109                );
110            }
111            None => {
112                // Exhaustion is graceful (never a panic): the used-count must
113                // be unchanged on the failure path. The pool was at its hard
114                // ceiling, so capacity equals maximum_capacity.
115                debug_assert_eq!(
116                    self.inner.used(),
117                    used_before,
118                    "a failed checkout must not change the used-count"
119                );
120                debug_assert_eq!(
121                    self.inner.capacity(),
122                    self.inner.maximum_capacity(),
123                    "checkout only fails once the pool is grown to its maximum"
124                );
125            }
126        }
127
128        // Post-condition: the accounting invariant still holds on exit.
129        #[cfg(debug_assertions)]
130        self.check_invariants();
131        result
132    }
133
134    /// Full accounting-invariant sweep for the buffer pool, used as a
135    /// `debug_assert!`-guarded pre/post-condition on every public mutating
136    /// method. Encodes the `available + checked_out == capacity` contract in
137    /// terms of `poule`'s accounting: the number of checked-out buffers
138    /// (`used`) plus the number still available equals the live capacity, and
139    /// capacity stays bounded by the configured hard maximum. Compiled out
140    /// entirely in release.
141    #[cfg(debug_assertions)]
142    fn check_invariants(&self) {
143        let used = self.inner.used();
144        let capacity = self.inner.capacity();
145        let maximum = self.inner.maximum_capacity();
146
147        // `available + checked_out == capacity`: every slot in the live
148        // capacity is either checked out or available, never both, never lost.
149        debug_assert!(
150            used <= capacity,
151            "checked-out buffers ({used}) must never exceed live capacity ({capacity})"
152        );
153        let available = capacity - used;
154        debug_assert_eq!(
155            available + used,
156            capacity,
157            "available ({available}) + checked_out ({used}) must equal capacity ({capacity})"
158        );
159
160        // Capacity grows lazily but is hard-bounded by the configured maximum.
161        debug_assert!(
162            capacity <= maximum,
163            "live capacity ({capacity}) must never exceed maximum_capacity ({maximum})"
164        );
165    }
166}
167
168impl ops::Deref for Pool {
169    type Target = poule::Pool<BufferMetadata>;
170
171    fn deref(&self) -> &Self::Target {
172        &self.inner
173    }
174}
175
176impl ops::DerefMut for Pool {
177    fn deref_mut(&mut self) -> &mut poule::Pool<BufferMetadata> {
178        &mut self.inner
179    }
180}
181
182#[derive(Debug, PartialEq, Eq, Clone)]
183pub struct BufferMetadata {
184    position: usize,
185    end: usize,
186}
187
188impl Default for BufferMetadata {
189    fn default() -> Self {
190        Self::new()
191    }
192}
193
194impl BufferMetadata {
195    pub fn new() -> BufferMetadata {
196        BufferMetadata {
197            position: 0,
198            end: 0,
199        }
200    }
201}
202
203pub struct Checkout {
204    pub inner: poule::Checkout<BufferMetadata>,
205}
206
207/*
208impl ops::Deref for Checkout {
209    type Target = poule::Checkout<BufferMetadata>;
210
211    fn deref(&self) -> &Self::Target {
212        &self.inner
213    }
214}
215
216impl ops::DerefMut for Checkout {
217    fn deref_mut(&mut self) -> &mut poule::Checkout<BufferMetadata> {
218        &mut self.inner
219    }
220}
221*/
222
223impl Drop for Checkout {
224    fn drop(&mut self) {
225        let old_buffer_count = BUFFER_COUNT.fetch_sub(1, Ordering::SeqCst);
226        // Gauge-underflow guard: every live `Checkout` was paired with a
227        // `fetch_add(1)` at checkout time, so the global in-use counter must
228        // be strictly positive when one is dropped. A zero here means a
229        // double-checkin or an unbalanced add/sub — a real accounting bug, not
230        // a rounding issue. `fetch_sub` wraps in release; the assert makes the
231        // violation loud in debug/test/fuzz.
232        debug_assert!(
233            old_buffer_count >= 1,
234            "buffer in-use gauge underflow on checkin: count was {old_buffer_count} before decrement"
235        );
236        gauge!(names::buffer::IN_USE, old_buffer_count - 1);
237    }
238}
239
240impl Checkout {
241    pub fn available_data(&self) -> usize {
242        self.inner.end - self.inner.position
243    }
244
245    pub fn available_space(&self) -> usize {
246        self.capacity() - self.inner.end
247    }
248
249    pub fn capacity(&self) -> usize {
250        self.inner.extra().len()
251    }
252
253    pub fn empty(&self) -> bool {
254        self.inner.position == self.inner.end
255    }
256
257    /// Internal buffer-window invariant: `position <= end <= capacity`. The
258    /// occupied window `[position, end)` is the live data; everything outside
259    /// it is free space. Used as a `debug_assert!`-guarded pre/post-condition
260    /// on the slice-mutating methods. Compiled out in release.
261    #[cfg(debug_assertions)]
262    fn check_invariants(&self) {
263        let position = self.inner.position;
264        let end = self.inner.end;
265        let capacity = self.capacity();
266        debug_assert!(
267            position <= end,
268            "buffer position ({position}) must not pass end ({end})"
269        );
270        debug_assert!(
271            end <= capacity,
272            "buffer end ({end}) must not exceed capacity ({capacity})"
273        );
274        // consumed-prefix + available_data + available_space == capacity:
275        // the three regions partition the buffer with no overlap or loss.
276        debug_assert_eq!(
277            position + self.available_data() + self.available_space(),
278            capacity,
279            "consumed prefix + available data + free space must tile the whole buffer"
280        );
281    }
282
283    pub fn consume(&mut self, count: usize) -> usize {
284        #[cfg(debug_assertions)]
285        self.check_invariants();
286        let available_before = self.available_data();
287        let cnt = cmp::min(count, available_before);
288        // `consume` can never advance past the available data — `cnt` is
289        // clamped above, so this read can never underflow `available_data`.
290        debug_assert!(
291            cnt <= available_before,
292            "consume count ({cnt}) must not exceed available data ({available_before})"
293        );
294        self.inner.position += cnt;
295        if self.inner.position > self.capacity() / 2 {
296            //trace!("consume shift: pos {}, end {}", self.position, self.end);
297            self.shift();
298        }
299        // Post-condition: exactly `cnt` bytes left the readable window (a
300        // shift relocates but does not change `available_data`).
301        debug_assert_eq!(
302            self.available_data(),
303            available_before - cnt,
304            "consume must shrink available data by exactly the consumed count"
305        );
306        #[cfg(debug_assertions)]
307        self.check_invariants();
308        cnt
309    }
310
311    pub fn fill(&mut self, count: usize) -> usize {
312        #[cfg(debug_assertions)]
313        self.check_invariants();
314        let data_before = self.available_data();
315        let space_before = self.available_space();
316        let cnt = cmp::min(count, space_before);
317        // `fill` can never claim more than the free space — `cnt` is clamped,
318        // so advancing `end` by `cnt` can never overrun the buffer.
319        debug_assert!(
320            cnt <= space_before,
321            "fill count ({cnt}) must not exceed available space ({space_before})"
322        );
323        self.inner.end += cnt;
324        if self.available_space() < self.available_data() + cnt {
325            //trace!("fill shift: pos {}, end {}", self.position, self.end);
326            self.shift();
327        }
328        // Post-condition: exactly `cnt` bytes entered the readable window (a
329        // shift relocates but does not change `available_data`).
330        debug_assert_eq!(
331            self.available_data(),
332            data_before + cnt,
333            "fill must grow available data by exactly the filled count"
334        );
335        #[cfg(debug_assertions)]
336        self.check_invariants();
337        cnt
338    }
339
340    pub fn reset(&mut self) {
341        self.inner.position = 0;
342        self.inner.end = 0;
343        // Post-condition: a reset buffer is empty and offers its full
344        // capacity as free space.
345        debug_assert_eq!(self.available_data(), 0, "reset must empty the buffer");
346        debug_assert_eq!(
347            self.available_space(),
348            self.capacity(),
349            "reset must restore the full capacity as free space"
350        );
351        #[cfg(debug_assertions)]
352        self.check_invariants();
353    }
354
355    pub fn sync(&mut self, end: usize, position: usize) {
356        // Pre-condition: the caller must supply a coherent window —
357        // `position <= end <= capacity`. `sync` restores a previously valid
358        // window (e.g. after a `Vec`-backed parse), so a violation here is a
359        // caller logic bug, not network input.
360        debug_assert!(
361            position <= end,
362            "sync position ({position}) must not pass end ({end})"
363        );
364        debug_assert!(
365            end <= self.capacity(),
366            "sync end ({end}) must not exceed capacity ({})",
367            self.capacity()
368        );
369        self.inner.position = position;
370        self.inner.end = end;
371        // Post-condition: the readable window matches the requested span.
372        debug_assert_eq!(
373            self.available_data(),
374            end - position,
375            "sync must expose exactly end - position readable bytes"
376        );
377        #[cfg(debug_assertions)]
378        self.check_invariants();
379    }
380
381    pub fn data(&self) -> &[u8] {
382        &self.inner.extra()[self.inner.position..self.inner.end]
383    }
384
385    pub fn space(&mut self) -> &mut [u8] {
386        let range = self.inner.end..self.capacity();
387        &mut self.inner.extra_mut()[range]
388    }
389
390    pub fn shift(&mut self) {
391        #[cfg(debug_assertions)]
392        self.check_invariants();
393        let pos = self.inner.position;
394        let end = self.inner.end;
395        let data_before = self.available_data();
396        if pos > 0 {
397            // SAFETY: src and dst point into the same checkout buffer
398            // (`self.inner.extra`); the slice indexing above bounds-checks
399            // both ranges (`pos..end` and `..length`) against the live
400            // buffer length. `ptr::copy` is overlap-safe.
401            unsafe {
402                let length = end - pos;
403                ptr::copy(
404                    self.inner.extra()[pos..end].as_ptr(),
405                    self.inner.extra_mut()[..length].as_mut_ptr(),
406                    length,
407                );
408                self.inner.position = 0;
409                self.inner.end = length;
410            }
411        }
412        // Post-condition: shift relocates the readable window to the front but
413        // never changes how many bytes are readable.
414        debug_assert_eq!(
415            self.available_data(),
416            data_before,
417            "shift must preserve the amount of readable data"
418        );
419        #[cfg(debug_assertions)]
420        self.check_invariants();
421    }
422
423    pub fn delete_slice(&mut self, start: usize, length: usize) -> Option<usize> {
424        #[cfg(debug_assertions)]
425        self.check_invariants();
426        let data_before = self.available_data();
427        if start + length >= self.available_data() {
428            // Out-of-range deletes are a graceful no-op: nothing changed.
429            debug_assert_eq!(
430                self.available_data(),
431                data_before,
432                "rejected delete_slice must not mutate the buffer"
433            );
434            return None;
435        }
436
437        // SAFETY: src and dst point into the same checkout buffer
438        // (`self.inner.extra`). The early-return above guarantees
439        // `start + length < available_data`, and slice indexing
440        // bounds-checks both `begin+length..end` and `begin..next_end`
441        // against the live buffer length. `ptr::copy` is overlap-safe.
442        unsafe {
443            let begin = self.inner.position + start;
444            let next_end = self.inner.end - length;
445            ptr::copy(
446                self.inner.extra()[begin + length..self.inner.end].as_ptr(),
447                self.inner.extra_mut()[begin..next_end].as_mut_ptr(),
448                self.inner.end - (begin + length),
449            );
450            self.inner.end = next_end;
451        }
452        // Post-condition: removing `length` bytes from the middle shrinks the
453        // readable window by exactly `length`.
454        debug_assert_eq!(
455            self.available_data(),
456            data_before - length,
457            "delete_slice must shrink available data by exactly the deleted length"
458        );
459        #[cfg(debug_assertions)]
460        self.check_invariants();
461        Some(self.available_data())
462    }
463
464    pub fn replace_slice(&mut self, data: &[u8], start: usize, length: usize) -> Option<usize> {
465        #[cfg(debug_assertions)]
466        self.check_invariants();
467        let data_before = self.available_data();
468        let data_len = data.len();
469        if start + length > self.available_data()
470            || self.inner.position + start + data_len > self.capacity()
471        {
472            // Rejected replace is a graceful no-op.
473            debug_assert_eq!(
474                self.available_data(),
475                data_before,
476                "rejected replace_slice must not mutate the buffer"
477            );
478            return None;
479        }
480        // Pre-condition: the replacement window lies within the readable data
481        // (guaranteed by the early-return above). The net length change is
482        // `data_len - length`.
483        debug_assert!(
484            start + length <= data_before,
485            "replace_slice window [{start}, {}) must lie within available data ({data_before})",
486            start + length
487        );
488
489        // SAFETY: every `ptr::copy` below moves bytes inside the same
490        // checkout buffer (`self.inner.extra`) or copies from the caller's
491        // `data` slice into it. The two early-return checks above bound
492        // the affected ranges against `available_data()` and `capacity()`,
493        // and each slice indexing site is bounds-checked. `ptr::copy` is
494        // overlap-safe.
495        unsafe {
496            let begin = self.inner.position + start;
497            let slice_end = begin + data_len;
498            // we reduced the data size
499            if data_len < length {
500                ptr::copy(
501                    data.as_ptr(),
502                    self.inner.extra_mut()[begin..slice_end].as_mut_ptr(),
503                    data_len,
504                );
505
506                ptr::copy(
507                    self.inner.extra()[start + length..self.inner.end].as_ptr(),
508                    self.inner.extra_mut()[slice_end..].as_mut_ptr(),
509                    self.inner.end - (start + length),
510                );
511                self.inner.end -= length - data_len;
512
513            // we put more data in the buffer
514            } else {
515                ptr::copy(
516                    self.inner.extra()[start + length..self.inner.end].as_ptr(),
517                    self.inner.extra_mut()[start + data_len..].as_mut_ptr(),
518                    self.inner.end - (start + length),
519                );
520                ptr::copy(
521                    data.as_ptr(),
522                    self.inner.extra_mut()[begin..slice_end].as_mut_ptr(),
523                    data_len,
524                );
525                self.inner.end += data_len - length;
526            }
527        }
528        // Post-condition: the readable window grew/shrank by exactly the net
529        // difference between the inserted data and the replaced span. Compare
530        // as i64 to keep the arithmetic signed and avoid usize underflow in
531        // the assertion itself.
532        debug_assert_eq!(
533            self.available_data() as i64,
534            data_before as i64 + data_len as i64 - length as i64,
535            "replace_slice must change available data by exactly data_len - length"
536        );
537        #[cfg(debug_assertions)]
538        self.check_invariants();
539        Some(self.available_data())
540    }
541
542    pub fn insert_slice(&mut self, data: &[u8], start: usize) -> Option<usize> {
543        #[cfg(debug_assertions)]
544        self.check_invariants();
545        let data_before = self.available_data();
546        let data_len = data.len();
547        if start > self.available_data()
548            || self.inner.position + self.inner.end + data_len > self.capacity()
549        {
550            // Rejected insert is a graceful no-op.
551            debug_assert_eq!(
552                self.available_data(),
553                data_before,
554                "rejected insert_slice must not mutate the buffer"
555            );
556            return None;
557        }
558        // Pre-condition: the insertion point lies within the readable data and
559        // the resulting buffer stays within capacity (guaranteed above).
560        debug_assert!(
561            start <= data_before,
562            "insert_slice start ({start}) must lie within available data ({data_before})"
563        );
564
565        // SAFETY: both `ptr::copy` calls touch `self.inner.extra` (same
566        // allocation) or copy from `data` into it. The early-return checks
567        // bound `start <= available_data` and the resulting tail
568        // `position + end + data_len <= capacity`, and each slice indexing
569        // site is bounds-checked. `ptr::copy` is overlap-safe.
570        unsafe {
571            let begin = self.inner.position + start;
572            let slice_end = begin + data_len;
573            ptr::copy(
574                self.inner.extra()[start..self.inner.end].as_ptr(),
575                self.inner.extra_mut()[start + data_len..].as_mut_ptr(),
576                self.inner.end - start,
577            );
578            ptr::copy(
579                data.as_ptr(),
580                self.inner.extra_mut()[begin..slice_end].as_mut_ptr(),
581                data_len,
582            );
583            self.inner.end += data_len;
584        }
585        // Post-condition: inserting `data_len` bytes grows the readable window
586        // by exactly `data_len`.
587        debug_assert_eq!(
588            self.available_data(),
589            data_before + data_len,
590            "insert_slice must grow available data by exactly the inserted length"
591        );
592        #[cfg(debug_assertions)]
593        self.check_invariants();
594        Some(self.available_data())
595    }
596}
597
598impl Write for Checkout {
599    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
600        match self.space().write(buf) {
601            Ok(size) => {
602                self.fill(size);
603                Ok(size)
604            }
605            err => err,
606        }
607    }
608
609    fn flush(&mut self) -> io::Result<()> {
610        Ok(())
611    }
612}
613
614impl Read for Checkout {
615    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
616        let len = cmp::min(self.available_data(), buf.len());
617        // SAFETY: `len = min(available_data, buf.len())`, so the source
618        // range `position..position+len` lies inside `self.inner.extra` and
619        // the destination `buf[..len]` fits the caller's `&mut [u8]`. The
620        // two slices are in different allocations; `ptr::copy` is
621        // overlap-safe regardless.
622        unsafe {
623            ptr::copy(
624                self.inner.extra()[self.inner.position..self.inner.position + len].as_ptr(),
625                buf.as_mut_ptr(),
626                len,
627            );
628            self.inner.position += len;
629        }
630        Ok(len)
631    }
632}
633
634#[cfg(test)]
635mod tests {
636    use super::*;
637    use std::io::{Read, Write};
638
639    /// Helper: create a Pool and return it directly.
640    fn create_test_pool(buffer_size: usize, max_count: usize) -> Pool {
641        Pool::with_capacity(max_count, max_count, buffer_size)
642    }
643
644    /// Helper: checkout a buffer and write initial content into it.
645    fn checkout_with_data(pool: &mut Pool, data: &[u8]) -> Checkout {
646        let mut buf = pool.checkout().expect("checkout should succeed");
647        let n = buf.write(data).expect("write should succeed");
648        assert_eq!(n, data.len(), "all bytes should be written");
649        buf
650    }
651
652    // -----------------------------------------------------------------------
653    // Pool checkout / checkin lifecycle
654    // -----------------------------------------------------------------------
655
656    #[test]
657    fn test_pool_checkout_returns_buffer() {
658        let mut pool = create_test_pool(1024, 2);
659        let buf = pool.checkout();
660        assert!(
661            buf.is_some(),
662            "first checkout from a fresh pool must succeed"
663        );
664        let buf = buf.unwrap();
665        assert_eq!(buf.capacity(), 1024);
666        assert_eq!(buf.available_data(), 0);
667        assert_eq!(buf.available_space(), 1024);
668    }
669
670    #[test]
671    fn test_pool_checkin_on_drop() {
672        let mut pool = create_test_pool(128, 1);
673        {
674            let _buf = pool.checkout().expect("checkout should succeed");
675            assert_eq!(pool.inner.used(), 1);
676        }
677        assert_eq!(pool.inner.used(), 0);
678        let buf2 = pool.checkout();
679        assert!(buf2.is_some(), "checkout after checkin should succeed");
680    }
681
682    #[test]
683    fn test_pool_auto_grow() {
684        let mut pool = Pool::with_capacity(1, 4, 256);
685        let _b1 = pool.checkout().expect("first checkout");
686        let _b2 = pool.checkout().expect("second checkout triggers growth");
687        let _b3 = pool.checkout().expect("third checkout");
688    }
689
690    // -----------------------------------------------------------------------
691    // Write / Read trait impls
692    // -----------------------------------------------------------------------
693
694    #[test]
695    fn test_checkout_write_and_read_data() {
696        let mut pool = create_test_pool(1024, 2);
697        let mut buf = pool.checkout().unwrap();
698
699        let payload = b"hello world";
700        let written = buf.write(payload).unwrap();
701        assert_eq!(written, payload.len());
702        assert_eq!(buf.available_data(), payload.len());
703        assert_eq!(buf.data(), payload);
704    }
705
706    #[test]
707    fn test_checkout_read_trait() {
708        let mut pool = create_test_pool(1024, 2);
709        let mut buf = checkout_with_data(&mut pool, b"hello");
710
711        let mut out = [0u8; 5];
712        let n = buf.read(&mut out).unwrap();
713        assert_eq!(n, 5);
714        assert_eq!(&out, b"hello");
715    }
716
717    #[test]
718    fn test_consume_and_fill() {
719        let mut pool = create_test_pool(1024, 2);
720        let mut buf = checkout_with_data(&mut pool, b"abcdefghij");
721
722        let consumed = buf.consume(3);
723        assert_eq!(consumed, 3);
724        assert_eq!(buf.data(), b"defghij");
725        assert_eq!(buf.available_data(), 7);
726
727        let filled = buf.fill(0);
728        assert_eq!(filled, 0);
729    }
730
731    #[test]
732    fn test_empty() {
733        let mut pool = create_test_pool(64, 2);
734        let buf = pool.checkout().unwrap();
735        assert!(buf.empty(), "freshly checked-out buffer should be empty");
736    }
737
738    #[test]
739    fn test_reset() {
740        let mut pool = create_test_pool(1024, 2);
741        let mut buf = checkout_with_data(&mut pool, b"data");
742        assert!(!buf.empty());
743
744        buf.reset();
745        assert!(buf.empty());
746        assert_eq!(buf.available_data(), 0);
747    }
748
749    #[test]
750    fn test_sync() {
751        let mut pool = create_test_pool(1024, 2);
752        let mut buf = checkout_with_data(&mut pool, b"hello world");
753        buf.sync(5, 2);
754        assert_eq!(buf.available_data(), 3);
755    }
756
757    // -----------------------------------------------------------------------
758    // shift() -- unsafe ptr::copy
759    // -----------------------------------------------------------------------
760
761    #[test]
762    fn test_shift_moves_data_to_start() {
763        let mut pool = create_test_pool(1024, 2);
764        let mut buf = checkout_with_data(&mut pool, b"hello world");
765
766        buf.inner.position = 5;
767        assert_eq!(buf.data(), b" world");
768
769        buf.shift();
770        assert_eq!(buf.inner.position, 0);
771        assert_eq!(buf.inner.end, 6);
772        assert_eq!(buf.data(), b" world");
773    }
774
775    #[test]
776    fn test_shift_noop_when_position_zero() {
777        let mut pool = create_test_pool(1024, 2);
778        let mut buf = checkout_with_data(&mut pool, b"hello");
779
780        assert_eq!(buf.inner.position, 0);
781        buf.shift();
782        assert_eq!(buf.data(), b"hello");
783        assert_eq!(buf.inner.position, 0);
784        assert_eq!(buf.inner.end, 5);
785    }
786
787    #[test]
788    fn test_consume_triggers_auto_shift() {
789        let mut pool = create_test_pool(256, 2);
790        let mut buf = pool.checkout().unwrap();
791        let capacity = buf.capacity();
792
793        let fill_count = capacity / 2 + 2;
794        let data: Vec<u8> = (0..fill_count as u8).collect();
795        let written = buf.write(&data).unwrap();
796        assert_eq!(written, fill_count);
797
798        let consume_count = capacity / 2 + 1;
799        buf.consume(consume_count);
800
801        assert_eq!(buf.inner.position, 0);
802        let remaining = fill_count - consume_count;
803        assert_eq!(buf.available_data(), remaining);
804    }
805
806    // -----------------------------------------------------------------------
807    // delete_slice() -- unsafe ptr::copy
808    // -----------------------------------------------------------------------
809
810    #[test]
811    fn test_delete_slice_middle() {
812        let mut pool = create_test_pool(1024, 2);
813        let mut buf = checkout_with_data(&mut pool, b"hello world!");
814
815        let result = buf.delete_slice(3, 5);
816        assert!(result.is_some());
817        assert_eq!(buf.data(), b"helrld!");
818    }
819
820    #[test]
821    fn test_delete_slice_from_start() {
822        let mut pool = create_test_pool(1024, 2);
823        let mut buf = checkout_with_data(&mut pool, b"hello world!");
824
825        let result = buf.delete_slice(0, 3);
826        assert!(result.is_some());
827        assert_eq!(buf.data(), b"lo world!");
828    }
829
830    #[test]
831    fn test_delete_slice_near_end() {
832        let mut pool = create_test_pool(1024, 2);
833        let mut buf = checkout_with_data(&mut pool, b"hello world!");
834
835        let result = buf.delete_slice(7, 4);
836        assert!(result.is_some());
837        assert_eq!(buf.data(), b"hello w!");
838    }
839
840    #[test]
841    fn test_delete_slice_out_of_bounds_returns_none() {
842        let mut pool = create_test_pool(1024, 2);
843        let mut buf = checkout_with_data(&mut pool, b"hello");
844
845        let result = buf.delete_slice(0, 5);
846        assert!(result.is_none());
847
848        let result = buf.delete_slice(3, 5);
849        assert!(result.is_none());
850    }
851
852    #[test]
853    fn test_delete_slice_single_byte() {
854        let mut pool = create_test_pool(1024, 2);
855        let mut buf = checkout_with_data(&mut pool, b"abcd");
856
857        let result = buf.delete_slice(1, 1);
858        assert!(result.is_some());
859        assert_eq!(buf.data(), b"acd");
860    }
861
862    // -----------------------------------------------------------------------
863    // replace_slice() -- unsafe ptr::copy
864    // -----------------------------------------------------------------------
865
866    #[test]
867    fn test_replace_slice_same_size() {
868        let mut pool = create_test_pool(1024, 2);
869        let mut buf = checkout_with_data(&mut pool, b"hello world");
870
871        let result = buf.replace_slice(b"earth", 6, 5);
872        assert!(result.is_some());
873        assert_eq!(buf.data(), b"hello earth");
874    }
875
876    #[test]
877    fn test_replace_slice_shrink() {
878        let mut pool = create_test_pool(1024, 2);
879        let mut buf = checkout_with_data(&mut pool, b"hello world");
880
881        let result = buf.replace_slice(b"hi", 6, 5);
882        assert!(result.is_some());
883        assert_eq!(buf.data(), b"hello hi");
884    }
885
886    #[test]
887    fn test_replace_slice_grow() {
888        let mut pool = create_test_pool(1024, 2);
889        let mut buf = checkout_with_data(&mut pool, b"hello world");
890
891        let result = buf.replace_slice(b"universe", 6, 5);
892        assert!(result.is_some());
893        assert_eq!(buf.data(), b"hello universe");
894    }
895
896    #[test]
897    fn test_replace_slice_at_start() {
898        let mut pool = create_test_pool(1024, 2);
899        let mut buf = checkout_with_data(&mut pool, b"hello world");
900
901        let result = buf.replace_slice(b"hey", 0, 5);
902        assert!(result.is_some());
903        assert_eq!(buf.data(), b"hey world");
904    }
905
906    #[test]
907    fn test_replace_slice_out_of_bounds_returns_none() {
908        let mut pool = create_test_pool(1024, 2);
909        let mut buf = checkout_with_data(&mut pool, b"hello");
910
911        let result = buf.replace_slice(b"x", 4, 5);
912        assert!(result.is_none());
913    }
914
915    #[test]
916    fn test_replace_slice_exceeds_data_bounds_returns_none() {
917        let mut pool = create_test_pool(256, 2);
918        let mut buf = checkout_with_data(&mut pool, b"hello");
919
920        let result = buf.replace_slice(b"xyz", 3, 5);
921        assert!(result.is_none());
922    }
923
924    #[test]
925    fn test_replace_slice_replacement_exceeds_capacity_returns_none() {
926        let mut pool = create_test_pool(256, 2);
927        let mut buf = pool.checkout().unwrap();
928        let capacity = buf.capacity();
929
930        let data = vec![b'x'; capacity];
931        let written = buf.write(&data).unwrap();
932        assert_eq!(written, capacity);
933
934        buf.inner.position = capacity - 2;
935        assert_eq!(buf.available_data(), 2);
936
937        // position + start + data_len = (capacity-2) + 0 + 5 = capacity+3 > capacity
938        let result = buf.replace_slice(b"abcde", 0, 1);
939        assert!(result.is_none());
940    }
941
942    // -----------------------------------------------------------------------
943    // insert_slice() -- unsafe ptr::copy
944    // -----------------------------------------------------------------------
945
946    #[test]
947    fn test_insert_slice_at_start() {
948        let mut pool = create_test_pool(1024, 2);
949        let mut buf = checkout_with_data(&mut pool, b"world");
950
951        let result = buf.insert_slice(b"hello ", 0);
952        assert!(result.is_some());
953        assert_eq!(buf.data(), b"hello world");
954    }
955
956    #[test]
957    fn test_insert_slice_in_middle() {
958        let mut pool = create_test_pool(1024, 2);
959        let mut buf = checkout_with_data(&mut pool, b"helo");
960
961        let result = buf.insert_slice(b"l", 2);
962        assert!(result.is_some());
963        assert_eq!(buf.data(), b"hello");
964    }
965
966    #[test]
967    fn test_insert_slice_at_end() {
968        let mut pool = create_test_pool(1024, 2);
969        let mut buf = checkout_with_data(&mut pool, b"hello");
970
971        let result = buf.insert_slice(b" world", 5);
972        assert!(result.is_some());
973        assert_eq!(buf.data(), b"hello world");
974    }
975
976    #[test]
977    fn test_insert_slice_exceeds_capacity_returns_none() {
978        let mut pool = create_test_pool(256, 2);
979        let mut buf = pool.checkout().unwrap();
980        let capacity = buf.capacity();
981
982        let data = vec![b'x'; capacity];
983        let written = buf.write(&data).unwrap();
984        assert_eq!(written, capacity);
985        assert_eq!(buf.available_space(), 0);
986
987        let result = buf.insert_slice(b"y", 0);
988        assert!(result.is_none());
989    }
990
991    #[test]
992    fn test_insert_slice_beyond_data_returns_none() {
993        let mut pool = create_test_pool(1024, 2);
994        let mut buf = checkout_with_data(&mut pool, b"hello");
995
996        let result = buf.insert_slice(b"x", 6);
997        assert!(result.is_none());
998    }
999
1000    // -----------------------------------------------------------------------
1001    // Combined operations -- exercise multiple unsafe paths together
1002    // -----------------------------------------------------------------------
1003
1004    #[test]
1005    fn test_write_consume_shift_write_again() {
1006        let mut pool = create_test_pool(32, 2);
1007        let mut buf = checkout_with_data(&mut pool, b"first");
1008
1009        buf.consume(5);
1010        assert_eq!(buf.available_data(), 0);
1011
1012        let n = buf.write(b"second").unwrap();
1013        assert_eq!(n, 6);
1014        assert_eq!(buf.data(), b"second");
1015    }
1016
1017    #[test]
1018    fn test_delete_then_insert() {
1019        let mut pool = create_test_pool(1024, 2);
1020        let mut buf = checkout_with_data(&mut pool, b"hello cruel world");
1021
1022        buf.delete_slice(6, 6);
1023        assert_eq!(buf.data(), b"hello world");
1024
1025        buf.insert_slice(b"beautiful ", 6);
1026        assert_eq!(buf.data(), b"hello beautiful world");
1027    }
1028
1029    #[test]
1030    fn test_multiple_replace_operations() {
1031        let mut pool = create_test_pool(1024, 2);
1032        let mut buf = checkout_with_data(&mut pool, b"aXbXc");
1033
1034        buf.replace_slice(b"12", 1, 1);
1035        assert_eq!(buf.data(), b"a12bXc");
1036
1037        buf.replace_slice(b"34", 4, 1);
1038        assert_eq!(buf.data(), b"a12b34c");
1039    }
1040}