use monoloop_contracts::ConnectorError;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::Arc;
use tokio::sync::Notify;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CancellationReason {
CallerRequested,
RunShutdown,
DeadlineExceeded,
Other(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TerminationReason {
CancelEscalation,
CallerForced,
SupervisorTeardown,
Other(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ControlDisposition {
Accepted,
AlreadyRequested,
AlreadyTerminal,
ControlUnavailable,
}
#[derive(Debug)]
pub struct ControlState {
terminal: AtomicBool,
cancel_requested: AtomicBool,
terminate_requested: AtomicBool,
preferred_end: AtomicU8,
notify: Notify,
}
impl Default for ControlState {
fn default() -> Self {
Self {
terminal: AtomicBool::new(false),
cancel_requested: AtomicBool::new(false),
terminate_requested: AtomicBool::new(false),
preferred_end: AtomicU8::new(0),
notify: Notify::new(),
}
}
}
impl ControlState {
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub fn is_terminal(&self) -> bool {
self.terminal.load(Ordering::SeqCst)
}
pub fn mark_terminal(&self) {
self.terminal.store(true, Ordering::SeqCst);
self.notify.notify_waiters();
}
pub fn cancel_requested(&self) -> bool {
self.cancel_requested.load(Ordering::SeqCst)
}
pub fn terminate_requested(&self) -> bool {
self.terminate_requested.load(Ordering::SeqCst)
}
pub(crate) fn preferred_end_kind(&self) -> Option<PreferredEnd> {
match self.preferred_end.load(Ordering::SeqCst) {
1 => Some(PreferredEnd::Cancelled),
2 => Some(PreferredEnd::Terminated),
_ => None,
}
}
pub fn notify(&self) -> &Notify {
&self.notify
}
pub(crate) fn request_cancel(&self) -> ControlDisposition {
if self.is_terminal() {
return ControlDisposition::AlreadyTerminal;
}
if self.cancel_requested.swap(true, Ordering::SeqCst) {
return ControlDisposition::AlreadyRequested;
}
let _ = self
.preferred_end
.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst);
self.notify.notify_waiters();
ControlDisposition::Accepted
}
pub(crate) fn request_terminate(&self) -> ControlDisposition {
if self.is_terminal() {
return ControlDisposition::AlreadyTerminal;
}
if self.terminate_requested.swap(true, Ordering::SeqCst) {
return ControlDisposition::AlreadyRequested;
}
self.preferred_end.store(2, Ordering::SeqCst);
self.notify.notify_waiters();
ControlDisposition::Accepted
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PreferredEnd {
Cancelled,
Terminated,
}
#[derive(Clone, Debug)]
pub struct ConnectionControlHandle {
state: Arc<ControlState>,
}
impl ConnectionControlHandle {
pub fn new(state: Arc<ControlState>) -> Self {
Self { state }
}
pub fn state(&self) -> &Arc<ControlState> {
&self.state
}
pub fn cancel(&self, _reason: CancellationReason) -> ControlDisposition {
self.state.request_cancel()
}
pub fn terminate(&self, _reason: TerminationReason) -> ControlDisposition {
self.state.request_terminate()
}
pub async fn interrupted(&self) {
loop {
if self.state.cancel_requested()
|| self.state.terminate_requested()
|| self.state.is_terminal()
{
return;
}
self.state.notify.notified().await;
}
}
pub fn interrupt_error(&self) -> Option<ConnectorError> {
if self.state.terminate_requested() {
Some(ConnectorError::terminated())
} else if self.state.cancel_requested() {
Some(ConnectorError::cancelled())
} else {
None
}
}
}