Skip to content

Commit 61a0301

Browse files
Add BytesMut::try_reserve()
1 parent 16132ad commit 61a0301

File tree

4 files changed

+200
-14
lines changed

4 files changed

+200
-14
lines changed

src/bytes_mut.rs

Lines changed: 110 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ use crate::bytes::Vtable;
1616
#[allow(unused)]
1717
use crate::loom::sync::atomic::AtomicMut;
1818
use crate::loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
19+
use crate::try_alloc::{
20+
AllocStrategy, FallibleAllocStrategy, PanickingAllocStrategy, TryReserveError,
21+
};
1922
use crate::{Buf, BufMut, Bytes, TryGetError};
2023

2124
/// A unique reference to a contiguous slice of memory.
@@ -599,12 +602,99 @@ impl BytesMut {
599602
}
600603

601604
// will always succeed
602-
let _ = self.reserve_inner(additional, true);
605+
let _ = self.reserve_inner(additional, true, PanickingAllocStrategy);
606+
}
607+
608+
/// Reserves capacity for at least `additional` more bytes to be inserted
609+
/// into the given `BytesMut`.
610+
///
611+
/// More than `additional` bytes may be reserved in order to avoid frequent
612+
/// reallocations. A call to `reserve` may result in an allocation.
613+
///
614+
/// Before allocating new buffer space, the function will attempt to reclaim
615+
/// space in the existing buffer. If the current handle references a view
616+
/// into a larger original buffer, and all other handles referencing part
617+
/// of the same original buffer have been dropped, then the current view
618+
/// can be copied/shifted to the front of the buffer and the handle can take
619+
/// ownership of the full buffer, provided that the full buffer is large
620+
/// enough to fit the requested additional capacity.
621+
///
622+
/// This optimization will only happen if shifting the data from the current
623+
/// view to the front of the buffer is not too expensive in terms of the
624+
/// (amortized) time required. The precise condition is subject to change;
625+
/// as of now, the length of the data being shifted needs to be at least as
626+
/// large as the distance that it's shifted by. If the current view is empty
627+
/// and the original buffer is large enough to fit the requested additional
628+
/// capacity, then reallocations will never happen.
629+
///
630+
/// # Examples
631+
///
632+
/// In the following example, a new buffer is allocated.
633+
///
634+
/// ```
635+
/// # fn main() -> Result<(), bytes::TryReserveError> {
636+
/// use bytes::BytesMut;
637+
///
638+
/// let mut buf = BytesMut::from(&b"hello"[..]);
639+
/// buf.try_reserve(64)?;
640+
/// assert!(buf.capacity() >= 69);
641+
/// # Ok(())
642+
/// # }
643+
/// ```
644+
///
645+
/// In the following example, the existing buffer is reclaimed.
646+
///
647+
/// ```
648+
/// # fn main() -> Result<(), bytes::TryReserveError> {
649+
/// use bytes::{BytesMut, BufMut};
650+
///
651+
/// let mut buf = BytesMut::with_capacity(128);
652+
/// buf.put(&[0; 64][..]);
653+
///
654+
/// let ptr = buf.as_ptr();
655+
/// let other = buf.split();
656+
///
657+
/// assert!(buf.is_empty());
658+
/// assert_eq!(buf.capacity(), 64);
659+
///
660+
/// drop(other);
661+
/// buf.try_reserve(128)?;
662+
///
663+
/// assert_eq!(buf.capacity(), 128);
664+
/// assert_eq!(buf.as_ptr(), ptr);
665+
/// # Ok(())
666+
/// # }
667+
/// ```
668+
///
669+
/// # Errors
670+
///
671+
/// Errors if the new capacity overflows `usize` or the underlying allocator fails.
672+
#[inline]
673+
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
674+
let len = self.len();
675+
let rem = self.capacity() - len;
676+
677+
if additional <= rem {
678+
// The handle can already store at least `additional` more bytes, so
679+
// there is no further work needed to be done.
680+
return Ok(());
681+
}
682+
683+
self.reserve_inner(additional, true, FallibleAllocStrategy)
684+
.map(|_| ())
603685
}
604686

