use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
use super::types::{MessageEnqueueCallback, QueuedUserMessage};
static SESSION_ROUTES: Mutex<Option<HashMap<Uuid, MessageEnqueueCallback>>> = Mutex::new(None);
pub fn register_session_route(session_id: Uuid, enqueue: MessageEnqueueCallback) {
match SESSION_ROUTES.lock() {
Ok(mut guard) => {
guard
.get_or_insert_with(HashMap::new)
.insert(session_id, enqueue.clone());
super::restart_recovery::claim_session(session_id, &enqueue);
}
Err(e) => {
tracing::error!(
target: "background_task",
"Could not register resume route for session {session_id}: {e}"
);
}
}
}
#[cfg(test)]
pub fn resolve_route(
session_id: Uuid,
executing: &MessageEnqueueCallback,
) -> MessageEnqueueCallback {
if let Some(route) = session_route(session_id) {
return route;
}
if super::restart_recovery::awaits_channel_route(session_id) {
return super::restart_recovery::parking_route();
}
executing.clone()
}
static LOCAL_ROUTE: Mutex<Option<MessageEnqueueCallback>> = Mutex::new(None);
pub fn register_local_route(enqueue: MessageEnqueueCallback) {
match LOCAL_ROUTE.lock() {
Ok(mut guard) => *guard = Some(enqueue),
Err(e) => {
tracing::error!(
target: "background_task",
"Could not register the local delivery route: {e}"
);
}
}
}
pub type TurnProbe = Arc<dyn Fn() -> bool + Send + Sync>;
static TURN_PROBES: Mutex<Option<HashMap<Uuid, TurnProbe>>> = Mutex::new(None);
pub fn register_turn_probe(session_id: Uuid, probe: TurnProbe) {
match TURN_PROBES.lock() {
Ok(mut guard) => {
guard
.get_or_insert_with(HashMap::new)
.insert(session_id, probe);
}
Err(e) => {
tracing::error!(
target: "background_task",
"Could not register the in-flight probe for session {session_id}: {e}"
);
}
}
}
pub(crate) fn turn_probe(session_id: Uuid) -> Option<TurnProbe> {
match TURN_PROBES.lock() {
Ok(guard) => guard.as_ref()?.get(&session_id).cloned(),
Err(e) => {
tracing::error!(
target: "background_task",
"Could not read the in-flight probe for session {session_id}: {e}"
);
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelOwnership {
Owned,
Occupied { occupant: Uuid },
Unknown,
}
pub type ChannelOwnerProbe = Arc<dyn Fn() -> ChannelOwnership + Send + Sync>;
static CHANNEL_OWNER_PROBES: Mutex<Option<HashMap<Uuid, ChannelOwnerProbe>>> = Mutex::new(None);
pub fn register_channel_owner_probe(session_id: Uuid, probe: ChannelOwnerProbe) {
match CHANNEL_OWNER_PROBES.lock() {
Ok(mut guard) => {
guard
.get_or_insert_with(HashMap::new)
.insert(session_id, probe);
}
Err(e) => {
tracing::error!(
target: "background_task",
"Could not register the channel-ownership probe for session {session_id}: {e}"
);
}
}
}
fn channel_owner_probe(session_id: Uuid) -> Option<ChannelOwnerProbe> {
match CHANNEL_OWNER_PROBES.lock() {
Ok(guard) => guard.as_ref()?.get(&session_id).cloned(),
Err(e) => {
tracing::error!(
target: "background_task",
"Could not read the channel-ownership probe for session {session_id}: {e}"
);
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Delivery {
Delivered,
Parked,
NoRoute,
RefusedInFlight { redirected_to: Option<Uuid> },
Redirected { to: Uuid },
}
pub fn deliver_to_session(session_id: Uuid, msg: QueuedUserMessage, interrupt: bool) -> Delivery {
const REDIRECT_HOP_CAP: usize = 3;
let mut target = session_id;
let mut hops: usize = 0;
let mut msg = msg;
loop {
if let Some(probe) = channel_owner_probe(target)
&& let ChannelOwnership::Occupied { occupant } = probe()
{
if hops >= REDIRECT_HOP_CAP {
tracing::warn!(
target: "background_task",
"Redirect cap hit for session {session_id}: its channel-owner chain \
(ending at session {occupant}) did not stabilise after {hops} hops; \
parking instead of delivering"
);
super::restart_recovery::parking_route()(target, msg);
return Delivery::Parked;
}
hops += 1;
tracing::info!(
target: "background_task",
"Redirecting delivery for session {target}: its channel is occupied by \
session {occupant} (hop {hops}); delivering to the new owner"
);
if hops == 1 {
msg.context_text = format!(
"[redirected — originally for session {session_id}, which no longer \
owns this channel] {}",
msg.context_text
);
}
target = occupant;
continue;
}
break;
}
if !interrupt && turn_probe(target).is_some_and(|probe| probe()) {
tracing::info!(
target: "background_task",
"Refusing delivery to session {target}: mid-turn and interrupt not set"
);
return Delivery::RefusedInFlight {
redirected_to: (hops > 0).then_some(target),
};
}
if let Some(route) = session_route(target) {
route(target, msg);
return if hops > 0 {
Delivery::Redirected { to: target }
} else {
Delivery::Delivered
};
}
if super::restart_recovery::awaits_channel_route(target) {
super::restart_recovery::parking_route()(target, msg);
return Delivery::Parked;
}
let local = match LOCAL_ROUTE.lock() {
Ok(guard) => guard.clone(),
Err(e) => {
tracing::error!(
target: "background_task",
"Could not read the local delivery route for session {target}: {e}"
);
None
}
};
match local {
Some(route) => {
route(target, msg);
if hops > 0 {
Delivery::Redirected { to: target }
} else {
Delivery::Delivered
}
}
None => {
tracing::error!(
target: "background_task",
"Nothing can receive a message for session {target}; it is dropped: {}",
msg.display_text
);
Delivery::NoRoute
}
}
}
pub fn session_route(session_id: Uuid) -> Option<MessageEnqueueCallback> {
match SESSION_ROUTES.lock() {
Ok(guard) => guard.as_ref()?.get(&session_id).cloned(),
Err(e) => {
tracing::error!(
target: "background_task",
"Could not read resume route for session {session_id}: {e}"
);
None
}
}
}
pub fn claim_for_channel(session_id: Uuid, enqueue: Option<MessageEnqueueCallback>) {
if let Some(enqueue) = enqueue {
register_session_route(session_id, enqueue);
}
}