use std::cell::UnsafeCell;
use std::collections::{HashMap, VecDeque};
#[cfg(feature = "io-uring")]
use std::sync::OnceLock;
use std::sync::{
Arc, Weak,
atomic::{AtomicBool, AtomicU64, Ordering},
};
use parking_lot::RwLock;
use fibre::mpmc::{AsyncReceiver, AsyncSender, bounded_async};
use fibre::spsc;
use fibre::{RecvError, TryRecvError, TrySendError};
use crate::error::ZmqError;
use crate::message::FrameBatch;
use crate::socket::patterns::sub_matcher::{PrefixMatcher, SubscriptionMatcher};
use crate::log_rpq_spin_deadlock;
#[cfg(feature = "io-uring")]
use crate::io_uring_backend::ops::{WAKEUP_STATE_SIGNALED, WAKEUP_STATE_SLEEPING};
#[cfg(feature = "io-uring")]
#[derive(Clone)]
pub(crate) struct UringWakeup {
pub event_fd: eventfd::EventFD,
pub worker_asleep: Arc<std::sync::atomic::AtomicU8>,
}
struct ExclusiveCell<T>(UnsafeCell<T>);
unsafe impl<T: Send> Send for ExclusiveCell<T> {}
unsafe impl<T: Send> Sync for ExclusiveCell<T> {}
impl<T> ExclusiveCell<T> {
fn new(v: T) -> Self {
Self(UnsafeCell::new(v))
}
#[allow(clippy::mut_from_ref)]
unsafe fn get_mut(&self) -> &mut T {
unsafe { &mut *self.0.get() }
}
}
pub(crate) fn pipe_lwm(capacity: usize, drain_delta: usize) -> usize {
(capacity / 2).max(capacity.saturating_sub(drain_delta))
}
const RESERVED_ONE: u64 = 1 << 32;
const QUEUED_ONE: u64 = 1;
#[inline(always)]
fn queued_of(counts: u64) -> usize {
(counts & u32::MAX as u64) as usize
}
#[inline(always)]
fn reserved_of(counts: u64) -> usize {
(counts >> 32) as usize
}
#[inline]
fn audit_slot<T: Send + 'static>(slot: &PipeSlot<T>, site: &str, occupancy: impl FnOnce() -> usize) {
#[cfg(feature = "diagnostics")]
{
let occ = occupancy();
let counts = slot.counts.load(Ordering::Acquire);
let reserved = reserved_of(counts);
if reserved < occ && !slot.audit_reported.swap(true, Ordering::AcqRel) {
let queued = queued_of(counts);
println!(
"[RPQ-DESYNC pid={} pipe={} site={}] reserved({}) < occupancy({}) \
— item(s) in channel with no backing reservation; queued={}",
std::process::id(),
slot.pipe_id,
site,
reserved,
occ,
queued,
);
}
}
#[cfg(not(feature = "diagnostics"))]
let _ = (slot, site, occupancy);
}
pub(crate) struct PipeSlot<T: Send + 'static> {
pub(crate) pipe_id: usize,
tx: ExclusiveCell<spsc::BoundedAsyncSender<T>>,
rx: ExclusiveCell<spsc::BoundedAsyncReceiver<T>>,
capacity: usize,
pub(crate) counts: AtomicU64,
pub(crate) lwm: usize,
#[allow(dead_code)]
pub(crate) audit_reported: AtomicBool,
#[cfg(feature = "diagnostics")]
pub(crate) total_committed: AtomicU64,
#[cfg(feature = "diagnostics")]
pub(crate) total_popped: AtomicU64,
#[cfg(feature = "io-uring")]
pub(crate) uring_wakeup: Arc<OnceLock<UringWakeup>>,
}
#[cfg(feature = "diagnostics")]
impl<T: Send + 'static> Drop for PipeSlot<T> {
fn drop(&mut self) {
let committed = self.total_committed.load(Ordering::Relaxed);
let popped = self.total_popped.load(Ordering::Relaxed);
if committed != popped {
tracing::warn!(
pipe_id = self.pipe_id,
committed,
popped,
stranded = committed - popped,
"[MSG-CONSERVATION] PipeSlot dropped with undelivered messages"
);
} else {
tracing::debug!(
pipe_id = self.pipe_id,
committed,
"[MSG-CONSERVATION] PipeSlot dropped clean (committed == popped)"
);
}
}
}
impl<T: Send + 'static> PipeSlot<T> {
pub fn len(&self) -> usize {
queued_of(self.counts.load(Ordering::Acquire))
}
pub fn reserved(&self) -> usize {
reserved_of(self.counts.load(Ordering::Acquire))
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn is_congested(&self) -> bool {
self.len() >= self.capacity()
}
pub fn is_drained(&self) -> bool {
self.len() < self.lwm
}
}
struct SendReservation<T: Send + 'static> {
slot: Arc<PipeSlot<T>>,
committed: bool,
}
impl<T: Send + 'static> SendReservation<T> {
fn new(slot: Arc<PipeSlot<T>>) -> Self {
slot.counts.fetch_add(RESERVED_ONE, Ordering::AcqRel);
Self {
slot,
committed: false,
}
}
fn commit(&mut self) {
self.committed = true;
}
}
impl<T: Send + 'static> Drop for SendReservation<T> {
fn drop(&mut self) {
if !self.committed {
self.slot.counts.fetch_sub(RESERVED_ONE, Ordering::AcqRel);
}
}
}
fn push_ready_token<T: Send + 'static>(
ready_tx: &AsyncSender<Arc<PipeSlot<T>>>,
slot: &Arc<PipeSlot<T>>,
site: &str,
) -> bool {
let mut spins = 0usize;
loop {
match ready_tx.try_send(Arc::clone(slot)) {
Ok(()) => return true,
Err(TrySendError::Full(_)) => {
spins += 1;
log_rpq_spin_deadlock!(spins, site, "Full");
std::thread::yield_now();
}
Err(TrySendError::Closed(_)) => return false,
Err(TrySendError::Sent(_)) => unreachable!(),
}
}
}
pub(crate) struct ReadyPipeQueue<T: Send + 'static> {
pub(crate) pipes: Arc<RwLock<HashMap<usize, Arc<PipeSlot<T>>>>>,
pub(crate) ready_rx: AsyncReceiver<Arc<PipeSlot<T>>>,
ready_tx: AsyncSender<Arc<PipeSlot<T>>>,
}
impl<T: Send + 'static> ReadyPipeQueue<T> {
pub fn new(ready_capacity: usize) -> Self {
let (tx, rx) = bounded_async((ready_capacity * 2).max(1));
Self {
pipes: Arc::new(RwLock::new(HashMap::new())),
ready_rx: rx,
ready_tx: tx,
}
}
pub fn register_pipe(
&self,
pipe_id: usize,
capacity: usize,
drain_delta: usize,
) -> ReadyPipeSender<T> {
let mut pipes = self.pipes.write();
if let Some(slot) = pipes.get(&pipe_id) {
return ReadyPipeSender {
slot: Arc::downgrade(slot),
ready_tx: self.ready_tx.clone(),
};
}
let (tx, rx) = spsc::bounded_async(capacity.max(1));
#[cfg(feature = "io-uring")]
let uring_wakeup = Arc::new(OnceLock::new());
let slot = Arc::new(PipeSlot {
pipe_id,
tx: ExclusiveCell::new(tx),
rx: ExclusiveCell::new(rx),
capacity: capacity.max(1),
counts: AtomicU64::new(0),
lwm: pipe_lwm(capacity, drain_delta),
audit_reported: AtomicBool::new(false),
#[cfg(feature = "diagnostics")]
total_committed: AtomicU64::new(0),
#[cfg(feature = "diagnostics")]
total_popped: AtomicU64::new(0),
#[cfg(feature = "io-uring")]
uring_wakeup,
});
pipes.insert(pipe_id, Arc::clone(&slot));
ReadyPipeSender {
slot: Arc::downgrade(&slot),
ready_tx: self.ready_tx.clone(),
}
}
pub fn deregister_pipe(&self, pipe_id: usize) {
let removed = self.pipes.write().remove(&pipe_id);
#[cfg(feature = "diagnostics")]
if let Some(slot) = &removed {
let queued = slot.len();
let refs = Arc::strong_count(slot);
if queued > 0 && refs == 1 {
tracing::warn!(
pipe_id,
queued,
committed = slot.total_committed.load(Ordering::Relaxed),
popped = slot.total_popped.load(Ordering::Relaxed),
"[MSG-CONSERVATION] deregister_pipe: messages queued but NO ready token references the slot — unreachable"
);
} else {
tracing::debug!(
pipe_id,
queued,
strong_refs = refs,
committed = slot.total_committed.load(Ordering::Relaxed),
popped = slot.total_popped.load(Ordering::Relaxed),
"[MSG-CONSERVATION] deregister_pipe"
);
}
}
drop(removed);
}
pub async fn pop(&self) -> Result<(usize, T), ZmqError> {
loop {
let slot = match self.ready_rx.recv().await {
Ok(s) => s,
Err(RecvError::Disconnected) => {
return Err(ZmqError::InvalidState("ready queue closed"));
}
};
match unsafe { slot.rx.get_mut() }.try_recv() {
Ok(item) => {
let prev = slot
.counts
.fetch_sub(RESERVED_ONE + QUEUED_ONE, Ordering::AcqRel);
let prev_queued = queued_of(prev);
debug_assert!(prev_queued > 0);
#[cfg(feature = "diagnostics")]
slot.total_popped.fetch_add(1, Ordering::Relaxed);
audit_slot(&slot, "pop", || unsafe { slot.rx.get_mut() }.len());
if prev_queued > 1 {
push_ready_token(&self.ready_tx, &slot, "pop spinning on ready_tx");
}
#[cfg(feature = "io-uring")]
if slot.is_drained() {
if let Some(wakeup) = slot.uring_wakeup.get() {
if wakeup.worker_asleep.load(Ordering::Relaxed) == WAKEUP_STATE_SLEEPING {
if wakeup
.worker_asleep
.compare_exchange(
WAKEUP_STATE_SLEEPING,
WAKEUP_STATE_SIGNALED,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
let _ = wakeup.event_fd.write(1);
}
}
}
}
return Ok((slot.pipe_id, item));
}
Err(_e @ TryRecvError::Empty) | Err(_e @ TryRecvError::Disconnected) => {
#[cfg(feature = "diagnostics")]
if slot.len() > 0 {
tracing::warn!(
pipe_id = slot.pipe_id,
queued = slot.len(),
error = ?_e,
"[MSG-CONSERVATION] pop: ready token discarded while messages are committed"
);
}
continue;
}
}
}
}
pub fn try_pop(&self) -> Option<(usize, T)> {
loop {
let slot = match self.ready_rx.try_recv() {
Ok(s) => s,
Err(_) => return None,
};
match unsafe { slot.rx.get_mut() }.try_recv() {
Ok(item) => {
let prev = slot
.counts
.fetch_sub(RESERVED_ONE + QUEUED_ONE, Ordering::AcqRel);
let prev_queued = queued_of(prev);
debug_assert!(prev_queued > 0);
#[cfg(feature = "diagnostics")]
slot.total_popped.fetch_add(1, Ordering::Relaxed);
audit_slot(&slot, "try_pop", || unsafe { slot.rx.get_mut() }.len());
if prev_queued > 1 {
push_ready_token(&self.ready_tx, &slot, "try_pop spinning on ready_tx");
}
#[cfg(feature = "io-uring")]
if slot.is_drained() {
if let Some(wakeup) = slot.uring_wakeup.get() {
if wakeup.worker_asleep.load(Ordering::Relaxed) == WAKEUP_STATE_SLEEPING {
if wakeup
.worker_asleep
.compare_exchange(
WAKEUP_STATE_SLEEPING,
WAKEUP_STATE_SIGNALED,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
let _ = wakeup.event_fd.write(1);
}
}
}
}
return Some((slot.pipe_id, item));
}
Err(_e @ TryRecvError::Empty) => {
#[cfg(feature = "diagnostics")]
if slot.len() > 0 {
tracing::warn!(
pipe_id = slot.pipe_id,
queued = slot.len(),
error = ?_e,
"[MSG-CONSERVATION] try_pop: ready token discarded while messages are committed"
);
}
return None;
}
Err(_e @ TryRecvError::Disconnected) => {
#[cfg(feature = "diagnostics")]
if slot.len() > 0 {
tracing::warn!(
pipe_id = slot.pipe_id,
queued = slot.len(),
error = ?_e,
"[MSG-CONSERVATION] try_pop: ready token discarded while messages are committed"
);
}
continue;
}
}
}
}
pub async fn pop_batch(&self, out: &mut Vec<T>, max: usize) -> Result<(usize, usize), ZmqError> {
loop {
let slot = match self.ready_rx.recv().await {
Ok(s) => s,
Err(RecvError::Disconnected) => {
return Err(ZmqError::InvalidState("ready queue closed"));
}
};
if let Some(res) = self.drain_slot(&slot, out, max) {
return Ok(res);
}
}
}
pub fn try_pop_batch(&self, out: &mut Vec<T>, max: usize) -> Option<(usize, usize)> {
loop {
let slot = match self.ready_rx.try_recv() {
Ok(s) => s,
Err(_) => return None,
};
if let Some(res) = self.drain_slot(&slot, out, max) {
return Some(res);
}
}
}
fn drain_slot(&self, slot: &Arc<PipeSlot<T>>, out: &mut Vec<T>, max: usize) -> Option<(usize, usize)> {
let committed = slot.len();
let cap = committed.min(max.max(1));
if cap == 0 {
return None;
}
let got = match unsafe { slot.rx.get_mut() }.try_recv_batch_mut(out, cap) {
Ok(n) => n,
Err(_e @ TryRecvError::Empty) | Err(_e @ TryRecvError::Disconnected) => {
#[cfg(feature = "diagnostics")]
tracing::warn!(
pipe_id = slot.pipe_id,
committed,
error = ?_e,
"[MSG-CONSERVATION] drain_slot: ready token discarded while messages are committed"
);
return None;
}
};
debug_assert!(got > 0 && got <= committed);
let prev = slot
.counts
.fetch_sub(got as u64 * (RESERVED_ONE + QUEUED_ONE), Ordering::AcqRel);
#[cfg(feature = "diagnostics")]
slot.total_popped.fetch_add(got as u64, Ordering::Relaxed);
audit_slot(slot, "pop_batch", || unsafe { slot.rx.get_mut() }.len());
if queued_of(prev) > got {
push_ready_token(&self.ready_tx, slot, "pop_batch spinning on ready_tx");
}
#[cfg(feature = "io-uring")]
if slot.is_drained() {
if let Some(wakeup) = slot.uring_wakeup.get() {
if wakeup.worker_asleep.load(Ordering::Relaxed) == WAKEUP_STATE_SLEEPING {
if wakeup
.worker_asleep
.compare_exchange(
WAKEUP_STATE_SLEEPING,
WAKEUP_STATE_SIGNALED,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
let _ = wakeup.event_fd.write(1);
}
}
}
}
Some((slot.pipe_id, got))
}
pub fn close(&self) {
self.pipes.write().clear();
self.ready_tx.close();
}
}
pub(crate) struct ReadyPipeSender<T: Send + 'static> {
slot: Weak<PipeSlot<T>>,
ready_tx: AsyncSender<Arc<PipeSlot<T>>>,
}
impl<T: Send + 'static> ReadyPipeSender<T> {
#[cfg(feature = "io-uring")]
pub fn bind_uring_wakeup(&self, wakeup: UringWakeup) {
if let Some(slot) = self.slot.upgrade() {
let _ = slot.uring_wakeup.set(wakeup);
}
}
pub async fn send(&self, item: T) -> Result<(), ZmqError> {
let slot = self.slot.upgrade().ok_or(ZmqError::ConnectionClosed)?;
let mut reservation = SendReservation::new(Arc::clone(&slot));
let tx = unsafe { slot.tx.get_mut() };
match tx.try_send(item) {
Ok(()) => {}
Err(TrySendError::Closed(_)) => return Err(ZmqError::ConnectionClosed),
Err(TrySendError::Full(returned)) => {
tx.send(returned).await.map_err(|_| ZmqError::ConnectionClosed)?;
}
Err(TrySendError::Sent(_)) => unreachable!(),
}
let prev = slot.counts.fetch_add(QUEUED_ONE, Ordering::AcqRel);
#[cfg(feature = "diagnostics")]
slot.total_committed.fetch_add(1, Ordering::Relaxed);
reservation.commit();
if queued_of(prev) == 0
&& !push_ready_token(&self.ready_tx, &slot, "send spinning on ready_tx")
{
return Err(ZmqError::ConnectionClosed);
}
audit_slot(&slot, "send", || slot.len());
Ok(())
}
pub fn try_send(&self, item: T) -> Result<(), TrySendError<T>> {
let slot = match self.slot.upgrade() {
Some(s) => s,
None => return Err(TrySendError::Closed(item)),
};
let mut reservation = SendReservation::new(Arc::clone(&slot));
unsafe { slot.tx.get_mut() }.try_send(item)?;
let prev = slot.counts.fetch_add(QUEUED_ONE, Ordering::AcqRel);
#[cfg(feature = "diagnostics")]
slot.total_committed.fetch_add(1, Ordering::Relaxed);
reservation.commit();
if queued_of(prev) == 0 {
push_ready_token(&self.ready_tx, &slot, "try_send spinning on ready_tx");
}
audit_slot(&slot, "try_send", || slot.len());
Ok(())
}
pub fn try_send_batch(&self, items: &mut VecDeque<T>, get_weight: impl Fn(&T) -> usize) -> usize {
let slot = match self.slot.upgrade() {
Some(s) => s,
None => return 0,
};
let n = items.len();
if n == 0 {
return 0;
}
slot
.counts
.fetch_add(n as u64 * RESERVED_ONE, Ordering::AcqRel);
let mut sent_batches = 0usize;
let mut total_weight = 0usize;
let mut had_zero_transition = false;
let tx = unsafe { slot.tx.get_mut() };
while let Some(item) = items.pop_front() {
let weight = get_weight(&item);
match tx.try_send(item) {
Ok(()) => {
sent_batches += 1;
total_weight += weight;
let prev = slot.counts.fetch_add(QUEUED_ONE, Ordering::AcqRel);
if queued_of(prev) == 0 {
had_zero_transition = true;
}
}
Err(TrySendError::Full(returned)) => {
items.push_front(returned);
break;
}
Err(TrySendError::Closed(returned)) => {
items.push_front(returned);
break;
}
Err(TrySendError::Sent(_)) => unreachable!(),
}
}
if sent_batches < n {
slot
.counts
.fetch_sub((n - sent_batches) as u64 * RESERVED_ONE, Ordering::AcqRel);
}
#[cfg(feature = "diagnostics")]
slot
.total_committed
.fetch_add(sent_batches as u64, Ordering::Relaxed);
if had_zero_transition {
push_ready_token(&self.ready_tx, &slot, "try_send_batch spinning on ready_tx");
}
audit_slot(&slot, "try_send_batch", || slot.len());
total_weight
}
pub async fn send_batch_mut(&self, items: &mut Vec<T>) -> Result<usize, ZmqError> {
let slot = self.slot.upgrade().ok_or(ZmqError::ConnectionClosed)?;
let mut total_sent = 0;
let tx = unsafe { slot.tx.get_mut() };
while !items.is_empty() {
let sent_this_pass = match tx.try_send_batch_mut(items) {
Ok(n) => n,
Err(fibre::SendError::Closed) => return Err(ZmqError::ConnectionClosed),
Err(fibre::SendError::Sent) => unreachable!(),
};
if sent_this_pass > 0 {
total_sent += sent_this_pass;
let prev = slot.counts.fetch_add(
sent_this_pass as u64 * (RESERVED_ONE + QUEUED_ONE),
Ordering::AcqRel,
);
#[cfg(feature = "diagnostics")]
slot
.total_committed
.fetch_add(sent_this_pass as u64, Ordering::Relaxed);
if queued_of(prev) == 0
&& !push_ready_token(&self.ready_tx, &slot, "send_batch_mut spinning on ready_tx")
{
return Err(ZmqError::ConnectionClosed);
}
audit_slot(&slot, "send_batch_mut_sync_pass", || slot.len());
}
if items.is_empty() {
break;
}
let mut temp = vec![items.remove(0)];
struct WaitGuard<'a, T> {
items: &'a mut Vec<T>,
temp: &'a mut Vec<T>,
}
impl<'a, T> Drop for WaitGuard<'a, T> {
fn drop(&mut self) {
if !self.temp.is_empty() {
self.items.insert(0, self.temp.remove(0));
}
}
}
let guard = WaitGuard {
items: &mut *items,
temp: &mut temp,
};
if tx.send_batch_mut(guard.temp).await.is_err() {
return Err(ZmqError::ConnectionClosed);
}
drop(guard);
total_sent += 1;
let prev = slot
.counts
.fetch_add(RESERVED_ONE + QUEUED_ONE, Ordering::AcqRel);
#[cfg(feature = "diagnostics")]
slot.total_committed.fetch_add(1, Ordering::Relaxed);
if queued_of(prev) == 0
&& !push_ready_token(&self.ready_tx, &slot, "send_batch_mut spinning on ready_tx")
{
return Err(ZmqError::ConnectionClosed);
}
audit_slot(&slot, "send_batch_mut_async_pass", || slot.len());
}
Ok(total_sent)
}
pub fn queued_count(&self) -> usize {
self.slot.upgrade().map(|s| s.len()).unwrap_or(0)
}
pub fn reserved_count(&self) -> usize {
self.slot.upgrade().map(|s| s.reserved()).unwrap_or(0)
}
pub fn len(&self) -> usize {
self.slot.upgrade().map(|s| s.len()).unwrap_or(0)
}
pub fn capacity(&self) -> usize {
self
.slot
.upgrade()
.map(|s| s.capacity())
.unwrap_or(usize::MAX)
}
pub fn is_congested(&self) -> bool {
self
.slot
.upgrade()
.map(|s| s.is_congested())
.unwrap_or(false)
}
pub fn is_drained(&self) -> bool {
self.slot.upgrade().map(|s| s.is_drained()).unwrap_or(true)
}
}
pub(crate) enum PipeMessageSender {
DirectAnonymous(ReadyPipeSender<FrameBatch>),
FilteredAnonymous {
sender: ReadyPipeSender<FrameBatch>,
trie: Arc<PrefixMatcher>,
},
DirectAddressed {
sender: ReadyPipeSender<FrameBatch>,
},
SubscriptionSink {
peer_idx: u32,
matcher: Arc<SubscriptionMatcher>,
},
}
#[inline]
fn apply_subscription_frame(matcher: &SubscriptionMatcher, peer_idx: u32, body: &[u8]) {
match body.first() {
Some(0x01) => matcher.subscribe(peer_idx, &body[1..]),
Some(0x00) => {
matcher.unsubscribe(peer_idx, &body[1..]);
}
_ => {}
}
}
#[inline]
fn apply_subscription_batch(matcher: &SubscriptionMatcher, peer_idx: u32, batch: &FrameBatch) -> usize {
for frame in batch.iter() {
apply_subscription_frame(matcher, peer_idx, frame.data().unwrap_or(&[]));
}
batch.len()
}
#[inline(always)]
fn debug_assert_complete_message(batch: &FrameBatch, site: &str) {
debug_assert!(
batch.last().map_or(true, |m| !m.is_more()),
"PipeMessageSender::{site}: FrameBatch ends with MORE set — logical message split across sends ({} frames)",
batch.len(),
);
#[cfg(not(debug_assertions))]
let _ = (batch, site);
}
impl PipeMessageSender {
#[cfg(feature = "io-uring")]
pub fn bind_uring_wakeup(&self, wakeup: UringWakeup) {
match self {
Self::DirectAnonymous(s) => s.bind_uring_wakeup(wakeup),
Self::FilteredAnonymous { sender, .. } => sender.bind_uring_wakeup(wakeup),
Self::DirectAddressed { sender } => sender.bind_uring_wakeup(wakeup),
Self::SubscriptionSink { .. } => {}
}
}
pub async fn send(&self, batch: FrameBatch) -> Result<(), ZmqError> {
debug_assert_complete_message(&batch, "send");
match self {
Self::DirectAnonymous(s) => s.send(batch).await,
Self::FilteredAnonymous { sender, trie } => {
let topic: &[u8] = batch.first().and_then(|m| m.data()).unwrap_or(&[]);
if trie.matches(topic) {
sender.send(batch).await
} else {
Ok(())
}
}
Self::DirectAddressed { sender } => sender.send(batch).await,
Self::SubscriptionSink { peer_idx, matcher } => {
apply_subscription_batch(matcher, *peer_idx, &batch);
Ok(())
}
}
}
pub async fn send_batch_mut(&self, items: &mut Vec<FrameBatch>) -> Result<usize, ZmqError> {
#[cfg(debug_assertions)]
for batch in items.iter() {
debug_assert_complete_message(batch, "send_batch_mut");
}
match self {
Self::DirectAnonymous(s) => s.send_batch_mut(items).await,
Self::DirectAddressed { sender } => sender.send_batch_mut(items).await,
Self::FilteredAnonymous { sender, trie } => {
items.retain(|batch| {
let topic = batch.first().and_then(|m| m.data()).unwrap_or(&[]);
trie.matches(topic)
});
if items.is_empty() {
return Ok(0);
}
sender.send_batch_mut(items).await
}
Self::SubscriptionSink { peer_idx, matcher } => {
let mut consumed = 0usize;
for batch in items.drain(..) {
consumed += apply_subscription_batch(matcher, *peer_idx, &batch);
}
Ok(consumed)
}
}
}
pub fn try_send_sync(&self, batch: FrameBatch) -> Result<(), TrySendError<FrameBatch>> {
debug_assert_complete_message(&batch, "try_send_sync");
match self {
Self::DirectAnonymous(s) => s.try_send(batch),
Self::FilteredAnonymous { sender, trie } => {
let topic: &[u8] = batch.first().and_then(|m| m.data()).unwrap_or(&[]);
if trie.matches(topic) {
sender.try_send(batch)
} else {
Ok(())
}
}
Self::DirectAddressed { sender } => sender.try_send(batch),
Self::SubscriptionSink { peer_idx, matcher } => {
apply_subscription_batch(matcher, *peer_idx, &batch);
Ok(())
}
}
}
pub fn try_send_batch(&self, items: &mut VecDeque<FrameBatch>) -> usize {
#[cfg(debug_assertions)]
for batch in items.iter() {
debug_assert_complete_message(batch, "try_send_batch");
}
match self {
Self::DirectAnonymous(s) => s.try_send_batch(items, |b| b.len()),
Self::FilteredAnonymous { sender, trie } => {
let n = items.len();
if n == 0 {
return 0;
}
let match_count = items
.iter()
.filter(|b| trie.matches(b.first().and_then(|m| m.data()).unwrap_or(&[])))
.count();
if match_count == 0 {
let total = items.iter().map(|b| b.len()).sum::<usize>();
items.clear();
return total;
}
let slot = match sender.slot.upgrade() {
Some(s) => s,
None => return 0,
};
slot
.counts
.fetch_add(match_count as u64 * RESERVED_ONE, Ordering::AcqRel);
let mut sent_batches = 0usize;
let mut total_frames = 0usize;
let mut had_zero_transition = false;
let tx = unsafe { slot.tx.get_mut() };
while let Some(item) = items.pop_front() {
let topic: &[u8] = item.first().and_then(|m| m.data()).unwrap_or(&[]);
if trie.matches(topic) {
let frame_count = item.len();
match tx.try_send(item) {
Ok(()) => {
sent_batches += 1;
total_frames += frame_count;
let prev = slot.counts.fetch_add(QUEUED_ONE, Ordering::AcqRel);
if queued_of(prev) == 0 {
had_zero_transition = true;
}
}
Err(TrySendError::Full(returned)) => {
items.push_front(returned);
break;
}
Err(TrySendError::Closed(returned)) => {
items.push_front(returned);
break;
}
_ => unreachable!(),
}
} else {
total_frames += item.len();
}
}
if sent_batches < match_count {
slot.counts.fetch_sub(
(match_count - sent_batches) as u64 * RESERVED_ONE,
Ordering::AcqRel,
);
}
#[cfg(feature = "diagnostics")]
slot
.total_committed
.fetch_add(sent_batches as u64, Ordering::Relaxed);
if had_zero_transition {
push_ready_token(
&sender.ready_tx,
&slot,
"try_send_batch filtered spinning on ready_tx",
);
}
total_frames
}
Self::DirectAddressed { sender } => sender.try_send_batch(items, |b| b.len()),
Self::SubscriptionSink { peer_idx, matcher } => {
let mut consumed = 0usize;
while let Some(batch) = items.pop_front() {
consumed += apply_subscription_batch(matcher, *peer_idx, &batch);
}
consumed
}
}
}
pub fn queued_count(&self) -> usize {
match self {
Self::DirectAnonymous(s) => s.queued_count(),
Self::FilteredAnonymous { sender, .. } => sender.queued_count(),
Self::DirectAddressed { sender } => sender.queued_count(),
Self::SubscriptionSink { .. } => 0,
}
}
pub fn reserved_count(&self) -> usize {
match self {
Self::DirectAnonymous(s) => s.reserved_count(),
Self::FilteredAnonymous { sender, .. } => sender.reserved_count(),
Self::DirectAddressed { sender } => sender.reserved_count(),
Self::SubscriptionSink { .. } => 0,
}
}
pub fn len(&self) -> usize {
match self {
Self::DirectAnonymous(s) => s.len(),
Self::FilteredAnonymous { sender, .. } => sender.len(),
Self::DirectAddressed { sender } => sender.len(),
Self::SubscriptionSink { .. } => 0,
}
}
pub fn capacity(&self) -> usize {
match self {
Self::DirectAnonymous(s) => s.capacity(),
Self::FilteredAnonymous { sender, .. } => sender.capacity(),
Self::DirectAddressed { sender } => sender.capacity(),
Self::SubscriptionSink { .. } => 0,
}
}
pub fn is_congested(&self) -> bool {
match self {
Self::DirectAnonymous(s) => s.is_congested(),
Self::FilteredAnonymous { sender, .. } => sender.is_congested(),
Self::DirectAddressed { sender } => sender.is_congested(),
Self::SubscriptionSink { .. } => false,
}
}
pub fn is_drained(&self) -> bool {
match self {
Self::DirectAnonymous(s) => s.is_drained(),
Self::FilteredAnonymous { sender, .. } => sender.is_drained(),
Self::DirectAddressed { sender } => sender.is_drained(),
Self::SubscriptionSink { .. } => true,
}
}
}
impl std::fmt::Debug for PipeMessageSender {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DirectAnonymous(_) => write!(f, "PipeMessageSender::DirectAnonymous"),
Self::FilteredAnonymous { .. } => write!(f, "PipeMessageSender::FilteredAnonymous"),
Self::DirectAddressed { .. } => write!(f, "PipeMessageSender::DirectAddressed"),
Self::SubscriptionSink { peer_idx, .. } => {
write!(f, "PipeMessageSender::SubscriptionSink(peer_idx={peer_idx})")
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fibre::TrySendError;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn test_ready_pipe_queue_try_pop_lost_wakeup_repro() {
const NUM_PRODUCERS: usize = 4;
const ATTEMPTS_PER_PRODUCER: usize = 500_000;
let queue = Arc::new(ReadyPipeQueue::<usize>::new(128));
let stop_signal = Arc::new(AtomicBool::new(false));
let mut senders = Vec::new();
let mut producer_handles = Vec::new();
for pipe_id in 0..NUM_PRODUCERS {
let sender = Arc::new(queue.register_pipe(pipe_id, 1, 0));
senders.push(sender.clone());
let sender_clone = sender.clone();
let stop_clone = stop_signal.clone();
producer_handles.push(std::thread::spawn(move || {
let mut seq = 0;
while !stop_clone.load(Ordering::Relaxed) && seq < ATTEMPTS_PER_PRODUCER {
match sender_clone.try_send(seq) {
Ok(()) => seq += 1,
Err(TrySendError::Full(_)) => std::thread::yield_now(),
Err(_) => break,
}
}
}));
}
let start_time = std::time::Instant::now();
let mut lost_wakeup_detected = false;
while start_time.elapsed() < std::time::Duration::from_secs(5) {
if let Some((_, _item)) = queue.try_pop() {
} else {
let pipes = queue.pipes.read();
for pipe_id in 0..NUM_PRODUCERS {
if let Some(slot) = pipes.get(&pipe_id) {
let rx_len = unsafe { slot.rx.get_mut() }.len();
let has_items = rx_len > 0;
let reserved = slot.reserved();
let has_ready_signal = !queue.ready_rx.is_empty();
if has_items && reserved == 0 && !has_ready_signal {
println!(
"\n[LOST WAKEUP] pipe={} rx_len={} reserved={} queued={} ready_rx_len={}",
pipe_id,
rx_len,
reserved,
slot.len(),
queue.ready_rx.len()
);
lost_wakeup_detected = true;
break;
}
}
}
if lost_wakeup_detected {
break;
}
std::thread::yield_now();
}
}
stop_signal.store(true, Ordering::Release);
queue.close();
for h in producer_handles {
let _ = h.join();
}
assert!(
!lost_wakeup_detected,
"REGRESSION: A lost-wakeup deadlock was detected!"
);
}
#[test]
fn test_ready_pipe_queue_pipe_deregistration_cleanup() {
let queue = ReadyPipeQueue::<i32>::new(10);
let sender = queue.register_pipe(1, 10, 0);
assert_eq!(queue.pipes.read().len(), 1);
queue.deregister_pipe(1);
assert_eq!(queue.pipes.read().len(), 0);
let res = sender.try_send(42);
assert!(res.is_err(), "sending on a deregistered pipe must fail");
}
}
#[cfg(test)]
mod livelock_repro_tests {
use super::*;
use std::sync::Arc;
use std::time::Duration;
use tokio::time::timeout;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_ready_pipe_queue_livelock_repro() {
let queue = Arc::new(ReadyPipeQueue::<i32>::new(10));
let sender = Arc::new(queue.register_pipe(1, 1, 0));
sender.try_send(100).unwrap();
let sender_clone = sender.clone();
let blocked_sender = tokio::spawn(async move {
let _ = sender_clone.send(200).await;
});
tokio::time::sleep(Duration::from_millis(50)).await;
let (id, val) = queue.pop().await.unwrap();
assert_eq!(id, 1);
assert_eq!(val, 100);
let queue_clone = queue.clone();
let pop_task = tokio::spawn(async move { queue_clone.pop().await.unwrap() });
let result = timeout(Duration::from_secs(2), pop_task).await;
blocked_sender.abort();
assert!(
result.is_ok(),
"pop() spun indefinitely instead of waiting for the blocked sender"
);
}
}
#[cfg(test)]
mod pop_counter_desync_regression {
use super::*;
use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::time::Duration;
use tokio::time::timeout;
async fn ingress_style_push(sender: &ReadyPipeSender<usize>, buf: &mut VecDeque<usize>) {
loop {
sender.try_send_batch(buf, |_| 1);
match buf.front().copied() {
None => return,
Some(front) => {
sender.send(front).await.expect("blocked send must succeed");
buf.pop_front();
}
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_pop_counter_desync_deadlock_regression() {
const CAP: usize = 100; const BATCH: usize = 128; const TOTAL: usize = 300_000;
let queue = Arc::new(ReadyPipeQueue::<usize>::new(8));
let sender = Arc::new(queue.register_pipe(0, CAP, 0));
let producer = {
let sender = sender.clone();
tokio::spawn(async move {
let mut next = 0usize;
let mut buf: VecDeque<usize> = VecDeque::with_capacity(BATCH);
while next < TOTAL {
let end = (next + BATCH).min(TOTAL);
buf.extend(next..end);
next = end;
ingress_style_push(&sender, &mut buf).await;
}
})
};
let consumer = {
let queue = queue.clone();
tokio::spawn(async move {
let mut got = 0usize;
while got < TOTAL {
match timeout(Duration::from_secs(5), queue.pop()).await {
Ok(Ok(_)) => got += 1,
Ok(Err(e)) => panic!("pop() errored after {got}/{TOTAL}: {e:?}"),
Err(_) => panic!(
"DEADLOCK: pop() stalled after {got}/{TOTAL} messages — \
queued_count/reserved_count desynced from rx (the [RPQ-DESYNC] bug)"
),
}
}
got
})
};
producer.await.expect("producer task");
let got = consumer.await.expect("consumer task");
assert_eq!(got, TOTAL, "messages were lost in the ready pipe queue");
let pipes = queue.pipes.read();
let slot = pipes.get(&0).expect("pipe slot present");
assert_eq!(unsafe { slot.rx.get_mut() }.len(), 0, "rx not fully drained");
assert_eq!(
slot.len(),
0,
"queued_count leaked"
);
assert_eq!(
slot.reserved(),
0,
"reserved_count leaked"
);
}
}
#[cfg(test)]
mod cancellation_safety_tests {
use crate::Msg;
use super::*;
use std::sync::Arc;
use std::time::Duration;
use tokio::time::timeout;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_cancellation_rollback() {
let queue = Arc::new(ReadyPipeQueue::<i32>::new(10));
let sender = queue.register_pipe(1, 1, 0);
sender.send(100).await.unwrap();
let pipes = queue.pipes.read();
let slot = pipes.get(&1).unwrap().clone();
drop(pipes);
let reserved_before = slot.reserved();
let queued_before = slot.len();
let _ = timeout(Duration::from_millis(20), sender.send(200)).await;
let reserved_after = slot.reserved();
let queued_after = slot.len();
assert_eq!(
reserved_after, reserved_before,
"cancelled send must not leave a reservation: before={} after={}",
reserved_before, reserved_after
);
assert_eq!(
queued_after, queued_before,
"cancelled send must not inflate queued_count"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_massive_cancellation_storm() {
let queue = Arc::new(ReadyPipeQueue::<i32>::new(10));
let sender = Arc::new(tokio::sync::Mutex::new(queue.register_pipe(1, 1, 0)));
sender.lock().await.send(0).await.unwrap();
let pipes = queue.pipes.read();
let slot = pipes.get(&1).unwrap().clone();
drop(pipes);
let mut tasks = Vec::new();
for i in 1..=1000 {
let s = sender.clone();
tasks.push(tokio::spawn(async move {
let _ = timeout(Duration::from_millis(1), async {
let _ = s.lock().await.send(i).await;
})
.await;
}));
}
for t in tasks {
let _ = t.await;
}
tokio::time::sleep(Duration::from_millis(50)).await;
let reserved = slot.reserved();
let queued = slot.len();
assert_eq!(
reserved, queued,
"after all cancellations reserved_count ({}) must equal queued_count ({})",
reserved, queued
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_concurrent_send_cancel_race() {
let queue = Arc::new(ReadyPipeQueue::<i32>::new(10));
let sender = Arc::new(tokio::sync::Mutex::new(queue.register_pipe(1, 4, 0)));
let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
let mut tasks = Vec::new();
for i in 0..8 {
let s = sender.clone();
let stop2 = stop.clone();
tasks.push(tokio::spawn(async move {
let mut seq = i;
while !stop2.load(Ordering::Relaxed) {
if seq % 2 == 0 {
let _ = timeout(Duration::from_micros(10), async {
let _ = s.lock().await.send(seq).await;
})
.await;
} else {
let _ = s.lock().await.send(seq).await;
}
seq += 8;
tokio::task::yield_now().await;
}
}));
}
let queue2 = queue.clone();
let consumer = tokio::spawn(async move {
let deadline = tokio::time::Instant::now() + Duration::from_secs(1);
while tokio::time::Instant::now() < deadline {
tokio::select! {
biased;
_ = queue2.pop() => {}
_ = tokio::time::sleep(Duration::from_millis(1)) => {}
}
}
});
consumer.await.unwrap();
stop.store(true, Ordering::Release);
for t in &tasks {
t.abort();
}
for t in tasks {
let _ = t.await;
}
while queue.try_pop().is_some() {}
let pipes = queue.pipes.read();
let slot = pipes.get(&1).unwrap();
let reserved = slot.reserved();
let queued = slot.len();
drop(pipes);
assert_eq!(
reserved, queued,
"after concurrent send/cancel storm reserved={} queued={}",
reserved, queued
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_cancellation_safe_no_livelock() {
let queue = Arc::new(ReadyPipeQueue::<i32>::new(10));
let sender = queue.register_pipe(1, 1, 0);
sender.send(100).await.unwrap();
let _ = timeout(Duration::from_millis(50), sender.send(200)).await;
let (id, val) = queue.pop().await.unwrap();
assert_eq!(id, 1);
assert_eq!(val, 100);
let queue2 = queue.clone();
let pop_task = tokio::spawn(async move { queue2.pop().await.unwrap() });
let result = timeout(Duration::from_millis(200), pop_task).await;
assert!(
result.is_err(),
"pop() returned unexpectedly — phantom reservation or ghost message present"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn test_exact_rzmq_ready_pipe_queue_uaf_crash() {
println!("\n--- STARTING DETERMINISTIC READY_PIPE_QUEUE CRASH TEST ---");
let queue = Arc::new(ReadyPipeQueue::<FrameBatch>::new(10));
let mut senders = Vec::new();
for i in 0..4 {
senders.push(Arc::new(tokio::sync::Mutex::new(queue.register_pipe(i, 1, 0))));
}
let stop = Arc::new(AtomicBool::new(false));
let mut handles = Vec::new();
for t_id in 0..20 {
let senders_clone = senders.clone();
let stop_clone = stop.clone();
handles.push(tokio::spawn(async move {
let mut seq = t_id * 10000;
let mut rng = u64::wrapping_mul(seq as u64, 0x9E37_79B9_7F4A_7C15);
while !stop_clone.load(Ordering::Relaxed) {
rng = rng.wrapping_mul(0x2545_F491_4F6C_DD1D);
let target_pipe = (rng % 4) as usize;
let sender = &senders_clone[target_pipe];
let mut batch = FrameBatch::new();
batch.push(Msg::from_static(b"chaos-data"));
let timeout_us = 10 + (rng % 150);
let _ = timeout(Duration::from_micros(timeout_us), async {
let _ = sender.lock().await.send(batch).await;
})
.await;
seq += 1;
tokio::task::yield_now().await;
}
}));
}
for t_id in 0..20 {
let queue_clone = queue.clone();
let stop_clone = stop.clone();
handles.push(tokio::spawn(async move {
let mut rng = u64::wrapping_mul((t_id + 100) as u64, 0x9E37_79B9_7F4A_7C15);
while !stop_clone.load(Ordering::Relaxed) {
rng = rng.wrapping_mul(0x2545_F491_4F6C_DD1D);
let timeout_us = 10 + (rng % 150);
let _ = timeout(Duration::from_micros(timeout_us), queue_clone.pop()).await;
tokio::task::yield_now().await;
}
}));
}
tokio::time::sleep(Duration::from_secs(10)).await;
println!("[SYS] Stopping tasks...");
stop.store(true, Ordering::SeqCst);
for h in handles {
let _ = h.await;
}
println!("--- REPRO COMPLETED SUCCESSFULLY (No Segfault occurred) ---");
}
}