use std::sync::{Arc, OnceLock};
use liminal::channel::{ChannelRestartPolicy, ChannelSupervisor, ClusterObserver};
use liminal::envelope::Envelope;
use crate::ServerError;
use crate::cluster::discovery::{ClusterResolver, as_resolver};
use crate::config::types::ClusterConfig;
#[derive(Clone, Debug)]
pub struct ChannelCluster {
supervisor: ChannelSupervisor,
resolver: Option<Arc<ClusterResolver>>,
observer: Arc<ServerObserver>,
}
#[derive(Debug, Default)]
pub struct ServerObserver {
cluster: OnceLock<Arc<dyn ClusterObserver>>,
console: OnceLock<Arc<crate::console::ConsoleHub>>,
#[cfg(test)]
witness: OnceLock<Arc<dyn ClusterObserver>>,
#[cfg(test)]
held_departures: std::sync::Mutex<Option<HeldDepartures>>,
}
#[cfg(test)]
type HeldDepartures = (String, Vec<u64>);
impl ServerObserver {
pub fn set_cluster(&self, cluster: Arc<dyn ClusterObserver>) -> Result<(), ServerError> {
self.cluster
.set(cluster)
.map_err(|_| ServerError::ClusterJoin {
message: "the channel observer already forwards to a cluster".to_owned(),
})
}
#[cfg(test)]
pub(crate) fn set_witness(&self, witness: Arc<dyn ClusterObserver>) -> Result<(), ServerError> {
self.witness
.set(witness)
.map_err(|_| ServerError::ClusterJoin {
message: "the channel observer already has a witness".to_owned(),
})
}
#[cfg(test)]
pub(crate) fn hold_departures(&self, channel: &str) -> std::io::Result<()> {
*self.held()? = Some((channel.to_owned(), Vec::new()));
Ok(())
}
#[cfg(test)]
pub(crate) fn release_departures(&self) -> std::io::Result<()> {
let held = self.held()?.take();
if let Some((channel, pids)) = held {
for pid in pids {
self.on_unsubscribe(&channel, pid);
}
}
Ok(())
}
#[cfg(test)]
fn held(&self) -> std::io::Result<std::sync::MutexGuard<'_, Option<HeldDepartures>>> {
self.held_departures.lock().map_err(|error| {
std::io::Error::other(format!("the held-departures lock was poisoned: {error}"))
})
}
#[cfg(test)]
fn held_back(&self, channel: &str, subscriber_pid: u64) -> bool {
let Ok(mut held) = self.held_departures.lock() else {
return false;
};
match held.as_mut() {
Some((held_channel, pids)) if held_channel == channel => {
pids.push(subscriber_pid);
true
}
_ => false,
}
}
pub fn set_console(&self, console: Arc<crate::console::ConsoleHub>) -> Result<(), ServerError> {
self.console
.set(console)
.map_err(|_| ServerError::ConfigValidation {
message: "the channel observer already feeds a console hub".to_owned(),
})
}
}
impl ServerObserver {
pub fn peer(&self, name: &str, present: bool) {
if let Some(console) = self.console.get()
&& let Err(error) = console.emit(crate::console::TopologyChange::Peer {
name: name.to_owned(),
present,
})
{
tracing::warn!(%error, peer = name, "console topology change was not applied");
}
}
}
impl ClusterObserver for ServerObserver {
fn on_subscribe(&self, channel: &str, subscriber_pid: u64) {
if let Some(console) = self.console.get()
&& let Err(error) = console.attach(channel)
{
tracing::warn!(%error, channel, "console tap did not attach");
}
#[cfg(test)]
if let Some(witness) = self.witness.get() {
witness.on_subscribe(channel, subscriber_pid);
}
if crate::console::tappable(channel)
&& let Some(cluster) = self.cluster.get()
{
cluster.on_subscribe(channel, subscriber_pid);
}
}
fn on_unsubscribe(&self, channel: &str, subscriber_pid: u64) {
#[cfg(test)]
if self.held_back(channel, subscriber_pid) {
return;
}
if let Some(console) = self.console.get() {
if let Err(error) = console.detach(channel) {
tracing::warn!(%error, channel, "console tap did not detach");
}
if let Err(error) = console.tap().forget_receiver(subscriber_pid) {
console.observe_failed();
tracing::warn!(%error, channel, "TAP_RECEIVER_FAILED");
}
}
#[cfg(test)]
if let Some(witness) = self.witness.get() {
witness.on_unsubscribe(channel, subscriber_pid);
}
if crate::console::tappable(channel)
&& let Some(cluster) = self.cluster.get()
{
cluster.on_unsubscribe(channel, subscriber_pid);
}
}
fn on_publish(&self, channel: &str, envelope: &Envelope) {
if !crate::console::tappable(channel) {
return;
}
if let Some(cluster) = self.cluster.get() {
cluster.on_publish(channel, envelope);
}
}
}
impl ChannelCluster {
#[must_use]
pub const fn observer(&self) -> &Arc<ServerObserver> {
&self.observer
}
#[must_use]
pub const fn supervisor(&self) -> &ChannelSupervisor {
&self.supervisor
}
#[must_use]
pub const fn resolver(&self) -> Option<&Arc<ClusterResolver>> {
self.resolver.as_ref()
}
}
fn server_channel_policy() -> ChannelRestartPolicy {
ChannelRestartPolicy::default()
}
pub fn build_channel_cluster(
cluster: Option<&ClusterConfig>,
) -> Result<ChannelCluster, ServerError> {
let Some(cluster) = cluster else {
let supervisor =
ChannelSupervisor::with_policy(server_channel_policy()).map_err(|error| {
ServerError::ConfigValidation {
message: format!("failed to start channel supervisor: {error}"),
}
})?;
return Ok(with_observer(supervisor, None));
};
let resolver = Arc::new(ClusterResolver::new());
let supervisor = ChannelSupervisor::with_distribution(
cluster.node_name.clone(),
node_creation(),
cluster.cookie.clone(),
as_resolver(Arc::clone(&resolver)),
server_channel_policy(),
)
.map_err(|error| ServerError::ClusterJoin {
message: format!("failed to start clustered channel supervisor: {error}"),
})?;
Ok(with_observer(supervisor, Some(resolver)))
}
fn with_observer(
supervisor: ChannelSupervisor,
resolver: Option<Arc<ClusterResolver>>,
) -> ChannelCluster {
let observer = Arc::new(ServerObserver::default());
supervisor.install_observer(Arc::clone(&observer) as Arc<dyn ClusterObserver>);
ChannelCluster {
supervisor,
resolver,
observer,
}
}
fn node_creation() -> u32 {
let since_epoch = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |duration| duration.as_secs());
let folded = u32::try_from(since_epoch & u64::from(u32::MAX)).unwrap_or(u32::MAX);
(folded & 0x7fff_ffff).max(1)
}