#[cfg(ring)]
pub(crate) mod spare;
use core::cell::{Cell, RefCell};
use core::ptr::NonNull;
use core::task::Waker;
use std::alloc::{Layout, alloc_zeroed, dealloc};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering, fence};
use kovan_queue::seg_queue::SegQueue;
pub(crate) const CLASSES: [usize; 4] = [512, 4 * 1024, 16 * 1024, 64 * 1024];
pub(crate) const LARGEST: usize = CLASSES[CLASSES.len() - 1];
const WHOLE: u8 = u8::MAX;
pub(crate) const DEFAULT_CAP: usize = 64 << 20;
fn class_for(len: usize) -> usize {
CLASSES
.iter()
.position(|&size| size >= len)
.unwrap_or(CLASSES.len() - 1)
}
fn layout(class: usize) -> Layout {
let size = CLASSES[class];
let align = if size >= 4096 { 4096 } else { 64 };
unsafe { Layout::from_size_align_unchecked(size, align) }
}
fn layout_whole(size: usize) -> Layout {
unsafe { Layout::from_size_align_unchecked(size, 4096) }
}
struct Raw(NonNull<u8>);
unsafe impl Send for Raw {}
struct Returned {
raw: Raw,
class: u8,
cap: u32,
}
#[repr(C)]
struct Back {
next: *mut Back,
cap: u32,
class: u32,
}
pub(crate) struct Home {
returned: AtomicPtr<Back>,
waiting: AtomicBool,
waiters: SegQueue<Waker>,
}
impl Home {
fn give_back(&self, returned: Returned) {
let back = returned.raw.0.cast::<Back>().as_ptr();
let mut head = self.returned.load(Ordering::Relaxed);
loop {
unsafe {
back.write(Back {
next: head,
cap: returned.cap,
class: u32::from(returned.class),
});
}
match self.returned.compare_exchange_weak(
head,
back,
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(now) => head = now,
}
}
fence(Ordering::SeqCst);
if self.waiting.load(Ordering::Relaxed) {
self.wake_waiters();
}
}
fn take_all(&self) -> Taken {
if self.returned.load(Ordering::Relaxed).is_null() {
return Taken(core::ptr::null_mut());
}
Taken(self.returned.swap(core::ptr::null_mut(), Ordering::Acquire))
}
fn wake_waiters(&self) {
if self.waiting.swap(false, Ordering::AcqRel) {
while let Ok(waker) = self.waiters.try_pop() {
waker.wake();
}
}
}
}
impl Drop for Home {
fn drop(&mut self) {
for r in self.take_all() {
unsafe { dealloc(r.raw.0.as_ptr(), returned_layout(r.class, r.cap)) };
}
}
}
struct Taken(*mut Back);
impl Iterator for Taken {
type Item = Returned;
fn next(&mut self) -> Option<Returned> {
let back = NonNull::new(self.0)?;
let Back { next, cap, class } = unsafe { back.as_ptr().read() };
self.0 = next;
Some(Returned {
raw: Raw(back.cast()),
class: class as u8,
cap,
})
}
}
fn returned_layout(class: u8, cap: u32) -> Layout {
if class == WHOLE {
layout_whole(cap as usize)
} else {
layout(usize::from(class))
}
}
pub(crate) struct Pool {
home: Arc<Home>,
free: [RefCell<Vec<NonNull<u8>>>; CLASSES.len()],
cap: usize,
top: Option<usize>,
charged: Cell<usize>,
exhausted: Cell<u64>,
}
unsafe impl Send for Pool {}
impl Pool {
pub(crate) fn new(cap: usize) -> Self {
let cap = cap.max(CLASSES[0]);
Self {
home: Arc::new(Home {
returned: AtomicPtr::new(core::ptr::null_mut()),
waiting: AtomicBool::new(false),
waiters: SegQueue::new(),
}),
free: Default::default(),
cap,
top: CLASSES.iter().rposition(|&size| size <= cap),
charged: Cell::new(0),
exhausted: Cell::new(0),
}
}
pub(crate) fn charged(&self) -> usize {
self.charged.get()
}
pub(crate) fn exhausted(&self) -> u64 {
self.exhausted.get()
}
fn is_home(&self, home: &Arc<Home>) -> bool {
Arc::ptr_eq(&self.home, home)
}
pub(crate) fn get(&self, len: usize) -> Option<PoolBuf> {
self.get_class(class_for(len).min(self.top?))
}
fn get_class(&self, class: usize) -> Option<PoolBuf> {
let free = self.free[class].borrow_mut().pop();
let ptr = free
.or_else(|| self.take_returned(class))
.or_else(|| self.allocate(class))?;
Some(self.buf(ptr, class as u8, CLASSES[class]))
}
pub(crate) fn could_hold_whole(&self, len: usize) -> bool {
let size = if len <= LARGEST {
CLASSES[class_for(len)]
} else {
len.next_multiple_of(4096)
};
size <= self.cap
}
pub(crate) fn cap(&self) -> usize {
self.cap
}
pub(crate) fn get_whole(&self, len: usize) -> Option<PoolBuf> {
if len <= LARGEST {
return self.get_class(class_for(len));
}
let size = len.checked_next_multiple_of(4096)?;
u32::try_from(size).ok()?;
let _ = self.take_returned(usize::MAX);
if !self.make_room(size) {
return None;
}
let ptr = NonNull::new(unsafe { alloc_zeroed(layout_whole(size)) })?;
self.charged.set(self.charged.get() + size);
Some(self.buf(ptr, WHOLE, size))
}
fn buf(&self, ptr: NonNull<u8>, class: u8, cap: usize) -> PoolBuf {
PoolBuf {
ptr,
class,
cap: cap as u32,
start: 0,
end: 0,
home: Arc::clone(&self.home),
}
}
fn take_returned(&self, class: usize) -> Option<NonNull<u8>> {
let mut found = None;
for r in self.home.take_all() {
if r.class == WHOLE {
self.free_whole(r.raw.0, r.cap);
} else if found.is_none() && usize::from(r.class) == class {
found = Some(r.raw.0);
} else {
self.free[usize::from(r.class)].borrow_mut().push(r.raw.0);
}
}
found
}
fn allocate(&self, class: usize) -> Option<NonNull<u8>> {
let size = CLASSES[class];
if !self.make_room(size) {
return None;
}
let ptr = NonNull::new(unsafe { alloc_zeroed(layout(class)) })?;
self.charged.set(self.charged.get() + size);
Some(ptr)
}
fn make_room(&self, size: usize) -> bool {
let fits = |charged: usize| charged.saturating_add(size) <= self.cap;
if fits(self.charged.get()) {
return true;
}
for class in (0..CLASSES.len()).rev() {
let mut free = self.free[class].borrow_mut();
while let Some(ptr) = free.pop() {
unsafe { dealloc(ptr.as_ptr(), layout(class)) };
self.charged.set(self.charged.get() - CLASSES[class]);
if fits(self.charged.get()) {
return true;
}
}
}
self.exhausted.set(self.exhausted.get() + 1);
false
}
fn free_whole(&self, ptr: NonNull<u8>, cap: u32) {
unsafe { dealloc(ptr.as_ptr(), layout_whole(cap as usize)) };
self.charged.set(self.charged.get() - cap as usize);
}
fn take_back(&self, ptr: NonNull<u8>, class: u8, cap: u32) {
if class == WHOLE {
self.free_whole(ptr, cap);
} else {
self.free[usize::from(class)].borrow_mut().push(ptr);
}
if self.home.waiting.load(Ordering::Relaxed) {
self.home.wake_waiters();
}
}
pub(crate) fn wait_for_return(&self, waker: Waker) {
if self.charged.get() == 0 {
waker.wake();
return;
}
self.home.waiters.push(waker);
self.home.waiting.store(true, Ordering::SeqCst);
fence(Ordering::SeqCst);
let idle = self.free.iter().any(|f| !f.borrow().is_empty());
if idle || !self.home.returned.load(Ordering::SeqCst).is_null() {
self.home.wake_waiters();
}
}
}
impl Drop for Pool {
fn drop(&mut self) {
for (class, free) in self.free.iter_mut().enumerate() {
for ptr in free.get_mut().drain(..) {
unsafe { dealloc(ptr.as_ptr(), layout(class)) };
}
}
self.home.wake_waiters();
}
}
thread_local! {
static FALLBACK: Pool = Pool::new(DEFAULT_CAP);
}
pub(crate) fn with_pool<R>(f: impl FnOnce(Option<&Pool>) -> R) -> R {
crate::rt::context::current(|shard| match shard {
Some(shard) => f(Some(shard.pool())),
None => {
let fallback = FALLBACK
.try_with(core::ptr::from_ref)
.unwrap_or(core::ptr::null());
f(unsafe { fallback.as_ref() })
}
})
}
pub(crate) fn copy_vectored(src: &[u8], bufs: &mut [std::io::IoSliceMut<'_>]) -> usize {
let mut n = 0;
for buf in bufs.iter_mut() {
let rest = &src[n..];
if rest.is_empty() {
break;
}
let k = rest.len().min(buf.len());
buf[..k].copy_from_slice(&rest[..k]);
n += k;
}
n
}
pub(crate) fn append_spare(src: &[u8], buf: &mut Vec<u8>) -> usize {
let n = src.len().min(buf.capacity() - buf.len());
buf.extend_from_slice(&src[..n]);
n
}
pub(crate) struct PoolBuf {
ptr: NonNull<u8>,
class: u8,
cap: u32,
start: u32,
end: u32,
home: Arc<Home>,
}
unsafe impl Send for PoolBuf {}
unsafe impl Sync for PoolBuf {}
impl PoolBuf {
pub(crate) fn capacity(&self) -> usize {
self.cap as usize
}
pub(crate) fn filled(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
self.ptr.as_ptr().add(self.start as usize),
(self.end - self.start) as usize,
)
}
}
pub(crate) fn is_empty(&self) -> bool {
self.start == self.end
}
pub(crate) fn spare(&mut self) -> &mut [u8] {
self.start = 0;
self.end = 0;
unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.capacity()) }
}
pub(crate) fn set_filled(&mut self, n: usize) {
debug_assert!(n <= self.capacity());
self.start = 0;
self.end = n.min(self.capacity()) as u32;
}
pub(crate) fn fill_from(&mut self, bytes: &[u8]) -> usize {
let n = bytes.len().min(self.capacity());
self.spare()[..n].copy_from_slice(&bytes[..n]);
self.set_filled(n);
n
}
pub(crate) fn append(&mut self, bytes: &[u8]) -> usize {
let cap = self.capacity();
let (start, end) = (self.start as usize, self.end as usize);
if start > 0 && end + bytes.len() > cap {
unsafe {
core::ptr::copy(self.ptr.as_ptr().add(start), self.ptr.as_ptr(), end - start)
};
self.start = 0;
self.end = (end - start) as u32;
}
let end = self.end as usize;
let n = (cap - end).min(bytes.len());
unsafe { core::ptr::copy_nonoverlapping(bytes.as_ptr(), self.ptr.as_ptr().add(end), n) };
self.end += n as u32;
n
}
pub(crate) fn consume(&mut self, n: usize) {
let n = n.min((self.end - self.start) as usize);
self.start += n as u32;
}
pub(crate) fn read_into(&mut self, out: &mut [u8]) -> usize {
let n = self.peek_into(out);
self.consume(n);
n
}
pub(crate) fn read_into_vectored(&mut self, bufs: &mut [std::io::IoSliceMut<'_>]) -> usize {
let n = copy_vectored(self.filled(), bufs);
self.consume(n);
n
}
pub(crate) fn append_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> usize {
let mut n = 0;
for buf in bufs {
let taken = self.append(buf);
n += taken;
if taken < buf.len() {
break;
}
}
n
}
pub(crate) fn peek_into(&self, out: &mut [u8]) -> usize {
let src = self.filled();
let n = src.len().min(out.len());
out[..n].copy_from_slice(&src[..n]);
n
}
}
impl Drop for PoolBuf {
fn drop(&mut self) {
let returned = crate::rt::context::current(|shard| match shard {
Some(shard) if shard.pool().is_home(&self.home) => {
shard.pool().take_back(self.ptr, self.class, self.cap);
false
}
_ => true,
});
if returned {
self.home.give_back(Returned {
raw: Raw(self.ptr),
class: self.class,
cap: self.cap,
});
}
}
}
impl core::fmt::Debug for PoolBuf {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PoolBuf")
.field("capacity", &self.capacity())
.field("filled", &(self.end - self.start))
.finish()
}
}
#[cfg(all(test, not(loom)))]
mod tests;