#[cfg(feature = "relay")]
mod cluster;
mod hub;
pub(crate) mod memory;
mod session;
mod socket;
mod sse;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::MutexGuard;
use std::sync::PoisonError;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use axum::Router;
use axum::http::HeaderMap;
use axum::http::header::ORIGIN;
use axum::routing::get;
use axum::routing::post;
use leptoskit_db::Db;
use leptoskit_db::ReadEntry;
use serde_json::Value;
use tokio::sync::broadcast;
use tokio::sync::mpsc;
use super::Identify;
use super::Identity;
use super::Invocation;
use super::ReadSink;
use super::registration;
use crate::Kind;
use crate::live::EVENTS_PATH;
use crate::live::FRAMES_PATH;
use crate::live::MAX_RESULT_BYTES;
use crate::live::PresenceMember;
use crate::live::WireError;
const OUTGOING: usize = 32;
const FRAME_BURST: u64 = 512;
const FRAMES_PER_SECOND: u64 = 256;
const INCOMING: usize = 64;
#[derive(Clone)]
pub struct Live {
inner: Arc<Inner>,
}
struct Inner {
db: Db,
identify: Arc<dyn Identify>,
origin: Option<String>,
stall: std::time::Duration,
shared: Arc<Shared>,
}
const STALL: std::time::Duration = std::time::Duration::from_secs(crate::live::HEARTBEAT_SECONDS);
struct Shared {
drain: broadcast::Sender<()>,
stats: Stats,
hub: hub::Hub,
connections: Mutex<HashMap<u32, mpsc::Sender<Outbound>>>,
rooms: Mutex<HashMap<String, Room>>,
events: Mutex<HashMap<String, sse::Handle>>,
#[cfg(feature = "relay")]
relay: std::sync::OnceLock<crate::server::Relay>,
#[cfg(feature = "relay")]
cluster: tokio::sync::OnceCell<Option<Arc<cluster::Cluster>>>,
}
#[derive(Default)]
struct Room {
members: BTreeMap<u32, PresenceMember>,
}
fn room_key(name: &str, channel: &str) -> String {
format!("{name}\0{channel}")
}
fn wire_channel(room: &str) -> &str {
room.split_once('\0').map_or(room, |(_, channel)| channel)
}
#[derive(Default)]
struct Stats {
connections: AtomicU64,
subscriptions: AtomicU64,
transitions: AtomicU64,
resumes: AtomicU64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct LiveStats {
pub connections: u64,
pub subscriptions: u64,
pub reruns: u64,
pub transitions: u64,
pub commits: u64,
pub resumes: u64,
pub replica_reruns: u64,
pub relay_fills: u64,
}
#[derive(Clone, Debug)]
pub(crate) enum Outbound {
Frame { text: String, version: Option<u64> },
Heartbeat,
}
#[derive(Debug)]
pub(crate) enum Incoming {
Frame(crate::live::ClientFrame),
Invalid(WireError),
}
impl Live {
pub fn new(db: Db) -> Self {
let hub = hub::Hub::new(db.system("the live hub reads every commit"));
Self {
inner: Arc::new(Inner {
db,
identify: Arc::new(super::Anonymous),
origin: None,
stall: STALL,
shared: Arc::new(Shared {
drain: broadcast::channel(8).0,
stats: Stats::default(),
hub,
connections: Mutex::new(HashMap::new()),
rooms: Mutex::new(HashMap::new()),
events: Mutex::new(HashMap::new()),
#[cfg(feature = "relay")]
relay: std::sync::OnceLock::new(),
#[cfg(feature = "relay")]
cluster: tokio::sync::OnceCell::new(),
}),
}),
}
}
#[must_use]
pub fn identify(self, identify: impl Identify) -> Self {
self.identify_arc(Arc::new(identify))
}
#[must_use]
pub fn identify_arc(self, identify: Arc<dyn Identify>) -> Self {
self.with(|inner| inner.identify = identify)
}
#[must_use]
pub fn origin(self, origin: impl Into<String>) -> Self {
let origin = origin.into();
self.with(|inner| {
inner.origin = Some(origin.trim_end_matches('/').to_owned());
})
}
#[cfg(feature = "relay")]
#[must_use]
pub fn relay(self, relay: &crate::server::Relay) -> Self {
let _ = self.inner.shared.relay.set(relay.clone());
self.hub().use_relay(relay.clone());
self
}
#[must_use]
pub fn rerun_on_replicas(self, enabled: bool) -> Self {
self.hub().use_replicas(enabled, hub::REPLICA_WAIT);
self
}
#[must_use]
pub fn stall_timeout(self, timeout: std::time::Duration) -> Self {
self.with(|inner| inner.stall = timeout)
}
pub(crate) fn stall(&self) -> std::time::Duration {
self.inner.stall
}
#[must_use]
pub fn replica_wait(self, wait: std::time::Duration) -> Self {
self.hub().use_replicas(true, wait);
self
}
pub async fn start(&self) -> Result<(), WireError> {
self.hub().start().await?;
#[cfg(feature = "relay")]
if let Some(relay) = self.inner.shared.relay.get() {
self.inner
.shared
.cluster
.get_or_init(|| cluster::Cluster::start(self, relay))
.await;
}
Ok(())
}
pub fn is_started(&self) -> bool {
self.hub().is_started()
}
fn presence_members(&self, channel: &str) -> Vec<PresenceMember> {
let here: Vec<PresenceMember> = self
.rooms()
.get(channel)
.map(|room| room.members.values().cloned().collect())
.unwrap_or_default();
#[cfg(feature = "relay")]
if let Some(Some(cluster)) = self.inner.shared.cluster.get() {
return here.into_iter().chain(cluster.members(channel)).collect();
}
here
}
fn presence_changed(&self, channel: &str) {
#[cfg(feature = "relay")]
if let Some(Some(cluster)) = self.inner.shared.cluster.get() {
let members = self
.rooms()
.get(channel)
.map(|room| room.members.values().cloned().collect())
.unwrap_or_default();
cluster.presence(channel, members);
}
let senders: Vec<mpsc::Sender<Outbound>> = {
let rooms = self.rooms();
let Some(room) = rooms.get(channel) else {
return;
};
let connections = self.connections();
room.members
.keys()
.filter_map(|id| connections.get(id).cloned())
.collect()
};
let frame = crate::live::ServerFrame::Presence {
channel: wire_channel(channel).to_owned(),
members: self.presence_members(channel),
};
let Some(outbound) = Outbound::frame(&frame, None) else {
return;
};
for sender in senders {
let _ = sender.try_send(outbound.clone());
}
}
#[cfg_attr(
not(feature = "relay"),
expect(
unused_variables,
clippy::unused_self,
clippy::needless_pass_by_value,
reason = "only a relay reaches other servers"
)
)]
fn signal_elsewhere(&self, channel: &str, from: Option<String>, message: &Value) {
#[cfg(feature = "relay")]
if let Some(Some(cluster)) = self.inner.shared.cluster.get() {
cluster.signal(channel, from, message);
}
}
fn with(self, change: impl FnOnce(&mut Inner)) -> Self {
let mut inner = Inner {
db: self.inner.db.clone(),
identify: Arc::clone(&self.inner.identify),
origin: self.inner.origin.clone(),
stall: self.inner.stall,
shared: Arc::clone(&self.inner.shared),
};
change(&mut inner);
Self {
inner: Arc::new(inner),
}
}
pub fn router<S>(&self) -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/live", get(socket::upgrade))
.route(EVENTS_PATH, get(sse::events))
.route(FRAMES_PATH, post(sse::frames))
.with_state(self.clone())
}
pub fn drain(&self) {
let _ = self.inner.shared.drain.send(());
}
#[must_use]
pub fn stats(&self) -> LiveStats {
let stats = &self.inner.shared.stats;
let (reruns, commits, replica_reruns, relay_fills) = self.hub().counts();
LiveStats {
connections: stats.connections.load(Ordering::Relaxed),
subscriptions: stats.subscriptions.load(Ordering::Relaxed),
reruns,
transitions: stats.transitions.load(Ordering::Relaxed),
commits,
resumes: stats.resumes.load(Ordering::Relaxed),
replica_reruns,
relay_fills,
}
}
fn stats_ref(&self) -> &Stats {
&self.inner.shared.stats
}
fn hub(&self) -> &hub::Hub {
&self.inner.shared.hub
}
fn next_number(&self) -> u32 {
let number = self.stats_ref().connections.fetch_add(1, Ordering::Relaxed) + 1;
u32::try_from(number).unwrap_or(u32::MAX)
}
fn connections(&self) -> MutexGuard<'_, HashMap<u32, mpsc::Sender<Outbound>>> {
self.inner
.shared
.connections
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
fn rooms(&self) -> MutexGuard<'_, HashMap<String, Room>> {
self.inner
.shared
.rooms
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
fn event_streams(&self) -> MutexGuard<'_, HashMap<String, sse::Handle>> {
self.inner
.shared
.events
.lock()
.unwrap_or_else(PoisonError::into_inner)
}
fn origin_ok(&self, headers: &HeaderMap) -> bool {
let Some(allowed) = &self.inner.origin else {
return true;
};
match headers.get(ORIGIN) {
None => true,
Some(origin) => {
origin
.to_str()
.is_ok_and(|origin| origin.trim_end_matches('/') == allowed)
}
}
}
}
impl std::fmt::Debug for Live {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Live")
.field("version", &self.hub().applied())
.finish_non_exhaustive()
}
}
async fn run_query(
name: &str,
args: Value,
db: Db,
identity: Identity,
) -> Result<(Value, Vec<ReadEntry>), WireError> {
let Some(registration) = registration(name) else {
return Err(WireError::invalid(format!(
"no operation is named `{name}`"
)));
};
if registration.kind != Kind::Query {
return Err(WireError::invalid(format!(
"`{name}` is a {}, and only a query can be subscribed to",
registration.kind
)));
}
let sink = ReadSink::new();
let value = (registration.handler)(Invocation {
db,
identity,
request_id: None,
arguments: args,
reads: Some(sink.clone()),
})
.await
.map_err(|error| WireError::new(&error))?;
let size = serde_json::to_vec(&value).map_or(0, |bytes| bytes.len());
if size > MAX_RESULT_BYTES {
return Err(WireError {
status: 413,
message: format!(
"`{name}` returned {size} bytes, more than the {MAX_RESULT_BYTES}-byte live \
limit; paginate it"
),
});
}
Ok((value, sink.take()))
}