1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4use std::{
5 future::Future,
6 pin::Pin,
7 sync::{
8 Arc,
9 atomic::{AtomicBool, Ordering},
10 },
11 task::{Context, Poll},
12};
13
14use tokio::sync::{Notify, futures::OwnedNotified};
15
16pub struct SoftCycleListener<'a> {
20 notify: OwnedNotified,
21 controller: &'a SoftCycleController,
22}
23
24impl Future for SoftCycleListener<'_> {
25 type Output = bool;
26
27 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
28 let notify_pin = unsafe { self.as_mut().map_unchecked_mut(|s| &mut s.notify) };
29
30 notify_pin
31 .poll(cx)
32 .map(|_| self.controller.is_shutdown.load(Ordering::Relaxed))
33 }
34}
35
36pub struct SoftCycleController {
40 triggered: AtomicBool,
44 is_shutdown: AtomicBool,
46
47 notify: Arc<Notify>,
49}
50
51impl SoftCycleController {
52 #[allow(clippy::new_without_default)]
54 pub fn new() -> Self {
55 Self {
56 triggered: AtomicBool::new(false),
57 is_shutdown: AtomicBool::new(false),
58 notify: Arc::new(Notify::new()),
59 }
60 }
61
62 #[must_use = "Caller must check if the operation was successful"]
67 pub async fn try_restart(&self) -> bool {
68 self.try_trigger(false).await
69 }
70
71 #[must_use = "Caller must check if the operation was successful"]
76 pub async fn try_shutdown(&self) -> bool {
77 self.try_trigger(true).await
78 }
79
80 #[must_use = "Caller must check if the operation was successful"]
85 pub async fn try_trigger(&self, is_shutdown: bool) -> bool {
86 match self
87 .triggered
88 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
89 {
90 Ok(_) => {
91 self.is_shutdown.store(is_shutdown, Ordering::Relaxed);
92 self.notify.notify_waiters();
93 true
94 }
95 Err(_) => false,
96 }
97 }
98
99 pub fn clear(&self) {
101 self.is_shutdown.store(false, Ordering::Relaxed);
102 self.triggered.store(false, Ordering::Release);
103 }
104
105 #[must_use = "Caller must await the listener to receive the signal"]
111 pub fn listener<'a>(&'a self) -> SoftCycleListener<'a> {
112 SoftCycleListener {
113 notify: self.notify.clone().notified_owned(),
114 controller: self,
115 }
116 }
117}
118
119#[cfg(feature = "global_instance")]
120#[cfg_attr(docsrs, doc(cfg(feature = "global_instance")))]
121mod global;
122
123#[cfg(feature = "global_instance")]
124#[cfg_attr(docsrs, doc(cfg(feature = "global_instance")))]
125pub use global::*;