use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ConnectionState {
#[default]
Idle = 0,
Connected = 1,
Reading = 2,
Processing = 3,
Writing = 4,
Closing = 5,
Closed = 6,
Error = 7,
}
impl ConnectionState {
pub const COUNT: usize = 8;
#[inline(always)]
pub const fn from_u8(v: u8) -> Self {
match v {
0 => Self::Idle,
1 => Self::Connected,
2 => Self::Reading,
3 => Self::Processing,
4 => Self::Writing,
5 => Self::Closing,
6 => Self::Closed,
7 => Self::Error,
_ => Self::Error,
}
}
#[inline(always)]
pub const fn as_u8(self) -> u8 {
self as u8
}
#[inline(always)]
pub const fn is_terminal(self) -> bool {
matches!(self, Self::Closed | Self::Error)
}
#[inline(always)]
pub const fn can_accept_request(self) -> bool {
matches!(self, Self::Connected | Self::Idle)
}
#[inline(always)]
pub const fn is_active(self) -> bool {
matches!(self, Self::Reading | Self::Processing | Self::Writing)
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConnectionEvent {
Accept = 0,
DataReady = 1,
RequestComplete = 2,
ResponseReady = 3,
WriteComplete = 4,
KeepAlive = 5,
Timeout = 6,
Error = 7,
Close = 8,
}
impl ConnectionEvent {
pub const COUNT: usize = 9;
#[inline(always)]
pub const fn as_u8(self) -> u8 {
self as u8
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransitionAction {
None = 0,
StartRead = 1,
ContinueRead = 2,
Dispatch = 3,
StartWrite = 4,
ContinueWrite = 5,
Reset = 6,
InitiateClose = 7,
ForceClose = 8,
LogError = 9,
}
const TRANSITION_TABLE: [[TransitionEntry; ConnectionEvent::COUNT]; ConnectionState::COUNT] = {
use ConnectionState as S;
use TransitionAction as A;
const fn e(state: S, action: A) -> TransitionEntry {
TransitionEntry {
next_state: state.as_u8(),
action: action as u8,
}
}
const NOOP_IDLE: TransitionEntry = e(S::Idle, A::None);
const NOOP_CONN: TransitionEntry = e(S::Connected, A::None);
const NOOP_READ: TransitionEntry = e(S::Reading, A::None);
const NOOP_PROC: TransitionEntry = e(S::Processing, A::None);
const NOOP_WRIT: TransitionEntry = e(S::Writing, A::None);
const NOOP_CLOS: TransitionEntry = e(S::Closing, A::None);
const NOOP_CLSD: TransitionEntry = e(S::Closed, A::None);
const NOOP_ERR: TransitionEntry = e(S::Error, A::None);
const TO_ERROR: TransitionEntry = e(S::Error, A::ForceClose);
const TO_CLOSE: TransitionEntry = e(S::Closing, A::InitiateClose);
const TO_CLOSED: TransitionEntry = e(S::Closed, A::None);
[
[
e(S::Connected, A::StartRead), NOOP_IDLE, NOOP_IDLE, NOOP_IDLE, NOOP_IDLE, NOOP_IDLE, NOOP_IDLE, NOOP_IDLE, NOOP_IDLE, ],
[
NOOP_CONN, e(S::Reading, A::ContinueRead), NOOP_CONN, NOOP_CONN, NOOP_CONN, NOOP_CONN, TO_CLOSE, TO_ERROR, TO_CLOSE, ],
[
NOOP_READ, e(S::Reading, A::ContinueRead), e(S::Processing, A::Dispatch), NOOP_READ, NOOP_READ, NOOP_READ, TO_CLOSE, TO_ERROR, TO_CLOSE, ],
[
NOOP_PROC, NOOP_PROC, NOOP_PROC, e(S::Writing, A::StartWrite), NOOP_PROC, NOOP_PROC, TO_CLOSE, TO_ERROR, TO_CLOSE, ],
[
NOOP_WRIT, NOOP_WRIT, NOOP_WRIT, NOOP_WRIT, e(S::Closing, A::InitiateClose), e(S::Connected, A::Reset), TO_CLOSE, TO_ERROR, TO_CLOSE, ],
[
NOOP_CLOS, NOOP_CLOS, NOOP_CLOS, NOOP_CLOS, TO_CLOSED, NOOP_CLOS, TO_CLOSED, TO_CLOSED, TO_CLOSED, ],
[
NOOP_CLSD, NOOP_CLSD, NOOP_CLSD, NOOP_CLSD, NOOP_CLSD, NOOP_CLSD, NOOP_CLSD, NOOP_CLSD,
NOOP_CLSD,
],
[
NOOP_ERR, NOOP_ERR, NOOP_ERR, NOOP_ERR, NOOP_ERR, NOOP_ERR, NOOP_ERR, NOOP_ERR,
NOOP_ERR,
],
]
};
#[derive(Clone, Copy)]
struct TransitionEntry {
next_state: u8,
action: u8,
}
impl TransitionEntry {
#[inline(always)]
fn state(self) -> ConnectionState {
ConnectionState::from_u8(self.next_state)
}
#[inline(always)]
fn action(self) -> TransitionAction {
unsafe { std::mem::transmute(self.action) }
}
}
#[derive(Debug)]
pub struct Connection {
state: ConnectionState,
keep_alive: bool,
request_count: u32,
connected_at: Option<Instant>,
last_activity: Option<Instant>,
id: u64,
}
impl Connection {
#[inline]
pub fn new() -> Self {
Self {
state: ConnectionState::Idle,
keep_alive: true,
request_count: 0,
connected_at: None,
last_activity: None,
id: CONNECTION_ID_COUNTER.fetch_add(1, Ordering::Relaxed),
}
}
#[inline]
pub fn with_id(id: u64) -> Self {
Self {
state: ConnectionState::Idle,
keep_alive: true,
request_count: 0,
connected_at: None,
last_activity: None,
id,
}
}
#[inline(always)]
pub fn state(&self) -> ConnectionState {
self.state
}
#[inline(always)]
pub fn id(&self) -> u64 {
self.id
}
#[inline(always)]
pub fn request_count(&self) -> u32 {
self.request_count
}
#[inline(always)]
pub fn keep_alive(&self) -> bool {
self.keep_alive
}
#[inline(always)]
pub fn set_keep_alive(&mut self, enabled: bool) {
self.keep_alive = enabled;
}
#[inline]
pub fn age(&self) -> Option<Duration> {
self.connected_at.map(|t| t.elapsed())
}
#[inline]
pub fn idle_time(&self) -> Option<Duration> {
self.last_activity.map(|t| t.elapsed())
}
#[inline]
pub fn handle_event(&mut self, event: ConnectionEvent) -> TransitionAction {
let entry = TRANSITION_TABLE[self.state.as_u8() as usize][event.as_u8() as usize];
let new_state = entry.state();
let action = entry.action();
if new_state != self.state {
self.on_state_change(new_state);
}
self.state = new_state;
self.last_activity = Some(Instant::now());
action
}
#[inline]
pub fn try_transition(
&mut self,
event: ConnectionEvent,
) -> Result<TransitionAction, TransitionError> {
let entry = TRANSITION_TABLE[self.state.as_u8() as usize][event.as_u8() as usize];
let new_state = entry.state();
let action = entry.action();
if new_state == self.state && action == TransitionAction::None {
return Err(TransitionError::InvalidTransition {
from: self.state,
event,
});
}
if new_state != self.state {
self.on_state_change(new_state);
}
self.state = new_state;
self.last_activity = Some(Instant::now());
Ok(action)
}
#[inline]
fn on_state_change(&mut self, new_state: ConnectionState) {
match new_state {
ConnectionState::Connected => {
self.connected_at = Some(Instant::now());
CONNECTION_STATS.record_connected();
}
ConnectionState::Reading => {
}
ConnectionState::Processing => {
self.request_count += 1;
}
ConnectionState::Closed => {
CONNECTION_STATS.record_closed();
}
ConnectionState::Error => {
CONNECTION_STATS.record_error();
}
_ => {}
}
}
#[inline]
pub fn reset(&mut self) {
self.state = ConnectionState::Connected;
self.last_activity = Some(Instant::now());
CONNECTION_STATS.record_reuse();
}
#[inline]
pub fn force_close(&mut self) {
if !self.state.is_terminal() {
self.state = ConnectionState::Closed;
CONNECTION_STATS.record_closed();
}
}
#[inline]
pub fn should_close(&self, idle_timeout: Duration) -> bool {
if self.state.is_terminal() {
return true;
}
if let Some(idle_time) = self.idle_time()
&& idle_time > idle_timeout
{
return true;
}
false
}
}
impl Default for Connection {
fn default() -> Self {
Self::new()
}
}
pub trait Recyclable {
fn reset(&mut self);
fn is_clean(&self) -> bool;
fn generation(&self) -> u64;
fn increment_generation(&mut self);
}
#[derive(Debug)]
pub struct RecyclableConnection {
inner: Connection,
generation: u64,
recycle_count: u64,
read_buffer_capacity: usize,
write_buffer_capacity: usize,
user_data: Option<Box<dyn std::any::Any + Send + Sync>>,
}
impl RecyclableConnection {
pub fn new() -> Self {
Self {
inner: Connection::new(),
generation: 0,
recycle_count: 0,
read_buffer_capacity: 0,
write_buffer_capacity: 0,
user_data: None,
}
}
pub fn with_id(id: u64) -> Self {
Self {
inner: Connection::with_id(id),
generation: 0,
recycle_count: 0,
read_buffer_capacity: 0,
write_buffer_capacity: 0,
user_data: None,
}
}
pub fn with_capacities(read_capacity: usize, write_capacity: usize) -> Self {
Self {
inner: Connection::new(),
generation: 0,
recycle_count: 0,
read_buffer_capacity: read_capacity,
write_buffer_capacity: write_capacity,
user_data: None,
}
}
#[inline(always)]
pub fn inner(&self) -> &Connection {
&self.inner
}
#[inline(always)]
pub fn inner_mut(&mut self) -> &mut Connection {
&mut self.inner
}
#[inline(always)]
pub fn recycle_count(&self) -> u64 {
self.recycle_count
}
#[inline(always)]
pub fn read_buffer_capacity(&self) -> usize {
self.read_buffer_capacity
}
#[inline(always)]
pub fn write_buffer_capacity(&self) -> usize {
self.write_buffer_capacity
}
#[inline]
pub fn set_buffer_capacities(&mut self, read: usize, write: usize) {
self.read_buffer_capacity = read;
self.write_buffer_capacity = write;
}
pub fn set_user_data<T: std::any::Any + Send + Sync + 'static>(&mut self, data: T) {
self.user_data = Some(Box::new(data));
}
pub fn user_data<T: std::any::Any + Send + Sync + 'static>(&self) -> Option<&T> {
self.user_data.as_ref()?.downcast_ref::<T>()
}
pub fn take_user_data<T: std::any::Any + Send + Sync + 'static>(&mut self) -> Option<T> {
let boxed = self.user_data.take()?;
boxed.downcast::<T>().ok().map(|b| *b)
}
#[inline]
pub fn handle_event(&mut self, event: ConnectionEvent) -> TransitionAction {
self.inner.handle_event(event)
}
#[inline(always)]
pub fn state(&self) -> ConnectionState {
self.inner.state()
}
#[inline(always)]
pub fn id(&self) -> u64 {
self.inner.id()
}
pub fn prepare_for_recycle(&mut self) {
self.user_data = None;
self.inner.state = ConnectionState::Idle;
self.inner.keep_alive = true;
self.inner.request_count = 0;
self.inner.connected_at = None;
self.inner.last_activity = None;
self.generation += 1;
self.recycle_count += 1;
RECYCLE_STATS.record_recycle();
}
}
impl Default for RecyclableConnection {
fn default() -> Self {
Self::new()
}
}
impl Recyclable for RecyclableConnection {
fn reset(&mut self) {
self.prepare_for_recycle();
}
fn is_clean(&self) -> bool {
self.inner.state() == ConnectionState::Idle && self.user_data.is_none()
}
fn generation(&self) -> u64 {
self.generation
}
fn increment_generation(&mut self) {
self.generation += 1;
}
}
#[derive(Debug)]
pub struct RecyclePool<T: Recyclable + Default> {
objects: Vec<T>,
free_indices: Vec<usize>,
capacity: usize,
high_water_mark: usize,
#[allow(dead_code)] config: RecyclePoolConfig,
}
impl<T: Recyclable + Default> RecyclePool<T> {
pub fn new(capacity: usize) -> Self {
Self::with_config(capacity, RecyclePoolConfig::default())
}
pub fn with_config(capacity: usize, config: RecyclePoolConfig) -> Self {
let mut objects = Vec::with_capacity(capacity);
let mut free_indices = Vec::with_capacity(capacity);
for i in 0..capacity {
objects.push(T::default());
free_indices.push(i);
}
Self {
objects,
free_indices,
capacity,
high_water_mark: 0,
config,
}
}
#[inline]
pub fn acquire(&mut self) -> Option<PoolHandle<'_, T>> {
let index = self.free_indices.pop()?;
let obj = &mut self.objects[index];
if !obj.is_clean() {
obj.reset();
}
let active = self.capacity - self.free_indices.len();
if active > self.high_water_mark {
self.high_water_mark = active;
}
RECYCLE_STATS.record_acquire();
Some(PoolHandle {
pool: self,
index,
released: false,
_marker: std::marker::PhantomData,
})
}
#[inline]
pub fn try_acquire(&mut self) -> Option<PoolHandle<'_, T>> {
self.acquire()
}
#[inline]
fn release(&mut self, index: usize) {
if index < self.objects.len() {
self.objects[index].reset();
self.free_indices.push(index);
RECYCLE_STATS.record_release();
}
}
#[inline]
pub fn get(&self, index: usize) -> Option<&T> {
self.objects.get(index)
}
#[inline]
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.objects.get_mut(index)
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn available(&self) -> usize {
self.free_indices.len()
}
#[inline]
pub fn active(&self) -> usize {
self.capacity - self.free_indices.len()
}
#[inline]
pub fn high_water_mark(&self) -> usize {
self.high_water_mark
}
#[inline]
pub fn reset_high_water_mark(&mut self) {
self.high_water_mark = self.active();
}
#[inline]
pub fn is_exhausted(&self) -> bool {
self.free_indices.is_empty()
}
pub fn shrink(&mut self, min_capacity: usize) {
let target = min_capacity.max(self.active());
if target < self.capacity {
let before = self.free_indices.len();
let objects = &mut self.objects;
self.free_indices.retain(|&index| {
if index >= target {
objects[index].reset();
false
} else {
true
}
});
self.capacity -= before - self.free_indices.len();
}
}
pub fn grow(&mut self, additional: usize) {
let new_capacity = self.capacity + additional;
self.objects.reserve(additional);
for _ in 0..additional {
let index = self.objects.len();
self.objects.push(T::default());
self.free_indices.push(index);
}
self.capacity = new_capacity;
}
}
#[derive(Debug)]
pub struct PoolHandle<'a, T: Recyclable + Default> {
pool: *mut RecyclePool<T>,
index: usize,
released: bool,
_marker: std::marker::PhantomData<&'a mut T>,
}
impl<'a, T: Recyclable + Default> PoolHandle<'a, T> {
#[inline]
pub fn get(&self) -> &T {
unsafe { &(&(*self.pool).objects)[self.index] }
}
#[inline]
pub fn get_mut(&mut self) -> &mut T {
unsafe { &mut (&mut (*self.pool).objects)[self.index] }
}
#[inline]
pub fn index(&self) -> usize {
self.index
}
#[inline]
pub fn generation(&self) -> u64 {
self.get().generation()
}
#[inline]
pub fn release(mut self) {
if !self.released {
unsafe { (*self.pool).release(self.index) };
self.released = true;
}
}
}
impl<'a, T: Recyclable + Default> Drop for PoolHandle<'a, T> {
fn drop(&mut self) {
if !self.released {
unsafe { (*self.pool).release(self.index) };
}
}
}
impl<'a, T: Recyclable + Default> std::ops::Deref for PoolHandle<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.get()
}
}
impl<'a, T: Recyclable + Default> std::ops::DerefMut for PoolHandle<'a, T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.get_mut()
}
}
#[derive(Debug, Clone)]
pub struct RecyclePoolConfig {
pub initial_capacity: usize,
pub max_capacity: usize,
pub grow_by: usize,
pub shrink_threshold: f32,
pub min_capacity: usize,
}
impl Default for RecyclePoolConfig {
fn default() -> Self {
Self {
initial_capacity: 100,
max_capacity: 10000,
grow_by: 50,
shrink_threshold: 0.25,
min_capacity: 10,
}
}
}
impl RecyclePoolConfig {
pub fn new() -> Self {
Self::default()
}
pub fn initial_capacity(mut self, capacity: usize) -> Self {
self.initial_capacity = capacity;
self
}
pub fn max_capacity(mut self, max: usize) -> Self {
self.max_capacity = max;
self
}
pub fn grow_by(mut self, amount: usize) -> Self {
self.grow_by = amount;
self
}
pub fn shrink_threshold(mut self, threshold: f32) -> Self {
self.shrink_threshold = threshold;
self
}
pub fn min_capacity(mut self, min: usize) -> Self {
self.min_capacity = min;
self
}
}
#[derive(Debug, Default)]
pub struct RecycleStats {
acquires: AtomicU64,
releases: AtomicU64,
recycles: AtomicU64,
allocations: AtomicU64,
}
impl RecycleStats {
pub fn new() -> Self {
Self::default()
}
#[inline]
fn record_acquire(&self) {
self.acquires.fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn record_release(&self) {
self.releases.fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn record_recycle(&self) {
self.recycles.fetch_add(1, Ordering::Relaxed);
}
#[inline]
#[allow(dead_code)] fn record_allocation(&self) {
self.allocations.fetch_add(1, Ordering::Relaxed);
}
pub fn acquires(&self) -> u64 {
self.acquires.load(Ordering::Relaxed)
}
pub fn releases(&self) -> u64 {
self.releases.load(Ordering::Relaxed)
}
pub fn recycles(&self) -> u64 {
self.recycles.load(Ordering::Relaxed)
}
pub fn allocations(&self) -> u64 {
self.allocations.load(Ordering::Relaxed)
}
pub fn recycle_ratio(&self) -> f64 {
let acquires = self.acquires() as f64;
if acquires > 0.0 {
self.recycles() as f64 / acquires
} else {
0.0
}
}
pub fn hit_ratio(&self) -> f64 {
let acquires = self.acquires() as f64;
if acquires > 0.0 {
1.0 - (self.allocations() as f64 / acquires)
} else {
1.0
}
}
}
static RECYCLE_STATS: RecycleStats = RecycleStats {
acquires: AtomicU64::new(0),
releases: AtomicU64::new(0),
recycles: AtomicU64::new(0),
allocations: AtomicU64::new(0),
};
pub fn recycle_stats() -> &'static RecycleStats {
&RECYCLE_STATS
}
pub struct ConnectionRecycler {
pool: RecyclePool<RecyclableConnection>,
config: ConnectionConfig,
}
impl ConnectionRecycler {
pub fn new(capacity: usize) -> Self {
Self::with_config(capacity, ConnectionConfig::default())
}
pub fn with_config(capacity: usize, config: ConnectionConfig) -> Self {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(capacity);
for i in 0..capacity {
if let Some(conn) = pool.get_mut(i) {
conn.inner_mut().set_keep_alive(config.keep_alive);
}
}
Self { pool, config }
}
#[inline]
pub fn acquire(&mut self) -> Option<PoolHandle<'_, RecyclableConnection>> {
let handle = self.pool.acquire()?;
Some(handle)
}
pub fn stats(&self) -> RecyclerStats {
RecyclerStats {
capacity: self.pool.capacity(),
available: self.pool.available(),
active: self.pool.active(),
high_water_mark: self.pool.high_water_mark(),
}
}
pub fn config(&self) -> &ConnectionConfig {
&self.config
}
pub fn should_recycle(&self, conn: &RecyclableConnection) -> bool {
if conn.inner().request_count() >= self.config.max_requests {
return false;
}
if !conn.inner().keep_alive() {
return false;
}
if let Some(age) = conn.inner().age()
&& age > self.config.idle_timeout * 10
{
return false;
}
true
}
}
#[derive(Debug, Clone, Copy)]
pub struct RecyclerStats {
pub capacity: usize,
pub available: usize,
pub active: usize,
pub high_water_mark: usize,
}
#[derive(Debug, Clone)]
pub enum TransitionError {
InvalidTransition {
from: ConnectionState,
event: ConnectionEvent,
},
AlreadyClosed,
}
impl std::fmt::Display for TransitionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidTransition { from, event } => {
write!(f, "Invalid transition: {:?} + {:?}", from, event)
}
Self::AlreadyClosed => write!(f, "Connection already closed"),
}
}
}
impl std::error::Error for TransitionError {}
#[derive(Debug)]
pub struct ConnectionPool {
connections: Vec<Connection>,
free_indices: Vec<usize>,
capacity: usize,
}
impl ConnectionPool {
pub fn new(capacity: usize) -> Self {
let mut connections = Vec::with_capacity(capacity);
let mut free_indices = Vec::with_capacity(capacity);
for i in 0..capacity {
connections.push(Connection::with_id(i as u64));
free_indices.push(i);
}
Self {
connections,
free_indices,
capacity,
}
}
#[inline]
pub fn acquire(&mut self) -> Option<&mut Connection> {
let index = self.free_indices.pop()?;
let conn = &mut self.connections[index];
conn.state = ConnectionState::Idle;
CONNECTION_STATS.record_pool_acquire();
Some(conn)
}
#[inline]
pub fn release(&mut self, id: u64) {
let index = id as usize;
if index < self.capacity {
self.connections[index].force_close();
self.free_indices.push(index);
CONNECTION_STATS.record_pool_release();
}
}
#[inline]
pub fn get(&self, id: u64) -> Option<&Connection> {
let index = id as usize;
if index < self.capacity {
Some(&self.connections[index])
} else {
None
}
}
#[inline]
pub fn get_mut(&mut self, id: u64) -> Option<&mut Connection> {
let index = id as usize;
if index < self.capacity {
Some(&mut self.connections[index])
} else {
None
}
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
pub fn available(&self) -> usize {
self.free_indices.len()
}
#[inline]
pub fn active(&self) -> usize {
self.capacity - self.free_indices.len()
}
}
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub idle_timeout: Duration,
pub max_requests: u32,
pub keep_alive: bool,
pub read_timeout: Duration,
pub write_timeout: Duration,
}
impl Default for ConnectionConfig {
fn default() -> Self {
Self {
idle_timeout: Duration::from_secs(60),
max_requests: 1000,
keep_alive: true,
read_timeout: Duration::from_secs(30),
write_timeout: Duration::from_secs(30),
}
}
}
impl ConnectionConfig {
pub fn new() -> Self {
Self::default()
}
pub fn idle_timeout(mut self, timeout: Duration) -> Self {
self.idle_timeout = timeout;
self
}
pub fn max_requests(mut self, max: u32) -> Self {
self.max_requests = max;
self
}
pub fn keep_alive(mut self, enabled: bool) -> Self {
self.keep_alive = enabled;
self
}
pub fn read_timeout(mut self, timeout: Duration) -> Self {
self.read_timeout = timeout;
self
}
pub fn write_timeout(mut self, timeout: Duration) -> Self {
self.write_timeout = timeout;
self
}
}
#[derive(Debug, Default)]
pub struct ConnectionStats {
connected: AtomicU64,
closed: AtomicU64,
errors: AtomicU64,
reuses: AtomicU64,
pool_acquires: AtomicU64,
pool_releases: AtomicU64,
#[allow(dead_code)] transitions: AtomicU64,
}
impl ConnectionStats {
pub fn new() -> Self {
Self::default()
}
#[inline]
fn record_connected(&self) {
self.connected.fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn record_closed(&self) {
self.closed.fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn record_error(&self) {
self.errors.fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn record_reuse(&self) {
self.reuses.fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn record_pool_acquire(&self) {
self.pool_acquires.fetch_add(1, Ordering::Relaxed);
}
#[inline]
fn record_pool_release(&self) {
self.pool_releases.fetch_add(1, Ordering::Relaxed);
}
pub fn connected(&self) -> u64 {
self.connected.load(Ordering::Relaxed)
}
pub fn closed(&self) -> u64 {
self.closed.load(Ordering::Relaxed)
}
pub fn errors(&self) -> u64 {
self.errors.load(Ordering::Relaxed)
}
pub fn reuses(&self) -> u64 {
self.reuses.load(Ordering::Relaxed)
}
pub fn active(&self) -> u64 {
let connected = self.connected.load(Ordering::Relaxed);
let closed = self.closed.load(Ordering::Relaxed);
connected.saturating_sub(closed)
}
pub fn pool_acquires(&self) -> u64 {
self.pool_acquires.load(Ordering::Relaxed)
}
pub fn pool_releases(&self) -> u64 {
self.pool_releases.load(Ordering::Relaxed)
}
}
static CONNECTION_ID_COUNTER: AtomicU64 = AtomicU64::new(0);
static CONNECTION_STATS: ConnectionStats = ConnectionStats {
connected: AtomicU64::new(0),
closed: AtomicU64::new(0),
errors: AtomicU64::new(0),
reuses: AtomicU64::new(0),
pool_acquires: AtomicU64::new(0),
pool_releases: AtomicU64::new(0),
transitions: AtomicU64::new(0),
};
pub fn connection_stats() -> &'static ConnectionStats {
&CONNECTION_STATS
}
pub struct StateMachineExecutor {
events: Vec<(u64, ConnectionEvent)>,
#[allow(dead_code)] batch_size: usize,
}
impl StateMachineExecutor {
pub fn new(batch_size: usize) -> Self {
Self {
events: Vec::with_capacity(batch_size),
batch_size,
}
}
#[inline]
pub fn queue(&mut self, conn_id: u64, event: ConnectionEvent) {
self.events.push((conn_id, event));
}
#[inline]
pub fn process(&mut self, pool: &mut ConnectionPool) -> Vec<(u64, TransitionAction)> {
let mut results = Vec::with_capacity(self.events.len());
for (conn_id, event) in self.events.drain(..) {
if let Some(conn) = pool.get_mut(conn_id) {
let action = conn.handle_event(event);
results.push((conn_id, action));
}
}
results
}
#[inline]
pub fn process_with<F>(&mut self, pool: &mut ConnectionPool, mut callback: F)
where
F: FnMut(u64, TransitionAction),
{
for (conn_id, event) in self.events.drain(..) {
if let Some(conn) = pool.get_mut(conn_id) {
let action = conn.handle_event(event);
callback(conn_id, action);
}
}
}
#[inline]
pub fn has_pending(&self) -> bool {
!self.events.is_empty()
}
#[inline]
pub fn pending_count(&self) -> usize {
self.events.len()
}
#[inline]
pub fn clear(&mut self) {
self.events.clear();
}
}
impl Default for StateMachineExecutor {
fn default() -> Self {
Self::new(64)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_connection_state_size() {
assert_eq!(std::mem::size_of::<ConnectionState>(), 1);
}
#[test]
fn test_connection_event_size() {
assert_eq!(std::mem::size_of::<ConnectionEvent>(), 1);
}
#[test]
fn test_transition_entry_size() {
assert_eq!(std::mem::size_of::<TransitionEntry>(), 2);
}
#[test]
fn test_basic_state_transitions() {
let mut conn = Connection::new();
assert_eq!(conn.state(), ConnectionState::Idle);
let action = conn.handle_event(ConnectionEvent::Accept);
assert_eq!(conn.state(), ConnectionState::Connected);
assert_eq!(action, TransitionAction::StartRead);
let action = conn.handle_event(ConnectionEvent::DataReady);
assert_eq!(conn.state(), ConnectionState::Reading);
assert_eq!(action, TransitionAction::ContinueRead);
let action = conn.handle_event(ConnectionEvent::RequestComplete);
assert_eq!(conn.state(), ConnectionState::Processing);
assert_eq!(action, TransitionAction::Dispatch);
let action = conn.handle_event(ConnectionEvent::ResponseReady);
assert_eq!(conn.state(), ConnectionState::Writing);
assert_eq!(action, TransitionAction::StartWrite);
let action = conn.handle_event(ConnectionEvent::WriteComplete);
assert_eq!(conn.state(), ConnectionState::Closing);
assert_eq!(action, TransitionAction::InitiateClose);
}
#[test]
fn test_keep_alive_transition() {
let mut conn = Connection::new();
conn.handle_event(ConnectionEvent::Accept);
conn.handle_event(ConnectionEvent::DataReady);
conn.handle_event(ConnectionEvent::RequestComplete);
conn.handle_event(ConnectionEvent::ResponseReady);
let action = conn.handle_event(ConnectionEvent::KeepAlive);
assert_eq!(conn.state(), ConnectionState::Connected);
assert_eq!(action, TransitionAction::Reset);
}
#[test]
fn test_error_transition() {
let mut conn = Connection::new();
conn.handle_event(ConnectionEvent::Accept);
let action = conn.handle_event(ConnectionEvent::Error);
assert_eq!(conn.state(), ConnectionState::Error);
assert_eq!(action, TransitionAction::ForceClose);
assert!(conn.state().is_terminal());
}
#[test]
fn test_timeout_transition() {
let mut conn = Connection::new();
conn.handle_event(ConnectionEvent::Accept);
let action = conn.handle_event(ConnectionEvent::Timeout);
assert_eq!(conn.state(), ConnectionState::Closing);
assert_eq!(action, TransitionAction::InitiateClose);
}
#[test]
fn test_connection_pool() {
let mut pool = ConnectionPool::new(10);
assert_eq!(pool.capacity(), 10);
assert_eq!(pool.available(), 10);
assert_eq!(pool.active(), 0);
let conn1 = pool.acquire().unwrap();
let id1 = conn1.id();
assert_eq!(pool.available(), 9);
assert_eq!(pool.active(), 1);
let conn2 = pool.acquire().unwrap();
let id2 = conn2.id();
assert_eq!(pool.available(), 8);
pool.release(id1);
assert_eq!(pool.available(), 9);
pool.release(id2);
assert_eq!(pool.available(), 10);
}
#[test]
fn test_connection_config() {
let config = ConnectionConfig::new()
.idle_timeout(Duration::from_secs(120))
.max_requests(500)
.keep_alive(false)
.read_timeout(Duration::from_secs(10))
.write_timeout(Duration::from_secs(10));
assert_eq!(config.idle_timeout, Duration::from_secs(120));
assert_eq!(config.max_requests, 500);
assert!(!config.keep_alive);
}
#[test]
fn test_state_machine_executor() {
let mut pool = ConnectionPool::new(5);
let mut executor = StateMachineExecutor::new(10);
let conn = pool.acquire().unwrap();
let conn_id = conn.id();
executor.queue(conn_id, ConnectionEvent::Accept);
executor.queue(conn_id, ConnectionEvent::DataReady);
executor.queue(conn_id, ConnectionEvent::RequestComplete);
assert!(executor.has_pending());
assert_eq!(executor.pending_count(), 3);
let results = executor.process(&mut pool);
assert_eq!(results.len(), 3);
assert!(!executor.has_pending());
let conn = pool.get(conn_id).unwrap();
assert_eq!(conn.state(), ConnectionState::Processing);
}
#[test]
fn test_request_count() {
let mut conn = Connection::new();
assert_eq!(conn.request_count(), 0);
conn.handle_event(ConnectionEvent::Accept);
conn.handle_event(ConnectionEvent::DataReady);
conn.handle_event(ConnectionEvent::RequestComplete); assert_eq!(conn.request_count(), 1);
conn.handle_event(ConnectionEvent::ResponseReady);
conn.handle_event(ConnectionEvent::KeepAlive);
conn.handle_event(ConnectionEvent::DataReady);
conn.handle_event(ConnectionEvent::RequestComplete);
assert_eq!(conn.request_count(), 2);
}
#[test]
fn test_try_transition_valid() {
let mut conn = Connection::new();
let result = conn.try_transition(ConnectionEvent::Accept);
assert!(result.is_ok());
assert_eq!(result.unwrap(), TransitionAction::StartRead);
}
#[test]
fn test_try_transition_invalid() {
let mut conn = Connection::new();
let result = conn.try_transition(ConnectionEvent::DataReady);
assert!(result.is_err());
}
#[test]
fn test_connection_stats() {
let stats = connection_stats();
let _ = stats.connected();
let _ = stats.closed();
let _ = stats.errors();
let _ = stats.active();
let _ = stats.reuses();
}
#[test]
fn test_state_is_terminal() {
assert!(ConnectionState::Closed.is_terminal());
assert!(ConnectionState::Error.is_terminal());
assert!(!ConnectionState::Connected.is_terminal());
assert!(!ConnectionState::Processing.is_terminal());
}
#[test]
fn test_state_is_active() {
assert!(ConnectionState::Reading.is_active());
assert!(ConnectionState::Processing.is_active());
assert!(ConnectionState::Writing.is_active());
assert!(!ConnectionState::Idle.is_active());
assert!(!ConnectionState::Connected.is_active());
}
#[test]
fn test_recyclable_connection_basic() {
let mut conn = RecyclableConnection::new();
assert_eq!(conn.generation(), 0);
assert_eq!(conn.recycle_count(), 0);
assert!(conn.is_clean());
conn.handle_event(ConnectionEvent::Accept);
conn.handle_event(ConnectionEvent::DataReady);
conn.prepare_for_recycle();
assert_eq!(conn.generation(), 1);
assert_eq!(conn.recycle_count(), 1);
assert!(conn.is_clean());
assert_eq!(conn.state(), ConnectionState::Idle);
}
#[test]
fn test_recyclable_connection_user_data() {
let mut conn = RecyclableConnection::new();
conn.set_user_data(42u32);
assert_eq!(conn.user_data::<u32>(), Some(&42));
let data = conn.take_user_data::<u32>();
assert_eq!(data, Some(42));
assert!(conn.user_data::<u32>().is_none());
}
#[test]
fn test_recyclable_connection_buffer_capacities() {
let conn = RecyclableConnection::with_capacities(4096, 8192);
assert_eq!(conn.read_buffer_capacity(), 4096);
assert_eq!(conn.write_buffer_capacity(), 8192);
}
#[test]
fn test_recycle_pool_basic() {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(5);
assert_eq!(pool.capacity(), 5);
assert_eq!(pool.available(), 5);
assert_eq!(pool.active(), 0);
assert!(!pool.is_exhausted());
{
let mut handle = pool.acquire().unwrap();
handle.handle_event(ConnectionEvent::Accept);
assert_eq!(handle.state(), ConnectionState::Connected);
}
assert_eq!(pool.available(), 5);
assert_eq!(pool.active(), 0);
}
#[test]
fn test_recycle_pool_exhaustion() {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(2);
assert_eq!(pool.capacity(), 2);
assert_eq!(pool.available(), 2);
assert!(!pool.is_exhausted());
{
let h = pool.acquire().unwrap();
assert_eq!(h.index(), 1); }
assert_eq!(pool.available(), 2);
{
let h = pool.acquire().unwrap();
assert_eq!(h.index(), 1);
}
assert!(!pool.is_exhausted());
}
#[test]
fn test_recycle_pool_generation_tracking() {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(1);
let gen1 = {
let handle = pool.acquire().unwrap();
handle.generation()
};
let gen2 = {
let handle = pool.acquire().unwrap();
handle.generation()
};
assert!(gen2 > gen1);
}
#[test]
fn test_recycle_pool_high_water_mark() {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(5);
{
let _ = pool.acquire().unwrap();
}
assert!(pool.high_water_mark() >= 1);
let hwm_before = pool.high_water_mark();
pool.reset_high_water_mark();
assert!(pool.high_water_mark() <= hwm_before);
}
#[test]
fn test_recycle_pool_grow() {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(2);
assert_eq!(pool.capacity(), 2);
pool.grow(3);
assert_eq!(pool.capacity(), 5);
assert_eq!(pool.available(), 5);
}
#[test]
fn test_recycle_pool_shrink_only_drops_high_free_indices() {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(4);
pool.shrink(2);
assert_eq!(pool.capacity(), 2);
assert_eq!(pool.available(), 2);
assert_eq!(pool.active(), 0);
{
let h = pool.acquire().unwrap();
assert!(h.index() < 2);
}
assert_eq!(pool.available(), 2);
}
#[test]
fn test_recycle_pool_release_after_shrink_and_grow() {
let mut pool: RecyclePool<RecyclableConnection> = RecyclePool::new(4);
pool.shrink(2);
pool.grow(2);
assert_eq!(pool.capacity(), 4);
assert_eq!(pool.available(), 4);
let index = {
let h = pool.acquire().unwrap();
h.index()
};
assert!(index >= 4);
assert_eq!(pool.available(), 4);
assert_eq!(pool.active(), 0);
}
#[test]
fn test_recycle_pool_config() {
let config = RecyclePoolConfig::new()
.initial_capacity(50)
.max_capacity(500)
.grow_by(25)
.shrink_threshold(0.1)
.min_capacity(5);
assert_eq!(config.initial_capacity, 50);
assert_eq!(config.max_capacity, 500);
assert_eq!(config.grow_by, 25);
assert!((config.shrink_threshold - 0.1).abs() < 0.001);
assert_eq!(config.min_capacity, 5);
}
#[test]
fn test_connection_recycler() {
let mut recycler = ConnectionRecycler::new(10);
let stats = recycler.stats();
assert_eq!(stats.capacity, 10);
assert_eq!(stats.available, 10);
assert_eq!(stats.active, 0);
{
let mut handle = recycler.acquire().unwrap();
handle.handle_event(ConnectionEvent::Accept);
}
let stats = recycler.stats();
assert_eq!(stats.active, 0);
assert_eq!(stats.available, 10);
}
#[test]
fn test_recycle_stats() {
let stats = recycle_stats();
let _ = stats.acquires();
let _ = stats.releases();
let _ = stats.recycles();
let _ = stats.allocations();
let _ = stats.recycle_ratio();
let _ = stats.hit_ratio();
}
#[test]
fn test_recyclable_trait() {
let mut conn = RecyclableConnection::new();
assert!(conn.is_clean());
assert_eq!(conn.generation(), 0);
conn.handle_event(ConnectionEvent::Accept);
conn.increment_generation();
assert_eq!(conn.generation(), 1);
conn.reset();
assert!(conn.is_clean());
assert_eq!(conn.generation(), 2); }
}