1 //! POSIX conditional variable implementation based on user-space wait queues.
2 use super::{abi, error::expect_success_aborting, spin::SpinMutex, task, time::with_tmos_strong};
3 use crate::{mem::replace, ptr::NonNull, sys::mutex::Mutex, time::Duration};
5 // The implementation is inspired by the queue-based implementation shown in
6 // Andrew D. Birrell's paper "Implementing Condition Variables with Semaphores"
9 waiters: SpinMutex<waiter_queue::WaiterQueue>,
12 unsafe impl Send for Condvar {}
13 unsafe impl Sync for Condvar {}
15 pub type MovableCondvar = Condvar;
19 pub const fn new() -> Condvar {
20 Condvar { waiters: SpinMutex::new(waiter_queue::WaiterQueue::new()) }
24 pub unsafe fn init(&mut self) {}
26 pub unsafe fn notify_one(&self) {
27 self.waiters.with_locked(|waiters| {
28 if let Some(task) = waiters.pop_front() {
30 match unsafe { abi::wup_tsk(task) } {
31 // The task already has a token.
33 // Can't undo the effect; abort the program on failure
35 expect_success_aborting(er, &"wup_tsk");
42 pub unsafe fn notify_all(&self) {
43 self.waiters.with_locked(|waiters| {
44 while let Some(task) = waiters.pop_front() {
46 match unsafe { abi::wup_tsk(task) } {
47 // The task already has a token.
49 // Can't undo the effect; abort the program on failure
51 expect_success_aborting(er, &"wup_tsk");
58 pub unsafe fn wait(&self, mutex: &Mutex) {
59 // Construct `Waiter`.
60 let mut waiter = waiter_queue::Waiter::new();
61 let waiter = NonNull::from(&mut waiter);
63 self.waiters.with_locked(|waiters| unsafe {
64 waiters.insert(waiter);
67 unsafe { mutex.unlock() };
69 // Wait until `waiter` is removed from the queue
71 // Park the current task
72 expect_success_aborting(unsafe { abi::slp_tsk() }, &"slp_tsk");
74 if !self.waiters.with_locked(|waiters| unsafe { waiters.is_queued(waiter) }) {
79 unsafe { mutex.lock() };
82 pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
83 // Construct and pin `Waiter`
84 let mut waiter = waiter_queue::Waiter::new();
85 let waiter = NonNull::from(&mut waiter);
87 self.waiters.with_locked(|waiters| unsafe {
88 waiters.insert(waiter);
91 unsafe { mutex.unlock() };
93 // Park the current task and do not wake up until the timeout elapses
94 // or the task gets woken up by `notify_*`
95 match with_tmos_strong(dur, |tmo| {
96 let er = unsafe { abi::tslp_tsk(tmo) };
98 // We were unparked. Are we really dequeued?
99 if self.waiters.with_locked(|waiters| unsafe { waiters.is_queued(waiter) }) {
100 // No we are not. Continue waiting.
108 expect_success_aborting(er, &"tslp_tsk");
112 // Remove `waiter` from `self.waiters`. If `waiter` is still in
113 // `waiters`, it means we woke up because of a timeout. Otherwise,
114 // we woke up because of `notify_*`.
115 let success = self.waiters.with_locked(|waiters| unsafe { !waiters.remove(waiter) });
117 unsafe { mutex.lock() };
121 pub unsafe fn destroy(&self) {}
127 pub struct WaiterQueue {
128 head: Option<ListHead>,
131 #[derive(Copy, Clone)]
133 first: NonNull<Waiter>,
134 last: NonNull<Waiter>,
137 unsafe impl Send for ListHead {}
138 unsafe impl Sync for ListHead {}
141 // These fields are only accessed through `&[mut] WaiterQueue`.
142 /// The waiting task's ID. Will be zeroed when the task is woken up
143 /// and removed from a queue.
146 prev: Option<NonNull<Waiter>>,
147 next: Option<NonNull<Waiter>>,
150 unsafe impl Send for Waiter {}
151 unsafe impl Sync for Waiter {}
155 pub fn new() -> Self {
156 let task = task::current_task_id();
157 let priority = task::task_priority(abi::TSK_SELF);
159 // Zeroness of `Waiter::task` indicates whether the `Waiter` is
160 // linked to a queue or not. This invariant is important for
162 debug_assert_ne!(task, 0);
164 Self { task, priority, prev: None, next: None }
170 pub const fn new() -> Self {
176 /// - The caller must own `*waiter_ptr`. The caller will lose the
177 /// ownership until `*waiter_ptr` is removed from `self`.
179 /// - `*waiter_ptr` must be valid until it's removed from the queue.
181 /// - `*waiter_ptr` must not have been previously inserted to a `WaiterQueue`.
183 pub unsafe fn insert(&mut self, mut waiter_ptr: NonNull<Waiter>) {
185 let waiter = waiter_ptr.as_mut();
187 debug_assert!(waiter.prev.is_none());
188 debug_assert!(waiter.next.is_none());
190 if let Some(head) = &mut self.head {
191 // Find the insertion position and insert `waiter`
193 let mut cursor = head.last;
195 if waiter.priority >= cursor.as_ref().priority {
196 // `cursor` and all previous waiters have the same or higher
197 // priority than `current_task_priority`. Insert the new
198 // waiter right after `cursor`.
201 cursor = if let Some(prev) = cursor.as_ref().prev {
209 if let Some(mut insert_after) = insert_after {
210 // Insert `waiter` after `insert_after`
211 let insert_before = insert_after.as_ref().next;
213 waiter.prev = Some(insert_after);
214 insert_after.as_mut().next = Some(waiter_ptr);
216 waiter.next = insert_before;
217 if let Some(mut insert_before) = insert_before {
218 insert_before.as_mut().prev = Some(waiter_ptr);
220 head.last = waiter_ptr;
223 // Insert `waiter` to the front
224 waiter.next = Some(head.first);
225 head.first.as_mut().prev = Some(waiter_ptr);
226 head.first = waiter_ptr;
229 // `waiter` is the only element
230 self.head = Some(ListHead { first: waiter_ptr, last: waiter_ptr });
235 /// Given a `Waiter` that was previously inserted to `self`, remove
236 /// it from `self` if it's still there.
238 pub unsafe fn remove(&mut self, mut waiter_ptr: NonNull<Waiter>) -> bool {
240 let waiter = waiter_ptr.as_mut();
241 if waiter.task != 0 {
242 let head = self.head.as_mut().unwrap();
244 match (waiter.prev, waiter.next) {
245 (Some(mut prev), Some(mut next)) => {
246 prev.as_mut().next = Some(next);
247 next.as_mut().prev = Some(prev);
249 (None, Some(mut next)) => {
251 next.as_mut().prev = None;
253 (Some(mut prev), None) => {
254 prev.as_mut().next = None;
271 /// Given a `Waiter` that was previously inserted to `self`, return a
272 /// flag indicating whether it's still in `self`.
274 pub unsafe fn is_queued(&self, waiter: NonNull<Waiter>) -> bool {
275 unsafe { waiter.as_ref().task != 0 }
279 pub fn pop_front(&mut self) -> Option<abi::ID> {
281 let head = self.head.as_mut()?;
282 let waiter = head.first.as_mut();
285 let id = replace(&mut waiter.task, 0);
288 if let Some(mut next) = waiter.next {
290 next.as_mut().prev = None;