use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Instant;
use parking_lot::Mutex;
use tokio::sync::mpsc;
use tokio::sync::Notify;
use crate::error::RuntimeResult;
use crate::task_group::TaskId;
use crate::task_group::TaskKind;
use crate::task_spawner::TaskSpawner;
use crate::RuntimeContractPolicy;
use crate::RuntimeContractViolation;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CriticalFailureKind {
Panicked,
ExitedUnexpectedly,
Unrecoverable,
}
impl CriticalFailureKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Panicked => "panicked",
Self::ExitedUnexpectedly => "exited-unexpectedly",
Self::Unrecoverable => "unrecoverable",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CriticalFailure {
kind: CriticalFailureKind,
task_kind: TaskKind,
sequence: u64,
observed_at: Instant,
}
impl CriticalFailure {
#[must_use]
pub const fn kind(self) -> CriticalFailureKind {
self.kind
}
#[must_use]
pub const fn task_kind(self) -> TaskKind {
self.task_kind
}
#[must_use]
pub const fn sequence(self) -> u64 {
self.sequence
}
#[must_use]
pub const fn observed_at(self) -> Instant {
self.observed_at
}
}
#[derive(Debug, Clone)]
pub(crate) struct CriticalRegistration {
pub(crate) failures: CriticalFailureState,
pub(crate) task_kind: TaskKind,
pub(crate) expects_until_cancelled: bool,
}
#[derive(Debug)]
struct CriticalFailureStateInner {
pending: Mutex<Option<CriticalFailure>>,
subscribers: Mutex<Vec<mpsc::Sender<CriticalFailure>>>,
sequence: AtomicU64,
occurrences: AtomicU64,
changed: Notify,
}
#[derive(Debug, Clone)]
pub struct CriticalFailureState {
inner: Arc<CriticalFailureStateInner>,
}
impl Default for CriticalFailureState {
fn default() -> Self {
Self::new()
}
}
impl CriticalFailureState {
#[must_use]
pub fn new() -> Self {
Self {
inner: Arc::new(CriticalFailureStateInner {
pending: Mutex::new(None),
subscribers: Mutex::new(Vec::new()),
sequence: AtomicU64::new(1),
occurrences: AtomicU64::new(0),
changed: Notify::new(),
}),
}
}
pub fn record(&self, kind: CriticalFailureKind, task_kind: TaskKind) -> CriticalFailure {
let failure = CriticalFailure {
kind,
task_kind,
sequence: self.inner.sequence.fetch_add(1, Ordering::AcqRel),
observed_at: Instant::now(),
};
self.inner.occurrences.fetch_add(1, Ordering::AcqRel);
{
let mut pending = self.inner.pending.lock();
if pending.is_none() {
*pending = Some(failure);
}
}
self.inner.changed.notify_waiters();
self.notify_subscribers(failure);
failure
}
fn notify_subscribers(&self, failure: CriticalFailure) {
let mut subscribers = self.inner.subscribers.lock();
subscribers
.retain(|subscriber| !matches!(subscriber.try_send(failure), Err(mpsc::error::TrySendError::Closed(_))));
}
#[must_use]
pub fn pending(&self) -> Option<CriticalFailure> {
*self.inner.pending.lock()
}
#[must_use]
pub fn handle(&self) -> Option<CriticalFailure> {
self.inner.pending.lock().take()
}
#[must_use]
pub fn occurrences(&self) -> u64 {
self.inner.occurrences.load(Ordering::Acquire)
}
pub async fn wait(&self) -> CriticalFailure {
loop {
let changed = self.inner.changed.notified();
if let Some(failure) = self.pending() {
return failure;
}
changed.await;
}
}
pub fn subscribe(&self, capacity: usize) -> Result<CriticalFailureSubscription, RuntimeContractViolation> {
if capacity == 0 {
return Err(RuntimeContractViolation::InvalidConfiguration {
policy: RuntimeContractPolicy::CriticalFailureSubscriptionCapacityPositive,
});
}
let (sender, receiver) = mpsc::channel(capacity);
self.inner.subscribers.lock().push(sender);
Ok(CriticalFailureSubscription { receiver })
}
pub fn spawn_monitor<F>(
&self,
owner: &TaskSpawner,
name: impl Into<crate::TaskName>,
on_failure: F,
) -> RuntimeResult<TaskId>
where
F: Fn(CriticalFailure) + Send + 'static,
{
let failures = self.clone();
let cancellation = owner.cancellation_token();
owner.spawn(name, TaskKind::Worker, async move {
loop {
tokio::select! {
biased;
_ = cancellation.cancelled() => break,
_ = failures.wait() => {
if let Some(failure) = failures.handle() {
on_failure(failure);
}
}
}
}
})
}
}
#[derive(Debug)]
pub struct CriticalFailureSubscription {
receiver: mpsc::Receiver<CriticalFailure>,
}
impl CriticalFailureSubscription {
pub async fn recv(&mut self) -> Option<CriticalFailure> {
self.receiver.recv().await
}
#[must_use]
pub fn try_recv(&mut self) -> Option<CriticalFailure> {
self.receiver.try_recv().ok()
}
}