use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll};
use crate::waker;
pub struct Semaphore<const MAX: u8> {
count: crate::sync::atomic::AtomicU8,
waiters: [crate::sync::atomic::AtomicU32; 32],
}
impl<const MAX: u8> Semaphore<MAX> {
#[cfg(not(loom))]
pub const fn new(initial: u8) -> Self {
Self {
count: crate::sync::atomic::AtomicU8::new(initial),
waiters: [const { crate::sync::atomic::AtomicU32::new(0) }; 32],
}
}
#[cfg(loom)]
pub fn new(initial: u8) -> Self {
Self {
count: crate::sync::atomic::AtomicU8::new(initial),
waiters: core::array::from_fn(|_| crate::sync::atomic::AtomicU32::new(0)),
}
}
pub fn try_acquire(&self) -> bool {
loop {
let c = self.count.load(crate::sync::atomic::Ordering::Acquire);
if c == 0 {
return false;
}
if self
.count
.compare_exchange_weak(
c,
c - 1,
crate::sync::atomic::Ordering::AcqRel,
crate::sync::atomic::Ordering::Acquire,
)
.is_ok()
{
return true;
}
}
}
pub fn acquire(&self) -> Acquire<'_, MAX> {
Acquire {
sem: self,
registered: None,
}
}
pub fn release(&self) {
for (prio, queue) in self.waiters.iter().enumerate().rev() {
loop {
let q = queue.load(crate::sync::atomic::Ordering::Acquire);
if q == 0 {
break; }
let bit = q & q.wrapping_neg();
match queue.compare_exchange_weak(
q,
q & !bit,
crate::sync::atomic::Ordering::AcqRel,
crate::sync::atomic::Ordering::Acquire,
) {
Ok(_) => {
self.count.store(1, crate::sync::atomic::Ordering::Release);
waker::wake_task(crate::task::TaskId::new(
prio as u8,
bit.trailing_zeros() as u8,
));
return;
}
Err(_) => continue, }
}
}
let mut c = self.count.load(crate::sync::atomic::Ordering::Acquire);
loop {
if c >= MAX {
return;
}
match self.count.compare_exchange_weak(
c,
c + 1,
crate::sync::atomic::Ordering::AcqRel,
crate::sync::atomic::Ordering::Acquire,
) {
Ok(_) => return,
Err(actual) => c = actual,
}
}
}
fn register_waiter(&self, id: crate::task::TaskId) {
let mask = 1u32 << id.index();
self.waiters[id.priority() as usize].fetch_or(mask, crate::sync::atomic::Ordering::Release);
}
#[cfg(any(loom, feature = "test-support"))]
#[doc(hidden)]
pub fn debug_waiters(&self) -> [u32; 32] {
let mut w = [0u32; 32];
for (i, q) in self.waiters.iter().enumerate() {
w[i] = q.load(crate::sync::atomic::Ordering::Acquire);
}
w
}
fn remove_waiter(&self, id: crate::task::TaskId) {
let mask = 1u32 << id.index();
self.waiters[id.priority() as usize]
.fetch_and(!mask, crate::sync::atomic::Ordering::AcqRel);
}
}
pub struct Acquire<'a, const MAX: u8> {
sem: &'a Semaphore<MAX>,
registered: Option<crate::task::TaskId>,
}
impl<'a, const MAX: u8> Drop for Acquire<'a, MAX> {
fn drop(&mut self) {
if let Some(id) = self.registered.take() {
self.sem.remove_waiter(id);
}
}
}
impl<'a, const MAX: u8> Future for Acquire<'a, MAX> {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
let this = unsafe { self.get_unchecked_mut() };
if this.sem.try_acquire() {
return Poll::Ready(());
}
let id = crate::executor::current_task()
.expect("Semaphore::acquire().await polled outside of a task context");
this.sem.register_waiter(id);
this.registered = Some(id);
if this.sem.try_acquire() {
if let Some(id) = this.registered.take() {
this.sem.remove_waiter(id);
}
return Poll::Ready(());
}
Poll::Pending
}
}
unsafe impl<const MAX: u8> Sync for Semaphore<MAX> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn semaphore_try_acquire_release() {
crate::kernel_test! {
let sem: Semaphore<3> = Semaphore::new(1);
assert!(sem.try_acquire());
assert!(!sem.try_acquire());
sem.release();
assert!(sem.try_acquire());
}
}
#[test]
fn semaphore_counting() {
crate::kernel_test! {
let sem: Semaphore<3> = Semaphore::new(2);
assert!(sem.try_acquire());
assert!(sem.try_acquire());
assert!(!sem.try_acquire());
sem.release();
assert!(sem.try_acquire());
assert!(!sem.try_acquire());
}
}
#[test]
fn acquire_future_ready_when_available() {
crate::kernel_test! {
let sem: Semaphore<1> = Semaphore::new(1);
let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
let mut cx = Context::from_waker(&waker);
let mut fut = sem.acquire();
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 acquire_future_panics_without_task_context() {
crate::kernel_test! {
let sem: Semaphore<1> = Semaphore::new(0);
let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
let mut cx = Context::from_waker(&waker);
let mut fut = sem.acquire();
let pinned = unsafe { Pin::new_unchecked(&mut fut) };
let _ = pinned.poll(&mut cx);
}
}
}
#[cfg(test)]
mod b9_tests {
use super::*;
#[test]
fn two_waiters_both_woken() {
crate::kernel_test! {
let sem: Semaphore<1> = Semaphore::new(0);
sem.register_waiter(crate::task::TaskId::new(1, 0));
sem.register_waiter(crate::task::TaskId::new(2, 0));
assert_eq!(sem.debug_waiters()[1], 1, "waiter (1,0)");
assert_eq!(sem.debug_waiters()[2], 1, "waiter (2,0)");
sem.release();
assert_eq!(crate::waker::next_ready(), Some(crate::task::TaskId::new(2, 0)), "highest priority first");
sem.release();
assert_eq!(crate::waker::next_ready(), Some(crate::task::TaskId::new(1, 0)), "second waiter woken");
assert_eq!(crate::waker::next_ready(), None);
assert_eq!(sem.debug_waiters()[1], 0);
assert_eq!(sem.debug_waiters()[2], 0);
}
}
#[test]
fn remove_waiter_on_drop_clears_registration() {
crate::kernel_test! {
let sem: Semaphore<1> = Semaphore::new(0);
sem.register_waiter(crate::task::TaskId::new(3, 1));
assert_eq!(sem.debug_waiters()[3], 1 << 1);
sem.remove_waiter(crate::task::TaskId::new(3, 1));
assert_eq!(sem.debug_waiters()[3], 0);
}
}
}