Skip to main content

ax_task/
wait_queue.rs

1use alloc::collections::VecDeque;
2
3use crate::{
4    AxTaskRef, CurrentTask, current_run_queue, select_wake_run_queue,
5    sync::{PreemptIrqSaveState, SpinLock, SpinLockIrqSaveGuard},
6};
7
8/// A queue to store sleeping tasks.
9///
10/// # Examples
11///
12/// ```
13/// use core::sync::atomic::{AtomicU32, Ordering};
14///
15/// use ax_task::WaitQueue;
16///
17/// static VALUE: AtomicU32 = AtomicU32::new(0);
18/// static WQ: WaitQueue = WaitQueue::new();
19///
20/// ax_task::init_scheduler();
21/// // spawn a new task that updates `VALUE` and notifies the main task
22/// ax_task::spawn(|| {
23///     assert_eq!(VALUE.load(Ordering::Acquire), 0);
24///     VALUE.fetch_add(1, Ordering::Release);
25///     WQ.notify_one(true); // wake up the main task
26/// });
27///
28/// WQ.wait(); // block until `notify()` is called
29/// assert_eq!(VALUE.load(Ordering::Acquire), 1);
30/// ```
31pub struct WaitQueue {
32    queue: SpinLock<VecDeque<AxTaskRef>>,
33}
34
35pub(crate) type WaitQueueGuard<'a> = SpinLockIrqSaveGuard<'a, VecDeque<AxTaskRef>>;
36
37impl Default for WaitQueue {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl WaitQueue {
44    /// Creates an empty wait queue.
45    pub const fn new() -> Self {
46        Self {
47            queue: SpinLock::new(VecDeque::new()),
48        }
49    }
50
51    /// Returns whether this queue currently contains no blocked tasks.
52    pub fn is_empty(&self) -> bool {
53        self.queue.lock_irqsave().is_empty()
54    }
55
56    /// Cancel events by removing the task from the wait queue.
57    /// If `from_timer_list` is true, try to remove the task from the timer list.
58    fn cancel_events(&self, curr: CurrentTask, _from_timer_list: bool) {
59        // A task can be woken by only one event (timer or `notify()`), so remove it from the other queue.
60        if curr.in_wait_queue() {
61            // wake up by timer (timeout).
62            self.queue.lock_irqsave().retain(|t| !curr.ptr_eq(t));
63            curr.set_in_wait_queue(false);
64        }
65
66        // Try to cancel a timer event from timer lists.
67        // Just mark task's current timer ticket ID as expired.
68        if _from_timer_list {
69            curr.timer_ticket_expired();
70            // Note:
71            //  this task is still not removed from timer list of target CPU,
72            //  which may cause some redundant timer events because it still needs to
73            //  go through the process of expiring an event from the timer list and invoking the callback.
74            //  (it can be considered a lazy-removal strategy, it will be ignored when it is about to take effect.)
75        }
76    }
77
78    /// Blocks the current task and put it into the wait queue, until other task
79    /// notifies it.
80    #[track_caller]
81    pub fn wait(&self) {
82        crate::api::might_sleep();
83        current_run_queue::<PreemptIrqSaveState>().blocked_resched(self.queue.lock_irqsave());
84        self.cancel_events(crate::current(), false);
85    }
86
87    /// Blocks the current task and put it into the wait queue, until the given
88    /// `condition` becomes true.
89    ///
90    /// Note that even other tasks notify this task, it will not wake up until
91    /// the condition becomes true.
92    #[track_caller]
93    pub fn wait_until<F>(&self, condition: F)
94    where
95        F: Fn() -> bool,
96    {
97        crate::api::might_sleep();
98        let curr = crate::current();
99        loop {
100            let mut rq = current_run_queue::<PreemptIrqSaveState>();
101            let wq = self.queue.lock_irqsave();
102            if condition() {
103                break;
104            }
105
106            rq.blocked_resched(wq);
107            // Preemption may occur here.
108        }
109        self.cancel_events(curr, false);
110    }
111
112    /// Blocks the current task and put it into the wait queue, until other tasks
113    /// notify it, or the given duration has elapsed.
114    #[track_caller]
115    pub fn wait_timeout(&self, dur: core::time::Duration) -> bool {
116        crate::api::might_sleep();
117        let mut rq = current_run_queue::<PreemptIrqSaveState>();
118        let curr = crate::current();
119        let deadline = ax_hal::time::monotonic_time() + dur;
120        debug!(
121            "task wait_timeout: {} deadline={:?}",
122            curr.id_name(),
123            deadline
124        );
125        let timeout = loop {
126            crate::timers::set_alarm_wakeup(deadline, curr.clone());
127            rq.blocked_resched(self.queue.lock_irqsave());
128
129            // Still in the wait queue means the timer path woke us. Re-check
130            // the monotonic deadline so an early wake cannot truncate sleeps.
131            if !curr.in_wait_queue() {
132                break false;
133            }
134            if ax_hal::time::monotonic_time() >= deadline {
135                break true;
136            }
137        };
138
139        // Always try to remove the task from the timer list.
140        self.cancel_events(curr, true);
141        timeout
142    }
143
144    /// Blocks the current task and put it into the wait queue, until the given
145    /// `condition` becomes true, or the given duration has elapsed.
146    ///
147    /// Note that even other tasks notify this task, it will not wake up until
148    /// the above conditions are met.
149    #[track_caller]
150    pub fn wait_timeout_until<F>(&self, dur: core::time::Duration, condition: F) -> bool
151    where
152        F: Fn() -> bool,
153    {
154        crate::api::might_sleep();
155        let curr = crate::current();
156        let deadline = ax_hal::time::monotonic_time() + dur;
157        debug!(
158            "task wait_timeout: {}, deadline={:?}",
159            curr.id_name(),
160            deadline
161        );
162        let mut timeout = true;
163        loop {
164            let mut rq = current_run_queue::<PreemptIrqSaveState>();
165            if ax_hal::time::monotonic_time() >= deadline {
166                break;
167            }
168            let wq = self.queue.lock_irqsave();
169            if condition() {
170                timeout = false;
171                break;
172            }
173
174            crate::timers::set_alarm_wakeup(deadline, curr.clone());
175            rq.blocked_resched(wq);
176            // Preemption may occur here.
177        }
178        // Always try to remove the task from the timer list.
179        self.cancel_events(curr, true);
180        timeout
181    }
182
183    /// Wakes up one task in the wait queue, usually the first one.
184    /// If `resched` is true, the current task will be preempted when the
185    /// preemption is enabled.
186    pub fn notify_one(&self, resched: bool) -> bool {
187        let task = self.pop_front();
188        if let Some(task) = task {
189            unblock_one_task(task, resched);
190            return true;
191        }
192        false
193    }
194
195    /// Wakes up one task from IRQ context.
196    ///
197    /// This method is intended for low-level deferred notification paths. It
198    /// only unblocks the worker and marks the current task for rescheduling
199    /// after IRQ/preemption guards are released; it must not be used as a
200    /// substitute for publishing the condition that the waiter will observe.
201    pub fn notify_one_from_irq(&self) -> bool {
202        self.notify_one(true)
203    }
204
205    /// Wakes up one task in the wait queue and runs a callback on it.
206    ///
207    /// The callback `func` is invoked while holding the wait-queue lock and
208    /// before the selected task is unblocked. It receives the task's ID as a
209    /// `u64` when a task is available, or `0` if the wait queue is empty.
210    /// This can be used for lock handoff or other bookkeeping associated with
211    /// the waking task.
212    ///
213    /// If `resched` is true, the current task will be preempted when the
214    /// preemption is enabled.
215    pub fn notify_one_with<F>(&self, resched: bool, func: F) -> bool
216    where
217        F: Fn(u64),
218    {
219        let task = {
220            let mut wq = self.queue.lock_irqsave();
221            match wq.pop_front() {
222                Some(task) => {
223                    func(task.id().as_u64());
224                    task.set_in_wait_queue(false);
225                    Some(task)
226                }
227                None => {
228                    func(0);
229                    None
230                }
231            }
232        };
233
234        if let Some(task) = task {
235            unblock_one_task(task, resched);
236            return true;
237        }
238        false
239    }
240
241    /// Wakes all tasks in the wait queue.
242    ///
243    /// If `resched` is true, the current task will yield.
244    pub fn notify_all(&self, resched: bool) {
245        while self.notify_one(resched) {
246            // loop until the wait queue is empty
247        }
248    }
249
250    /// Wakes all tasks from IRQ context.
251    ///
252    /// This method is intended for low-level deferred notification paths. It
253    /// only unblocks workers and marks the current task for rescheduling after
254    /// IRQ/preemption guards are released; it must not be used as a substitute
255    /// for publishing the condition that waiters will observe.
256    pub fn notify_all_from_irq(&self) {
257        while self.notify_one_from_irq() {
258            // loop until the wait queue is empty
259        }
260    }
261
262    fn pop_front(&self) -> Option<AxTaskRef> {
263        let mut wq = self.queue.lock_irqsave();
264        let task = wq.pop_front()?;
265        task.set_in_wait_queue(false);
266        Some(task)
267    }
268}
269
270fn unblock_one_task(task: AxTaskRef, resched: bool) {
271    // Select run queue by the CPU set of the task.
272    select_wake_run_queue::<PreemptIrqSaveState>(&task).unblock_task(task, resched)
273}
274
275#[cfg(test)]
276mod coverage_tests {
277    use super::*;
278
279    fn wait_queue_new_and_default_hold_for_test() -> bool {
280        // Test WaitQueue::new() and Default
281        let wq = WaitQueue::new();
282        let wq_default = WaitQueue::default();
283
284        // Both should create valid WaitQueue instances
285        // We can't easily test the internal state without locking,
286        // but we can verify the struct exists and can be created
287        let _wq_ref = &wq;
288        let _wq_default_ref = &wq_default;
289
290        true
291    }
292
293    #[test]
294    fn wait_queue_new_and_default_hold() {
295        assert!(wait_queue_new_and_default_hold_for_test());
296    }
297}