use crate::error::ZmqError;
use crate::message::FrameBatch;
use crate::socket::connection_iface::ISocketConnection;
use crate::socket::patterns::sub_matcher::SubscriptionMatcher;
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use parking_lot::RwLock;
use xs_foundation::collections::map::VecMapU32;
thread_local! {
static SEND_TARGETS: RefCell<Vec<(u32, Arc<dyn ISocketConnection>)>> = RefCell::new(Vec::new());
}
#[derive(Debug)]
struct PeerSlot {
pipe_read_id: usize,
uri: String,
conn: Arc<dyn ISocketConnection>,
match_stamp: AtomicU64,
}
#[derive(Debug, Default)]
struct DistributorInner {
peers: VecMapU32<PeerSlot>,
by_uri: HashMap<String, u32>,
by_pipe: HashMap<usize, u32>,
free: Vec<u32>,
next_idx: u32,
}
impl DistributorInner {
fn alloc_idx(&mut self) -> u32 {
if let Some(idx) = self.free.pop() {
idx
} else {
let idx = self.next_idx;
self.next_idx += 1;
idx
}
}
fn remove_idx(&mut self, peer_idx: u32) {
if let Some(slot) = self.peers.remove(&peer_idx) {
self.by_uri.remove(&slot.uri);
self.by_pipe.remove(&slot.pipe_read_id);
self.free.push(peer_idx);
}
}
}
#[derive(Debug, Default)]
pub(crate) struct Distributor {
inner: RwLock<DistributorInner>,
send_gen: AtomicU64,
}
impl Distributor {
pub fn new() -> Self {
Self::default()
}
pub fn add_peer(
&self,
pipe_read_id: usize,
endpoint_uri: String,
conn: Arc<dyn ISocketConnection>,
) -> u32 {
let mut inner = self.inner.write();
if let Some(&idx) = inner.by_uri.get(&endpoint_uri) {
return idx;
}
let idx = inner.alloc_idx();
inner.by_uri.insert(endpoint_uri.clone(), idx);
inner.by_pipe.insert(pipe_read_id, idx);
inner.peers.insert(
idx,
PeerSlot {
pipe_read_id,
uri: endpoint_uri,
conn,
match_stamp: AtomicU64::new(0),
},
);
idx
}
pub fn remove_peer_by_pipe(&self, pipe_read_id: usize) -> Option<u32> {
let mut inner = self.inner.write();
let idx = inner.by_pipe.get(&pipe_read_id).copied()?;
inner.remove_idx(idx);
Some(idx)
}
pub fn remove_peer_by_idx(&self, peer_idx: u32) {
self.inner.write().remove_idx(peer_idx);
}
pub async fn send_matched_multipart(
&self,
zmtp_frames: FrameBatch,
matcher: &SubscriptionMatcher,
core_handle: usize,
) -> Result<(), Vec<(u32, ZmqError)>> {
if zmtp_frames.is_empty() {
return Ok(());
}
let generation = self.send_gen.fetch_add(1, Ordering::Relaxed).wrapping_add(1);
let mut targets = SEND_TARGETS.with(|t| std::mem::take(&mut *t.borrow_mut()));
targets.clear();
{
let topic: &[u8] = zmtp_frames.first().and_then(|m| m.data()).unwrap_or(&[]);
let inner = self.inner.read();
if !inner.peers.is_empty() {
matcher.for_each_match(topic, |idx| {
if let Some(slot) = inner.peers.get(&idx) {
if slot.match_stamp.swap(generation, Ordering::Relaxed) != generation {
targets.push((idx, slot.conn.clone()));
}
}
});
}
}
if targets.is_empty() {
SEND_TARGETS.with(|t| *t.borrow_mut() = targets);
return Ok(());
}
let mut failed: Vec<(u32, ZmqError)> = Vec::new();
let last = targets.len() - 1;
let mut original = Some(zmtp_frames);
for (i, (idx, conn)) in targets.iter().enumerate() {
let batch = if i == last {
original.take().expect("original batch consumed early")
} else {
original.as_ref().expect("original batch present").clone()
};
match conn.try_send_multipart_owned_sync(batch) {
Ok(()) => {}
Err((returned, ZmqError::ResourceLimitReached)) => match conn.send_multipart_owned(returned).await {
Ok(()) => {}
Err((_, ZmqError::ResourceLimitReached)) | Err((_, ZmqError::Timeout)) => {
tracing::trace!(
handle = core_handle, peer_idx = *idx,
"PUB (Distributor) dropping message due to HWM/Timeout for peer"
);
}
Err((_, e @ ZmqError::ConnectionClosed)) => {
tracing::debug!(handle = core_handle, peer_idx = *idx, "PUB (Distributor) peer disconnected during send");
failed.push((*idx, e));
}
Err((_, e)) => {
tracing::error!(handle = core_handle, peer_idx = *idx, error = %e, "PUB (Distributor) send encountered unexpected error");
failed.push((*idx, e));
}
},
Err((_, e @ ZmqError::ConnectionClosed)) => {
tracing::debug!(handle = core_handle, peer_idx = *idx, "PUB (Distributor) peer disconnected during send");
failed.push((*idx, e));
}
Err((_, e)) => {
tracing::error!(handle = core_handle, peer_idx = *idx, error = %e, "PUB (Distributor) send encountered unexpected error");
failed.push((*idx, e));
}
}
}
targets.clear();
SEND_TARGETS.with(|t| *t.borrow_mut() = targets);
if failed.is_empty() {
Ok(())
} else {
Err(failed)
}
}
}