use std::{
cell::RefCell,
sync::{Arc, Weak},
};
use super::{
BufferPool, CachedBuf, NUM_CLASSES,
inbox::{ChainIter, CrossThreadInbox, SEALED},
};
pub(crate) struct TlsPoolEntry {
pub(crate) pool_id: u64,
pub(crate) tid: u64,
pub(crate) pool_weak: Weak<BufferPool>,
pub(crate) inbox: Arc<CrossThreadInbox>,
pub(crate) local: [Vec<CachedBuf>; NUM_CLASSES],
pub(crate) local_bytes: usize,
pub(crate) class_bytes: [usize; NUM_CLASSES],
pub(crate) active_classes: usize,
}
impl TlsPoolEntry {
fn new(pool: &Arc<BufferPool>) -> Self {
let inbox = Arc::new(CrossThreadInbox::new());
pool.register_inbox(&inbox);
Self {
pool_id: pool.pool_id,
tid: current_thread_id(),
pool_weak: Arc::downgrade(pool),
inbox,
local: [const { Vec::new() }; NUM_CLASSES],
local_bytes: 0,
class_bytes: [0; NUM_CLASSES],
active_classes: 0,
}
}
#[inline]
pub(crate) fn push_local(&mut self, cls: usize, buf: CachedBuf) {
let bytes = buf.cap;
if self.local[cls].is_empty() {
self.active_classes += 1;
}
self.local[cls].push(buf);
self.local_bytes += bytes;
self.class_bytes[cls] += bytes;
}
#[inline]
pub(crate) fn pop_local(&mut self, cls: usize) -> Option<CachedBuf> {
let buf = self.local[cls].pop()?;
let bytes = buf.cap;
self.local_bytes -= bytes;
self.class_bytes[cls] -= bytes;
if self.local[cls].is_empty() {
self.active_classes -= 1;
}
Some(buf)
}
#[inline]
pub(crate) fn spill_one_local(&mut self, cls: usize) -> Option<CachedBuf> {
self.pop_local(cls)
}
pub(crate) fn worst_over_share(&self, share: usize, first_large_class: usize) -> Option<usize> {
let mut worst = 0usize;
let mut victim = None;
for c in 0..first_large_class {
if !self.local[c].is_empty() && self.class_bytes[c] > share {
let over = self.class_bytes[c] - share;
if over > worst {
worst = over;
victim = Some(c);
}
}
}
victim
}
pub(crate) fn try_make_room(
&mut self,
cls: usize,
bytes: usize,
cap: usize,
first_large_class: usize,
) -> (bool, Vec<(usize, CachedBuf)>) {
let active = if self.local[cls].is_empty() {
self.active_classes + 1
} else {
self.active_classes
};
let share = cap / active.max(1);
if self.class_bytes[cls].saturating_add(bytes) > share {
return (false, Vec::new());
}
let mut victims = Vec::new();
let mut cur_victim: Option<usize> = None;
while self.local_bytes.saturating_add(bytes) > cap {
let need_new_victim = match cur_victim {
None => true,
Some(v) => self.class_bytes[v] <= share || self.local[v].is_empty(),
};
if need_new_victim {
cur_victim = self.worst_over_share(share, first_large_class);
}
let Some(v) = cur_victim else {
break;
};
if let Some(buf) = self.spill_one_local(v) {
victims.push((v, buf));
} else {
cur_victim = None;
}
}
let made_room = self.local_bytes.saturating_add(bytes) <= cap;
(made_room, victims)
}
fn sweep(&mut self, pool: Option<&BufferPool>, retire: bool) {
let tid = self.tid;
for cls in 0..NUM_CLASSES {
let chain = self.inbox.seal_and_drain(cls);
if !chain.is_null() && chain != SEALED {
for node in ChainIter::new(chain) {
Self::sweep_node(cls, node, pool, retire, tid);
}
}
for buf in self.local[cls].drain(..) {
Self::sweep_node(cls, buf, pool, retire, tid);
}
}
self.local_bytes = 0;
self.class_bytes = [0; NUM_CLASSES];
self.active_classes = 0;
}
#[inline]
fn sweep_node(cls: usize, node: CachedBuf, pool: Option<&BufferPool>, retire: bool, tid: u64) {
let cap = node.cap;
if let Some(pool) = pool
&& retire
&& !pool.is_closed()
&& pool.depot.push(cls, node, tid)
{
return;
}
if let Some(pool) = pool {
pool.budget_for(cls).release(cap as i64);
}
}
pub(crate) fn drain_and_release(&mut self, pool: &BufferPool) {
self.sweep(Some(pool), false);
}
}
impl Drop for TlsPoolEntry {
fn drop(&mut self) {
let pool_opt = self.pool_weak.upgrade();
self.sweep(pool_opt.as_deref(), true);
}
}
pub(crate) struct TlsPoolManager {
pub(crate) fast: Option<TlsPoolEntry>,
pub(crate) others: Vec<TlsPoolEntry>,
}
impl TlsPoolManager {
const fn new() -> Self {
Self {
fast: None,
others: Vec::new(),
}
}
pub(crate) fn find(&self, pool_id: u64) -> Option<&TlsPoolEntry> {
if let Some(entry) = self.fast.as_ref()
&& entry.pool_id == pool_id
{
return Some(entry);
}
self.others.iter().find(|e| e.pool_id == pool_id)
}
pub(crate) fn find_mut(&mut self, pool_id: u64) -> Option<&mut TlsPoolEntry> {
if let Some(entry) = self.fast.as_mut()
&& entry.pool_id == pool_id
{
return Some(entry);
}
self.others.iter_mut().find(|e| e.pool_id == pool_id)
}
pub(crate) fn get_or_create<'a>(&'a mut self, pool: &Arc<BufferPool>) -> &'a mut TlsPoolEntry {
if self
.fast
.as_ref()
.is_some_and(|e| e.pool_id == pool.pool_id)
{
return unsafe { self.fast.as_mut().unwrap_unchecked() };
}
if let Some(mut entry) = self.fast.take() {
match entry.pool_weak.upgrade() {
Some(p) if p.is_closed() => entry.drain_and_release(&p),
Some(_) => self.others.push(entry),
None => {}
}
}
if let Some(idx) = self.others.iter().position(|e| e.pool_id == pool.pool_id) {
let entry = self.others.swap_remove(idx);
return self.fast.insert(entry);
}
self
.others
.retain_mut(|entry| match entry.pool_weak.upgrade() {
Some(p) if !p.is_closed() => true,
Some(p) => {
entry.drain_and_release(&p);
false
}
None => false,
});
self.fast.insert(TlsPoolEntry::new(pool))
}
}
thread_local! {
pub(crate) static TLS_POOLS: RefCell<TlsPoolManager> = const { RefCell::new(TlsPoolManager::new()) };
}
pub use crate::thread::current_thread_id;