use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::cluster::ClusterSystem;
use crate::cluster::framing::ControlMessage;
use crate::cluster::membership::ClusterEvent;
use crate::cluster::transport::Transport;
use crate::prelude::*;
use tokio::sync::mpsc;
use crate::app::coordinator::{
Coordinator, CoordinatorState, NotifyNodeFailed, NotifyNodeJoined, NotifyNodeLeft,
NotifySingletonStopped, NotifySpawnAck, SerializableNodeInfo,
};
use crate::app::node_info::NodeInfo;
use crate::app::singleton::SingletonGeneration;
use crate::app::spawn_sender::{SingletonStopRequest, SingletonStopSender, SpawnSender};
pub fn start_coordinator(
cluster: &ClusterSystem,
mut state: CoordinatorState,
) -> Endpoint<Coordinator> {
let local_info = NodeInfo::new(
cluster.identity().clone(),
cluster.node_class().clone(),
cluster.node_metadata().clone(),
);
state.cluster_view.upsert_node(local_info);
let queue_depth = Arc::new(AtomicUsize::new(0));
let (spawn_tx, spawn_rx) = mpsc::unbounded_channel();
let (stop_tx, stop_rx) = mpsc::unbounded_channel();
let state = state
.with_spawn_sender(SpawnSender::new(spawn_tx, Arc::clone(&queue_depth)))
.with_singleton_stop_sender(SingletonStopSender::new(stop_tx));
let coordinator_ep = cluster.start_actor("coordinator", Coordinator, state);
let bridge_ep = coordinator_ep.clone();
let mut events = cluster.subscribe_events();
let node_registry = cluster.node_registry().clone();
tokio::spawn(async move {
run_bridge_loop(&mut events, &node_registry, &bridge_ep).await;
});
let transport = Arc::clone(cluster.transport());
let local_node_id = cluster.identity().node_id_string();
let spawn_registry = Arc::clone(cluster.spawn_registry());
let receptionist = cluster.receptionist().clone();
let ack_ep = coordinator_ep.clone();
tokio::spawn(run_spawn_drain_loop(
transport,
local_node_id,
spawn_registry,
receptionist,
ack_ep,
spawn_rx,
queue_depth,
));
let stop_transport = Arc::clone(cluster.transport());
let stop_local_node_id = cluster.identity().node_id_string();
let stop_receptionist = cluster.receptionist().clone();
let stop_ep = coordinator_ep.clone();
tokio::spawn(run_singleton_stop_drain_loop(
stop_transport,
stop_local_node_id,
stop_receptionist,
stop_ep,
stop_rx,
));
coordinator_ep
}
async fn run_bridge_loop(
events: &mut tokio::sync::broadcast::Receiver<ClusterEvent>,
node_registry: &crate::cluster::NodeRegistry,
coordinator: &Endpoint<Coordinator>,
) {
loop {
match events.recv().await {
Ok(event) => match event {
ClusterEvent::NodeJoined(identity) => {
let node_id = identity.node_id_string();
let (class, metadata) = match node_registry.get(&node_id) {
Some(entry) => (entry.class, entry.metadata),
None => {
tracing::warn!(
"Node {} joined but no registry entry found — using defaults",
node_id
);
(
crate::cluster::config::NodeClass::Worker,
std::collections::HashMap::new(),
)
}
};
let _ = coordinator
.send(NotifyNodeJoined {
node_id,
info: SerializableNodeInfo {
name: identity.name,
host: identity.host,
port: identity.port,
incarnation: identity.incarnation,
class,
metadata,
},
})
.await;
}
ClusterEvent::NodeFailed(identity) => {
let _ = coordinator
.send_async(NotifyNodeFailed {
node_id: identity.node_id_string(),
})
.await;
}
ClusterEvent::NodeLeft(identity) => {
let _ = coordinator
.send_async(NotifyNodeLeft {
node_id: identity.node_id_string(),
})
.await;
}
ClusterEvent::SpawnAckOk { request_id, .. } => {
let _ = coordinator
.send(NotifySpawnAck {
request_id,
success: true,
error: None,
})
.await;
}
ClusterEvent::SpawnAckErr { request_id, error } => {
let _ = coordinator
.send(NotifySpawnAck {
request_id,
success: false,
error: Some(error),
})
.await;
}
ClusterEvent::SingletonStopped {
label,
stopped_generation,
} => {
let _ = coordinator
.send(NotifySingletonStopped {
label,
stopped_generation: SingletonGeneration::from_packed(
stopped_generation,
),
})
.await;
}
_ => {}
},
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!("Cluster bridge lagged, missed {n} events");
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
tracing::info!("Cluster event channel closed — bridge shutting down");
break;
}
}
}
}
struct AckGuard {
coordinator: Endpoint<Coordinator>,
request_id: u64,
sent: bool,
}
impl AckGuard {
fn new(coordinator: Endpoint<Coordinator>, request_id: u64) -> Self {
Self {
coordinator,
request_id,
sent: false,
}
}
fn ack(mut self, success: bool, error: Option<String>) {
self.sent = true;
let coord = self.coordinator.clone();
let id = self.request_id;
tokio::spawn(async move {
let _ = coord
.send(NotifySpawnAck {
request_id: id,
success,
error,
})
.await;
});
}
}
impl Drop for AckGuard {
fn drop(&mut self) {
if self.sent {
return;
}
if tokio::runtime::Handle::try_current().is_err() {
return;
}
let coord = self.coordinator.clone();
let id = self.request_id;
tokio::spawn(async move {
let _ = coord
.send(NotifySpawnAck {
request_id: id,
success: false,
error: Some("spawn task panicked or was cancelled".into()),
})
.await;
});
}
}
async fn run_spawn_drain_loop(
transport: Arc<Transport>,
local_node_id: String,
spawn_registry: Arc<crate::cluster::sync::SpawnRegistry>,
receptionist: crate::receptionist::Receptionist,
coordinator: Endpoint<Coordinator>,
mut rx: mpsc::UnboundedReceiver<crate::app::spawn_sender::SpawnItem>,
queue_depth: Arc<AtomicUsize>,
) {
while let Some((node_id, request, enqueued_at)) = rx.recv().await {
let depth = queue_depth
.fetch_sub(1, Ordering::Relaxed)
.saturating_sub(1);
crate::instrument::spawn_drain_queue_depth(depth as f64);
crate::instrument::spawn_drain_dispatch(enqueued_at.elapsed());
if node_id == local_node_id {
tracing::debug!(
"Spawning actor locally: label={}, type={}",
request.label,
request.actor_type_name
);
let registry = Arc::clone(&spawn_registry);
let receptionist = receptionist.clone();
let guard = AckGuard::new(coordinator.clone(), request.request_id);
tokio::spawn(async move {
let factory_start = std::time::Instant::now();
let result = registry
.spawn(
receptionist,
&request.label,
&request.actor_type_name,
&request.initial_state,
)
.await;
crate::instrument::spawn_drain_factory("local", factory_start.elapsed());
match result {
Ok(()) => guard.ack(true, None),
Err(e) => guard.ack(false, Some(e.to_string())),
}
});
} else {
tracing::debug!(
"Sending SpawnActor to {node_id}: label={}, type={}",
request.label,
request.actor_type_name
);
let transport = Arc::clone(&transport);
tokio::spawn(async move {
let factory_start = std::time::Instant::now();
if let Err(e) = transport
.send_control(&node_id, ControlMessage::SpawnActor(request))
.await
{
tracing::warn!("Failed to send spawn request to {node_id}: {e}");
}
crate::instrument::spawn_drain_factory("remote", factory_start.elapsed());
});
}
}
}
async fn run_singleton_stop_drain_loop(
transport: Arc<Transport>,
local_node_id: String,
receptionist: crate::receptionist::Receptionist,
coordinator: Endpoint<Coordinator>,
mut rx: mpsc::UnboundedReceiver<SingletonStopRequest>,
) {
while let Some(req) = rx.recv().await {
if req.target_node_id == local_node_id {
tracing::debug!("Stopping local singleton {} for handoff", req.label);
receptionist.stop(&req.label);
let _ = coordinator
.send(NotifySingletonStopped {
label: req.label,
stopped_generation: SingletonGeneration::from_packed(req.generation),
})
.await;
} else {
tracing::debug!(
"Sending StopSingleton to {} for {}",
req.target_node_id,
req.label
);
if let Err(e) = transport
.send_control(
&req.target_node_id,
ControlMessage::StopSingleton {
label: req.label.clone(),
generation: req.generation,
},
)
.await
{
tracing::warn!(
"Failed to send StopSingleton to {}: {e}",
req.target_node_id
);
}
}
}
}