use std::sync::{Arc, OnceLock, Weak};
use dashmap::DashMap;
use tokio::sync::{Mutex, RwLock, mpsc};
use tokio_util::sync::CancellationToken;
use crate::{
core::{OutcomeTx, SupervisorCore, TaskOutcome},
events::{Bus, Event, EventKind, RejectionKind},
identity::TaskId,
};
use super::{config::ControllerConfig, slot::SlotState};
mod protocol;
use protocol::{
AdmissionResult, CompletionResult, ControllerCommand, IdentityOperation, IdentityReply,
RemovalResult, Submission,
};
mod handle;
pub(crate) use handle::ControllerHandle;
mod task;
use task::ControllerTask;
mod admission;
mod identity;
mod lifecycle;
mod queue;
mod workers;
mod introspect;
mod shutdown;
#[cfg(test)]
use super::{
error::ControllerError,
slot::{AdmissionTransition, SlotPhase},
spec::ControllerSpec,
};
#[cfg(test)]
use crate::RuntimeError;
#[cfg(test)]
use std::future::Future;
#[cfg(test)]
use tokio::{sync::oneshot, task::JoinSet, time::Instant};
pub(crate) struct Controller {
config: ControllerConfig,
supervisor: Weak<SupervisorCore>,
bus: Bus,
shutdown_token: CancellationToken,
slots: DashMap<Arc<str>, Arc<Mutex<SlotState>>>,
watchers: DashMap<TaskId, OutcomeTx>,
tx: mpsc::Sender<ControllerCommand>,
rx: RwLock<Option<mpsc::Receiver<ControllerCommand>>>,
shutting_down: std::sync::atomic::AtomicBool,
task: OnceLock<ControllerTask>,
}
impl Controller {
pub fn new(config: ControllerConfig, supervisor: &Arc<SupervisorCore>, bus: Bus) -> Arc<Self> {
let (tx, rx) = mpsc::channel(config.queue_capacity().get());
let shutdown_token = supervisor.shutdown_started_token();
Arc::new(Self {
config,
supervisor: Arc::downgrade(supervisor),
bus,
shutdown_token,
slots: DashMap::new(),
watchers: DashMap::new(),
tx,
rx: RwLock::new(Some(rx)),
shutting_down: std::sync::atomic::AtomicBool::new(false),
task: OnceLock::new(),
})
}
fn finalize_rejected(&self, id: TaskId, kind: RejectionKind, reason: &str) {
if let Some((_, tx)) = self.watchers.remove(&id) {
let _ = tx.send(TaskOutcome::Rejected {
kind,
reason: Arc::from(reason),
});
}
}
fn mark_shutting_down(&self) {
self.shutting_down
.store(true, std::sync::atomic::Ordering::Release);
}
fn is_shutting_down(&self) -> bool {
self.shutdown_token.is_cancelled()
|| self
.shutting_down
.load(std::sync::atomic::Ordering::Acquire)
}
fn finalize_remaining_watchers(&self) {
let pending: Vec<TaskId> = self.watchers.iter().map(|entry| *entry.key()).collect();
for id in pending {
self.bus.publish(
Event::new(EventKind::ControllerRejected)
.with_id(id)
.with_rejection_kind(RejectionKind::ControllerShuttingDown)
.with_reason(crate::reasons::CONTROLLER_SHUTTING_DOWN),
);
self.finalize_rejected(
id,
RejectionKind::ControllerShuttingDown,
crate::reasons::CONTROLLER_SHUTTING_DOWN,
);
}
}
pub fn handle(&self) -> ControllerHandle {
ControllerHandle::new(self.tx.clone())
}
}
#[cfg(test)]
mod tests;