#[cfg(feature = "loom")]
use loom::sync::Mutex;
#[cfg(feature = "loom")]
use loom::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
#[cfg(not(feature = "loom"))]
use std::sync::Mutex;
#[cfg(not(feature = "loom"))]
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll, Waker};
use core::future::Future;
use core::pin::Pin;
use futures::task::AtomicWaker;
use smallvec::SmallVec;
struct WakerSlot(AtomicWaker);
impl WakerSlot {
fn new() -> Self {
Self(AtomicWaker::new())
}
fn register(&self, waker: &Waker) {
self.0.register(waker);
}
fn wake(&self) {
self.0.wake();
}
}
type WaiterRegistry = SmallVec<[Arc<WakerSlot>; crate::sized::SIGNAL_WAITERS_INLINE_CAP]>;
struct Level {
fired: AtomicBool,
waiters: Mutex<WaiterRegistry>,
}
impl Level {
fn new() -> Arc<Self> {
Arc::new(Self {
fired: AtomicBool::new(false),
waiters: Mutex::new(SmallVec::new()),
})
}
fn waiters(&self) -> impl core::ops::DerefMut<Target = WaiterRegistry> + '_ {
self.waiters.lock().unwrap_or_else(|err| err.into_inner())
}
fn fire(&self) {
if self.fired.swap(true, Ordering::AcqRel) {
return;
}
let drained = core::mem::take(&mut *self.waiters());
for slot in drained {
slot.wake();
}
}
fn is_fired(&self) -> bool {
self.fired.load(Ordering::Acquire)
}
}
#[derive(Clone)]
pub struct Signal {
levels: Arc<[Arc<Level>]>,
}
impl Signal {
#[must_use]
pub fn new() -> Self {
Self {
levels: Arc::from([Level::new()]),
}
}
#[must_use]
pub fn child(&self) -> Self {
let mut levels = Vec::with_capacity(self.levels.len() + 1);
levels.extend(self.levels.iter().cloned());
levels.push(Level::new());
Self {
levels: Arc::from(levels),
}
}
pub fn fire(&self) {
if let Some(own) = self.levels.last() {
own.fire();
}
}
#[must_use]
pub fn is_fired(&self) -> bool {
self.levels.iter().any(|level| level.is_fired())
}
#[must_use]
pub fn fired(&self) -> Fired {
Fired {
levels: self.levels.clone(),
slot: OnceLock::new(),
}
}
#[must_use]
pub fn guard(self) -> SignalGuard {
SignalGuard {
signal: self,
armed: true,
}
}
}
impl Default for Signal {
fn default() -> Self {
Self::new()
}
}
impl core::fmt::Debug for Signal {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("Signal")
.field("depth", &self.levels.len())
.field("fired", &self.is_fired())
.finish()
}
}
pub struct Fired {
levels: Arc<[Arc<Level>]>,
slot: OnceLock<Arc<WakerSlot>>,
}
impl Fired {
pub fn poll_shared(&self, cx: &mut Context<'_>) -> Poll<()> {
let slot = self.slot.get_or_init(|| {
let slot = Arc::new(WakerSlot::new());
for level in self.levels.iter() {
level.waiters().push(slot.clone());
}
slot
});
slot.register(cx.waker());
if self.levels.iter().any(|level| level.is_fired()) {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
impl Future for Fired {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
self.poll_shared(cx)
}
}
impl Drop for Fired {
fn drop(&mut self) {
let Some(slot) = self.slot.take() else {
return;
};
for level in self.levels.iter() {
level
.waiters()
.retain(|candidate| !Arc::ptr_eq(candidate, &slot));
}
}
}
pub struct SignalGuard {
signal: Signal,
armed: bool,
}
impl SignalGuard {
#[must_use]
pub fn signal(&self) -> &Signal {
&self.signal
}
pub fn disarm(mut self) -> Signal {
self.armed = false;
self.signal.clone()
}
}
impl Drop for SignalGuard {
fn drop(&mut self) {
if self.armed {
self.signal.fire();
}
}
}
#[cfg(all(test, not(feature = "loom")))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::atomic::AtomicUsize;
use std::task::Wake;
use super::*;
struct CountingWaker(AtomicUsize);
impl Wake for CountingWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
fn counting_waker() -> (Arc<CountingWaker>, Waker) {
let counter = Arc::new(CountingWaker(AtomicUsize::new(0)));
let waker = Waker::from(counter.clone());
(counter, waker)
}
#[test]
fn fire_is_sticky_and_idempotent() {
let signal = Signal::new();
assert!(!signal.is_fired());
signal.fire();
signal.fire();
assert!(signal.is_fired());
}
#[test]
fn late_observer_resolves_on_first_poll() {
let signal = Signal::new();
signal.fire();
let mut fired = signal.fired();
let (_, waker) = counting_waker();
let mut cx = Context::from_waker(&waker);
assert_eq!(Pin::new(&mut fired).poll(&mut cx), Poll::Ready(()));
}
#[test]
fn registered_waiter_is_woken_by_fire() {
let signal = Signal::new();
let mut fired = signal.fired();
let (counter, waker) = counting_waker();
let mut cx = Context::from_waker(&waker);
assert_eq!(Pin::new(&mut fired).poll(&mut cx), Poll::Pending);
signal.fire();
assert_eq!(counter.0.load(Ordering::Relaxed), 1);
assert_eq!(Pin::new(&mut fired).poll(&mut cx), Poll::Ready(()));
}
#[test]
fn child_observes_parent_fire() {
let parent = Signal::new();
let child = parent.child();
parent.fire();
assert!(child.is_fired());
}
#[test]
fn parent_is_untouched_by_child_fire() {
let parent = Signal::new();
let child = parent.child();
child.fire();
assert!(child.is_fired());
assert!(!parent.is_fired());
}
#[test]
fn grandchild_observes_root_through_the_merge() {
let root = Signal::new();
let grandchild = root.child().child();
root.fire();
assert!(grandchild.is_fired());
}
#[test]
fn guard_fires_own_level_on_drop() {
let signal = Signal::new();
let observer = signal.clone();
{
let _guard = signal.guard();
assert!(!observer.is_fired());
}
assert!(observer.is_fired());
}
#[test]
fn disarmed_guard_does_not_fire_on_drop() {
let signal = Signal::new();
let observer = signal.clone();
let guard = signal.guard();
let returned = guard.disarm();
assert!(!observer.is_fired());
assert!(!returned.is_fired());
}
#[test]
fn dropping_a_pending_waiter_unregisters_its_slot() {
let signal = Signal::new();
let (_, waker) = counting_waker();
let mut cx = Context::from_waker(&waker);
{
let mut fired = signal.fired();
assert_eq!(Pin::new(&mut fired).poll(&mut cx), Poll::Pending);
assert_eq!(signal.levels[0].waiters.lock().unwrap().len(), 1);
}
assert_eq!(signal.levels[0].waiters.lock().unwrap().len(), 0);
}
#[test]
fn a_poisoned_waiter_registry_still_wakes_its_waiters() {
let signal = Signal::new();
let mut fired = signal.fired();
let (counter, waker) = counting_waker();
let mut cx = Context::from_waker(&waker);
assert_eq!(Pin::new(&mut fired).poll(&mut cx), Poll::Pending);
let poisoner = signal.clone();
let outcome = catch_unwind(AssertUnwindSafe(|| {
let _held = poisoner.levels[0].waiters.lock().expect("uncontended lock");
panic!("poison the waiter registry");
}));
assert!(outcome.is_err(), "the poisoning closure must have unwound");
assert!(signal.levels[0].waiters.is_poisoned());
signal.fire();
assert_eq!(
counter.0.load(Ordering::Relaxed),
1,
"a poisoned registry must still deliver the wakeup"
);
assert_eq!(Pin::new(&mut fired).poll(&mut cx), Poll::Ready(()));
}
#[test]
fn repolling_does_not_grow_the_waiter_list() {
let signal = Signal::new();
let mut fired = signal.fired();
let (_, waker) = counting_waker();
let mut cx = Context::from_waker(&waker);
for _ in 0..64 {
assert_eq!(Pin::new(&mut fired).poll(&mut cx), Poll::Pending);
}
assert_eq!(signal.levels[0].waiters.lock().unwrap().len(), 1);
}
}