Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for custom allocators/ref counting #269

Open
mzabaluev opened this issue Jun 25, 2019 · 1 comment

Comments

@mzabaluev
Copy link

commented Jun 25, 2019

Allocation and reference counting of the buffers could be abstracted behind a generic type parameter. This can enable:

  • Lightweight non-atomic reference counting for single-threaded use (raised previously as #200).
  • Fast task-bound, non-global allocators.
  • Use in no_std configurations with a custom allocator.

Here's a sketch of a possible abstraction API:

use core::alloc::AllocErr;
use core::ptr::NonNull;
#[cfg(feature = "system_alloc")]
use std::alloc::System as SystemAlloc;

/// Represents results of an allocation: a handle that represents
/// the allocated block and is used to keep track of
/// the references, and the buffer's total capacity (which may be larger
/// than requested).
pub struct BufAllocation<H>(pub H, pub usize);

/// Handle to a shared buffer descriptor.
///
/// A type implementing this trait is an opaque handle that provides
/// low-level access to a shared data buffer and is used by higher-level
/// container implementations to keep track of references to the buffer.
/// The implementing type should also implement `Clone` by producing another
/// handle referring to the same buffer.
///
/// Neither the handle, nor a shared descriptor structure that it possibly
/// points to, own buffer's data. If the handle is dropped without the
/// `release` method called on it first, the implementation will leak the data,
/// the reference, or both.
///
/// The implementation type can exhibit specific thread safety
/// characteristics with regard to `Send` and `Sync`.
/// For global thread-safe allocators, it can be modeled as an `Arc`
/// pointing to a structure with the data pointer and the buffer's
/// allocated size.
///
/// It is up to the implementation whether to allocate the descriptor
/// and the data block in a single contiguous allocation or separately.
/// For arena-like allocators that never free individual allocations,
/// `BufHandle` could contain just the data pointer and a pointer to the
/// shared arena state.
///
pub trait BufHandle: Sized {
    fn ptr(&self) -> NonNull<u8>;

    /// Release the reference on the buffer represented by `self`
    /// and deallocate buffer data if the reference was unique.
    /// The typical place for calling this method is the `Drop`
    /// implementation of a type containing the handle referenced by `self`.
    ///
    /// # Safety
    ///
    /// The only safe way to use this function is to call it exactly once
    /// per the lifetime of the handle, without calling any other methods
    /// afterwards before the handle is dropped. The caller also has to
    /// ensure that no use of the allocated buffer data occurs after the
    /// last reference to it is released.
    ///
    unsafe fn release(&mut self);

    unsafe fn make_unique(
        &mut self,
        data_ptr: *const u8,
        data_len: usize,
        new_capacity: usize,
    ) -> Result<BufAllocation<Self>, AllocErr>;
}

pub trait AllocBuf {
    type Handle: BufHandle;

    fn alloc_buf(
        &mut self,
        capacity: usize,
    ) -> Result<BufAllocation<Self::Handle>, AllocErr>;
}

#[cfg(feature = "system_alloc")]
impl AllocBuf for SystemAlloc {
    ...
}

Bytes and BytesMut could then be parameterized with a BufHandle implementation in a backward-compatible way:

pub struct BytesMut<H = ArcBuf<SystemAlloc>> {
    inner: InnerMut<H>,
}

impl BytesMut<ArcBuf<SystemAlloc>> {
    pub fn with_capacity(capacity: usize) -> Self {
        BytesMut::with_capacity_in(SystemAlloc, capacity)
    }
}

impl<H: BufHandle> BytesMut<H> {
    pub fn with_capacity_in<A>(mut alloc: A, capacity: usize) -> Self
    where
        A: AllocBuf<Handle = H>,
    {
        let BufAllocation(buf, cap) =
            alloc.alloc_buf(capacity).unwrap_or_else(|_| {
                alloc::handle_alloc_error(
                    Layout::from_size_align(capacity, 1).unwrap(),
                )
            });
        let ptr = buf.ptr();
        BytesMut {
            inner: InnerMut::new(buf, ptr, 0, cap)
        }
    }
}

#[derive(Clone)]
pub struct Bytes<H = ArcBuf<SystemAlloc>> {
    buf: H,
    ptr: *const u8,
    len: usize,
}

Thread safety shall then depend on the choice of the allocator:

unsafe impl<H: Send> Send for Bytes<H> {}
unsafe impl<H: Sync> Sync for Bytes<H> {}
@mzabaluev

This comment has been minimized.

Copy link
Author

commented Jun 28, 2019

I have made the PoC API simpler and more flexible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.