arrow_buffer/bytes.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! This module contains an implementation of a contiguous immutable memory region that knows
19//! how to de-allocate itself, [`Bytes`].
20//! Note that this is a low-level functionality of this crate.
21
22use core::slice;
23use std::ptr::NonNull;
24use std::{fmt::Debug, fmt::Formatter};
25
26use crate::alloc::Deallocation;
27use crate::buffer::dangling_ptr;
28#[cfg(feature = "pool")]
29use crate::{MemoryPool, TrackedReservation};
30
31/// A continuous, fixed-size, immutable memory region that knows how to de-allocate itself.
32///
33/// Note that this structure is an internal implementation detail of the
34/// arrow-rs crate. While it has the same name and similar API as
35/// [`bytes::Bytes`] it is not limited to rust's global allocator nor u8
36/// alignment. It is possible to create a `Bytes` from `bytes::Bytes` using the
37/// `From` implementation.
38///
39/// In the most common case, this buffer is allocated using [`alloc`](std::alloc::alloc)
40/// with an alignment of [`ALIGNMENT`](crate::alloc::ALIGNMENT)
41///
42/// When the region is allocated by a different allocator, [Deallocation::Custom], this calls the
43/// custom deallocator to deallocate the region when it is no longer needed.
44///
45pub(crate) struct Bytes {
46 /// The raw pointer to be beginning of the region
47 ptr: NonNull<u8>,
48
49 /// The number of bytes visible to this region. This is always smaller than its capacity (when available).
50 len: usize,
51
52 /// how to deallocate this region
53 deallocation: Deallocation,
54
55 /// Memory reservation for tracking memory usage
56 #[cfg(feature = "pool")]
57 pub(super) reservation: TrackedReservation,
58}
59
60impl Bytes {
61 /// Takes ownership of an allocated memory region,
62 ///
63 /// # Arguments
64 ///
65 /// * `ptr` - Pointer to raw parts
66 /// * `len` - Length of raw parts in **bytes**
67 /// * `deallocation` - Type of allocation
68 ///
69 /// # Safety
70 ///
71 /// This function is unsafe as there is no guarantee that the given pointer is valid for `len`
72 /// bytes. If the `ptr` and `capacity` come from a `Buffer`, then this is guaranteed.
73 #[inline]
74 pub(crate) unsafe fn new(ptr: NonNull<u8>, len: usize, deallocation: Deallocation) -> Bytes {
75 Bytes {
76 ptr,
77 len,
78 deallocation,
79 #[cfg(feature = "pool")]
80 reservation: TrackedReservation::default(),
81 }
82 }
83
84 fn as_slice(&self) -> &[u8] {
85 self
86 }
87
88 #[inline]
89 pub(crate) fn len(&self) -> usize {
90 self.len
91 }
92
93 #[inline]
94 pub(crate) fn ptr(&self) -> NonNull<u8> {
95 self.ptr
96 }
97
98 pub(crate) fn capacity(&self) -> usize {
99 match self.deallocation {
100 Deallocation::Standard(layout) => layout.size(),
101 // we only know the size of the custom allocation
102 // its underlying capacity might be larger
103 Deallocation::Custom(_, size) => size,
104 }
105 }
106
107 /// Register this [`Bytes`] with the provided [`MemoryPool`], replacing any prior reservation.
108 #[cfg(feature = "pool")]
109 pub(crate) fn claim(&self, pool: &dyn MemoryPool) {
110 self.reservation.claim(pool, self.capacity());
111 }
112
113 /// Resize the memory reservation of this buffer
114 ///
115 /// This is a no-op if this buffer doesn't have a reservation.
116 #[cfg(feature = "pool")]
117 fn resize_reservation(&self, new_size: usize) {
118 self.reservation.resize(new_size);
119 }
120
121 /// Try to reallocate the underlying memory region to a new size (smaller or larger).
122 ///
123 /// Only works for bytes allocated with the standard allocator.
124 /// Returns `Err` if the memory was allocated with a custom allocator,
125 /// or the call to `realloc` failed, for whatever reason.
126 /// In case of `Err`, the [`Bytes`] will remain as it was (i.e. have the old size).
127 ///
128 /// `on_reallocated` is called after [`Bytes`] has updated its internal
129 /// pointer, but before resizing the memory reservation, which may call user
130 /// code.
131 pub(crate) fn try_realloc(
132 &mut self,
133 new_len: usize,
134 on_reallocated: impl FnOnce(NonNull<u8>),
135 ) -> Result<(), ()> {
136 if let Deallocation::Standard(old_layout) = self.deallocation {
137 if old_layout.size() == new_len {
138 return Ok(()); // Nothing to do
139 }
140
141 if let Ok(new_layout) = std::alloc::Layout::from_size_align(new_len, old_layout.align())
142 {
143 let old_ptr = self.ptr.as_ptr();
144
145 let new_ptr = match new_layout.size() {
146 0 => {
147 // SAFETY: Verified that old_layout.size != new_len (0)
148 unsafe { std::alloc::dealloc(self.ptr.as_ptr(), old_layout) };
149 Some(dangling_ptr())
150 }
151 // SAFETY: the call to `realloc` is safe if all the following hold (from https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html#method.realloc):
152 // * `old_ptr` must be currently allocated via this allocator (guaranteed by the invariant/contract of `Bytes`)
153 // * `old_layout` must be the same layout that was used to allocate that block of memory (same)
154 // * `new_len` must be greater than zero
155 // * `new_len`, when rounded up to the nearest multiple of `layout.align()`, must not overflow `isize` (guaranteed by the success of `Layout::from_size_align`)
156 _ => NonNull::new(unsafe { std::alloc::realloc(old_ptr, old_layout, new_len) }),
157 };
158
159 if let Some(ptr) = new_ptr {
160 self.ptr = ptr;
161 self.len = new_len;
162 self.deallocation = Deallocation::Standard(new_layout);
163 on_reallocated(ptr);
164
165 #[cfg(feature = "pool")]
166 {
167 // Resize reservation
168 self.resize_reservation(new_len);
169 }
170
171 return Ok(());
172 }
173 }
174 }
175
176 Err(())
177 }
178
179 #[inline]
180 pub(crate) fn deallocation(&self) -> &Deallocation {
181 &self.deallocation
182 }
183}
184
185// Deallocation is Send + Sync, repeating the bound here makes that refactoring safe
186// The only field that is not automatically Send+Sync then is the NonNull ptr
187unsafe impl Send for Bytes where Deallocation: Send {}
188unsafe impl Sync for Bytes where Deallocation: Sync {}
189
190impl Drop for Bytes {
191 #[inline]
192 fn drop(&mut self) {
193 match &self.deallocation {
194 Deallocation::Standard(layout) => match layout.size() {
195 0 => {} // Nothing to do
196 _ => unsafe { std::alloc::dealloc(self.ptr.as_ptr(), *layout) },
197 },
198 // The automatic drop implementation will free the memory once the reference count reaches zero
199 Deallocation::Custom(_allocation, _size) => (),
200 }
201 }
202}
203
204impl std::ops::Deref for Bytes {
205 type Target = [u8];
206
207 fn deref(&self) -> &[u8] {
208 unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) }
209 }
210}
211
212impl PartialEq for Bytes {
213 fn eq(&self, other: &Bytes) -> bool {
214 self.as_slice() == other.as_slice()
215 }
216}
217
218impl Debug for Bytes {
219 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
220 write!(f, "Bytes {{ ptr: {:?}, len: {}, data: ", self.ptr, self.len)?;
221
222 f.debug_list().entries(self.iter()).finish()?;
223
224 write!(f, " }}")
225 }
226}
227
228impl From<bytes::Bytes> for Bytes {
229 fn from(value: bytes::Bytes) -> Self {
230 let len = value.len();
231 Self {
232 len,
233 // `bytes::Bytes` is shared and immutable, so the buffer is never written
234 // through this pointer; the cast only changes constness.
235 ptr: NonNull::new(value.as_ptr().cast_mut()).unwrap(),
236 deallocation: Deallocation::Custom(std::sync::Arc::new(value), len),
237 #[cfg(feature = "pool")]
238 reservation: TrackedReservation::default(),
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_from_bytes() {
249 let message = b"hello arrow";
250
251 // we can create a Bytes from bytes::Bytes (created from slices)
252 let c_bytes: bytes::Bytes = message.as_ref().into();
253 let a_bytes: Bytes = c_bytes.into();
254 assert_eq!(a_bytes.as_slice(), message);
255
256 // we can create a Bytes from bytes::Bytes (created from Vec)
257 let c_bytes: bytes::Bytes = bytes::Bytes::from(message.to_vec());
258 let a_bytes: Bytes = c_bytes.into();
259 assert_eq!(a_bytes.as_slice(), message);
260 }
261
262 #[cfg(feature = "pool")]
263 mod pool_tests {
264 use super::*;
265
266 use crate::pool::TrackingMemoryPool;
267
268 #[test]
269 fn test_bytes_with_pool() {
270 // Create a standard allocation
271 let buffer = unsafe {
272 let layout =
273 std::alloc::Layout::from_size_align(1024, crate::alloc::ALIGNMENT).unwrap();
274 let ptr = std::alloc::alloc(layout);
275 assert!(!ptr.is_null());
276
277 Bytes::new(
278 NonNull::new(ptr).unwrap(),
279 1024,
280 Deallocation::Standard(layout),
281 )
282 };
283
284 // Create a memory pool
285 let pool = TrackingMemoryPool::default();
286 assert_eq!(pool.used(), 0);
287
288 // Reserve memory and assign to buffer. Claim twice.
289 buffer.claim(&pool);
290 assert_eq!(pool.used(), 1024);
291 buffer.claim(&pool);
292 assert_eq!(pool.used(), 1024);
293
294 // Memory should be released when buffer is dropped
295 drop(buffer);
296 assert_eq!(pool.used(), 0);
297 }
298
299 #[test]
300 fn test_bytes_drop_releases_pool() {
301 let pool = TrackingMemoryPool::default();
302
303 {
304 // Create a buffer with pool
305 let _buffer = unsafe {
306 let layout =
307 std::alloc::Layout::from_size_align(1024, crate::alloc::ALIGNMENT).unwrap();
308 let ptr = std::alloc::alloc(layout);
309 assert!(!ptr.is_null());
310
311 let bytes = Bytes::new(
312 NonNull::new(ptr).unwrap(),
313 1024,
314 Deallocation::Standard(layout),
315 );
316
317 bytes.claim(&pool);
318 bytes
319 };
320
321 assert_eq!(pool.used(), 1024);
322 }
323
324 // Buffer has been dropped, memory should be released
325 assert_eq!(pool.used(), 0);
326 }
327 }
328}