use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use crate::waker;
const NO_WAITER: u32 = 0xFFFF_FFFF;
pub struct Signal {
fired: crate::sync::atomic::AtomicBool,
waiter: crate::sync::atomic::AtomicU32,
}
impl Signal {
#[cfg(not(loom))]
pub const fn new() -> Self {
Self {
fired: crate::sync::atomic::AtomicBool::new(false),
waiter: crate::sync::atomic::AtomicU32::new(NO_WAITER),
}
}
#[cfg(loom)]
pub fn new() -> Self {
Self {
fired: crate::sync::atomic::AtomicBool::new(false),
waiter: crate::sync::atomic::AtomicU32::new(NO_WAITER),
}
}
pub fn signal(&self) {
self.fired
.store(true, crate::sync::atomic::Ordering::Release);
let w = self
.waiter
.swap(NO_WAITER, crate::sync::atomic::Ordering::AcqRel);
if w != NO_WAITER {
waker::wake_task(crate::task::TaskId::from_u16(w as u16));
}
}
pub fn try_take(&self) -> bool {
self.fired
.compare_exchange(
true,
false,
crate::sync::atomic::Ordering::AcqRel,
crate::sync::atomic::Ordering::Acquire,
)
.is_ok()
}
pub fn reset(&self) {
self.fired
.store(false, crate::sync::atomic::Ordering::Release);
}
pub fn wait(&self) -> Wait<'_> {
Wait {
sig: self,
registered: None,
}
}
}
impl Default for Signal {
fn default() -> Self {
Self::new()
}
}
pub struct Wait<'a> {
sig: &'a Signal,
registered: Option<crate::task::TaskId>,
}
impl<'a> Drop for Wait<'a> {
fn drop(&mut self) {
if self.registered.take().is_some() {
self.sig
.waiter
.store(NO_WAITER, crate::sync::atomic::Ordering::Release);
}
}
}
impl<'a> Future for Wait<'a> {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
let this = unsafe { self.get_unchecked_mut() };
if this.sig.try_take() {
return Poll::Ready(());
}
let id = crate::executor::current_task()
.expect("Signal::wait() polled outside of a task context");
let prev = this
.sig
.waiter
.swap(id.as_u16() as u32, crate::sync::atomic::Ordering::AcqRel);
debug_assert_eq!(
prev, NO_WAITER,
"rivet: two tasks awaiting the same Signal concurrently — \
a peripheral has exactly one owning task by construction"
);
this.registered = Some(id);
if this.sig.try_take() {
if this.registered.take().is_some() {
this.sig
.waiter
.store(NO_WAITER, crate::sync::atomic::Ordering::Release);
}
return Poll::Ready(());
}
Poll::Pending
}
}
unsafe impl Sync for Signal {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_take_false_when_unfired() {
crate::kernel_test! {
let sig = Signal::new();
assert!(!sig.try_take());
}
}
#[test]
fn signal_then_try_take() {
crate::kernel_test! {
let sig = Signal::new();
sig.signal();
assert!(sig.try_take());
assert!(!sig.try_take(), "try_take consumes the latch");
}
}
#[test]
fn reset_clears_stale_latch() {
crate::kernel_test! {
let sig = Signal::new();
sig.signal();
sig.reset();
assert!(!sig.try_take());
}
}
#[test]
fn wait_ready_when_already_fired() {
crate::kernel_test! {
let sig = Signal::new();
sig.signal();
let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
let mut cx = Context::from_waker(&waker);
let mut fut = sig.wait();
let pinned = unsafe { Pin::new_unchecked(&mut fut) };
assert_eq!(pinned.poll(&mut cx), Poll::Ready(()));
}
}
#[test]
#[should_panic(expected = "outside of a task context")]
fn wait_panics_without_task_context() {
crate::kernel_test! {
let sig = Signal::new();
let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
let mut cx = Context::from_waker(&waker);
let mut fut = sig.wait();
let pinned = unsafe { Pin::new_unchecked(&mut fut) };
let _ = pinned.poll(&mut cx);
}
}
#[test]
fn signal_after_registration_wakes() {
crate::kernel_test! {
let sig = Signal::new();
let id = crate::task::TaskId::new(1, 0);
let waker = crate::waker::task_waker(id);
let mut cx = Context::from_waker(&waker);
let mut fut = sig.wait();
let pinned = unsafe { Pin::new_unchecked(&mut fut) };
crate::executor::set_current_for_test(id.priority(), id.index());
assert_eq!(pinned.poll(&mut cx), Poll::Pending);
sig.signal();
assert_eq!(crate::waker::next_ready(), Some(id));
}
}
#[test]
fn drop_clears_registration_not_latch() {
crate::kernel_test! {
let sig = Signal::new();
let id = crate::task::TaskId::new(2, 0);
{
let mut fut = sig.wait();
let pinned = unsafe { Pin::new_unchecked(&mut fut) };
let waker = crate::waker::task_waker(id);
let mut cx = Context::from_waker(&waker);
crate::executor::set_current_for_test(id.priority(), id.index());
assert_eq!(pinned.poll(&mut cx), Poll::Pending);
}
sig.signal();
assert_eq!(crate::waker::next_ready(), None);
}
}
#[test]
fn signal_during_cancellation_stays_observable() {
crate::kernel_test! {
let sig = Signal::new();
let id = crate::task::TaskId::new(3, 0);
{
let mut fut = sig.wait();
let pinned = unsafe { Pin::new_unchecked(&mut fut) };
let waker = crate::waker::task_waker(id);
let mut cx = Context::from_waker(&waker);
crate::executor::set_current_for_test(id.priority(), id.index());
assert_eq!(pinned.poll(&mut cx), Poll::Pending);
sig.signal();
}
assert!(sig.try_take());
}
}
}