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 parking_lot::RwLock;
use plaza::agent::{Agent, AgentId};
use plaza::error::PlazaError;
use plaza::session::{
ConnectionId, MessageTarget, PresenceEvent, Session, SessionMessage, SessionReceiver, SessionSender,
};
use plaza::snapshot::SnapshotData;
use serde::de::DeserializeOwned;
use serde::Serialize;
use fibre::mpsc;
use tracing::{debug, trace, warn};
use crate::codec::WireCodec;
use crate::error::SessionLayerError;
use crate::stats::TransportStats;
pub const DEFAULT_BROADCAST_CAPACITY: usize = 256;
pub const DEFAULT_CLIENT_QUEUE_CAPACITY: usize = 64;
pub type SerializedSessionMessage<ID> = SessionMessage<Vec<u8>, ID, Vec<u8>>;
pub type OutboundFrame = Vec<u8>;
struct ClientHandle<ID: AgentId> {
agent: Agent<ID>,
to_client_tx: mpsc::BoundedAsyncSender<OutboundFrame>,
rtt_us: AtomicU64,
min_rtt_us: AtomicU64,
samples: 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<SerializedSessionMessage<ID>>,
raw_incoming_rx: RwLock<Option<mpsc::BoundedAsyncReceiver<SerializedSessionMessage<ID>>>>,
presence_tx: SessionSender<PresenceEvent<ID>>,
presence_rx: RwLock<Option<SessionReceiver<PresenceEvent<ID>>>>,
stats: Arc<TransportStats>,
}
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 {
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(),
}
}
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);
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),
},
);
debug!(transport = self.transport, conn_id, agent = %agent.label(), "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.label(), "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>, serialized_ops: Vec<Vec<u8>>) {
if self
.raw_incoming_tx
.try_send(SessionMessage::Ops {
from,
ops: serialized_ops,
})
.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<SerializedSessionMessage<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_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, SnapshotPayload: Send + 'static, C: WireCodec> {
transport: &'static str,
codec: C,
manager: Arc<ConnectionManager<ID>>,
deserialized_rx: RwLock<Option<SessionReceiver<SessionMessage<Op, ID, SnapshotPayload>>>>,
_phantom: PhantomData<fn() -> (Op, SnapshotPayload)>,
}
impl<Op, ID, SnapshotPayload, C> Debug for TransportSession<Op, ID, SnapshotPayload, C>
where
Op: Send + 'static,
ID: AgentId,
SnapshotPayload: Send + 'static,
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, SnapshotPayload, C> TransportSession<Op, ID, SnapshotPayload, C>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
ID: AgentId,
SnapshotPayload: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
C: WireCodec,
{
pub fn new(transport: &'static str, codec: C, capacity: usize) -> Arc<Self> {
let manager = Arc::new(ConnectionManager::new(transport, capacity));
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, SnapshotPayload, C>(
transport,
codec,
manager.take_raw_incoming(),
deserialized_tx,
));
session
}
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, SnapshotPayload>) -> Result<OutboundFrame, SessionLayerError> {
self.codec.encode(&msg).map_err(|source| SessionLayerError::Serialization {
transport: self.transport,
context: "session message",
source,
})
}
}
async fn deserialize_bridge<Op, ID, SnapshotPayload, C>(
transport: &'static str,
codec: C,
raw_rx: mpsc::BoundedAsyncReceiver<SerializedSessionMessage<ID>>,
typed_tx: SessionSender<SessionMessage<Op, ID, SnapshotPayload>>,
) where
Op: DeserializeOwned + Clone + Debug + Send + 'static,
ID: AgentId,
SnapshotPayload: DeserializeOwned + Clone + Debug + Send + 'static,
C: WireCodec,
{
loop {
let Ok(raw) = raw_rx.recv().await else {
debug!(transport, "Raw incoming channel closed; deserialize bridge stopping.");
return;
};
let typed = match raw {
SessionMessage::Ops { from, ops } => {
let decoded: Result<Vec<Op>, _> = ops.iter().map(|bytes| codec.decode::<Op>(bytes)).collect();
match decoded {
Ok(ops) => SessionMessage::Ops { from, ops },
Err(source) => {
warn!(
transport,
error = %source,
agent = %from.label(),
"Discarding client ops that failed to decode."
);
continue;
}
}
}
SessionMessage::StateData { from, data } => match codec.decode::<SnapshotPayload>(&data.payload) {
Ok(payload) => SessionMessage::StateData {
from,
data: SnapshotData { payload },
},
Err(source) => {
warn!(transport, error = %source, "Discarding inbound snapshot 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, SnapshotPayload, C> Session<Op, ID, SnapshotPayload> for TransportSession<Op, ID, SnapshotPayload, C>
where
Op: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
ID: AgentId,
SnapshotPayload: Serialize + DeserializeOwned + Clone + Debug + Send + Sync + 'static,
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, SnapshotPayload>,
) -> 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, SnapshotPayload>> {
take_stream(&self.deserialized_rx, "incoming messages")
}
fn on_presence_change(&self) -> SessionReceiver<PresenceEvent<ID>> {
self.manager.take_presence()
}
}