use crate::io::worker::local_worker;
use crate::io::{Buffer, FixedBuffer};
use crate::utils::assert_hint;
use nix::libc;
use std::cell::UnsafeCell;
use std::io::IoSliceMut;
thread_local! {
static BUF_POOL: UnsafeCell<Option<BufPool>> = const {
UnsafeCell::new(None)
};
}
pub(crate) fn init_local_buf_pool(number_of_fixed_buffers: u16, default_buffer_cap: u32) {
BUF_POOL.with(|buf_pool_static| {
let buf_pool_ = unsafe { &mut *buf_pool_static.get() };
assert!(buf_pool_.is_none(), "BufPool is already initialized.");
let buf_pool = BufPool::new(number_of_fixed_buffers, default_buffer_cap);
*buf_pool_ = Some(buf_pool);
});
}
pub(crate) fn uninit_local_buf_pool() {
BUF_POOL.with(|buf_pool_static| unsafe {
let buf_pool_ = &mut *buf_pool_static.get();
assert!(buf_pool_.is_some(), "BufPool is not initialized.");
*buf_pool_ = None;
});
}
#[inline(always)]
pub fn buf_pool() -> &'static mut BufPool {
BUF_POOL.with(|buf_pool| unsafe {
let buf_pool_ = &mut *buf_pool.get();
assert_hint(buf_pool_.is_some(), "BufPool is not initialized.");
buf_pool_.as_mut().unwrap()
})
}
#[inline(always)]
pub fn buffer() -> Buffer {
buf_pool().get()
}
#[inline(always)]
pub fn full_buffer() -> Buffer {
buf_pool().get_full()
}
pub struct BufPool {
#[cfg(target_os = "linux")]
pub(crate) pool_of_fixed_buffers: Vec<Buffer>,
#[cfg(not(target_os = "linux"))]
pub(crate) pool: Vec<Buffer>,
#[cfg(target_os = "linux")]
pub(crate) fixed_buffers: Box<[IoSliceMut<'static>]>,
default_buffer_cap: u32,
#[cfg(target_os = "linux")]
pub(crate) pool_of_non_fixed_buffers: Vec<Buffer>,
}
impl BufPool {
fn new(number_of_fixed_buffers: u16, default_buffer_cap: u32) -> Self {
#[cfg(not(target_os = "linux"))]
{
Self {
pool: (0..number_of_fixed_buffers)
.map(|_| Buffer::new(default_buffer_cap))
.collect(),
default_buffer_cap,
}
}
#[cfg(target_os = "linux")]
{
if number_of_fixed_buffers == 0 {
return Self {
fixed_buffers: Box::new([]),
pool_of_fixed_buffers: Vec::new(),
default_buffer_cap,
pool_of_non_fixed_buffers: Vec::new(),
};
}
let mut fixed_buffers: Box<[IoSliceMut<'static>]> = (0..number_of_fixed_buffers)
.map(|_| {
let slice = Box::leak(vec![0; default_buffer_cap as _].into_boxed_slice());
IoSliceMut::new(slice)
})
.collect();
let pool_of_fixed_buffers = fixed_buffers
.iter_mut()
.enumerate()
.map(|(i, buf)| {
Buffer::new_fixed(
buf.as_mut_ptr(),
default_buffer_cap as _,
u16::try_from(i).expect("number_of_fixed_buffers > u16::MAX"),
)
})
.collect();
let iovecs: Vec<libc::iovec> = fixed_buffers
.iter_mut()
.map(|buf| libc::iovec {
iov_base: buf.as_mut_ptr().cast(),
iov_len: buf.len() as _,
})
.collect();
local_worker().register_buffers(&iovecs);
Self {
fixed_buffers,
pool_of_fixed_buffers,
default_buffer_cap,
pool_of_non_fixed_buffers: Vec::new(),
}
}
}
fn deallocate_buffers(&mut self) {
#[cfg(not(target_os = "linux"))]
{
for buf in self.pool.drain(..) {
buf.deallocate();
}
}
#[cfg(target_os = "linux")]
{
for buf in self.pool_of_non_fixed_buffers.drain(..) {
buf.deallocate();
}
if self.pool_of_fixed_buffers.is_empty() {
return;
}
for buf in self.pool_of_fixed_buffers.drain(..) {
buf.deallocate();
}
local_worker().unregister_buffers();
for buf in &mut self.fixed_buffers {
unsafe { drop(Box::from_raw(std::ptr::from_mut::<[u8]>(buf.as_mut()))) };
}
}
}
#[cfg(test)]
pub(super) fn len(&self) -> usize {
#[cfg(not(target_os = "linux"))]
{
self.pool.len()
}
#[cfg(target_os = "linux")]
{
self.pool_of_fixed_buffers.len() + self.pool_of_non_fixed_buffers.len()
}
}
#[inline(always)]
pub fn default_buffer_capacity(&self) -> u32 {
self.default_buffer_cap
}
#[inline(always)]
pub fn get_buffer_with_any_len(&mut self) -> Buffer {
#[cfg(not(target_os = "linux"))]
{
self.pool
.pop()
.unwrap_or_else(|| Buffer::new_from_pool(self))
}
#[cfg(target_os = "linux")]
if let Some(fixed_buf) = self.pool_of_fixed_buffers.pop() {
fixed_buf
} else {
self.pool_of_non_fixed_buffers
.pop()
.unwrap_or_else(|| Buffer::new_from_pool(self))
}
}
#[inline(always)]
pub fn get_full(&mut self) -> Buffer {
let mut buffer = self.get_buffer_with_any_len();
buffer.set_len_to_capacity();
buffer
}
#[inline(always)]
pub fn get(&mut self) -> Buffer {
let mut buffer = self.get_buffer_with_any_len();
buffer.clear();
buffer
}
#[inline(always)]
pub(crate) fn put(&mut self, buf: Buffer) {
#[cfg(not(target_os = "linux"))]
{
if buf.capacity() == self.default_buffer_cap {
unsafe {
self.pool.push(buf);
}
}
buf.deallocate();
return;
}
#[cfg(target_os = "linux")]
{
if buf.is_fixed() {
self.pool_of_fixed_buffers.push(buf);
} else if buf.capacity() == self.default_buffer_cap {
self.pool_of_non_fixed_buffers.push(buf);
} else {
buf.deallocate();
}
}
}
}
impl Drop for BufPool {
fn drop(&mut self) {
self.deallocate_buffers();
}
}
#[allow(clippy::future_not_send, reason = "It is a test.")]
#[cfg(test)]
pub(crate) async fn get_fixed_buffer() -> Buffer {
if cfg!(target_os = "linux") {
let mut buf = buffer();
while !buf.is_fixed() {
crate::yield_now().await;
buf = buffer();
}
buf
} else {
panic!("get_fixed is not supported on this OS.");
}
}
#[allow(clippy::future_not_send, reason = "It is a test.")]
#[cfg(test)]
pub(crate) async fn get_full_fixed_buffer() -> Buffer {
if cfg!(target_os = "linux") {
let mut buf = full_buffer();
while !buf.is_fixed() {
crate::yield_now().await;
buf = buffer();
}
buf
} else {
panic!("get_fixed is not supported on this OS.");
}
}