use std::{
sync::{Arc, atomic::Ordering},
time::Duration,
};
use tokio::{
sync::{mpsc, oneshot},
time::timeout,
};
use super::SupervisorCore;
use crate::{
core::registry::{
AddBatchItem, AddReplyRx, CancelDecision, CancelReplyRx, OutcomeTx, RegistryCommand,
RemovalCompletion, RemoveReplyRx,
},
error::RuntimeError,
events::{Event, EventKind},
identity::TaskId,
tasks::TaskSpec,
};
impl SupervisorCore {
pub(super) fn mark_shutting_down(&self) {
let _gate = self
.admission_gate
.lock()
.unwrap_or_else(|error| error.into_inner());
self.shutting_down.store(true, Ordering::Release);
}
fn command_admission(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
let gate = self
.admission_gate
.lock()
.unwrap_or_else(|error| error.into_inner());
if self.is_shutting_down() {
None
} else {
Some(gate)
}
}
pub(super) async fn close_admission_and_fence_registry(&self) -> Result<(), RuntimeError> {
self.mark_shutting_down();
self.registry.fence().await
}
pub(crate) async fn add_task(&self, spec: TaskSpec) -> Result<TaskId, RuntimeError> {
let (id, reply) = self
.enqueue_add_task_wait(TaskId::next(), spec, None)
.await
.map_err(|(error, _done)| error)?;
Self::await_add_reply(id, reply).await
}
pub(crate) async fn try_add_task(&self, spec: TaskSpec) -> Result<TaskId, RuntimeError> {
let (id, reply) = self
.enqueue_add_task(TaskId::next(), spec, None)
.map_err(|(error, _done)| error)?;
Self::await_add_reply(id, reply).await
}
pub(crate) async fn try_add_task_watched(
&self,
spec: TaskSpec,
) -> Result<(TaskId, tokio::sync::oneshot::Receiver<crate::TaskOutcome>), RuntimeError> {
let (tx, rx) = tokio::sync::oneshot::channel();
let (id, reply) = self
.enqueue_add_task(TaskId::next(), spec, Some(tx))
.map_err(|(error, _done)| error)?;
let id = Self::await_add_reply(id, reply).await?;
Ok((id, rx))
}
#[cfg(feature = "controller")]
pub(crate) fn add_task_with_id_watched(
&self,
id: TaskId,
spec: TaskSpec,
done: Option<OutcomeTx>,
) -> Result<(AddReplyRx, RemovalCompletion), (RuntimeError, Option<OutcomeTx>)> {
let completion = RemovalCompletion::new();
let (_id, reply) =
self.enqueue_add_task_with_completion(id, spec, done, Some(completion.clone()))?;
Ok((reply, completion))
}
pub(crate) async fn add_task_watched(
&self,
spec: TaskSpec,
) -> Result<(TaskId, tokio::sync::oneshot::Receiver<crate::TaskOutcome>), RuntimeError> {
let (tx, rx) = tokio::sync::oneshot::channel();
let (id, reply) = self
.enqueue_add_task_wait(TaskId::next(), spec, Some(tx))
.await
.map_err(|(error, _done)| error)?;
let id = Self::await_add_reply(id, reply).await?;
Ok((id, rx))
}
async fn await_add_reply(id: TaskId, reply: AddReplyRx) -> Result<TaskId, RuntimeError> {
match reply.await {
Ok(Ok(())) => Ok(id),
Ok(Err(error)) => Err(error),
Err(_) => Err(RuntimeError::ShuttingDown),
}
}
pub(super) fn enqueue_add_task(
&self,
id: TaskId,
spec: TaskSpec,
done: Option<OutcomeTx>,
) -> Result<(TaskId, AddReplyRx), (RuntimeError, Option<OutcomeTx>)> {
self.enqueue_add_task_with_completion(id, spec, done, None)
}
fn enqueue_add_task_with_completion(
&self,
id: TaskId,
spec: TaskSpec,
done: Option<OutcomeTx>,
completion: Option<RemovalCompletion>,
) -> Result<(TaskId, AddReplyRx), (RuntimeError, Option<OutcomeTx>)> {
if self.is_shutting_down() {
return Err((RuntimeError::ShuttingDown, done));
}
let label: Arc<str> = Arc::from(spec.task().name());
let permit = match self.cmd_tx.try_reserve() {
Ok(permit) => permit,
Err(mpsc::error::TrySendError::Full(())) => {
return Err((RuntimeError::CommandQueueFull, done));
}
Err(mpsc::error::TrySendError::Closed(())) => {
return Err((RuntimeError::ShuttingDown, done));
}
};
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err((RuntimeError::ShuttingDown, done));
};
Ok(self.commit_add(permit, id, label, spec, done, completion))
}
pub(super) async fn enqueue_add_task_wait(
&self,
id: TaskId,
spec: TaskSpec,
done: Option<OutcomeTx>,
) -> Result<(TaskId, AddReplyRx), (RuntimeError, Option<OutcomeTx>)> {
if self.is_shutting_down() {
return Err((RuntimeError::ShuttingDown, done));
}
let label: Arc<str> = Arc::from(spec.task().name());
let permit = match self.cmd_tx.reserve().await {
Ok(permit) => permit,
Err(_) => return Err((RuntimeError::ShuttingDown, done)),
};
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err((RuntimeError::ShuttingDown, done));
};
Ok(self.commit_add(permit, id, label, spec, done, None))
}
fn commit_add(
&self,
permit: mpsc::Permit<'_, RegistryCommand>,
id: TaskId,
label: Arc<str>,
spec: TaskSpec,
done: Option<OutcomeTx>,
completion: Option<RemovalCompletion>,
) -> (TaskId, AddReplyRx) {
let (reply, reply_rx) = oneshot::channel();
self.bus.publish(
Event::new(EventKind::TaskAddRequested)
.with_task(label)
.with_id(id),
);
permit.send(RegistryCommand::Add {
id,
spec,
outcome: done,
completion,
reply,
});
(id, reply_rx)
}
pub(super) async fn enqueue_add_batch_wait(
&self,
items: Vec<AddBatchItem>,
) -> Result<AddReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self
.cmd_tx
.reserve()
.await
.map_err(|_| RuntimeError::ShuttingDown)?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
let (reply, reply_rx) = oneshot::channel();
for item in &items {
self.bus.publish(
Event::new(EventKind::TaskAddRequested)
.with_task(Arc::clone(&item.label))
.with_id(item.id),
);
}
permit.send(RegistryCommand::AddBatch { items, reply });
Ok(reply_rx)
}
pub(super) async fn await_add_batch_reply(reply: AddReplyRx) -> Result<(), RuntimeError> {
match reply.await {
Ok(result) => result,
Err(_) => Err(RuntimeError::ShuttingDown),
}
}
pub(crate) async fn remove(&self, id: TaskId) -> Result<bool, RuntimeError> {
let reply = self.enqueue_remove_wait(id, None).await?;
Self::await_remove_reply(reply).await
}
pub(crate) async fn try_remove(&self, id: TaskId) -> Result<bool, RuntimeError> {
let reply = self.enqueue_remove(id, None)?;
Self::await_remove_reply(reply).await
}
pub(crate) async fn remove_by_label(&self, label: Arc<str>) -> Result<bool, RuntimeError> {
let reply = self.enqueue_remove_by_label_wait(label).await?;
Self::await_remove_reply(reply).await
}
pub(crate) async fn try_remove_by_label(&self, label: Arc<str>) -> Result<bool, RuntimeError> {
let reply = self.enqueue_remove_by_label(label)?;
Self::await_remove_reply(reply).await
}
async fn await_remove_reply(reply: RemoveReplyRx) -> Result<bool, RuntimeError> {
match reply.await {
Ok(result) => result,
Err(_) => Err(RuntimeError::ShuttingDown),
}
}
pub(super) fn enqueue_remove(
&self,
id: TaskId,
reason: Option<&'static str>,
) -> Result<RemoveReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self.cmd_tx.try_reserve().map_err(|error| match error {
mpsc::error::TrySendError::Full(()) => RuntimeError::CommandQueueFull,
mpsc::error::TrySendError::Closed(()) => RuntimeError::ShuttingDown,
})?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(self.commit_remove(permit, id, reason))
}
async fn enqueue_remove_wait(
&self,
id: TaskId,
reason: Option<&'static str>,
) -> Result<RemoveReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self
.cmd_tx
.reserve()
.await
.map_err(|_| RuntimeError::ShuttingDown)?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(self.commit_remove(permit, id, reason))
}
fn enqueue_remove_by_label(&self, label: Arc<str>) -> Result<RemoveReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self.cmd_tx.try_reserve().map_err(|error| match error {
mpsc::error::TrySendError::Full(()) => RuntimeError::CommandQueueFull,
mpsc::error::TrySendError::Closed(()) => RuntimeError::ShuttingDown,
})?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(Self::commit_remove_by_label(permit, label))
}
async fn enqueue_remove_by_label_wait(
&self,
label: Arc<str>,
) -> Result<RemoveReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self
.cmd_tx
.reserve()
.await
.map_err(|_| RuntimeError::ShuttingDown)?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(Self::commit_remove_by_label(permit, label))
}
fn commit_remove_by_label(
permit: mpsc::Permit<'_, RegistryCommand>,
label: Arc<str>,
) -> RemoveReplyRx {
let (reply, reply_rx) = oneshot::channel();
permit.send(RegistryCommand::RemoveByLabel { label, reply });
reply_rx
}
fn commit_remove(
&self,
permit: mpsc::Permit<'_, RegistryCommand>,
id: TaskId,
reason: Option<&'static str>,
) -> RemoveReplyRx {
let (reply, reply_rx) = oneshot::channel();
let mut event = Event::new(EventKind::TaskRemoveRequested).with_id(id);
if let Some(reason) = reason {
event = event.with_reason(reason);
}
self.bus.publish(event);
permit.send(RegistryCommand::Remove { id, reply });
reply_rx
}
fn enqueue_cancel(&self, id: TaskId) -> Result<CancelReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self.cmd_tx.try_reserve().map_err(|error| match error {
mpsc::error::TrySendError::Full(()) => RuntimeError::CommandQueueFull,
mpsc::error::TrySendError::Closed(()) => RuntimeError::ShuttingDown,
})?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(Self::commit_cancel(permit, id))
}
async fn enqueue_cancel_wait(&self, id: TaskId) -> Result<CancelReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self
.cmd_tx
.reserve()
.await
.map_err(|_| RuntimeError::ShuttingDown)?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(Self::commit_cancel(permit, id))
}
fn commit_cancel(permit: mpsc::Permit<'_, RegistryCommand>, id: TaskId) -> CancelReplyRx {
let (reply, reply_rx) = oneshot::channel();
permit.send(RegistryCommand::Cancel { id, reply });
reply_rx
}
fn enqueue_cancel_by_label(&self, label: Arc<str>) -> Result<CancelReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self.cmd_tx.try_reserve().map_err(|error| match error {
mpsc::error::TrySendError::Full(()) => RuntimeError::CommandQueueFull,
mpsc::error::TrySendError::Closed(()) => RuntimeError::ShuttingDown,
})?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(Self::commit_cancel_by_label(permit, label))
}
async fn enqueue_cancel_by_label_wait(
&self,
label: Arc<str>,
) -> Result<CancelReplyRx, RuntimeError> {
if self.is_shutting_down() {
return Err(RuntimeError::ShuttingDown);
}
let permit = self
.cmd_tx
.reserve()
.await
.map_err(|_| RuntimeError::ShuttingDown)?;
let Some(_admission) = self.command_admission() else {
drop(permit);
return Err(RuntimeError::ShuttingDown);
};
Ok(Self::commit_cancel_by_label(permit, label))
}
fn commit_cancel_by_label(
permit: mpsc::Permit<'_, RegistryCommand>,
label: Arc<str>,
) -> CancelReplyRx {
let (reply, reply_rx) = oneshot::channel();
permit.send(RegistryCommand::CancelByLabel { label, reply });
reply_rx
}
pub(crate) async fn list_tasks(&self) -> Vec<(TaskId, Arc<str>)> {
self.registry.list().await
}
#[cfg(test)]
pub(crate) async fn contains_id(&self, id: TaskId) -> bool {
self.registry.contains(id).await
}
#[cfg(all(test, feature = "controller"))]
pub(crate) fn registry_command_capacity(&self) -> usize {
self.cmd_tx.capacity()
}
#[cfg(test)]
pub(crate) async fn id_for_label(&self, name: &str) -> Option<TaskId> {
self.registry.id_for_label(name).await
}
pub(crate) async fn cancel(&self, id: TaskId) -> Result<bool, RuntimeError> {
let reply = self.enqueue_cancel_wait(id).await?;
let decision = Self::await_cancel_reply(reply).await?;
Self::wait_cancel_decision(decision, None).await
}
pub(crate) async fn try_cancel(&self, id: TaskId) -> Result<bool, RuntimeError> {
let decision = Self::await_cancel_reply(self.enqueue_cancel(id)?).await?;
Self::wait_cancel_decision(decision, None).await
}
pub(crate) async fn cancel_with_timeout(
&self,
id: TaskId,
wait_for: Duration,
) -> Result<bool, RuntimeError> {
let reply = self.enqueue_cancel_wait(id).await?;
let decision = Self::await_cancel_reply(reply).await?;
Self::wait_cancel_decision(decision, Some(wait_for)).await
}
pub(crate) async fn try_cancel_with_timeout(
&self,
id: TaskId,
wait_for: Duration,
) -> Result<bool, RuntimeError> {
let decision = Self::await_cancel_reply(self.enqueue_cancel(id)?).await?;
Self::wait_cancel_decision(decision, Some(wait_for)).await
}
pub(crate) async fn cancel_by_label(&self, label: Arc<str>) -> Result<bool, RuntimeError> {
let reply = self.enqueue_cancel_by_label_wait(label).await?;
let decision = Self::await_cancel_reply(reply).await?;
Self::wait_cancel_decision(decision, None).await
}
pub(crate) async fn try_cancel_by_label(&self, label: Arc<str>) -> Result<bool, RuntimeError> {
let decision = Self::await_cancel_reply(self.enqueue_cancel_by_label(label)?).await?;
Self::wait_cancel_decision(decision, None).await
}
pub(crate) async fn cancel_by_label_with_timeout(
&self,
label: Arc<str>,
wait_for: Duration,
) -> Result<bool, RuntimeError> {
let reply = self.enqueue_cancel_by_label_wait(label).await?;
let decision = Self::await_cancel_reply(reply).await?;
Self::wait_cancel_decision(decision, Some(wait_for)).await
}
pub(crate) async fn try_cancel_by_label_with_timeout(
&self,
label: Arc<str>,
wait_for: Duration,
) -> Result<bool, RuntimeError> {
let decision = Self::await_cancel_reply(self.enqueue_cancel_by_label(label)?).await?;
Self::wait_cancel_decision(decision, Some(wait_for)).await
}
async fn await_cancel_reply(
reply: CancelReplyRx,
) -> Result<Option<CancelDecision>, RuntimeError> {
match reply.await {
Ok(result) => result,
Err(_) => Err(RuntimeError::ShuttingDown),
}
}
async fn wait_cancel_decision(
decision: Option<CancelDecision>,
wait_for: Option<Duration>,
) -> Result<bool, RuntimeError> {
let Some(decision) = decision else {
return Ok(false);
};
let id = decision.id;
let claimed = decision.claimed;
if let Some(wait_for) = wait_for {
if timeout(wait_for, decision.wait()).await.is_err() && !decision.is_complete() {
return Err(RuntimeError::TaskTerminationTimeout {
id,
timeout: wait_for,
});
}
} else {
decision.wait().await;
}
Ok(claimed)
}
}