605687
// In separate function to allow the short-circuits in `reserve` and `try_reclaim` to
606688
// be inline-able. Significantly helps performance. Returns false if it did not succeed.
607-
fn reserve_inner(&mut self, additional: usize, allocate: bool) -> bool {
689+
fn reserve_inner<S>(
690+
&mut self,
691+
additional: usize,
692+
allocate: bool,
693+
strategy: S,
694+
) -> Result<bool, S::Err>
695+
where
696+
S: AllocStrategy,
697+
{
608698
let len = self.len();
609699
let kind = self.kind();
610700

@@ -650,21 +740,21 @@ impl BytesMut {
650740
self.cap += off;
651741
} else {
652742
if !allocate {
653-
return false;
743+
return Ok(false);
654744
}
655745
// Not enough space, or reusing might be too much overhead:
656746
// allocate more space!
657747
let mut v =
658748
ManuallyDrop::new(rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off));
659-
v.reserve(additional);
749+
strategy.fallible_reserve(&mut v, additional)?;
660750

661751
// Update the info
662752
self.ptr = vptr(v.as_mut_ptr().add(off));
663753
self.cap = v.capacity() - off;
664754
debug_assert_eq!(self.len, v.len() - off);
665755
}
666756

667-
return true;
757+
return Ok(true);
668758
}
669759
}
670760

