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}