use std::{sync::Arc, time::Duration};
use tokio::{
sync::{mpsc, oneshot},
time::timeout,
};
use super::super::SupervisorCore;
use crate::{
core::registry::{CancelDecision, CancelReplyRx, RegistryCommand},
error::RuntimeError,
identity::TaskId,
};
impl SupervisorCore {
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 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(in crate::core) 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(in crate::core) 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(in crate::core) 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(in crate::core) 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)
}
}