@@ -677,8 +767,8 @@ impl BytesMut {
677767
// Compute the new capacity
678768
let mut new_cap = match len.checked_add(additional) {
679769
Some(new_cap) => new_cap,
680-
None if !allocate => return false,
681-
None => panic!("overflow"),
770+
None if !allocate => return Ok(false),
771+
None => return Err(strategy.capacity_overflow()),
682772
};
683773

684774
unsafe {
@@ -711,7 +801,7 @@ impl BytesMut {
711801
self.cap = v.capacity();
712802
} else {
713803
if !allocate {
714-
return false;
804+
return Ok(false);
715805
}
716806
// calculate offset
717807
let off = (self.ptr.as_ptr() as usize) - (v.as_ptr() as usize);
@@ -720,7 +810,9 @@ impl BytesMut {
720810
// `Vec`, so it does not take the offset into account.
721811
//
722812
// Thus we have to manually add it here.
723-
new_cap = new_cap.checked_add(off).expect("overflow");
813+
new_cap = new_cap
814+
.checked_add(off)
815+
.ok_or_else(|| strategy.capacity_overflow())?;
724816

725817
// The vector capacity is not sufficient. The reserve request is
726818
// asking for more than the initial buffer capacity. Allocate more
@@ -744,18 +836,19 @@ impl BytesMut {
744836
// care about in the unused capacity before calling `reserve`.
745837
debug_assert!(off + len <= v.capacity());
746838
v.set_len(off + len);
747-
v.reserve(new_cap - v.len());
839+
let additional = new_cap - v.len();
840+
strategy.fallible_reserve(v, additional)?;
748841

749842
// Update the info
750843
self.ptr = vptr(v.as_mut_ptr().add(off));
751844
self.cap = v.capacity() - off;
752845
}
753846

754-
return true;
847+
return Ok(true);
755848
}
756849
}
757850
if !allocate {
758-
return false;
851+
return Ok(false);
759852
}
760853

761854
let original_capacity_repr = unsafe { (*shared).original_capacity_repr };
@@ -779,7 +872,7 @@ impl BytesMut {
779872
self.ptr = vptr(v.as_mut_ptr());
780873
self.cap = v.capacity();
781874
debug_assert_eq!(self.len, v.len());
782-
true
875+
Ok(true)
783876
}
784877

785878
/// Attempts to cheaply reclaim already allocated capacity for at least `additional` more
@@ -839,7 +932,10 @@ impl BytesMut {
839932
return true;
840933
}
841934

842-
self.reserve_inner(additional, false)
935+
match self.reserve_inner(additional, false, PanickingAllocStrategy) {
936+
Ok(reclaimed) => reclaimed,
937+
Err(never) => match never {},
938+
}
843939
}
844940

845941
/// Appends given bytes to this `BytesMut`.

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ mod bytes;
8383
mod bytes_mut;
8484
mod fmt;
8585
mod loom;
86+
mod try_alloc;
8687
pub use crate::bytes::Bytes;
8788
pub use crate::bytes_mut::BytesMut;
89+
pub use crate::try_alloc::TryReserveError;
8890

8991
// Optional Serde support
9092
#[cfg(feature = "serde")]

src/try_alloc.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use alloc::vec::Vec;
2+
use core::{
3+
convert::Infallible,
4+
fmt::{self, Display},
5+
};
6+
#[cfg(feature = "std")]
7+
use std::error::Error;
8+
9+
/// The error type for try_reserve methods.
10+
#[derive(Debug)]
11+
pub struct TryReserveError(TryReserveErrorInner);
12+
13+
#[derive(Debug)]
14+
pub(crate) enum TryReserveErrorInner {
15+
Std(alloc::collections::TryReserveError),
16+
Overflow,
17+
}
18+
19+
impl Display for TryReserveError {
20+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21+
match &self.0 {
22+
TryReserveErrorInner::Std(err) => Display::fmt(err, f),
23+
TryReserveErrorInner::Overflow => f.write_str("memory allocation failed because the computed capacity exceeded the collection's maximum"),
24+
}
25+
}
26+
}
27+
28+
#[cfg(feature = "std")]
29+
impl Error for TryReserveError {}
30+
31+
/// The allocation strategy
32+
///
33+
/// # Safety
34+
///
35+
/// `fallible_reserve` must behave the same as `Vec::reserve` or
36+
/// `Vec::try_reserve`.
37+
pub(crate) unsafe trait AllocStrategy {
38+
type Err;
39+
40+
fn fallible_reserve<T>(&self, vec: &mut Vec<T>, additional: usize) -> Result<(), Self::Err>;
41+
42+
#[track_caller]
43+
fn capacity_overflow(&self) -> Self::Err;
44+
}
45+
46+
pub(crate) struct PanickingAllocStrategy;
47+
48+
unsafe impl AllocStrategy for PanickingAllocStrategy {
49+
type Err = Infallible;
50+
51+
fn fallible_reserve<T>(&self, vec: &mut Vec<T>, additional: usize) -> Result<(), Self::Err> {
52+
vec.reserve(additional);
53+
Ok(())
54+
}
55+
56+
#[track_caller]
57+
fn capacity_overflow(&self) -> Self::Err {
58+
panic!("overflow")
59+
}
60+
}
61+
62+
pub(crate) struct FallibleAllocStrategy;
63+
64+
unsafe impl AllocStrategy for FallibleAllocStrategy {
65+
type Err = TryReserveError;
66+
67+
fn fallible_reserve<T>(&self, vec: &mut Vec<T>, additional: usize) -> Result<(), Self::Err> {
68+
vec.try_reserve(additional)
69+
.map_err(|err| TryReserveError(TryReserveErrorInner::Std(err)))
70+
}
71+
72+
#[track_caller]
73+
fn capacity_overflow(&self) -> Self::Err {
74+
TryReserveError(TryReserveErrorInner::Overflow)
75+
}
76+
}

tests/test_bytes.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,18 @@ fn bytes_reserve_overflow() {
10281028
bytes.reserve(usize::MAX);
10291029
}
10301030

1031+
#[test]
1032+
fn bytes_try_reserve_overflow() {
1033+
let mut bytes = BytesMut::with_capacity(1024);
1034+
bytes.put_slice(b"hello world");
1035+
1036+
let err = bytes.try_reserve(usize::MAX).unwrap_err();
1037+
assert_eq!(
1038+
"memory allocation failed because the computed capacity exceeded the collection's maximum",
1039+
err.to_string()
1040+
);
1041+
}
1042+
10311043
#[test]
10321044
fn bytes_with_capacity_but_empty() {
10331045
// See https://github.com/tokio-rs/bytes/issues/340

0 commit comments

Comments
 (0)