#![allow(clippy::expect_used)]
use std::fmt;
use std::marker::PhantomData;
use std::net::{SocketAddr, ToSocketAddrs};
use crate::connection_meta::{
AssignedConnectionConfig, CONN_GENERATION_MASK, CONN_ID_MASK, ConnectionConfig,
ConnectionError, ConnectionRuntimeIdentity, IngressStats, State, encode_conn_token,
token_conn_id, token_generation,
};
use crate::connection_state::ConnectionState;
use crate::observability::LatencyHistograms;
use crate::proactor::{Completion, Proactor, ProactorConfig, ProactorError};
use crate::ws::{
DataEvent as WsDataEvent, DataEventBatch as WsDataEventBatch, Event as WsEvent,
MarkedDataEvent as WsMarkedDataEvent, MarkedDataEventBatch as WsMarkedDataEventBatch,
};
pub const DEFAULT_POOL_INITIAL_CONN_CAPACITY: usize = 0;
pub const DEFAULT_POOL_COMPLETION_BATCH_CAPACITY: usize = 64;
pub const DEFAULT_POOL_POST_PROGRESS_SPIN_ITERS: usize = 0;
#[derive(Debug, Clone, Copy)]
pub struct PoolConfig {
pub proactor: ProactorConfig,
pub initial_conn_capacity: usize,
pub completion_batch_capacity: usize,
pub post_progress_spin_iters: usize,
}
impl PoolConfig {
#[must_use]
pub const fn new(proactor: ProactorConfig) -> Self {
Self {
proactor,
initial_conn_capacity: DEFAULT_POOL_INITIAL_CONN_CAPACITY,
completion_batch_capacity: DEFAULT_POOL_COMPLETION_BATCH_CAPACITY,
post_progress_spin_iters: DEFAULT_POOL_POST_PROGRESS_SPIN_ITERS,
}
}
#[must_use]
pub const fn with_initial_conn_capacity(mut self, capacity: usize) -> Self {
self.initial_conn_capacity = capacity;
self
}
#[must_use]
pub const fn with_completion_batch_capacity(mut self, capacity: usize) -> Self {
self.completion_batch_capacity = capacity;
self
}
#[must_use]
pub const fn with_post_progress_spin_iters(mut self, iters: usize) -> Self {
self.post_progress_spin_iters = iters;
self
}
}
impl Default for PoolConfig {
fn default() -> Self {
Self::new(ProactorConfig::default())
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct ConnHandle(u64);
impl ConnHandle {
#[inline]
#[must_use]
pub const fn as_u32(self) -> u32 {
token_conn_id(self.0)
}
#[inline]
#[must_use]
pub const fn as_u64(self) -> u64 {
self.0
}
#[inline]
#[must_use]
pub const fn generation(self) -> u32 {
token_generation(self.0)
}
#[inline]
#[must_use]
const fn from_parts(conn_id: u32, generation: u32) -> Self {
Self(encode_conn_token(conn_id, generation))
}
#[inline]
#[must_use]
const fn from_conn(conn: &ConnectionState) -> Self {
Self(conn.token())
}
}
pub struct Pool {
proactor: Proactor,
conns: Vec<Option<ConnectionState>>,
generations: Vec<u32>,
free_conn_ids: Vec<u32>,
free_bgids: Vec<u16>,
active_count: u32,
next_conn_id: u32,
next_bgid: u16,
completions_buf: Vec<Completion>,
post_progress_spin_iters: usize,
_not_send: PhantomData<*const ()>,
}
impl std::fmt::Debug for Pool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Pool")
.field("proactor", &self.proactor)
.field("active_count", &self.active_count)
.field("slot_len", &self.conns.len())
.field("slot_capacity", &self.conns.capacity())
.field("next_conn_id", &self.next_conn_id)
.field("next_bgid", &self.next_bgid)
.field("free_conn_ids", &self.free_conn_ids.len())
.field("free_bgids", &self.free_bgids.len())
.finish()
}
}
impl Pool {
pub fn new(cfg: PoolConfig) -> Result<Self, ProactorError> {
let proactor = Proactor::new(cfg.proactor)?;
Ok(Self {
proactor,
conns: Vec::with_capacity(cfg.initial_conn_capacity),
generations: Vec::with_capacity(cfg.initial_conn_capacity),
free_conn_ids: Vec::new(),
free_bgids: Vec::new(),
active_count: 0,
next_conn_id: 0,
next_bgid: 0,
completions_buf: Vec::with_capacity(cfg.completion_batch_capacity),
post_progress_spin_iters: cfg.post_progress_spin_iters,
_not_send: PhantomData,
})
}
pub fn connect_blocking(
&mut self,
cfg: ConnectionConfig,
) -> Result<ConnHandle, ConnectionError> {
let addr = resolve_addr(&cfg)?;
self.connect_blocking_to(cfg, addr)
}
pub fn connect_blocking_to(
&mut self,
cfg: ConnectionConfig,
addr: SocketAddr,
) -> Result<ConnHandle, ConnectionError> {
let handle = self.submit_connect_to(cfg, addr)?;
let conn_id = handle.as_u32();
match self.drive_conn_until_open(conn_id) {
Ok(()) => Ok(handle),
Err(e) => {
let _ = self.retire_slot(conn_id, Some(handle.generation()));
Err(e)
}
}
}
pub fn submit_connect(&mut self, cfg: ConnectionConfig) -> Result<ConnHandle, ConnectionError> {
let addr = resolve_addr(&cfg)?;
self.submit_connect_to(cfg, addr)
}
pub fn submit_connect_to(
&mut self,
cfg: ConnectionConfig,
addr: SocketAddr,
) -> Result<ConnHandle, ConnectionError> {
let identity = self.reserve_runtime_identity()?;
let assigned = AssignedConnectionConfig {
user: cfg,
identity,
};
let mut conn = match ConnectionState::new(assigned, addr) {
Ok(conn) => conn,
Err(e) => {
self.release_reserved_identity(identity);
return Err(e);
}
};
if let Err(e) = conn.submit_connect(&mut self.proactor) {
self.release_reserved_identity(identity);
return Err(e);
}
let conn_id = identity.conn_id;
let slot = self
.conns
.get_mut(conn_id as usize)
.expect("reserved slot must exist");
debug_assert!(slot.is_none());
*slot = Some(conn);
self.active_count += 1;
Ok(ConnHandle::from_parts(
identity.conn_id,
identity.generation,
))
}
pub fn remove_conn(&mut self, h: ConnHandle) -> Result<(), ConnectionError> {
if self.retire_slot(h.as_u32(), Some(h.generation())) {
Ok(())
} else {
Err(ConnectionError::InvalidState(State::Closed))
}
}
pub fn reconnect(
&mut self,
old: ConnHandle,
cfg: ConnectionConfig,
) -> Result<ConnHandle, ConnectionError> {
let addr = resolve_addr(&cfg)?;
self.reconnect_to(old, cfg, addr)
}
pub fn reconnect_to(
&mut self,
old: ConnHandle,
cfg: ConnectionConfig,
addr: SocketAddr,
) -> Result<ConnHandle, ConnectionError> {
let handle = self.submit_reconnect_to(old, cfg, addr)?;
let conn_id = handle.as_u32();
match self.drive_conn_until_open(conn_id) {
Ok(()) => Ok(handle),
Err(e) => {
let _ = self.retire_slot(conn_id, Some(handle.generation()));
Err(e)
}
}
}
pub fn submit_reconnect(
&mut self,
old: ConnHandle,
cfg: ConnectionConfig,
) -> Result<ConnHandle, ConnectionError> {
let addr = resolve_addr(&cfg)?;
self.submit_reconnect_to(old, cfg, addr)
}
pub fn submit_reconnect_to(
&mut self,
old: ConnHandle,
cfg: ConnectionConfig,
addr: SocketAddr,
) -> Result<ConnHandle, ConnectionError> {
self.remove_conn(old)?;
self.submit_connect_to(cfg, addr)
}
fn reserve_runtime_identity(&mut self) -> Result<ConnectionRuntimeIdentity, ConnectionError> {
let bgid = self.reserve_bgid()?;
let conn_id = match self.reserve_conn_id() {
Ok(conn_id) => conn_id,
Err(e) => {
self.release_bgid(bgid);
return Err(e);
}
};
let generation = self
.generations
.get(conn_id as usize)
.copied()
.expect("reserved generation must exist");
Ok(ConnectionRuntimeIdentity {
conn_id,
generation,
bgid,
})
}
fn reserve_conn_id(&mut self) -> Result<u32, ConnectionError> {
if let Some(conn_id) = self.free_conn_ids.pop() {
return Ok(conn_id);
}
let conn_id = self.next_conn_id;
if conn_id > CONN_ID_MASK as u32 {
return Err(ConnectionError::IdSpaceExhausted("conn_id"));
}
self.next_conn_id = conn_id + 1;
self.conns.push(None);
self.generations.push(0);
Ok(conn_id)
}
fn reserve_bgid(&mut self) -> Result<u16, ConnectionError> {
if let Some(bgid) = self.free_bgids.pop() {
return Ok(bgid);
}
let bgid = self.next_bgid;
let Some(next_bgid) = self.next_bgid.checked_add(1) else {
return Err(ConnectionError::IdSpaceExhausted("bgid"));
};
self.next_bgid = next_bgid;
Ok(bgid)
}
fn release_reserved_identity(&mut self, identity: ConnectionRuntimeIdentity) {
debug_assert!(
self.conns
.get(identity.conn_id as usize)
.is_some_and(Option::is_none)
);
self.free_conn_ids.push(identity.conn_id);
self.release_bgid(identity.bgid);
}
fn release_bgid(&mut self, bgid: u16) {
self.free_bgids.push(bgid);
}
fn retire_slot(&mut self, conn_id: u32, expected_generation: Option<u32>) -> bool {
let Some(slot) = self.conns.get_mut(conn_id as usize) else {
return false;
};
let Some(conn) = slot.as_ref() else {
return false;
};
if expected_generation.is_some_and(|expected| expected != conn.generation()) {
return false;
}
let mut dead = slot.take().expect("slot was Some above");
let was_active = conn_is_active(&dead);
let bgid = dead.bgid();
let mut recycle_bgid = dead.buf_ring.is_none();
if let Some(mut ring) = dead.buf_ring.take() {
match ring.unregister(&mut self.proactor) {
Ok(()) => recycle_bgid = true,
Err(e) => tracing::warn!(
conn_id,
bgid,
error = %e,
"failed to unregister buffer ring while removing connection; bgid will not be reused"
),
}
}
drop(dead);
if was_active {
self.active_count = self.active_count.saturating_sub(1);
}
self.recycle_conn_slot(conn_id);
if recycle_bgid {
self.release_bgid(bgid);
}
true
}
fn recycle_conn_slot(&mut self, conn_id: u32) {
let Some(generation) = self.generations.get_mut(conn_id as usize) else {
return;
};
if u64::from(*generation) >= CONN_GENERATION_MASK {
tracing::warn!(
conn_id,
generation = *generation,
"connection slot generation exhausted; slot will not be reused"
);
return;
}
*generation += 1;
self.free_conn_ids.push(conn_id);
}
fn drive_conn_until_open(&mut self, conn_id: u32) -> Result<(), ConnectionError> {
loop {
self.drive_open_once(1)?;
self.drive_target_handshake_event(conn_id)?;
let conn = self
.conns
.get_mut(conn_id as usize)
.and_then(Option::as_mut)
.expect("just-added conn must exist");
conn.sync_ws_open_state();
match conn.state() {
State::Open => return Ok(()),
State::Closed => return Err(ConnectionError::PeerClosed),
_ => {}
}
}
}
fn drive_target_handshake_event(&mut self, conn_id: u32) -> Result<(), ConnectionError> {
let Self {
conns,
active_count,
..
} = self;
let conn = conns
.get_mut(conn_id as usize)
.and_then(Option::as_mut)
.expect("just-added conn must exist");
if matches!(conn.state(), State::Open | State::Closed) {
return Ok(());
}
let was_active = conn_is_active(conn);
if let Some(res) = conn.ws.poll_event() {
match res {
Ok(WsEvent::HandshakeComplete) => {}
Ok(_) => {}
Err(e) => {
let mut first_err = None;
fail_conn_and_account(
conn,
ConnectionError::Ws(e),
&mut first_err,
active_count,
was_active,
);
return Err(first_err.expect("fail_conn_and_account stores first error"));
}
}
}
conn.sync_ws_open_state();
conn.sync_ws_close_state();
account_closed_transition(active_count, was_active, conn);
Ok(())
}
fn drive_open_once(&mut self, wait_nr: usize) -> Result<(), ConnectionError> {
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
proactor.wait_for_cqe(wait_nr)?;
completions_buf.clear();
proactor.drain_completions(|c| completions_buf.push(c));
for &c in completions_buf.iter() {
if let Some(conn) = conn_for_completion(conns, c) {
let was_active = conn_is_active(conn);
let result = conn.handle_completion(proactor, c);
let _ = finish_conn_result(conn, result, &mut first_err, active_count, was_active);
}
}
first_err.map_or(Ok(()), Err)
}
pub fn send_text(&mut self, h: ConnHandle, payload: &[u8]) -> Result<(), ConnectionError> {
let conn = self.conn_mut(h)?;
conn.assert_open()?;
conn.ws.send_text(payload)?;
Ok(())
}
pub fn send_binary(&mut self, h: ConnHandle, payload: &[u8]) -> Result<(), ConnectionError> {
let conn = self.conn_mut(h)?;
conn.assert_open()?;
conn.ws.send_binary(payload)?;
Ok(())
}
pub fn send_ping(&mut self, h: ConnHandle, payload: &[u8]) -> Result<(), ConnectionError> {
let conn = self.conn_mut(h)?;
conn.assert_open()?;
conn.ws.send_ping(payload)?;
Ok(())
}
pub fn send_pong(&mut self, h: ConnHandle, payload: &[u8]) -> Result<(), ConnectionError> {
let conn = self.conn_mut(h)?;
conn.assert_open()?;
conn.ws.send_pong(payload)?;
Ok(())
}
pub fn initiate_close(
&mut self,
h: ConnHandle,
code: u16,
reason: &str,
) -> Result<(), ConnectionError> {
let conn = self.conn_mut(h)?;
if matches!(conn.state(), State::Closed | State::Closing) {
return Ok(());
}
conn.ws.send_close(code, reason)?;
if matches!(conn.state(), State::Open) {
conn.state = State::Closing;
}
Ok(())
}
pub fn pump<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: FnMut(ConnHandle, WsEvent<'_>),
{
self.pump_impl(1, sink)
}
pub fn pump_nowait<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: FnMut(ConnHandle, WsEvent<'_>),
{
self.pump_impl(0, sink)
}
pub fn pump_spin<F>(&mut self, spin_iters: usize, sink: F) -> Result<bool, ConnectionError>
where
F: FnMut(ConnHandle, WsEvent<'_>),
{
self.pump_spin_impl(spin_iters, sink)
}
pub fn pump_data<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEvent<'a>),
{
self.pump_data_impl(1, sink)
}
pub fn pump_data_nowait<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEvent<'a>),
{
self.pump_data_impl(0, sink)
}
pub fn pump_data_marked<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEvent<'a>),
{
self.pump_data_marked_impl(1, sink)
}
pub fn pump_data_marked_nowait<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEvent<'a>),
{
self.pump_data_marked_impl(0, sink)
}
pub fn pump_data_batches<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEventBatch<'a>),
{
self.pump_data_batches_impl(1, sink)
}
pub fn pump_data_batches_nowait<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEventBatch<'a>),
{
self.pump_data_batches_impl(0, sink)
}
pub fn pump_data_marked_batches<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEventBatch<'a>),
{
self.pump_data_marked_batches_impl(1, sink)
}
pub fn pump_data_marked_batches_nowait<F>(&mut self, sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEventBatch<'a>),
{
self.pump_data_marked_batches_impl(0, sink)
}
pub fn pump_data_spin<F>(&mut self, spin_iters: usize, sink: F) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEvent<'a>),
{
self.pump_data_spin_impl(spin_iters, sink)
}
pub fn pump_data_spin_batches<F>(
&mut self,
spin_iters: usize,
sink: F,
) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEventBatch<'a>),
{
self.pump_data_spin_batches_impl(spin_iters, sink)
}
pub fn pump_data_spin_marked<F>(
&mut self,
spin_iters: usize,
sink: F,
) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEvent<'a>),
{
self.pump_data_spin_marked_impl(spin_iters, sink)
}
pub fn pump_data_spin_marked_batches<F>(
&mut self,
spin_iters: usize,
sink: F,
) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEventBatch<'a>),
{
self.pump_data_spin_marked_batches_impl(spin_iters, sink)
}
fn pump_data_impl<F>(&mut self, wait_nr: usize, mut sink: F) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEvent<'a>),
{
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
proactor.wait_for_cqe(wait_nr)?;
completions_buf.clear();
proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
first_err.map_or(Ok(()), Err)
}
fn pump_data_marked_impl<F>(
&mut self,
wait_nr: usize,
mut sink: F,
) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEvent<'a>),
{
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
proactor.wait_for_cqe(wait_nr)?;
completions_buf.clear();
proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data_marked(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
first_err.map_or(Ok(()), Err)
}
fn pump_data_batches_impl<F>(
&mut self,
wait_nr: usize,
mut sink: F,
) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEventBatch<'a>),
{
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
proactor.wait_for_cqe(wait_nr)?;
completions_buf.clear();
proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data_batches(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
first_err.map_or(Ok(()), Err)
}
fn pump_data_marked_batches_impl<F>(
&mut self,
wait_nr: usize,
mut sink: F,
) -> Result<(), ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEventBatch<'a>),
{
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
proactor.wait_for_cqe(wait_nr)?;
completions_buf.clear();
proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data_marked_batches(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
first_err.map_or(Ok(()), Err)
}
fn pump_data_spin_impl<F>(
&mut self,
spin_iters: usize,
mut sink: F,
) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEvent<'a>),
{
let post_progress_spin_iters = self.post_progress_spin_iters;
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
let mut progressed = false;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
for iter in 0..=spin_iters {
let cqes = drain_conn_completions_data(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
if cqes > 0 {
progressed = true;
drain_post_progress(post_progress_spin_iters, &mut first_err, |first_err| {
let _ = drain_conn_completions_data(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
first_err,
);
});
}
if progressed || first_err.is_some() {
break;
}
if iter < spin_iters {
std::hint::spin_loop();
}
}
match first_err {
Some(e) => Err(e),
None => Ok(progressed),
}
}
fn pump_data_spin_marked_impl<F>(
&mut self,
spin_iters: usize,
mut sink: F,
) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEvent<'a>),
{
let post_progress_spin_iters = self.post_progress_spin_iters;
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
let mut progressed = false;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
for iter in 0..=spin_iters {
let cqes = drain_conn_completions_data_marked(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
if cqes > 0 {
progressed = true;
drain_post_progress(post_progress_spin_iters, &mut first_err, |first_err| {
let _ = drain_conn_completions_data_marked(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
first_err,
);
});
}
if progressed || first_err.is_some() {
break;
}
if iter < spin_iters {
std::hint::spin_loop();
}
}
match first_err {
Some(e) => Err(e),
None => Ok(progressed),
}
}
fn pump_data_spin_batches_impl<F>(
&mut self,
spin_iters: usize,
mut sink: F,
) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsDataEventBatch<'a>),
{
let post_progress_spin_iters = self.post_progress_spin_iters;
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
let mut progressed = false;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
for iter in 0..=spin_iters {
let cqes = drain_conn_completions_data_batches(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
if cqes > 0 {
progressed = true;
drain_post_progress(post_progress_spin_iters, &mut first_err, |first_err| {
let _ = drain_conn_completions_data_batches(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
first_err,
);
});
}
if progressed || first_err.is_some() {
break;
}
if iter < spin_iters {
std::hint::spin_loop();
}
}
match first_err {
Some(e) => Err(e),
None => Ok(progressed),
}
}
fn pump_data_spin_marked_batches_impl<F>(
&mut self,
spin_iters: usize,
mut sink: F,
) -> Result<bool, ConnectionError>
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEventBatch<'a>),
{
let post_progress_spin_iters = self.post_progress_spin_iters;
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
let mut progressed = false;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
for iter in 0..=spin_iters {
let cqes = drain_conn_completions_data_marked_batches(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
&mut first_err,
);
if cqes > 0 {
progressed = true;
drain_post_progress(post_progress_spin_iters, &mut first_err, |first_err| {
let _ = drain_conn_completions_data_marked_batches(
conns,
proactor,
completions_buf,
active_count,
&mut sink,
first_err,
);
});
}
if progressed || first_err.is_some() {
break;
}
if iter < spin_iters {
std::hint::spin_loop();
}
}
match first_err {
Some(e) => Err(e),
None => Ok(progressed),
}
}
fn pump_impl<F>(&mut self, wait_nr: usize, mut sink: F) -> Result<(), ConnectionError>
where
F: FnMut(ConnHandle, WsEvent<'_>),
{
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
proactor.wait_for_cqe(wait_nr)?;
completions_buf.clear();
proactor.drain_completions(|c| completions_buf.push(c));
for &c in completions_buf.iter() {
if let Some(conn) = conn_for_completion(conns, c) {
let was_active = conn_is_active(conn);
let result = conn.handle_completion(proactor, c);
let _ = finish_conn_result(conn, result, &mut first_err, active_count, was_active);
}
}
for slot in conns.iter_mut() {
let Some(conn) = slot.as_mut() else { continue };
if matches!(conn.state(), State::Closed) {
conn.clear_ws_ingress_dirty();
continue;
}
let handle = ConnHandle::from_conn(conn);
while let Some(res) = conn.ws.poll_event() {
match res {
Ok(ev) => sink(handle, ev),
Err(e) => {
let was_active = conn_is_active(conn);
fail_conn_and_account(
conn,
ConnectionError::Ws(e),
&mut first_err,
active_count,
was_active,
);
break;
}
}
}
conn.sync_ws_open_state();
conn.sync_ws_close_state();
conn.clear_ws_ingress_dirty();
}
first_err.map_or(Ok(()), Err)
}
fn pump_spin_impl<F>(&mut self, spin_iters: usize, mut sink: F) -> Result<bool, ConnectionError>
where
F: FnMut(ConnHandle, WsEvent<'_>),
{
let Self {
proactor,
conns,
completions_buf,
active_count,
..
} = self;
let mut first_err: Option<ConnectionError> = None;
let mut progressed = false;
submit_conn_ops(conns, proactor, active_count, &mut first_err);
proactor.submit()?;
for iter in 0..=spin_iters {
let cqes = drain_conn_completions(
conns,
proactor,
completions_buf,
active_count,
&mut first_err,
);
progressed |= cqes > 0;
for slot in conns.iter_mut() {
let Some(conn) = slot.as_mut() else { continue };
if matches!(conn.state(), State::Closed) {
conn.clear_ws_ingress_dirty();
continue;
}
let handle = ConnHandle::from_conn(conn);
while let Some(res) = conn.ws.poll_event() {
progressed = true;
match res {
Ok(ev) => sink(handle, ev),
Err(e) => {
let was_active = conn_is_active(conn);
fail_conn_and_account(
conn,
ConnectionError::Ws(e),
&mut first_err,
active_count,
was_active,
);
break;
}
}
}
conn.sync_ws_open_state();
conn.sync_ws_close_state();
conn.clear_ws_ingress_dirty();
}
if progressed || first_err.is_some() {
break;
}
if iter < spin_iters {
std::hint::spin_loop();
}
}
match first_err {
Some(e) => Err(e),
None => Ok(progressed),
}
}
pub fn state(&self, h: ConnHandle) -> Option<State> {
self.conns
.get(h.as_u32() as usize)
.and_then(Option::as_ref)
.filter(|conn| conn.token() == h.as_u64())
.map(ConnectionState::state)
}
#[must_use]
pub fn conn_count(&self) -> usize {
self.active_count as usize
}
#[must_use]
pub fn ingress_stats(&self, h: ConnHandle) -> Option<IngressStats> {
self.conns
.get(h.as_u32() as usize)
.and_then(Option::as_ref)
.filter(|conn| conn.token() == h.as_u64())
.map(ConnectionState::ingress_stats)
}
#[must_use]
pub fn prometheus_metrics(&self) -> String {
let mut out = String::new();
self.write_prometheus_metrics(&mut out)
.expect("writing Prometheus metrics to String cannot fail");
out
}
pub fn write_prometheus_metrics<W: fmt::Write>(&self, out: &mut W) -> fmt::Result {
LatencyHistograms::write_prometheus_help(out)?;
write_ingress_prometheus_help(out)?;
for conn in self.conns.iter().flatten() {
conn.write_prometheus_metrics(out)?;
}
Ok(())
}
#[must_use]
pub fn prometheus_metrics_and_reset_interval(&mut self) -> String {
let mut out = String::new();
self.write_prometheus_metrics_and_reset_interval(&mut out)
.expect("writing Prometheus metrics to String cannot fail");
out
}
pub fn write_prometheus_metrics_and_reset_interval<W: fmt::Write>(
&mut self,
out: &mut W,
) -> fmt::Result {
LatencyHistograms::write_prometheus_help(out)?;
write_ingress_prometheus_help(out)?;
for conn in self.conns.iter_mut().flatten() {
conn.write_prometheus_metrics_and_reset_interval(out)?;
}
Ok(())
}
fn conn_mut(&mut self, h: ConnHandle) -> Result<&mut ConnectionState, ConnectionError> {
let conn = self
.conns
.get_mut(h.as_u32() as usize)
.and_then(Option::as_mut)
.ok_or(ConnectionError::InvalidState(State::Closed))?;
if conn.token() == h.as_u64() {
Ok(conn)
} else {
Err(ConnectionError::InvalidState(State::Closed))
}
}
}
fn resolve_addr(cfg: &ConnectionConfig) -> Result<SocketAddr, ConnectionError> {
(cfg.host.as_str(), cfg.port)
.to_socket_addrs()?
.next()
.ok_or_else(|| ConnectionError::DnsEmpty(cfg.host.clone()))
}
fn submit_conn_ops(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
active_count: &mut u32,
first_err: &mut Option<ConnectionError>,
) {
for slot in conns.iter_mut() {
let Some(conn) = slot.as_mut() else { continue };
let was_active = conn_is_active(conn);
if let Err(e) = conn.try_submit_send(proactor) {
fail_conn_and_account(conn, e, first_err, active_count, was_active);
continue;
}
if let Err(e) = conn.try_rearm_multishot(proactor) {
fail_conn_and_account(conn, e, first_err, active_count, was_active);
}
}
}
#[inline]
fn completion_token(c: Completion) -> u64 {
c.user_data.token()
}
#[inline]
fn conn_for_completion(
conns: &mut [Option<ConnectionState>],
c: Completion,
) -> Option<&mut ConnectionState> {
let token = completion_token(c);
let conn_id = token_conn_id(token);
let conn = conns.get_mut(conn_id as usize).and_then(Option::as_mut)?;
(conn.token() == token).then_some(conn)
}
fn drain_conn_completions(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &mut Vec<Completion>,
active_count: &mut u32,
first_err: &mut Option<ConnectionError>,
) -> usize {
completions_buf.clear();
let count = proactor.drain_completions(|c| completions_buf.push(c));
for &c in completions_buf.iter() {
if let Some(conn) = conn_for_completion(conns, c) {
let was_active = conn_is_active(conn);
let result = conn.handle_completion(proactor, c);
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
}
}
count
}
fn drain_conn_completions_data<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &mut Vec<Completion>,
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) -> usize
where
F: for<'a> FnMut(ConnHandle, WsDataEvent<'a>),
{
completions_buf.clear();
let count = proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data(
conns,
proactor,
completions_buf,
active_count,
sink,
first_err,
);
count
}
fn dispatch_conn_completions_data<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &[Completion],
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) where
F: for<'a> FnMut(ConnHandle, WsDataEvent<'a>),
{
let mut i = 0_usize;
while i < completions_buf.len() {
let c = completions_buf[i];
let token = completion_token(c);
let Some(conn) = conn_for_completion(conns, c) else {
i += 1;
continue;
};
let handle = ConnHandle::from_conn(conn);
if conn.can_handle_plain_recv_data_batch(c) {
let mut end = i + 1;
while end < completions_buf.len() {
let next = completions_buf[end];
if completion_token(next) != token || !conn.can_handle_plain_recv_data_batch(next) {
break;
}
end += 1;
}
if end > i + 1 {
let was_active = conn_is_active(conn);
let result =
conn.handle_plain_recv_data_batch(&completions_buf[i..end], &mut |ev| {
sink(handle, ev);
});
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i = end;
continue;
}
}
let was_active = conn_is_active(conn);
let result = conn.handle_completion_data(proactor, c, |ev| sink(handle, ev));
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i += 1;
}
}
fn drain_conn_completions_data_marked<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &mut Vec<Completion>,
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) -> usize
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEvent<'a>),
{
completions_buf.clear();
let count = proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data_marked(
conns,
proactor,
completions_buf,
active_count,
sink,
first_err,
);
count
}
fn dispatch_conn_completions_data_marked<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &[Completion],
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEvent<'a>),
{
let mut i = 0_usize;
while i < completions_buf.len() {
let c = completions_buf[i];
let token = completion_token(c);
let Some(conn) = conn_for_completion(conns, c) else {
i += 1;
continue;
};
let handle = ConnHandle::from_conn(conn);
if conn.can_handle_plain_recv_data_batch(c) {
let mut end = i + 1;
while end < completions_buf.len() {
let next = completions_buf[end];
if completion_token(next) != token || !conn.can_handle_plain_recv_data_batch(next) {
break;
}
end += 1;
}
if end > i + 1 {
let was_active = conn_is_active(conn);
let result =
conn.handle_plain_recv_data_batch_marked(&completions_buf[i..end], &mut |ev| {
sink(handle, ev);
});
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i = end;
continue;
}
}
let was_active = conn_is_active(conn);
let result = conn.handle_completion_data_marked(proactor, c, |ev| sink(handle, ev));
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i += 1;
}
}
fn drain_conn_completions_data_batches<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &mut Vec<Completion>,
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) -> usize
where
F: for<'a> FnMut(ConnHandle, WsDataEventBatch<'a>),
{
completions_buf.clear();
let count = proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data_batches(
conns,
proactor,
completions_buf,
active_count,
sink,
first_err,
);
count
}
fn dispatch_conn_completions_data_batches<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &[Completion],
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) where
F: for<'a> FnMut(ConnHandle, WsDataEventBatch<'a>),
{
let mut i = 0_usize;
while i < completions_buf.len() {
let c = completions_buf[i];
let token = completion_token(c);
let Some(conn) = conn_for_completion(conns, c) else {
i += 1;
continue;
};
let handle = ConnHandle::from_conn(conn);
if conn.can_handle_plain_recv_data_batch(c) {
let mut end = i + 1;
while end < completions_buf.len() {
let next = completions_buf[end];
if completion_token(next) != token || !conn.can_handle_plain_recv_data_batch(next) {
break;
}
end += 1;
}
let was_active = conn_is_active(conn);
let result =
conn.handle_plain_recv_data_event_batches(&completions_buf[i..end], &mut |batch| {
sink(handle, batch);
});
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i = end;
continue;
}
let was_active = conn_is_active(conn);
let result = conn.handle_completion_data_batch(proactor, c, |batch| sink(handle, batch));
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i += 1;
}
}
fn drain_conn_completions_data_marked_batches<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &mut Vec<Completion>,
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) -> usize
where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEventBatch<'a>),
{
completions_buf.clear();
let count = proactor.drain_completions(|c| completions_buf.push(c));
dispatch_conn_completions_data_marked_batches(
conns,
proactor,
completions_buf,
active_count,
sink,
first_err,
);
count
}
fn dispatch_conn_completions_data_marked_batches<F>(
conns: &mut [Option<ConnectionState>],
proactor: &mut Proactor,
completions_buf: &[Completion],
active_count: &mut u32,
sink: &mut F,
first_err: &mut Option<ConnectionError>,
) where
F: for<'a> FnMut(ConnHandle, WsMarkedDataEventBatch<'a>),
{
let mut i = 0_usize;
while i < completions_buf.len() {
let c = completions_buf[i];
let token = completion_token(c);
let Some(conn) = conn_for_completion(conns, c) else {
i += 1;
continue;
};
let handle = ConnHandle::from_conn(conn);
if conn.can_handle_plain_recv_data_batch(c) {
let mut end = i + 1;
while end < completions_buf.len() {
let next = completions_buf[end];
if completion_token(next) != token || !conn.can_handle_plain_recv_data_batch(next) {
break;
}
end += 1;
}
let was_active = conn_is_active(conn);
let result = conn.handle_plain_recv_data_event_batches_marked(
&completions_buf[i..end],
&mut |batch| {
sink(handle, batch);
},
);
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i = end;
continue;
}
let was_active = conn_is_active(conn);
let result =
conn.handle_completion_data_marked_batch(proactor, c, |batch| sink(handle, batch));
let _ = finish_conn_result(conn, result, first_err, active_count, was_active);
i += 1;
}
}
#[inline]
fn drain_post_progress<E, F>(
post_progress_spin_iters: usize,
first_err: &mut Option<E>,
mut drain: F,
) where
F: FnMut(&mut Option<E>),
{
for _ in 0..post_progress_spin_iters {
std::hint::spin_loop();
drain(first_err);
if first_err.is_some() {
break;
}
}
}
fn write_ingress_prometheus_help<W: fmt::Write>(out: &mut W) -> fmt::Result {
writeln!(
out,
"# HELP talaris_ingress_recv_data_cqes_total Positive-length recv data CQEs handled by a connection."
)?;
writeln!(out, "# TYPE talaris_ingress_recv_data_cqes_total counter")?;
writeln!(
out,
"# HELP talaris_ingress_recv_bytes_total Bytes carried by positive-length recv data CQEs. For TLS these are ciphertext bytes; for plain TCP these are plaintext bytes."
)?;
writeln!(out, "# TYPE talaris_ingress_recv_bytes_total counter")?;
writeln!(
out,
"# HELP talaris_ingress_recv_multishot_rearms_total Recv multishot SQEs submitted or rearmed."
)?;
writeln!(
out,
"# TYPE talaris_ingress_recv_multishot_rearms_total counter"
)?;
writeln!(
out,
"# HELP talaris_ingress_recv_ring_exhaustions_total Recv multishot terminations caused by provided-buffer ring exhaustion."
)?;
writeln!(
out,
"# TYPE talaris_ingress_recv_ring_exhaustions_total counter"
)?;
writeln!(
out,
"# HELP talaris_ingress_plain_recv_batches_total Consecutive plain TCP recv CQE runs handled by the data-pump batch path."
)?;
writeln!(
out,
"# TYPE talaris_ingress_plain_recv_batches_total counter"
)?;
writeln!(
out,
"# HELP talaris_ingress_plain_recv_batch_cqes_total Total recv CQEs included in plain TCP data-pump batch runs."
)?;
writeln!(
out,
"# TYPE talaris_ingress_plain_recv_batch_cqes_total counter"
)?;
writeln!(
out,
"# HELP talaris_ingress_plain_recv_copied_batches_total Plain TCP data-pump batch runs parsed through the reusable copy scratch buffer."
)?;
writeln!(
out,
"# TYPE talaris_ingress_plain_recv_copied_batches_total counter"
)?;
writeln!(
out,
"# HELP talaris_ingress_plain_recv_copied_bytes_total Bytes copied into the reusable plain TCP data-pump batch scratch buffer."
)?;
writeln!(
out,
"# TYPE talaris_ingress_plain_recv_copied_bytes_total counter"
)?;
writeln!(
out,
"# HELP talaris_ingress_plaintext_chunks_total Plaintext source chunks made available to WebSocket receive processing. TLS counts rustls plaintext slices; plain TCP counts recv/provided-buffer slices before optional copy batching."
)?;
writeln!(out, "# TYPE talaris_ingress_plaintext_chunks_total counter")?;
writeln!(
out,
"# HELP talaris_ingress_plaintext_bytes_total Plaintext bytes fed into the WebSocket parser."
)?;
writeln!(out, "# TYPE talaris_ingress_plaintext_bytes_total counter")?;
writeln!(
out,
"# HELP talaris_ingress_ws_data_drains_total Data-pump plaintext source chunks that reached WebSocket receive processing."
)?;
writeln!(out, "# TYPE talaris_ingress_ws_data_drains_total counter")?;
writeln!(
out,
"# HELP talaris_ingress_ws_data_drain_skips_total Data-pump drain attempts skipped because no plaintext arrived."
)?;
writeln!(
out,
"# TYPE talaris_ingress_ws_data_drain_skips_total counter"
)?;
writeln!(
out,
"# HELP talaris_ingress_ws_data_events_total Text/Binary data messages emitted to the user's data sink."
)?;
writeln!(out, "# TYPE talaris_ingress_ws_data_events_total counter")?;
writeln!(
out,
"# HELP talaris_ingress_ws_text_events_total Text messages emitted to the user's data sink."
)?;
writeln!(out, "# TYPE talaris_ingress_ws_text_events_total counter")?;
writeln!(
out,
"# HELP talaris_ingress_ws_binary_events_total Binary messages emitted to the user's data sink."
)?;
writeln!(out, "# TYPE talaris_ingress_ws_binary_events_total counter")
}
fn fail_conn(
conn: &mut ConnectionState,
err: ConnectionError,
first_err: &mut Option<ConnectionError>,
) -> bool {
let was_active = conn_is_active(conn);
tracing::warn!(conn_id = conn.conn_id(), error = %err, "pool conn failed");
conn.state = State::Closed;
if first_err.is_none() {
*first_err = Some(err);
}
was_active
}
#[inline]
fn conn_is_active(conn: &ConnectionState) -> bool {
!matches!(conn.state(), State::Closed)
}
#[inline]
fn account_closed_transition(active_count: &mut u32, was_active: bool, conn: &ConnectionState) {
if was_active && !conn_is_active(conn) {
*active_count = active_count.saturating_sub(1);
}
}
#[inline]
fn finish_conn_result<T>(
conn: &mut ConnectionState,
result: Result<T, ConnectionError>,
first_err: &mut Option<ConnectionError>,
active_count: &mut u32,
was_active: bool,
) -> Option<T> {
match result {
Ok(value) => {
conn.sync_ws_open_state();
conn.sync_ws_close_state();
account_closed_transition(active_count, was_active, conn);
Some(value)
}
Err(e) => {
fail_conn_and_account(conn, e, first_err, active_count, was_active);
None
}
}
}
#[inline]
fn fail_conn_and_account(
conn: &mut ConnectionState,
err: ConnectionError,
first_err: &mut Option<ConnectionError>,
active_count: &mut u32,
was_active: bool,
) {
let failed_active = fail_conn(conn, err, first_err);
if was_active || failed_active {
*active_count = active_count.saturating_sub(1);
}
}
impl Drop for Pool {
fn drop(&mut self) {
for slot in self.conns.iter_mut() {
if let Some(conn) = slot.as_mut()
&& let Some(mut ring) = conn.buf_ring.take()
{
let _ = ring.unregister(&mut self.proactor);
}
}
}
}
#[cfg(test)]
mod post_progress_tests {
use super::drain_post_progress;
#[test]
fn drain_post_progress_is_noop_without_budget() {
let mut calls = 0_u32;
let mut first_err = None::<()>;
drain_post_progress(0, &mut first_err, |_| {
calls += 1;
});
assert_eq!(calls, 0);
assert!(first_err.is_none());
}
#[test]
fn drain_post_progress_uses_full_budget_without_error() {
let mut calls = 0_u32;
let mut first_err = None::<()>;
drain_post_progress(4, &mut first_err, |_| {
calls += 1;
});
assert_eq!(calls, 4);
assert!(first_err.is_none());
}
#[test]
fn drain_post_progress_stops_after_first_error() {
let mut calls = 0_u32;
let mut first_err = None::<()>;
drain_post_progress(8, &mut first_err, |err| {
calls += 1;
if calls == 3 {
*err = Some(());
}
});
assert_eq!(calls, 3);
assert_eq!(first_err, Some(()));
}
}
#[cfg(all(test, target_os = "linux"))]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
mod tests {
use super::*;
use crate::connection_meta::{ConnectionConfig, State};
use crate::observability::MarkedDataEvent;
use crate::proactor::{OpKind, UserData};
use crate::test_helpers::{read_one_frame, run_echo_server};
use crate::ws::frame::{MAX_HEADER_LEN, encode_header};
use crate::ws::handshake::compute_accept;
use crate::ws::mask::mask_inplace;
use crate::ws::{DataEvent as WsDataEvent, OpCode, WsConfig};
use std::io::{Read, Write};
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
static COPY_BATCH_PAYLOAD: [u8; 512] = [b'x'; 512];
fn spawn_server<F>(f: F) -> (SocketAddr, thread::JoinHandle<()>)
where
F: FnOnce(TcpListener) + Send + 'static,
{
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let addr = listener.local_addr().unwrap();
(addr, thread::spawn(move || f(listener)))
}
fn accept_ws_upgrade(listener: TcpListener) -> TcpStream {
let (mut stream, _) = listener.accept().expect("accept");
stream.set_nodelay(true).unwrap();
let mut buf = [0_u8; 4096];
let mut req = Vec::new();
loop {
let n = stream.read(&mut buf).unwrap();
assert!(n > 0, "client closed before sending request");
req.extend_from_slice(&buf[..n]);
if req.windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}
let req_str = std::str::from_utf8(&req).unwrap();
let key = req_str
.lines()
.find(|line| line.to_ascii_lowercase().starts_with("sec-websocket-key:"))
.and_then(|line| line.split(':').nth(1))
.expect("Sec-WebSocket-Key header")
.trim();
let accept = compute_accept(key);
let resp = format!(
"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {accept}\r\n\r\n"
);
stream.write_all(resp.as_bytes()).unwrap();
stream
}
fn accept_ws_upgrade_recording_request(listener: TcpListener) -> (TcpStream, String) {
let (mut stream, _) = listener.accept().expect("accept");
stream.set_nodelay(true).unwrap();
let mut buf = [0_u8; 4096];
let mut req = Vec::new();
loop {
let n = stream.read(&mut buf).unwrap();
assert!(n > 0, "client closed before sending request");
req.extend_from_slice(&buf[..n]);
if req.windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}
let req_str = std::str::from_utf8(&req).unwrap().to_owned();
let key = req_str
.lines()
.find(|line| line.to_ascii_lowercase().starts_with("sec-websocket-key:"))
.and_then(|line| line.split(':').nth(1))
.expect("Sec-WebSocket-Key header")
.trim();
let accept = compute_accept(key);
let resp = format!(
"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {accept}\r\n\r\n"
);
stream.write_all(resp.as_bytes()).unwrap();
(stream, req_str)
}
fn run_request_assertion_server(listener: TcpListener) {
let (mut stream, req) = accept_ws_upgrade_recording_request(listener);
assert!(
req.starts_with("GET /real HTTP/1.1\r\n"),
"request was {req:?}"
);
assert!(req.contains("Host: localhost"), "request was {req:?}");
assert!(!req.contains("wrong-host"), "request was {req:?}");
assert!(!req.contains("GET /wrong"), "request was {req:?}");
let (opcode, _) = read_one_frame(&mut stream);
assert_eq!(opcode, OpCode::Close);
write_server_close(&mut stream);
}
fn write_server_frame(
out: &mut Vec<u8>,
opcode: OpCode,
payload: &[u8],
mask: Option<[u8; 4]>,
) {
let mut header = [0_u8; MAX_HEADER_LEN];
let hn = encode_header(&mut header, true, opcode, mask, payload.len() as u64);
out.extend_from_slice(&header[..hn]);
if let Some(mask_key) = mask {
let mut masked = payload.to_vec();
mask_inplace(&mut masked, mask_key);
out.extend_from_slice(&masked);
} else {
out.extend_from_slice(payload);
}
}
fn write_server_texts(stream: &mut TcpStream, messages: &[&[u8]]) {
let mut frames = Vec::new();
for message in messages {
write_server_frame(&mut frames, OpCode::Text, message, None);
}
stream.write_all(&frames).unwrap();
}
fn write_server_close(stream: &mut TcpStream) {
let mut frame = Vec::new();
write_server_frame(&mut frame, OpCode::Close, &1000_u16.to_be_bytes(), None);
stream.write_all(&frame).unwrap();
}
fn run_push_server(listener: TcpListener, messages: Vec<&'static [u8]>) {
let mut stream = accept_ws_upgrade(listener);
write_server_texts(&mut stream, &messages);
let (opcode, _) = read_one_frame(&mut stream);
assert_eq!(opcode, OpCode::Close);
write_server_close(&mut stream);
}
fn run_delayed_push_server(listener: TcpListener, messages: Vec<&'static [u8]>) {
let mut stream = accept_ws_upgrade(listener);
thread::sleep(Duration::from_millis(10));
write_server_texts(&mut stream, &messages);
let (opcode, _) = read_one_frame(&mut stream);
assert_eq!(opcode, OpCode::Close);
write_server_close(&mut stream);
}
fn run_idle_server(listener: TcpListener) {
let mut stream = accept_ws_upgrade(listener);
stream
.set_read_timeout(Some(Duration::from_millis(500)))
.unwrap();
let mut buf = [0_u8; 1024];
let _ = stream.read(&mut buf);
}
fn run_close_after_client_text_server(listener: TcpListener) {
let mut stream = accept_ws_upgrade(listener);
let (opcode, _) = read_one_frame(&mut stream);
assert_eq!(opcode, OpCode::Text);
write_server_close(&mut stream);
}
fn run_invalid_after_client_text_server(listener: TcpListener) {
let mut stream = accept_ws_upgrade(listener);
let (opcode, _) = read_one_frame(&mut stream);
assert_eq!(opcode, OpCode::Text);
let mut frame = Vec::new();
write_server_frame(
&mut frame,
OpCode::Text,
b"masked-from-server",
Some([1, 2, 3, 4]),
);
stream.write_all(&frame).unwrap();
}
fn plain_cfg(addr: SocketAddr, path: &str) -> ConnectionConfig {
ConnectionConfig::new("localhost", addr.port(), path).with_tls(false)
}
fn drive_until_open(pool: &mut Pool, handles: &[ConnHandle]) {
for _ in 0..500 {
pool.pump_nowait(|_, _| {}).unwrap();
if handles
.iter()
.all(|&handle| pool.state(handle) == Some(State::Open))
{
return;
}
thread::sleep(Duration::from_millis(1));
}
panic!("connections did not open");
}
fn drive_until_closed(pool: &mut Pool, handle: ConnHandle) {
for _ in 0..500 {
let _ = pool.pump_nowait(|_, _| {});
if pool.state(handle) == Some(State::Closed) {
return;
}
thread::sleep(Duration::from_millis(1));
}
panic!("connection did not close");
}
fn close_and_join(pool: &mut Pool, handle: ConnHandle, server: thread::JoinHandle<()>) {
pool.initiate_close(handle, 1000, "bye").unwrap();
drive_until_closed(pool, handle);
server.join().unwrap();
}
fn wait_for_text_event(pool: &mut Pool, handle: ConnHandle, expected: &str) {
let mut got: Option<String> = None;
for _ in 0..500 {
pool.pump_nowait(|event_handle, event| {
assert_eq!(event_handle, handle);
if let WsEvent::Text(text) = event {
got = Some(text.to_owned());
}
})
.unwrap();
if got.is_some() {
break;
}
thread::sleep(Duration::from_millis(1));
}
assert_eq!(got.as_deref(), Some(expected));
}
fn spin_until<F>(mut f: F)
where
F: FnMut() -> bool,
{
for _ in 0..500 {
if f() {
return;
}
thread::sleep(Duration::from_millis(1));
}
panic!("spin pump did not observe event");
}
#[test]
fn pool_single_conn_plain_ws_echo_roundtrip() {
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let local_addr = listener.local_addr().unwrap();
let (_shutdown_tx, shutdown_rx) = mpsc::channel::<()>();
let server = thread::spawn(move || run_echo_server(listener, shutdown_rx));
let cfg = ConnectionConfig::new("localhost", local_addr.port(), "/echo").with_tls(false);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let handle = pool.connect_blocking_to(cfg, local_addr).expect("connect");
assert_eq!(pool.state(handle), Some(State::Open));
pool.send_text(handle, b"hello").unwrap();
let mut got_text: Option<String> = None;
for _ in 0..50 {
pool.pump_data(|h, ev| {
assert_eq!(h, handle);
if let WsDataEvent::Text(s) = ev {
got_text = Some(s.to_owned());
}
})
.unwrap();
if got_text.is_some() {
break;
}
}
assert_eq!(got_text.as_deref(), Some("hello"));
pool.initiate_close(handle, 1000, "bye").unwrap();
for _ in 0..50 {
if matches!(pool.state(handle), Some(State::Closed | State::Closing)) {
let _ = pool.pump_nowait(|_, _| {});
}
if matches!(pool.state(handle), Some(State::Closed)) {
break;
}
let _ = pool.pump(|_, _| {});
}
server.join().unwrap();
}
#[test]
#[ignore = "需要外网 + test.deribit.com 可达;手动 --ignored 跑"]
fn tls_smoke_deribit_testnet() {
let cfg = ConnectionConfig::new("test.deribit.com", 443, "/ws/api/v2");
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let handle = pool
.connect_blocking(cfg)
.expect("tls handshake + ws upgrade");
assert_eq!(pool.state(handle), Some(State::Open));
eprintln!("TLS+WS handshake OK, sending public/test ...");
pool.send_text(
handle,
br#"{"jsonrpc":"2.0","id":1,"method":"public/test","params":{}}"#,
)
.unwrap();
let mut got = false;
for _ in 0..100 {
pool.pump(|_h, ev| {
if let WsEvent::Text(s) = ev {
eprintln!("got text: {s}");
got = true;
}
})
.unwrap();
if got {
break;
}
}
assert!(got, "no response from test.deribit.com");
pool.initiate_close(handle, 1000, "bye").unwrap();
for _ in 0..20 {
let _ = pool.pump_nowait(|_, _| {});
if matches!(pool.state(handle), Some(State::Closed)) {
break;
}
}
}
#[test]
fn pool_two_conns_no_cross_talk() {
let listener_a = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let addr_a = listener_a.local_addr().unwrap();
let listener_b = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let addr_b = listener_b.local_addr().unwrap();
let (_tx_a, rx_a) = mpsc::channel::<()>();
let (_tx_b, rx_b) = mpsc::channel::<()>();
let server_a = thread::spawn(move || run_echo_server(listener_a, rx_a));
let server_b = thread::spawn(move || run_echo_server(listener_b, rx_b));
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let cfg_a = ConnectionConfig::new("localhost", addr_a.port(), "/a").with_tls(false);
let cfg_b = ConnectionConfig::new("localhost", addr_b.port(), "/b").with_tls(false);
let h_a = pool.connect_blocking_to(cfg_a, addr_a).expect("connect a");
let h_b = pool.connect_blocking_to(cfg_b, addr_b).expect("connect b");
assert_eq!(pool.conn_count(), 2);
assert_ne!(h_a, h_b);
assert_eq!(pool.state(h_a), Some(State::Open));
assert_eq!(pool.state(h_b), Some(State::Open));
assert!(h_b.as_u32() > h_a.as_u32());
pool.send_text(h_a, b"alpha").unwrap();
pool.send_text(h_b, b"bravo").unwrap();
let mut a_text: Option<String> = None;
let mut b_text: Option<String> = None;
let mut wrong_route = false;
for _ in 0..200 {
pool.pump(|h, ev| {
if let WsEvent::Text(s) = ev {
if h == h_a {
if s != "alpha" {
wrong_route = true;
}
a_text = Some(s.to_owned());
} else if h == h_b {
if s != "bravo" {
wrong_route = true;
}
b_text = Some(s.to_owned());
} else {
wrong_route = true;
}
}
})
.unwrap();
if a_text.is_some() && b_text.is_some() {
break;
}
}
assert!(
!wrong_route,
"CQE 路由错位:handle 收到了不属于它的 payload"
);
assert_eq!(a_text.as_deref(), Some("alpha"));
assert_eq!(b_text.as_deref(), Some("bravo"));
pool.initiate_close(h_a, 1000, "bye").unwrap();
pool.initiate_close(h_b, 1000, "bye").unwrap();
for _ in 0..50 {
let _ = pool.pump_nowait(|_, _| {});
let done_a = matches!(pool.state(h_a), Some(State::Closed));
let done_b = matches!(pool.state(h_b), Some(State::Closed));
if done_a && done_b {
break;
}
let _ = pool.pump(|_, _| {});
}
server_a.join().unwrap();
server_b.join().unwrap();
}
#[test]
fn connect_blocking_to_does_not_drain_existing_connection_data() {
let (addr_a, server_a) =
spawn_server(|listener| run_push_server(listener, vec![b"pending-a"]));
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let h_a = pool
.connect_blocking_to(plain_cfg(addr_a, "/a"), addr_a)
.expect("connect a");
assert_eq!(pool.state(h_a), Some(State::Open));
let (addr_b, server_b) = spawn_server(|listener| run_push_server(listener, Vec::new()));
let h_b = pool
.connect_blocking_to(plain_cfg(addr_b, "/b"), addr_b)
.expect("connect b");
assert_eq!(pool.state(h_b), Some(State::Open));
assert_eq!(pool.conn_count(), 2);
wait_for_text_event(&mut pool, h_a, "pending-a");
close_and_join(&mut pool, h_a, server_a);
close_and_join(&mut pool, h_b, server_b);
}
#[test]
fn submit_connect_to_can_drive_multiple_handshakes_concurrently() {
let (addr_a, server_a) = spawn_server(|listener| run_push_server(listener, Vec::new()));
let (addr_b, server_b) = spawn_server(|listener| run_push_server(listener, Vec::new()));
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let h_a = pool
.submit_connect_to(plain_cfg(addr_a, "/a"), addr_a)
.expect("submit a");
let h_b = pool
.submit_connect_to(plain_cfg(addr_b, "/b"), addr_b)
.expect("submit b");
drive_until_open(&mut pool, &[h_a, h_b]);
assert_eq!(pool.conn_count(), 2);
assert_eq!(pool.state(h_a), Some(State::Open));
assert_eq!(pool.state(h_b), Some(State::Open));
close_and_join(&mut pool, h_a, server_a);
close_and_join(&mut pool, h_b, server_b);
}
#[test]
fn active_count_tracks_remote_close_parse_error_connect_failure_and_remove() {
let (close_addr, close_server) = spawn_server(run_close_after_client_text_server);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let close_handle = pool
.connect_blocking_to(plain_cfg(close_addr, "/close"), close_addr)
.expect("connect close server");
assert_eq!(pool.conn_count(), 1);
pool.send_text(close_handle, b"trigger").unwrap();
drive_until_closed(&mut pool, close_handle);
assert_eq!(pool.conn_count(), 0);
close_server.join().unwrap();
let (bad_addr, bad_server) = spawn_server(run_invalid_after_client_text_server);
let bad_handle = pool
.connect_blocking_to(plain_cfg(bad_addr, "/bad"), bad_addr)
.expect("connect bad server");
assert_eq!(pool.conn_count(), 1);
pool.send_text(bad_handle, b"trigger").unwrap();
let mut saw_error = false;
for _ in 0..500 {
if pool.pump_nowait(|_, _| {}).is_err() {
saw_error = true;
break;
}
thread::sleep(Duration::from_millis(1));
}
assert!(saw_error, "masked server frame must fail the connection");
assert_eq!(pool.state(bad_handle), Some(State::Closed));
assert_eq!(pool.conn_count(), 0);
bad_server.join().unwrap();
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let refused_addr = listener.local_addr().unwrap();
drop(listener);
let err = pool.connect_blocking_to(plain_cfg(refused_addr, "/refused"), refused_addr);
assert!(err.is_err(), "connecting to a closed listener should fail");
assert_eq!(pool.conn_count(), 0);
let (idle_addr, idle_server) = spawn_server(run_idle_server);
let idle_handle = pool
.connect_blocking_to(plain_cfg(idle_addr, "/idle"), idle_addr)
.expect("connect idle server");
assert_eq!(pool.conn_count(), 1);
pool.remove_conn(idle_handle).expect("remove idle conn");
assert_eq!(pool.conn_count(), 0);
assert_eq!(pool.state(idle_handle), None);
idle_server.join().unwrap();
}
#[test]
fn remove_conn_reuses_slot_with_new_generation_and_rejects_stale_handle() {
let (addr_a, server_a) = spawn_server(run_idle_server);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let old = pool
.connect_blocking_to(plain_cfg(addr_a, "/old"), addr_a)
.expect("connect old");
assert_eq!(pool.conn_count(), 1);
assert_eq!(pool.state(old), Some(State::Open));
assert_eq!(old.as_u32(), 0);
assert_eq!(old.generation(), 0);
pool.remove_conn(old).expect("remove old");
assert_eq!(pool.conn_count(), 0);
assert_eq!(pool.state(old), None);
assert!(pool.send_text(old, b"stale").is_err());
server_a.join().unwrap();
let (addr_b, server_b) = spawn_server(run_idle_server);
let new = pool
.connect_blocking_to(plain_cfg(addr_b, "/new"), addr_b)
.expect("connect new");
assert_eq!(pool.conn_count(), 1);
assert_eq!(pool.state(new), Some(State::Open));
assert_eq!(new.as_u32(), old.as_u32());
assert_eq!(new.generation(), old.generation() + 1);
assert_ne!(new, old);
assert_eq!(pool.state(old), None);
assert!(pool.send_text(old, b"still stale").is_err());
close_and_join(&mut pool, new, server_b);
}
#[test]
fn stale_completion_from_removed_generation_is_ignored_after_slot_reuse() {
let (addr_a, server_a) = spawn_server(run_idle_server);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let old = pool
.connect_blocking_to(plain_cfg(addr_a, "/old"), addr_a)
.expect("connect old");
let old_token = old.as_u64();
pool.remove_conn(old).expect("remove old");
server_a.join().unwrap();
let (addr_b, server_b) = spawn_server(run_idle_server);
let new = pool
.connect_blocking_to(plain_cfg(addr_b, "/new"), addr_b)
.expect("connect new");
assert_eq!(new.as_u32(), old.as_u32());
assert_ne!(new.as_u64(), old_token);
let stale = Completion {
user_data: UserData::new(OpKind::Recv, old_token),
result: -libc::ECANCELED,
flags: 0,
};
let mut first_err = None;
let Pool {
proactor,
conns,
active_count,
..
} = &mut pool;
dispatch_conn_completions_data(
conns,
proactor,
&[stale],
active_count,
&mut |_, _| panic!("stale completion must not reach sink"),
&mut first_err,
);
assert!(first_err.is_none());
assert_eq!(pool.state(new), Some(State::Open));
assert_eq!(pool.conn_count(), 1);
close_and_join(&mut pool, new, server_b);
}
#[test]
fn submit_reconnect_reuses_slot_and_invalidates_old_handle() {
let (addr_a, server_a) = spawn_server(run_idle_server);
let (addr_b, server_b) = spawn_server(run_idle_server);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let old = pool
.connect_blocking_to(plain_cfg(addr_a, "/old"), addr_a)
.expect("connect old");
let new = pool
.submit_reconnect_to(old, plain_cfg(addr_b, "/new"), addr_b)
.expect("submit reconnect");
assert_eq!(pool.state(old), None);
assert_eq!(new.as_u32(), old.as_u32());
assert_eq!(new.generation(), old.generation() + 1);
assert_eq!(pool.conn_count(), 1);
server_a.join().unwrap();
drive_until_open(&mut pool, &[new]);
assert_eq!(pool.state(new), Some(State::Open));
assert!(pool.send_text(old, b"stale").is_err());
close_and_join(&mut pool, new, server_b);
}
#[test]
fn reconnect_failure_retires_new_slot_and_keeps_pool_count_consistent() {
let (addr, server) = spawn_server(run_idle_server);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let old = pool
.connect_blocking_to(plain_cfg(addr, "/old"), addr)
.expect("connect old");
assert_eq!(pool.conn_count(), 1);
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let refused_addr = listener.local_addr().unwrap();
drop(listener);
let err = pool.reconnect_to(old, plain_cfg(refused_addr, "/refused"), refused_addr);
assert!(err.is_err(), "reconnect to a closed listener should fail");
assert_eq!(pool.state(old), None);
assert_eq!(pool.conn_count(), 0);
server.join().unwrap();
let (addr_next, server_next) = spawn_server(run_idle_server);
let next = pool
.connect_blocking_to(plain_cfg(addr_next, "/next"), addr_next)
.expect("connect after failed reconnect");
assert_eq!(next.as_u32(), old.as_u32());
assert!(next.generation() > old.generation());
assert_eq!(pool.conn_count(), 1);
close_and_join(&mut pool, next, server_next);
}
#[test]
fn pool_data_batch_and_marked_pumps_preserve_handle_routing() {
let (batch_addr, batch_server) = spawn_server(|listener| {
run_delayed_push_server(listener, vec![b"batch-a", b"batch-b"]);
});
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let batch_handle = pool
.connect_blocking_to(plain_cfg(batch_addr, "/batch"), batch_addr)
.expect("connect batch server");
let mut batch_texts = Vec::new();
for _ in 0..500 {
pool.pump_data_batches_nowait(|event_handle, batch| {
assert_eq!(event_handle, batch_handle);
assert!(!batch.is_empty());
for event in batch.iter() {
if let WsDataEvent::Text(text) = event {
batch_texts.push(text.to_owned());
}
}
})
.unwrap();
if batch_texts.len() >= 2 {
break;
}
thread::sleep(Duration::from_millis(1));
}
assert_eq!(batch_texts, ["batch-a", "batch-b"]);
close_and_join(&mut pool, batch_handle, batch_server);
let (marked_addr, marked_server) = spawn_server(|listener| {
run_delayed_push_server(listener, vec![b"marked"]);
});
let marked_cfg = plain_cfg(marked_addr, "/marked")
.with_observability_sample_rate_bps(10_000)
.with_observability_histograms(true);
let marked_handle = pool
.connect_blocking_to(marked_cfg, marked_addr)
.expect("connect marked server");
let mut marked_text: Option<String> = None;
let mut marked_sampled = false;
for _ in 0..500 {
pool.pump_data_marked_nowait(|event_handle, event| {
assert_eq!(event_handle, marked_handle);
if let MarkedDataEvent::Text { payload, meta } = event {
marked_text = Some(payload.to_owned());
marked_sampled = meta.sampled;
}
})
.unwrap();
if marked_text.is_some() {
break;
}
thread::sleep(Duration::from_millis(1));
}
assert_eq!(marked_text.as_deref(), Some("marked"));
assert!(marked_sampled);
close_and_join(&mut pool, marked_handle, marked_server);
}
#[test]
fn connection_config_endpoint_overrides_inner_ws_config_at_handshake() {
let (addr, server) = spawn_server(run_request_assertion_server);
let ws = WsConfig::new("wrong-host", "/wrong").with_initial_buffer_capacities(11, 22, 33);
let cfg = plain_cfg(addr, "/real").with_ws_config(ws);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let handle = pool.connect_blocking_to(cfg, addr).expect("connect");
assert_eq!(pool.state(handle), Some(State::Open));
close_and_join(&mut pool, handle, server);
}
#[test]
fn plain_copy_batch_records_ingress_stats() {
let (addr, server) = spawn_server(|listener| {
run_delayed_push_server(listener, vec![©_BATCH_PAYLOAD]);
});
let cfg = plain_cfg(addr, "/copy-batch")
.with_buf_ring(64, 64)
.with_plain_recv_batch_copy_max_bytes(4096)
.with_ingress_stats(true);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let handle = pool.connect_blocking_to(cfg, addr).expect("connect");
let mut got_len = None;
for _ in 0..500 {
pool.pump_data_nowait(|event_handle, event| {
assert_eq!(event_handle, handle);
if let WsDataEvent::Text(text) = event {
got_len = Some(text.len());
}
})
.unwrap();
if got_len.is_some() {
break;
}
thread::sleep(Duration::from_millis(1));
}
assert_eq!(got_len, Some(COPY_BATCH_PAYLOAD.len()));
let stats = pool.ingress_stats(handle).expect("stats");
assert!(stats.recv_data_cqes > 1, "stats: {stats:?}");
assert!(stats.plain_recv_batches > 0, "stats: {stats:?}");
assert!(stats.plain_recv_batch_cqes > 1, "stats: {stats:?}");
assert!(stats.plain_recv_copied_batches > 0, "stats: {stats:?}");
assert!(
stats.plain_recv_copied_bytes >= COPY_BATCH_PAYLOAD.len() as u64,
"stats: {stats:?}"
);
close_and_join(&mut pool, handle, server);
}
#[test]
#[ignore = "requires Linux 6.10+ IORING_RECVSEND_BUNDLE support"]
fn multishot_bundle_plain_ws_echo_roundtrip() {
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0)).unwrap();
let local_addr = listener.local_addr().unwrap();
let (_shutdown_tx, shutdown_rx) = mpsc::channel::<()>();
let server = thread::spawn(move || run_echo_server(listener, shutdown_rx));
let cfg = ConnectionConfig::new("localhost", local_addr.port(), "/echo")
.with_tls(false)
.with_recv_mode(crate::connection_meta::RecvMode::MultishotBundle);
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let handle = pool.connect_blocking_to(cfg, local_addr).expect("connect");
assert_eq!(pool.state(handle), Some(State::Open));
pool.send_text(handle, b"bundle").unwrap();
wait_for_text_event(&mut pool, handle, "bundle");
close_and_join(&mut pool, handle, server);
}
#[test]
fn pool_spin_data_pumps_preserve_handle_routing() {
let (data_addr, data_server) = spawn_server(|listener| {
run_delayed_push_server(listener, vec![b"spin-data"]);
});
let mut pool = Pool::new(PoolConfig::default()).expect("pool");
let data_handle = pool
.connect_blocking_to(plain_cfg(data_addr, "/spin-data"), data_addr)
.expect("connect spin data server");
let mut data_text: Option<String> = None;
spin_until(|| {
pool.pump_data_spin(1024, |event_handle, event| {
assert_eq!(event_handle, data_handle);
if let WsDataEvent::Text(text) = event {
data_text = Some(text.to_owned());
}
})
.unwrap();
data_text.is_some()
});
assert_eq!(data_text.as_deref(), Some("spin-data"));
close_and_join(&mut pool, data_handle, data_server);
let (batch_addr, batch_server) = spawn_server(|listener| {
run_delayed_push_server(listener, vec![b"spin-batch"]);
});
let batch_handle = pool
.connect_blocking_to(plain_cfg(batch_addr, "/spin-batch"), batch_addr)
.expect("connect spin batch server");
let mut batch_text: Option<String> = None;
spin_until(|| {
pool.pump_data_spin_batches(1024, |event_handle, batch| {
assert_eq!(event_handle, batch_handle);
for event in batch.iter() {
if let WsDataEvent::Text(text) = event {
batch_text = Some(text.to_owned());
}
}
})
.unwrap();
batch_text.is_some()
});
assert_eq!(batch_text.as_deref(), Some("spin-batch"));
close_and_join(&mut pool, batch_handle, batch_server);
let (marked_addr, marked_server) = spawn_server(|listener| {
run_delayed_push_server(listener, vec![b"spin-marked"]);
});
let marked_cfg =
plain_cfg(marked_addr, "/spin-marked").with_observability_sample_rate_bps(10_000);
let marked_handle = pool
.connect_blocking_to(marked_cfg, marked_addr)
.expect("connect spin marked server");
let mut marked_text: Option<String> = None;
let mut marked_sampled = false;
spin_until(|| {
pool.pump_data_spin_marked(1024, |event_handle, event| {
assert_eq!(event_handle, marked_handle);
if let MarkedDataEvent::Text { payload, meta } = event {
marked_text = Some(payload.to_owned());
marked_sampled = meta.sampled;
}
})
.unwrap();
marked_text.is_some()
});
assert_eq!(marked_text.as_deref(), Some("spin-marked"));
assert!(marked_sampled);
close_and_join(&mut pool, marked_handle, marked_server);
let (marked_batch_addr, marked_batch_server) = spawn_server(|listener| {
run_delayed_push_server(listener, vec![b"spin-marked-batch"]);
});
let marked_batch_cfg = plain_cfg(marked_batch_addr, "/spin-marked-batch")
.with_observability_sample_rate_bps(10_000);
let marked_batch_handle = pool
.connect_blocking_to(marked_batch_cfg, marked_batch_addr)
.expect("connect spin marked batch server");
let mut marked_batch_text: Option<String> = None;
let mut marked_batch_sampled = false;
spin_until(|| {
pool.pump_data_spin_marked_batches(1024, |event_handle, batch| {
assert_eq!(event_handle, marked_batch_handle);
for event in batch.iter() {
if let MarkedDataEvent::Text { payload, meta } = event {
marked_batch_text = Some(payload.to_owned());
marked_batch_sampled = meta.sampled;
}
}
})
.unwrap();
marked_batch_text.is_some()
});
assert_eq!(marked_batch_text.as_deref(), Some("spin-marked-batch"));
assert!(marked_batch_sampled);
close_and_join(&mut pool, marked_batch_handle, marked_batch_server);
}
}