use std::collections::HashMap;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
use std::sync::Arc;
use async_trait::async_trait;
use bytes::Bytes;
use parking_lot::RwLock;
use plaza::agent::{Agent, AgentId};
use plaza::error::PlazaError;
use plaza::session::{
ConnectionId, MessageTarget, PresenceEvent, Session, SessionMessage, SessionReceiver, SessionSender,
};
use serde::de::DeserializeOwned;
use serde::Serialize;
use fibre::mpsc;
use tracing::{debug, trace, warn};
use crate::codec::WireCodec;
use plaza_wire::frame::{self, ProtocolVersion};
use crate::error::SessionLayerError;
use crate::stats::TransportStats;
pub const DEFAULT_BROADCAST_CAPACITY: usize = 256;
pub const DEFAULT_CLIENT_QUEUE_CAPACITY: usize = 64;
pub struct IncomingFrame<ID: AgentId> {
pub from: Agent<ID>,
pub frame: Bytes,
}
pub type OutboundFrame = Bytes;
struct ClientHandle<ID: AgentId> {
agent: Agent<ID>,
to_client_tx: mpsc::BoundedAsyncSender<OutboundFrame>,
rtt_us: AtomicU64,
min_rtt_us: AtomicU64,
samples: AtomicU64,
protocol: AtomicU64,
}
fn take_stream<T: Send + 'static>(slot: &RwLock<Option<T>>, name: &str) -> T {
slot
.write()
.take()
.unwrap_or_else(|| panic!("the {name} stream was already taken; it has a single consumer"))
}
pub fn target_matches<ID: AgentId>(target: &MessageTarget<ID>, agent: &Agent<ID>) -> bool {
let agent_id = match agent.id() {
Some(id) => id,
None => return false,
};
match target {
MessageTarget::All => true,
MessageTarget::Agent(id) => id == agent_id,
MessageTarget::Agents(ids) => ids.contains(agent_id),
MessageTarget::AllExcept(id) => id != agent_id,
MessageTarget::AllExceptThese(ids) => !ids.contains(agent_id),
}
}
#[derive(Debug)]
pub struct ConnectionManager<ID: AgentId> {
transport: &'static str,
next_conn_id: AtomicU64,
connections: RwLock<HashMap<ConnectionId, ClientHandle<ID>>>,
raw_incoming_tx: mpsc::BoundedAsyncSender<IncomingFrame<ID>>,
raw_incoming_rx: RwLock<Option<mpsc::BoundedAsyncReceiver<IncomingFrame<ID>>>>,
presence_tx: SessionSender<PresenceEvent<ID>>,
presence_rx: RwLock<Option<SessionReceiver<PresenceEvent<ID>>>>,
stats: Arc<TransportStats>,
hello: Option<OutboundFrame>,
}
impl<ID: AgentId> Debug for ClientHandle<ID> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ClientHandle").field("agent", &self.agent).finish()
}
}
impl<ID: AgentId> ConnectionManager<ID> {
pub fn stats(&self) -> Arc<TransportStats> {
Arc::clone(&self.stats)
}
pub fn new(transport: &'static str, capacity: usize) -> Self {
Self::with_hello(transport, capacity, None)
}
pub fn with_hello(transport: &'static str, capacity: usize, hello: Option<OutboundFrame>) -> Self {
let (raw_incoming_tx, raw_incoming_rx) = mpsc::bounded_async(capacity);
let (presence_tx, presence_rx) = mpsc::bounded_async(capacity);
Self {
transport,
next_conn_id: AtomicU64::new(1),
connections: RwLock::new(HashMap::new()),
raw_incoming_tx,
raw_incoming_rx: RwLock::new(Some(raw_incoming_rx)),
presence_tx,
presence_rx: RwLock::new(Some(presence_rx)),
stats: TransportStats::new(),
hello,
}
}
pub fn register(&self, agent: Agent<ID>, to_client_tx: mpsc::BoundedAsyncSender<OutboundFrame>) -> ConnectionId {
let conn_id = self.next_conn_id.fetch_add(1, Ordering::Relaxed);
if let Some(hello) = &self.hello {
let _ = to_client_tx.try_send(hello.clone());
}
self.connections.write().insert(
conn_id,
ClientHandle {
agent: agent.clone(),
to_client_tx,
rtt_us: AtomicU64::new(0),
min_rtt_us: AtomicU64::new(0),
samples: AtomicU64::new(0),
protocol: AtomicU64::new(0),
},
);
debug!(transport = self.transport, conn_id, agent = %agent, "Connection registered.");
if self.presence_tx.try_send(PresenceEvent::Joined(agent)).is_err() {
self.stats.record_presence_dropped();
warn!(
transport = self.transport,
conn_id, "Join notification dropped: no controller listening, or its queue is full."
);
}
conn_id
}
pub fn deregister(&self, conn_id: ConnectionId) {
let handle = self.connections.write().remove(&conn_id);
match handle {
Some(handle) => {
debug!(transport = self.transport, conn_id, agent = %handle.agent, "Connection deregistered.");
if let Some(id) = handle.agent.id_cloned() {
if self.presence_tx.try_send(PresenceEvent::Left(id)).is_err() {
self.stats.record_presence_dropped();
warn!(transport = self.transport, conn_id, "Leave notification dropped.");
}
}
}
None => {
trace!(
transport = self.transport,
conn_id,
"Deregister for unknown connection; already gone."
);
}
}
}
pub fn forward_incoming(&self, from: Agent<ID>, frame: Bytes) {
if self
.raw_incoming_tx
.try_send(IncomingFrame { from, frame })
.is_err()
{
self.stats.record_inbound(true);
warn!(
transport = self.transport,
"Inbound ops dropped: controller queue full or closed."
);
} else {
self.stats.record_inbound(false);
}
}
pub fn broadcast(&self, target: &MessageTarget<ID>, frame: OutboundFrame) -> Result<(), SessionLayerError> {
let connections = self.connections.read();
let mut full: Option<ConnectionId> = None;
let (mut sent, mut dropped) = (0u64, 0u64);
for (conn_id, handle) in connections.iter() {
if !target_matches(target, &handle.agent) {
continue;
}
if handle.to_client_tx.try_send(frame.clone()).is_err() {
dropped += 1;
full.get_or_insert(*conn_id);
} else {
sent += 1;
}
}
self.stats.record_outbound(sent, dropped);
match full {
Some(conn_id) => Err(SessionLayerError::ClientSendFailed {
transport: self.transport,
conn_id,
reason: "client queue full or closed",
}),
None => Ok(()),
}
}
pub fn take_raw_incoming(&self) -> mpsc::BoundedAsyncReceiver<IncomingFrame<ID>> {
take_stream(&self.raw_incoming_rx, "raw incoming")
}
pub fn take_presence(&self) -> SessionReceiver<PresenceEvent<ID>> {
take_stream(&self.presence_rx, "presence")
}
pub fn record_protocol(&self, agent: &Agent<ID>, version: ProtocolVersion) {
let Some(id) = agent.id() else { return };
let connections = self.connections.read();
for handle in connections.values() {
if handle.agent.id() == Some(id) {
handle.protocol.store(version.0 as u64, Ordering::Relaxed);
}
}
}
pub fn protocol(&self, id: &ID) -> Option<ProtocolVersion> {
let connections = self.connections.read();
connections
.values()
.find(|h| h.agent.id() == Some(id))
.map(|h| ProtocolVersion(h.protocol.load(Ordering::Relaxed) as u32))
.filter(|v| *v != ProtocolVersion::UNKNOWN)
}
pub fn record_rtt(&self, conn_id: ConnectionId, rtt: Duration) {
let us = rtt.as_micros() as u64;
let connections = self.connections.read();
let Some(handle) = connections.get(&conn_id) else {
return;
};
handle.samples.fetch_add(1, Ordering::Relaxed);
let previous = handle.rtt_us.load(Ordering::Relaxed);
let smoothed = if previous == 0 { us } else { previous - previous / 8 + us / 8 };
handle.rtt_us.store(smoothed, Ordering::Relaxed);
let min = handle.min_rtt_us.load(Ordering::Relaxed);
if min == 0 || us < min {
handle.min_rtt_us.store(us, Ordering::Relaxed);
}
}
pub fn rtt(&self, conn_id: ConnectionId) -> Option<Duration> {
self.sample(conn_id, |h| h.rtt_us.load(Ordering::Relaxed))
}
pub fn min_rtt(&self, conn_id: ConnectionId) -> Option<Duration> {
self.sample(conn_id, |h| h.min_rtt_us.load(Ordering::Relaxed))
}
pub fn rtt_samples(&self, conn_id: ConnectionId) -> u64 {
self
.connections
.read()
.get(&conn_id)
.map(|h| h.samples.load(Ordering::Relaxed))
.unwrap_or(0)
}
pub fn agent_rtt(&self, id: &ID) -> Option<(Duration, u64)> {
let connections = self.connections.read();
let handle = connections.values().find(|h| h.agent.id() == Some(id))?;
let us = handle.min_rtt_us.load(Ordering::Relaxed);
(us > 0).then(|| (Duration::from_micros(us), handle.samples.load(Ordering::Relaxed)))
}
fn sample(&self, conn_id: ConnectionId, read: impl Fn(&ClientHandle<ID>) -> u64) -> Option<Duration> {
let connections = self.connections.read();
let us = read(connections.get(&conn_id)?);
(us > 0).then(|| Duration::from_micros(us))
}
pub fn connection_count(&self) -> usize {
self.connections.read().len()
}
}
pub struct TransportSession<Op: Send + 'static, ID: AgentId, C: WireCodec> {
transport: &'static str,
codec: C,
manager: Arc<ConnectionManager<ID>>,
deserialized_rx: RwLock<Option<SessionReceiver<SessionMessage<Op, ID>>>>,
_phantom: PhantomData<fn() -> Op>,
}
impl<Op, ID, C> Debug for TransportSession<Op, ID, C>
where
Op: Send + 'static,
ID: AgentId,
C: WireCodec,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransportSession")
.field("transport", &self.transport)
.field("codec", &self.codec.name())
.field("connections", &self.manager.connection_count())
.finish()
}
}
impl<Op, ID, C> TransportSession<Op, ID, C>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
ID: AgentId,
C: WireCodec,
{
pub fn with_protocol(transport: &'static str, codec: C, capacity: usize, protocol: ProtocolVersion) -> Arc<Self> {
let hello = (protocol != ProtocolVersion::UNKNOWN).then(|| {
let mut buf = Vec::new();
frame::begin(frame::Kind::Hello, &mut buf);
codec.encode_into(&protocol, &mut buf).expect("a u32 always encodes");
Bytes::from(buf)
});
let manager = Arc::new(ConnectionManager::with_hello(transport, capacity, hello));
let (deserialized_tx, deserialized_rx) = mpsc::bounded_async(capacity);
let session = Arc::new(Self {
transport,
codec: codec.clone(),
manager: manager.clone(),
deserialized_rx: RwLock::new(Some(deserialized_rx)),
_phantom: PhantomData,
});
tokio::spawn(deserialize_bridge::<Op, ID, C>(
transport,
codec,
manager.clone(),
protocol,
manager.take_raw_incoming(),
deserialized_tx,
));
session
}
pub fn new(transport: &'static str, codec: C, capacity: usize) -> Arc<Self> {
Self::with_protocol(transport, codec, capacity, ProtocolVersion::UNKNOWN)
}
pub fn manager(&self) -> &Arc<ConnectionManager<ID>> {
&self.manager
}
pub fn codec(&self) -> &C {
&self.codec
}
pub fn encode_message(&self, msg: SessionMessage<Op, ID>) -> Result<OutboundFrame, SessionLayerError> {
let mut buf = Vec::new();
frame::begin(frame::Kind::Ops, &mut buf);
self
.codec
.encode_into(&msg.ops, &mut buf)
.map_err(|source| SessionLayerError::Serialization {
transport: self.transport,
context: "session message",
source,
})?;
Ok(Bytes::from(buf))
}
}
async fn deserialize_bridge<Op, ID, C>(
transport: &'static str,
codec: C,
manager: Arc<ConnectionManager<ID>>,
expected: ProtocolVersion,
raw_rx: mpsc::BoundedAsyncReceiver<IncomingFrame<ID>>,
typed_tx: SessionSender<SessionMessage<Op, ID>>,
) where
Op: DeserializeOwned + Clone + Debug + Send + 'static,
ID: AgentId,
C: WireCodec,
{
loop {
let Ok(raw) = raw_rx.recv().await else {
debug!(transport, "Raw incoming channel closed; deserialize bridge stopping.");
return;
};
let (from, bytes) = (raw.from, raw.frame);
let Some((tag, body)) = frame::split(&bytes) else {
warn!(transport, agent = %from, "Discarding an empty frame.");
continue;
};
match frame::Kind::from_byte(tag) {
Some(frame::Kind::Ops) => {}
Some(frame::Kind::Hello) => {
match codec.decode::<ProtocolVersion>(body) {
Ok(theirs) => {
manager.record_protocol(&from, theirs);
if !theirs.agrees_with(expected) {
warn!(
transport,
agent = %from,
theirs = theirs.0,
ours = expected.0,
"Client declared a different protocol version."
);
}
}
Err(source) => warn!(transport, error = %source, agent = %from, "Discarding a malformed Hello."),
}
continue;
}
None => {
trace!(transport, kind = tag, agent = %from, "Skipping a frame of unknown kind.");
continue;
}
}
let typed = match codec.decode::<Vec<Op>>(body) {
Ok(ops) => SessionMessage::new(from, ops),
Err(source) => {
warn!(
transport,
error = %source,
agent = %from,
"Discarding client ops that failed to decode."
);
continue;
}
};
if typed_tx.send(typed).await.is_err() {
debug!(transport, "Controller stopped consuming; deserialize bridge stopping.");
return;
}
}
}
#[async_trait]
impl<Op, ID, C> Session<Op, ID> for TransportSession<Op, ID, C>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
ID: AgentId,
C: WireCodec,
{
async fn agent_join(&self, _agent_info: Agent<ID>) -> Result<ConnectionId, PlazaError<ID>> {
Err(PlazaError::NotImplemented(format!(
"{}: agents join by connecting, not via agent_join()",
self.transport
)))
}
async fn agent_leave(&self, _agent_id: &ID, conn_id: ConnectionId) -> Result<(), PlazaError<ID>> {
self.manager.deregister(conn_id);
Ok(())
}
async fn send_message(
&self,
target: MessageTarget<ID>,
msg: SessionMessage<Op, ID>,
) -> Result<(), PlazaError<ID>> {
let encoded = self.encode_message(msg)?;
self.manager.broadcast(&target, encoded)?;
Ok(())
}
fn subscribe_to_incoming_messages(&self) -> SessionReceiver<SessionMessage<Op, ID>> {
take_stream(&self.deserialized_rx, "incoming messages")
}
fn on_presence_change(&self) -> SessionReceiver<PresenceEvent<ID>> {
self.manager.take_presence()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fanning_a_frame_out_shares_one_buffer() {
let frame: OutboundFrame = Bytes::from(vec![7u8; 4096]);
let queued: Vec<OutboundFrame> = (0..32).map(|_| frame.clone()).collect();
for copy in &queued {
assert_eq!(copy.as_ptr(), frame.as_ptr(), "a recipient got its own copy of the frame");
assert_eq!(copy.len(), frame.len());
}
}
}