use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::{mpsc, oneshot};
use tokio_util::sync::CancellationToken;
const COMMAND_CAPACITY: usize = 64;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum ServiceStartPolicy {
#[default]
Automatic,
Manual,
Unavailable,
}
impl ServiceStartPolicy {
pub const fn as_str(self) -> &'static str {
match self {
Self::Automatic => "automatic",
Self::Manual => "manual",
Self::Unavailable => "unavailable",
}
}
}
impl fmt::Display for ServiceStartPolicy {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, Debug)]
pub struct RunContext {
cancellation: CancellationToken,
execution: crate::execution::ExecutionPlan,
}
impl RunContext {
pub fn new(cancellation: CancellationToken) -> Self {
Self { cancellation, execution: Default::default() }
}
pub(crate) fn with_execution(
cancellation: CancellationToken,
execution: crate::execution::ExecutionPlan,
) -> Self {
Self { cancellation, execution }
}
pub fn execution_spec(&self) -> crate::ExecutionSpec {
self.execution.spec
}
pub fn child(&self) -> Self {
Self::with_execution(
self.cancellation.child_token(),
self.execution.clone(),
)
}
pub fn execution_executor(
&self
) -> Option<&Arc<dyn crate::PreparedExecution>> {
self.execution.executor.as_ref()
}
pub async fn cancelled(&self) {
self.cancellation.cancelled().await;
}
pub fn cancellation_token(&self) -> CancellationToken {
self.cancellation.clone()
}
pub fn is_cancelled(&self) -> bool {
self.cancellation.is_cancelled()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ServiceStatus {
Running,
Stopping,
Stopped,
Failed,
}
impl fmt::Display for ServiceStatus {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let value = match self {
Self::Running => "running",
Self::Stopping => "stopping",
Self::Stopped => "stopped",
Self::Failed => "failed",
};
f.write_str(value)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ServiceSnapshot {
pub(crate) name: &'static str,
pub(crate) status: ServiceStatus,
pub(crate) active_executions: u64,
#[cfg(feature = "fleet")]
pub(crate) dispatch: crate::fleet::Dispatch,
pub(crate) start_policy: ServiceStartPolicy,
pub(crate) reloadable: bool,
pub(crate) generation: u64,
pub(crate) last_error: Option<String>,
pub(crate) reload_revision: u64,
pub(crate) last_reload_error: Option<String>,
}
impl ServiceSnapshot {
pub fn name(&self) -> &'static str {
self.name
}
pub fn status(&self) -> ServiceStatus {
self.status
}
pub fn active_executions(&self) -> u64 {
self.active_executions
}
#[cfg(feature = "fleet")]
pub fn dispatch(&self) -> crate::fleet::Dispatch {
self.dispatch
}
pub fn start_policy(&self) -> ServiceStartPolicy {
self.start_policy
}
pub fn is_reloadable(&self) -> bool {
self.reloadable
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn last_error(&self) -> Option<&str> {
self.last_error.as_deref()
}
pub fn reload_revision(&self) -> u64 {
self.reload_revision
}
pub fn last_reload_error(&self) -> Option<&str> {
self.last_reload_error.as_deref()
}
}
pub(crate) struct ExecutionGuard(Arc<AtomicU64>);
impl ExecutionGuard {
pub(crate) fn enter(count: &Arc<AtomicU64>) -> Self {
count.fetch_add(1, Ordering::SeqCst);
Self(count.clone())
}
}
impl Drop for ExecutionGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::SeqCst);
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ServiceManagerError {
RuntimeUnavailable,
NotFound(String),
Unavailable(String),
NotReloadable(String),
Busy { name: String, status: ServiceStatus },
ShuttingDown,
OperationFailed { name: String, message: String },
}
impl fmt::Display for ServiceManagerError {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
match self {
Self::RuntimeUnavailable => {
f.write_str("service runtime is unavailable")
}
Self::NotFound(name) => {
write!(f, "runnable service '{name}' is not registered")
}
Self::Unavailable(name) => {
write!(
f,
"runnable service '{name}' is unavailable on this runtime"
)
}
Self::NotReloadable(name) => {
write!(f, "runnable service '{name}' is not reloadable")
}
Self::Busy { name, status } => {
write!(f, "service '{name}' is currently {status}")
}
Self::ShuttingDown => {
f.write_str("service runtime is shutting down")
}
Self::OperationFailed { name, message } => {
write!(f, "service '{name}' operation failed: {message}")
}
}
}
}
impl std::error::Error for ServiceManagerError {}
type SnapshotReply =
oneshot::Sender<Result<ServiceSnapshot, ServiceManagerError>>;
type ListReply =
oneshot::Sender<Result<Vec<ServiceSnapshot>, ServiceManagerError>>;
pub(crate) enum ServiceCommand {
List { reply: ListReply },
Start { name: String, reply: SnapshotReply },
Stop { name: String, reply: SnapshotReply },
Reload { name: String, reply: SnapshotReply },
Reconcile { name: String, reply: SnapshotReply },
}
pub struct ServiceManager {
commands: mpsc::Sender<ServiceCommand>,
}
impl ServiceManager {
pub(crate) fn channel() -> (Arc<Self>, mpsc::Receiver<ServiceCommand>) {
let (commands, receiver) = mpsc::channel(COMMAND_CAPACITY);
(Arc::new(Self { commands }), receiver)
}
pub async fn list(
&self
) -> Result<Vec<ServiceSnapshot>, ServiceManagerError> {
let (reply, result) = oneshot::channel();
self.commands
.send(ServiceCommand::List { reply })
.await
.map_err(|_| ServiceManagerError::RuntimeUnavailable)?;
result.await.map_err(|_| ServiceManagerError::RuntimeUnavailable)?
}
pub async fn start(
&self,
name: impl Into<String>,
) -> Result<ServiceSnapshot, ServiceManagerError> {
let (reply, result) = oneshot::channel();
self.commands
.send(ServiceCommand::Start { name: name.into(), reply })
.await
.map_err(|_| ServiceManagerError::RuntimeUnavailable)?;
result.await.map_err(|_| ServiceManagerError::RuntimeUnavailable)?
}
pub async fn stop(
&self,
name: impl Into<String>,
) -> Result<ServiceSnapshot, ServiceManagerError> {
let (reply, result) = oneshot::channel();
self.commands
.send(ServiceCommand::Stop { name: name.into(), reply })
.await
.map_err(|_| ServiceManagerError::RuntimeUnavailable)?;
result.await.map_err(|_| ServiceManagerError::RuntimeUnavailable)?
}
pub async fn reload(
&self,
name: impl Into<String>,
) -> Result<ServiceSnapshot, ServiceManagerError> {
let (reply, result) = oneshot::channel();
self.commands
.send(ServiceCommand::Reload { name: name.into(), reply })
.await
.map_err(|_| ServiceManagerError::RuntimeUnavailable)?;
result.await.map_err(|_| ServiceManagerError::RuntimeUnavailable)?
}
pub(crate) async fn reconcile(
&self,
name: &'static str,
) -> Result<ServiceSnapshot, ServiceManagerError> {
let (reply, result) = oneshot::channel();
self.commands
.send(ServiceCommand::Reconcile { name: name.into(), reply })
.await
.map_err(|_| ServiceManagerError::RuntimeUnavailable)?;
result.await.map_err(|_| ServiceManagerError::RuntimeUnavailable)?
}
pub async fn restart(
&self,
name: impl Into<String>,
) -> Result<ServiceSnapshot, ServiceManagerError> {
let name = name.into();
self.stop(name.clone()).await?;
self.start(name).await
}
}