use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::{Mutex, MutexGuard, PoisonError};
const USAGE_STATISTICS_LENGTH: usize = 10;
pub trait IBatchStreamPoolItems<T>: Send + Sync {
fn create_item(&self) -> T;
fn clear_item(&self, _item: &mut T) {}
fn destroy_item(&self, _item: T) {}
}
pub type BatchStreamPoolStartTimer = Box<dyn Fn() + Send + Sync>;
struct PoolState<T> {
pool: Vec<T>,
disposed: bool,
usage: i32,
usage_statistics: [i32; USAGE_STATISTICS_LENGTH],
usage_statistics_slot: usize,
timer_is_running: bool,
current_update_tick: u64,
last_activity_tick: u64,
}
pub struct BatchStreamPoolBase<T, I: IBatchStreamPoolItems<T>> {
items: I,
start_timer: Option<BatchStreamPoolStartTimer>,
reclaim_immediately: bool,
state: Mutex<PoolState<T>>,
}
impl<T, I: IBatchStreamPoolItems<T>> BatchStreamPoolBase<T, I> {
pub fn new(items: I, reclaim_immediately: bool, start_timer: Option<BatchStreamPoolStartTimer>) -> Self {
let pool = Self {
items,
start_timer,
reclaim_immediately,
state: Mutex::new(PoolState {
pool: Vec::new(),
disposed: false,
usage: 0,
usage_statistics: [0; USAGE_STATISTICS_LENGTH],
usage_statistics_slot: 0,
timer_is_running: false,
current_update_tick: 0,
last_activity_tick: 0,
}),
};
let start = pool.ensure_update_timer(&mut pool.lock());
pool.notify_start_timer(start);
pool
}
fn lock(&self) -> MutexGuard<'_, PoolState<T>> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
pub fn current_usage(&self) -> i32 {
self.lock().usage
}
pub fn current_pool(&self) -> i32 {
self.lock().pool.len() as i32
}
pub fn timer_is_running(&self) -> bool {
self.lock().timer_is_running
}
fn ensure_update_timer(&self, state: &mut PoolState<T>) -> bool {
if state.timer_is_running || !self.needs_timer(state) {
return false;
}
state.timer_is_running = true;
true
}
fn notify_start_timer(&self, start: bool) {
if start {
if let Some(start_timer) = &self.start_timer {
start_timer();
}
}
}
fn needs_timer(&self, state: &PoolState<T>) -> bool {
!self.reclaim_immediately
&& state.current_update_tick.wrapping_sub(state.last_activity_tick)
< USAGE_STATISTICS_LENGTH as u64 * 2 + 1
}
pub fn update_timer_tick(&self) -> bool {
let mut state = self.lock();
state.current_update_tick = state.current_update_tick.wrapping_add(1);
let maximum_usage = state.usage_statistics.iter().copied().max().unwrap_or(0);
let recently_used_pooled_slots = maximum_usage - state.usage;
let keep_slots = recently_used_pooled_slots.max(10);
while (keep_slots as usize) < state.pool.len() {
if let Some(item) = state.pool.pop() {
self.items.destroy_item(item);
}
}
state.usage_statistics_slot = (state.usage_statistics_slot + 1) % USAGE_STATISTICS_LENGTH;
let slot = state.usage_statistics_slot;
state.usage_statistics[slot] = 0;
state.timer_is_running = self.needs_timer(&state);
state.timer_is_running
}
fn on_activity(&self, state: &mut PoolState<T>) -> bool {
state.last_activity_tick = state.current_update_tick;
self.ensure_update_timer(state)
}
pub fn get(&self) -> T {
let (item, start) = {
let mut state = self.lock();
state.usage += 1;
let slot = state.usage_statistics_slot;
if state.usage_statistics[slot] < state.usage {
state.usage_statistics[slot] = state.usage;
}
let start = self.on_activity(&mut state);
(state.pool.pop(), start)
};
self.notify_start_timer(start);
match item {
Some(item) => item,
None => self.items.create_item(),
}
}
pub fn return_item(&self, mut item: T) {
self.items.clear_item(&mut item);
let result = {
let mut state = self.lock();
state.usage -= 1;
if !state.disposed && !self.reclaim_immediately {
state.pool.push(item);
Ok(self.on_activity(&mut state))
} else {
Err(item)
}
};
match result {
Ok(start) => self.notify_start_timer(start),
Err(item) => self.items.destroy_item(item),
}
}
pub fn dispose(&self) {
let mut state = self.lock();
state.disposed = true;
for item in state.pool.drain(..) {
self.items.destroy_item(item);
}
}
}
impl<T, I: IBatchStreamPoolItems<T>> Drop for BatchStreamPoolBase<T, I> {
fn drop(&mut self) {
self.dispose();
}
}
pub struct BatchStreamObjectPoolItems<T> {
array_size: usize,
marker: PhantomData<fn() -> T>,
}
impl<T> IBatchStreamPoolItems<Box<[Option<T>]>> for BatchStreamObjectPoolItems<T> {
fn create_item(&self) -> Box<[Option<T>]> {
std::iter::repeat_with(|| None).take(self.array_size).collect()
}
fn clear_item(&self, item: &mut Box<[Option<T>]>) {
item.fill_with(|| None);
}
}
pub struct BatchStreamObjectPool<T> {
base: BatchStreamPoolBase<Box<[Option<T>]>, BatchStreamObjectPoolItems<T>>,
}
impl<T> BatchStreamObjectPool<T> {
pub const DEFAULT_ARRAY_SIZE: usize = 128;
pub fn new(reclaim_immediately: bool, array_size: usize, start_timer: Option<BatchStreamPoolStartTimer>) -> Self {
Self {
base: BatchStreamPoolBase::new(
BatchStreamObjectPoolItems { array_size, marker: PhantomData },
reclaim_immediately,
start_timer,
),
}
}
pub fn array_size(&self) -> usize {
self.base.items.array_size
}
}
impl<T> Deref for BatchStreamObjectPool<T> {
type Target = BatchStreamPoolBase<Box<[Option<T>]>, BatchStreamObjectPoolItems<T>>;
fn deref(&self) -> &Self::Target {
&self.base
}
}
pub struct BatchStreamMemoryPoolItems {
buffer_size: usize,
}
impl IBatchStreamPoolItems<Box<[u8]>> for BatchStreamMemoryPoolItems {
fn create_item(&self) -> Box<[u8]> {
vec![0u8; self.buffer_size].into_boxed_slice()
}
}
pub struct BatchStreamMemoryPool {
base: BatchStreamPoolBase<Box<[u8]>, BatchStreamMemoryPoolItems>,
}
impl BatchStreamMemoryPool {
pub const DEFAULT_BUFFER_SIZE: usize = 1024;
pub fn new(reclaim_immediately: bool, buffer_size: usize, start_timer: Option<BatchStreamPoolStartTimer>) -> Self {
Self {
base: BatchStreamPoolBase::new(
BatchStreamMemoryPoolItems { buffer_size },
reclaim_immediately,
start_timer,
),
}
}
pub fn buffer_size(&self) -> usize {
self.base.items.buffer_size
}
}
impl Deref for BatchStreamMemoryPool {
type Target = BatchStreamPoolBase<Box<[u8]>, BatchStreamMemoryPoolItems>;
fn deref(&self) -> &Self::Target {
&self.base
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn pools_are_send_and_sync() {
assert_send_sync::<BatchStreamMemoryPool>();
assert_send_sync::<BatchStreamObjectPool<Box<dyn std::any::Any + Send>>>();
assert_send_sync::<BatchStreamObjectPool<Arc<str>>>();
}
#[derive(Default)]
struct Counting {
created: AtomicUsize,
cleared: AtomicUsize,
destroyed: AtomicUsize,
}
impl IBatchStreamPoolItems<usize> for Arc<Counting> {
fn create_item(&self) -> usize {
self.created.fetch_add(1, Ordering::SeqCst)
}
fn clear_item(&self, _item: &mut usize) {
self.cleared.fetch_add(1, Ordering::SeqCst);
}
fn destroy_item(&self, _item: usize) {
self.destroyed.fetch_add(1, Ordering::SeqCst);
}
}
fn counting_pool(reclaim_immediately: bool) -> (BatchStreamPoolBase<usize, Arc<Counting>>, Arc<Counting>) {
let counting = Arc::new(Counting::default());
(BatchStreamPoolBase::new(counting.clone(), reclaim_immediately, None), counting)
}
#[test]
fn get_creates_and_return_pools_items() {
let (pool, counting) = counting_pool(false);
let a = pool.get();
let b = pool.get();
assert_eq!((0, 1), (a, b));
assert_eq!(2, pool.current_usage());
assert_eq!(0, pool.current_pool());
pool.return_item(a);
assert_eq!(1, pool.current_usage());
assert_eq!(1, pool.current_pool());
assert_eq!(1, counting.cleared.load(Ordering::SeqCst));
pool.return_item(b);
assert_eq!(1, pool.get());
assert_eq!(0, pool.get());
assert_eq!(2, counting.created.load(Ordering::SeqCst));
assert_eq!(0, counting.destroyed.load(Ordering::SeqCst));
}
#[test]
fn reclaim_immediately_destroys_returned_items_and_needs_no_timer() {
let (pool, counting) = counting_pool(true);
assert!(!pool.timer_is_running());
let a = pool.get();
pool.return_item(a);
assert_eq!(0, pool.current_usage());
assert_eq!(0, pool.current_pool());
assert_eq!(1, counting.cleared.load(Ordering::SeqCst));
assert_eq!(1, counting.destroyed.load(Ordering::SeqCst));
assert!(!pool.timer_is_running());
assert!(!pool.update_timer_tick());
}
#[test]
fn dispose_destroys_pooled_items_and_later_returns() {
let (pool, counting) = counting_pool(false);
let items: Vec<usize> = (0..3).map(|_| pool.get()).collect();
pool.return_item(items[0]);
pool.return_item(items[1]);
pool.dispose();
assert_eq!(0, pool.current_pool());
assert_eq!(2, counting.destroyed.load(Ordering::SeqCst));
pool.return_item(items[2]);
assert_eq!(0, pool.current_pool());
assert_eq!(0, pool.current_usage());
assert_eq!(3, counting.destroyed.load(Ordering::SeqCst));
}
#[test]
fn dropping_the_pool_destroys_pooled_items() {
let (pool, counting) = counting_pool(false);
let a = pool.get();
pool.return_item(a);
drop(pool);
assert_eq!(1, counting.destroyed.load(Ordering::SeqCst));
}
#[test]
fn tick_keeps_at_least_ten_items() {
let (pool, counting) = counting_pool(false);
let items: Vec<usize> = (0..8).map(|_| pool.get()).collect();
for item in items {
pool.return_item(item);
}
for _ in 0..30 {
pool.update_timer_tick();
}
assert_eq!(8, pool.current_pool());
assert_eq!(0, counting.destroyed.load(Ordering::SeqCst));
}
#[test]
fn tick_trims_to_the_peak_usage_of_the_last_ten_ticks() {
let (pool, counting) = counting_pool(false);
let items: Vec<usize> = (0..30).map(|_| pool.get()).collect();
for item in items {
pool.return_item(item);
}
assert_eq!(30, pool.current_pool());
for _ in 0..9 {
assert!(pool.update_timer_tick());
assert_eq!(30, pool.current_pool());
}
assert!(pool.update_timer_tick());
assert_eq!(30, pool.current_pool());
assert!(pool.update_timer_tick());
assert_eq!(10, pool.current_pool());
assert_eq!(20, counting.destroyed.load(Ordering::SeqCst));
}
#[test]
fn items_in_use_count_against_the_slots_to_keep() {
let (pool, _counting) = counting_pool(false);
let mut items: Vec<usize> = (0..40).map(|_| pool.get()).collect();
for item in items.drain(25..) {
pool.return_item(item);
}
assert_eq!(15, pool.current_pool());
pool.update_timer_tick();
assert_eq!(15, pool.current_pool());
let more: Vec<usize> = (0..15).map(|_| pool.get()).collect();
assert_eq!(0, pool.current_pool());
for item in more {
pool.return_item(item);
}
for item in items {
pool.return_item(item);
}
pool.update_timer_tick();
assert_eq!(40, pool.current_pool());
}
#[test]
fn timer_stops_after_21_idle_ticks_and_restarts_on_activity() {
let starts = Arc::new(AtomicUsize::new(0));
let counting = Arc::new(Counting::default());
let pool = {
let starts = starts.clone();
BatchStreamPoolBase::new(
counting,
false,
Some(Box::new(move || {
starts.fetch_add(1, Ordering::SeqCst);
})),
)
};
assert_eq!(1, starts.load(Ordering::SeqCst));
assert!(pool.timer_is_running());
let item = pool.get();
pool.return_item(item);
assert_eq!(1, starts.load(Ordering::SeqCst));
for _ in 0..20 {
assert!(pool.update_timer_tick());
}
assert!(!pool.update_timer_tick());
assert!(!pool.timer_is_running());
let item = pool.get();
assert_eq!(2, starts.load(Ordering::SeqCst));
assert!(pool.timer_is_running());
pool.return_item(item);
assert_eq!(2, starts.load(Ordering::SeqCst));
for _ in 0..15 {
assert!(pool.update_timer_tick());
}
let item = pool.get();
pool.return_item(item);
for _ in 0..20 {
assert!(pool.update_timer_tick());
}
assert!(!pool.update_timer_tick());
}
#[test]
fn start_timer_callback_may_use_the_pool() {
let pool: Arc<Mutex<Option<Arc<BatchStreamMemoryPool>>>> = Arc::new(Mutex::new(None));
let observed = Arc::new(AtomicUsize::new(0));
let created = {
let pool = pool.clone();
let observed = observed.clone();
Arc::new(BatchStreamMemoryPool::new(
false,
16,
Some(Box::new(move || {
if let Some(pool) = pool.lock().unwrap().as_ref() {
observed.store(pool.current_usage() as usize, Ordering::SeqCst);
}
})),
))
};
*pool.lock().unwrap() = Some(created.clone());
for _ in 0..21 {
created.update_timer_tick();
}
let buffer = created.get();
assert_eq!(1, observed.load(Ordering::SeqCst));
created.return_item(buffer);
*pool.lock().unwrap() = None;
}
#[test]
fn object_pool_hands_out_cleared_arrays() {
let pool = BatchStreamObjectPool::<Arc<str>>::new(false, BatchStreamObjectPool::<Arc<str>>::DEFAULT_ARRAY_SIZE, None);
assert_eq!(128, pool.array_size());
let mut array = pool.get();
assert_eq!(128, array.len());
assert!(array.iter().all(|slot| slot.is_none()));
let value: Arc<str> = Arc::from("value");
array[3] = Some(value.clone());
assert_eq!(2, Arc::strong_count(&value));
pool.return_item(array);
assert_eq!(1, Arc::strong_count(&value));
assert_eq!(1, pool.current_pool());
let array = pool.get();
assert!(array.iter().all(|slot| slot.is_none()));
}
#[test]
fn memory_pool_hands_out_buffers_of_the_requested_size() {
let pool = BatchStreamMemoryPool::new(false, BatchStreamMemoryPool::DEFAULT_BUFFER_SIZE, None);
assert_eq!(1024, pool.buffer_size());
let mut buffer = pool.get();
assert_eq!(1024, buffer.len());
buffer[0] = 42;
pool.return_item(buffer);
assert_eq!(42, pool.get()[0]);
}
#[test]
fn pool_can_be_shared_between_threads() {
let pool = Arc::new(BatchStreamMemoryPool::new(false, 64, None));
let threads: Vec<_> = (0..4)
.map(|_| {
let pool = pool.clone();
std::thread::spawn(move || {
for _ in 0..200 {
let buffer = pool.get();
pool.return_item(buffer);
}
})
})
.collect();
for thread in threads {
thread.join().unwrap();
}
assert_eq!(0, pool.current_usage());
assert!(pool.current_pool() >= 1 && pool.current_pool() <= 4);
}
}