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::Weak;
use std::time::Duration;
use futures::StreamExt;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use tokio::time::Instant;
use super::Live;
use super::Outbound;
use super::Shared;
use crate::live::PresenceMember;
use crate::live::ServerFrame;
use crate::server::Relay;
const ROOMS: Duration = Duration::from_secs(10);
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum Wire {
Signal {
instance: String,
channel: String,
from: Option<String>,
message: Value,
},
Presence {
instance: String,
channel: String,
members: Vec<PresenceMember>,
},
Rooms {
instance: String,
rooms: BTreeMap<String, Vec<PresenceMember>>,
},
}
pub(super) struct Cluster {
instance: String,
client: async_nats::Client,
subject: String,
state: Mutex<Remote>,
}
#[derive(Default)]
struct Remote {
rooms: HashMap<String, BTreeMap<String, Vec<PresenceMember>>>,
heard: HashMap<String, Instant>,
}
impl Cluster {
pub(super) async fn start(live: &Live, relay: &Relay) -> Option<Arc<Self>> {
let names = relay.names(&live.inner.db).await.ok()?;
let subject = names.channels.clone();
let client = relay.client().clone();
let messages = client.subscribe(subject.clone()).await.ok()?;
let cluster = Arc::new(Self {
instance: crate::server::relay::instance_id(),
client,
subject,
state: Mutex::new(Remote::default()),
});
tokio::spawn(listen(
Arc::downgrade(&cluster),
Arc::downgrade(&live.inner.shared),
messages,
));
tokio::spawn(heartbeat(
Arc::downgrade(&cluster),
Arc::downgrade(&live.inner.shared),
));
Some(cluster)
}
fn state(&self) -> MutexGuard<'_, Remote> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(super) fn members(&self, channel: &str) -> Vec<PresenceMember> {
self.state()
.rooms
.get(channel)
.into_iter()
.flat_map(|servers| servers.values().flatten().cloned())
.collect()
}
fn send(&self, wire: &Wire) {
let Ok(payload) = serde_json::to_vec(wire) else {
return;
};
let client = self.client.clone();
let subject = self.subject.clone();
tokio::spawn(async move {
let _ = client.publish(subject, payload.into()).await;
});
}
pub(super) fn signal(&self, channel: &str, from: Option<String>, message: &Value) {
self.send(&Wire::Signal {
instance: self.instance.clone(),
channel: channel.to_owned(),
from,
message: message.clone(),
});
}
pub(super) fn presence(&self, channel: &str, members: Vec<PresenceMember>) {
self.send(&Wire::Presence {
instance: self.instance.clone(),
channel: channel.to_owned(),
members,
});
}
fn receive(&self, wire: Wire) -> Vec<String> {
let mut state = self.state();
match wire {
Wire::Signal { .. } => Vec::new(),
Wire::Presence {
instance,
channel,
members,
} => {
state.heard.insert(instance.clone(), Instant::now());
let changed = state.set(&channel, &instance, members);
if changed { vec![channel] } else { Vec::new() }
}
Wire::Rooms { instance, rooms } => {
state.heard.insert(instance.clone(), Instant::now());
let mut changed = Vec::new();
let before: Vec<String> = state
.rooms
.iter()
.filter(|(_, servers)| servers.contains_key(&instance))
.map(|(channel, _)| channel.clone())
.collect();
for channel in before {
if !rooms.contains_key(&channel) && state.set(&channel, &instance, Vec::new()) {
changed.push(channel);
}
}
for (channel, members) in rooms {
if state.set(&channel, &instance, members) {
changed.push(channel);
}
}
changed
}
}
}
fn expire(&self) -> Vec<String> {
let mut state = self.state();
let now = Instant::now();
let gone: Vec<String> = state
.heard
.iter()
.filter(|(_, heard)| now.duration_since(**heard) > ROOMS * 3)
.map(|(instance, _)| instance.clone())
.collect();
let mut changed = Vec::new();
for instance in gone {
state.heard.remove(&instance);
let channels: Vec<String> = state.rooms.keys().cloned().collect();
for channel in channels {
if state.set(&channel, &instance, Vec::new()) {
changed.push(channel);
}
}
}
changed
}
}
impl Remote {
fn set(&mut self, channel: &str, instance: &str, members: Vec<PresenceMember>) -> bool {
let servers = self.rooms.entry(channel.to_owned()).or_default();
let changed = if members.is_empty() {
servers.remove(instance).is_some()
} else {
servers.insert(instance.to_owned(), members.clone()) != Some(members)
};
if servers.is_empty() {
self.rooms.remove(channel);
}
changed
}
}
async fn listen(
cluster: Weak<Cluster>,
shared: Weak<Shared>,
mut messages: async_nats::Subscriber,
) {
while let Some(message) = messages.next().await {
let (Some(cluster), Some(shared)) = (cluster.upgrade(), shared.upgrade()) else {
return;
};
let Ok(wire) = serde_json::from_slice::<Wire>(&message.payload) else {
continue;
};
let instance = match &wire {
Wire::Signal { instance, .. }
| Wire::Presence { instance, .. }
| Wire::Rooms { instance, .. } => instance,
};
if *instance == cluster.instance {
continue;
}
if let Wire::Signal {
channel,
from,
message,
..
} = &wire
{
deliver_signal(&shared, channel, from.clone(), message.clone());
continue;
}
for channel in cluster.receive(wire) {
send_presence(&shared, &cluster, &channel);
}
}
}
async fn heartbeat(cluster: Weak<Cluster>, shared: Weak<Shared>) {
let mut tick = tokio::time::interval(ROOMS);
tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
tick.tick().await;
let (Some(cluster), Some(shared)) = (cluster.upgrade(), shared.upgrade()) else {
return;
};
let rooms: BTreeMap<String, Vec<PresenceMember>> = shared
.rooms
.lock()
.unwrap_or_else(PoisonError::into_inner)
.iter()
.map(|(channel, room)| (channel.clone(), room.members.values().cloned().collect()))
.collect();
cluster.send(&Wire::Rooms {
instance: cluster.instance.clone(),
rooms,
});
for channel in cluster.expire() {
send_presence(&shared, &cluster, &channel);
}
}
}
fn deliver_signal(shared: &Shared, channel: &str, from: Option<String>, message: Value) {
let senders: Vec<tokio::sync::mpsc::Sender<Outbound>> = {
let rooms = shared.rooms.lock().unwrap_or_else(PoisonError::into_inner);
let Some(room) = rooms.get(channel) else {
return;
};
let connections = shared
.connections
.lock()
.unwrap_or_else(PoisonError::into_inner);
room.members
.keys()
.filter_map(|id| connections.get(id).cloned())
.collect()
};
let frame = ServerFrame::Signal {
channel: super::wire_channel(channel).to_owned(),
from,
message,
};
let Some(outbound) = Outbound::frame(&frame, None) else {
return;
};
for sender in senders {
let _ = sender.try_send(outbound.clone());
}
}
fn send_presence(shared: &Shared, cluster: &Cluster, channel: &str) {
let (mut members, senders) = {
let rooms = shared.rooms.lock().unwrap_or_else(PoisonError::into_inner);
let Some(room) = rooms.get(channel) else {
return;
};
let connections = shared
.connections
.lock()
.unwrap_or_else(PoisonError::into_inner);
let senders: Vec<tokio::sync::mpsc::Sender<Outbound>> = room
.members
.keys()
.filter_map(|id| connections.get(id).cloned())
.collect();
(room.members.values().cloned().collect::<Vec<_>>(), senders)
};
members.extend(cluster.members(channel));
let frame = ServerFrame::Presence {
channel: super::wire_channel(channel).to_owned(),
members,
};
let Some(outbound) = Outbound::frame(&frame, None) else {
return;
};
for sender in senders {
let _ = sender.try_send(outbound.clone());
}
}
#[cfg(test)]
mod tests {
use super::*;
fn member(id: &str) -> PresenceMember {
PresenceMember {
id: id.to_owned(),
state: Value::Null,
}
}
#[test]
fn presence_replaces_a_servers_members_and_rooms_drops_the_rest() {
let mut remote = Remote::default();
assert!(remote.set("room", "a", vec![member("ada")]));
assert!(!remote.set("room", "a", vec![member("ada")]));
assert!(remote.set("room", "b", vec![member("grace")]));
assert!(remote.set("room", "a", Vec::new()));
assert_eq!(remote.rooms["room"].len(), 1);
assert!(remote.set("room", "b", Vec::new()));
assert!(remote.rooms.is_empty());
}
}