Skip to main content

starnix_sync/
interruptible_event.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{InterruptibleEventRequeueOwnerLock, LockDepMutex};
6use std::sync::Arc;
7use std::sync::atomic::Ordering;
8
9/// A blocking object that can either be notified normally or interrupted.
10///
11/// To block using an `InterruptibleEvent`, first call [`Self::begin_wait`] (or
12/// [`Self::begin_wait_requeueable`] if the wait should allow dynamic PI owner assignment). At this
13/// point, the event is in a waiting state, and future calls to [`Self::notify`] or
14/// [`Self::interrupt`] will terminate the wait.
15///
16/// After `begin_wait` (or `begin_wait_requeueable`) returns, call [`EventWaitGuard::block_until`]
17/// on the returned guard to block the current thread until one of the following conditions occurs:
18///
19///  1. The given deadline expires.
20///  2. At least one of the `notify` or `interrupt` functions was called after the wait began.
21///
22/// Alternatively, dropping the [`EventWaitGuard`] without calling `block_until` cancels the wait
23/// and resets the event back to the ready state.
24///
25/// It's safe to call `notify` or `interrupt` at any time. However, only one wait cycle can be
26/// active at a time: `begin_wait` or `begin_wait_requeueable` cannot be called again until the
27/// [`EventWaitGuard`] it returns is consumed by `block_until` or dropped. When an
28/// `InterruptibleEvent` is reused across multiple wait cycles, `notify`, `interrupt`, and
29/// [`Self::assign_new_owner`] are not bound to a specific cycle token; see [`Self::begin_wait`] and
30/// [`Self::assign_new_owner`] for cross-cycle concurrency considerations.
31///
32/// `InterruptibleEvent` uses two-phase waiting so that clients can register for notification,
33/// perform some related work, and then start blocking. This approach ensures that clients do not
34/// miss notifications that arrive after they perform the related work but before they actually
35/// start blocking.
36///
37/// # Priority Inheritance and Dynamic Futex Owner Assignment
38///
39/// `InterruptibleEvent` supports Zircon Priority Inheritance (PI). If the target owner thread is
40/// known at wait time, initiate the wait with [`Self::begin_wait`] and pass the owner thread
41/// directly to `block_until` / `zx_futex_wait`.
42///
43/// When the owner thread is not known when waiting begins (for example, when a transaction is
44/// queued to a process-wide worker pool), initiate the wait with [`Self::begin_wait_requeueable`];
45/// [`Self::assign_new_owner`] can then be called by the worker thread that later dequeues the work
46/// item. `assign_new_owner` uses `zx_futex_requeue` to atomically transfer waiting threads to a
47/// secondary `requeue_target` futex while designating the worker thread as the futex PI owner.
48/// Waits started with [`Self::begin_wait`] reject `assign_new_owner` so a known PI owner cannot be
49/// overridden.
50///
51/// TODO(https://fxbug.dev/542307988): The long-term solution is a dedicated Zircon syscall (such as
52/// `zx_futex_assign_owner`) to assign or update the PI owner of an existing futex without
53/// requiring a secondary requeue target futex.
54#[derive(Debug)]
55pub struct InterruptibleEvent {
56    futex: zx::Futex,
57    requeue_target: zx::Futex,
58    /// Retains the PI owner assigned via `assign_new_owner` for the duration of the wait cycle.
59    ///
60    /// Keeping an `Arc<zx::Thread>` reference here (rather than only passing the handle once to
61    /// `zx_futex_requeue`) is required for three reasons:
62    ///  1. When `block_until` wakes at the intermediate `clean_deadline` to call `clean_stack()`,
63    ///     Zircon's `FutexWait` (`ResetOwnerIfNoWaiters`) clears `requeue_target`'s kernel owner
64    ///     before returning `ZX_ERR_TIMED_OUT`; re-waiting on `requeue_target` must pass the owner
65    ///     handle again to preserve PI for the rest of the wait.
66    ///  2. If `assign_new_owner` is called after `begin_wait_requeueable` but before the waiting
67    ///     thread enters `zx_futex_wait` in the kernel (or if a spurious wakeup occurs),
68    ///     `zx_futex_requeue` moves 0 kernel waiters, and a subsequent
69    ///     `requeue_target.wait(WAITING, None, ...)` would overwrite `requeue_target`'s kernel
70    ///     owner with `None`.
71    ///  3. Holding an `Arc<zx::Thread>` guarantees the underlying `zx_handle_t` stays open until
72    ///     `reset()`, avoiding `zx_handle_duplicate` syscalls and `ZX_POL_BAD_HANDLE` policy
73    ///     exceptions if the worker thread exits during the wait.
74    requeue_owner: LockDepMutex<Option<Arc<zx::Thread>>, InterruptibleEventRequeueOwnerLock>,
75}
76
77/// The initial state when no wait cycle is active.
78///
79///  * Transitions to `WAITING` after `begin_wait`, or `WAITING_REQUEUEABLE` after
80///    `begin_wait_requeueable`.
81const READY: i32 = 0;
82
83/// The event is waiting for a notification or an interruption, and does not allow dynamic PI
84/// owner assignment via `assign_new_owner` (also used on `requeue_target` after requeuing).
85///
86///  * Transitions to `NOTIFIED` after `notify`.
87///  * Transitions to `INTERRUPTED` after `interrupt`.
88///  * Transitions to `READY` when `EventWaitGuard` is dropped (either at the end of `block_until`
89///    or if dropped early).
90const WAITING: i32 = 1;
91
92/// The event has been notified and will wake up.
93///
94///  * Transitions to `READY` when `EventWaitGuard` is dropped.
95const NOTIFIED: i32 = 2;
96
97/// The event has been interrupted and will wake up.
98///
99///  * Transitions to `READY` when `EventWaitGuard` is dropped.
100const INTERRUPTED: i32 = 3;
101
102/// The event has been requeued to a secondary futex target with a new PI owner.
103///
104///  * Transitions to `READY` when `EventWaitGuard` is dropped.
105const REQUEUED: i32 = 4;
106
107/// The event is waiting on the primary `futex` and allows dynamic PI owner assignment via
108/// `assign_new_owner`.
109///
110///  * Transitions to `NOTIFIED` after `notify`.
111///  * Transitions to `INTERRUPTED` after `interrupt`.
112///  * Transitions to `REQUEUED` after `assign_new_owner` (on the primary `futex`; `requeue_target`
113///    then transitions to `WAITING`).
114///  * Transitions to `READY` when `EventWaitGuard` is dropped (either at the end of `block_until`
115///    or if dropped early).
116const WAITING_REQUEUEABLE: i32 = 5;
117
118/// A guard object to enforce that clients call `begin_wait` before `block_until`.
119///
120/// Dropping the guard (whether at the end of `block_until` or on an early abort before blocking)
121/// resets the underlying [`InterruptibleEvent`] back to the `READY` state so it can be reused.
122#[must_use = "call block_until to advance the event state machine"]
123#[derive(Debug)]
124pub struct EventWaitGuard<'a> {
125    event: &'a InterruptibleEvent,
126}
127
128impl Drop for EventWaitGuard<'_> {
129    fn drop(&mut self) {
130        self.event.reset();
131    }
132}
133
134impl<'a> EventWaitGuard<'a> {
135    /// The underlying event associated with this guard.
136    pub fn event(&self) -> &'a InterruptibleEvent {
137        self.event
138    }
139
140    /// Returns the owner of the underlying futex, if any.
141    pub fn get_owner(&self) -> Option<zx::Koid> {
142        self.event.get_owner()
143    }
144
145    /// Block the thread until either `deadline` expires, the event is notified, or the event is
146    /// interrupted.
147    pub fn block_until(
148        self,
149        new_owner: Option<&zx::Thread>,
150        deadline: zx::MonotonicInstant,
151    ) -> Result<(), WakeReason> {
152        self.event.block_until_with_clean_delay(new_owner, deadline, crate::STACK_CLEAN_DELAY)
153    }
154
155    #[cfg(test)]
156    fn block_until_with_clean_delay(
157        self,
158        new_owner: Option<&zx::Thread>,
159        deadline: zx::MonotonicInstant,
160        clean_delay: zx::MonotonicDuration,
161    ) -> Result<(), WakeReason> {
162        self.event.block_until_with_clean_delay(new_owner, deadline, clean_delay)
163    }
164}
165
166/// A description of why a `block_until` returned without the event being notified.
167#[derive(Debug, PartialEq, Eq)]
168pub enum WakeReason {
169    /// `block_until` returned because another thread interrupted the wait using `interrupt`.
170    Interrupted,
171
172    /// `block_until` returned because the given deadline expired.
173    DeadlineExpired,
174}
175
176impl Default for InterruptibleEvent {
177    fn default() -> Self {
178        InterruptibleEvent {
179            futex: zx::Futex::new(READY),
180            requeue_target: zx::Futex::new(READY),
181            requeue_owner: LockDepMutex::new(None),
182        }
183    }
184}
185
186struct LoadedState<'a> {
187    futex: &'a zx::Futex,
188    state: i32,
189    is_requeued: bool,
190}
191
192impl InterruptibleEvent {
193    pub fn new() -> Arc<Self> {
194        Arc::new(Self::default())
195    }
196
197    /// Loads the active futex (`self.futex`, or `self.requeue_target` once `assign_new_owner` has
198    /// transitioned `self.futex` to `REQUEUED`) together with its current state.
199    fn load_state(&self) -> LoadedState<'_> {
200        let state = self.futex.load(Ordering::Acquire);
201        if state == REQUEUED {
202            LoadedState {
203                futex: &self.requeue_target,
204                state: self.requeue_target.load(Ordering::Acquire),
205                is_requeued: true,
206            }
207        } else {
208            LoadedState { futex: &self.futex, state, is_requeued: false }
209        }
210    }
211
212    /// Returns the owner of the active underlying futex, if any.
213    pub fn get_owner(&self) -> Option<zx::Koid> {
214        self.load_state().futex.get_owner()
215    }
216
217    /// Called to initiate a wait cycle that does not allow dynamic PI owner assignment via
218    /// [`Self::assign_new_owner`].
219    ///
220    /// Calls to [`Self::notify`] or [`Self::interrupt`] after this function returns, and before the
221    /// returned [`EventWaitGuard`] is dropped, will cause the event to wake up. Calls that complete
222    /// while the event is idle (before `begin_wait` or after the previous `EventWaitGuard` has been
223    /// dropped) are ignored.
224    ///
225    /// Calls to [`Self::assign_new_owner`] during a wait started with `begin_wait` fail with
226    /// `Err(zx::Status::BAD_STATE)`, preserving any PI owner passed directly to
227    /// [`EventWaitGuard::block_until`]. Use [`Self::begin_wait_requeueable`] when dynamic PI owner
228    /// assignment is needed.
229    ///
230    /// When reusing an `InterruptibleEvent` across multiple wait cycles, `notify` and `interrupt`
231    /// are not tied to a specific wait cycle: an in-flight call intended for cycle `N` that races
232    /// with cycle `N`'s guard drop and cycle `N + 1`'s `begin_wait` can apply to cycle `N + 1`.
233    /// Callers reusing an event must tolerate spurious wakeups from a late `notify` (or late
234    /// `interrupt`).
235    ///
236    /// Once called, neither `begin_wait` nor `begin_wait_requeueable` can be called again until the
237    /// returned [`EventWaitGuard`] is consumed by [`EventWaitGuard::block_until`] or dropped.
238    ///
239    /// # Panics
240    ///
241    /// Panics if a previous [`EventWaitGuard`] for this event has not yet been dropped.
242    pub fn begin_wait(&self) -> EventWaitGuard<'_> {
243        self.begin_wait_with_state(WAITING)
244    }
245
246    /// Called to initiate a wait cycle that allows dynamic PI owner assignment via
247    /// [`Self::assign_new_owner`].
248    ///
249    /// Behaves like [`Self::begin_wait`], except [`Self::assign_new_owner`] can dynamically requeue
250    /// the wait to assign a PI owner once during this wait cycle. Requeue eligibility is cleared
251    /// automatically when the returned [`EventWaitGuard`] is dropped (or once `assign_new_owner`,
252    /// `notify`, or `interrupt` transitions the event out of `WAITING_REQUEUEABLE`).
253    ///
254    /// # Panics
255    ///
256    /// Panics if a previous [`EventWaitGuard`] for this event has not yet been dropped.
257    pub fn begin_wait_requeueable(&self) -> EventWaitGuard<'_> {
258        self.begin_wait_with_state(WAITING_REQUEUEABLE)
259    }
260
261    fn begin_wait_with_state(&self, initial_state: i32) -> EventWaitGuard<'_> {
262        self.requeue_target.store(READY, Ordering::Relaxed);
263        self.futex
264            .compare_exchange(READY, initial_state, Ordering::AcqRel, Ordering::Relaxed)
265            .expect("Tried to begin waiting on an event when not ready.");
266        EventWaitGuard { event: self }
267    }
268
269    /// Assigns `new_owner` as the Priority Inheritance (PI) owner for waiting thread(s).
270    ///
271    /// If the event is in a requeueable wait (started via [`Self::begin_wait_requeueable`]), this
272    /// dynamically requeues any threads waiting on `futex` to `requeue_target`, designating
273    /// `new_owner` as the futex PI owner.
274    ///
275    /// This establishes Zircon Priority Inheritance (PI) from the waiting thread(s) to
276    /// `new_owner` when the owner was not known when waiting began.
277    ///
278    /// Note: This can only succeed once per [`Self::begin_wait_requeueable`] cycle (while `futex`
279    /// is `WAITING_REQUEUEABLE`). Waits started with [`Self::begin_wait`], idle events, and events
280    /// that have already been requeued or woken return `Err(zx::Status::BAD_STATE)`.
281    ///
282    /// # Event Reuse and Cycle Turnover
283    ///
284    /// `assign_new_owner` is not bound to a specific wait-cycle token. Waits started with
285    /// [`Self::begin_wait`] use `WAITING` rather than `WAITING_REQUEUEABLE`, so an in-flight
286    /// `assign_new_owner` from an earlier cycle cannot requeue them or override a known `new_owner`
287    /// passed to [`EventWaitGuard::block_until`]. However, if an `InterruptibleEvent` is reused
288    /// across consecutive [`Self::begin_wait_requeueable`] cycles, an in-flight `assign_new_owner`
289    /// from cycle `N` that races with cycle `N`'s [`EventWaitGuard`] drop and cycle `N + 1`'s
290    /// `begin_wait_requeueable` can still succeed against cycle `N + 1`.
291    ///
292    /// TODO(https://fxbug.dev/542307988): Replace this requeue pattern with a dedicated Zircon
293    /// syscall to assign a new owner to a futex with waiting threads once available.
294    pub fn assign_new_owner(&self, new_owner: &Arc<zx::Thread>) -> Result<(), zx::Status> {
295        if self.futex.load(Ordering::Relaxed) != WAITING_REQUEUEABLE {
296            return Err(zx::Status::BAD_STATE);
297        }
298        // Hold `requeue_owner` across the `WAITING_REQUEUEABLE -> REQUEUED` transition,
299        // `zx_futex_requeue`, and `requeue_target` initialization so that any thread in
300        // `block_until` or `reset` that observes `futex == REQUEUED` and locks `requeue_owner` is
301        // serialized after the requeue completes (with kernel Priority Inheritance from
302        // `fuchsia_sync::Mutex` if `assign_new_owner` was preempted).
303        let mut requeue_owner = self.requeue_owner.lock();
304        if self
305            .futex
306            .compare_exchange(WAITING_REQUEUEABLE, REQUEUED, Ordering::AcqRel, Ordering::Relaxed)
307            .is_ok()
308        {
309            *requeue_owner = Some(Arc::clone(new_owner));
310            let res =
311                self.futex.requeue(0, REQUEUED, &self.requeue_target, u32::MAX, Some(new_owner));
312            // Setting `requeue_target` to `WAITING` after `zx_futex_requeue` ensures that any
313            // concurrent `wake()` loop does not issue `requeue_target.wake_all()` until after the
314            // kernel wait queue transfer has completed.
315            self.requeue_target.store(WAITING, Ordering::Release);
316            res
317        } else {
318            Err(zx::Status::BAD_STATE)
319        }
320    }
321
322    /// Returns whether `begin_wait` or `begin_wait_requeueable` has been called and the event has
323    /// not yet been notified, interrupted, or reset.
324    pub fn is_waiting(&self) -> bool {
325        let LoadedState { state, is_requeued, .. } = self.load_state();
326        // Treat `READY && is_requeued` as waiting: `assign_new_owner` has already claimed `futex`
327        // (`REQUEUED`) and is in the middle of transferring waiters before storing `WAITING` into
328        // `requeue_target`.
329        matches!(state, WAITING | WAITING_REQUEUEABLE) || (state == READY && is_requeued)
330    }
331
332    fn reset(&self) {
333        // `reset()` is only called when dropping `EventWaitGuard` (either at the end of
334        // `block_until` or if the guard is dropped before blocking), which is the unique guard
335        // created by `begin_wait` / `begin_wait_requeueable` (when `futex == READY`). Because
336        // `futex` is not set back to `READY` until the end of this function, at most one thread can
337        // execute `reset()` at a time.
338        //
339        // `assign_new_owner` can only transition `futex` to `REQUEUED` from `WAITING_REQUEUEABLE`,
340        // so we only need to serialize against `assign_new_owner` when `futex` is
341        // `WAITING_REQUEUEABLE` or `REQUEUED`.
342        let observed = self.futex.load(Ordering::Relaxed);
343        if matches!(observed, WAITING_REQUEUEABLE | REQUEUED) {
344            if self.futex.swap(NOTIFIED, Ordering::AcqRel) == REQUEUED {
345                *self.requeue_owner.lock() = None;
346            }
347            self.requeue_target.store(READY, Ordering::Relaxed);
348        }
349        // Use `Ordering::Release` so clearing `requeue_owner` and resetting `requeue_target`
350        // happen-before a subsequent `begin_wait*()`'s `AcqRel` CAS from `READY`.
351        self.futex.store(READY, Ordering::Release);
352    }
353
354    fn block_until_with_clean_delay(
355        &self,
356        new_owner: Option<&zx::Thread>,
357        deadline: zx::MonotonicInstant,
358        clean_delay: zx::MonotonicDuration,
359    ) -> Result<(), WakeReason> {
360        // We need to loop around the call to zx_futex_wait because we can receive spurious
361        // wakeups. Clean the stack once if the wait remains blocked for `clean_delay`.
362        let mut clean_deadline = crate::initial_clean_deadline(deadline, clean_delay);
363        loop {
364            let LoadedState { futex: target_futex, state, is_requeued } = self.load_state();
365            let expected_state = match state {
366                WAITING | WAITING_REQUEUEABLE => state,
367                // If `assign_new_owner()` set `futex = REQUEUED` before `requeue_target` was
368                // initialized to `WAITING`, `state` briefly reads `READY`; locking `requeue_owner`
369                // below serializes behind `assign_new_owner()` completing the transition.
370                READY if is_requeued => WAITING,
371                NOTIFIED => return Ok(()),
372                INTERRUPTED => return Err(WakeReason::Interrupted),
373                other => panic!("Unexpected event state: {other}"),
374            };
375
376            let requeued_owner = if is_requeued { self.requeue_owner.lock().clone() } else { None };
377            let effective_owner = if is_requeued { requeued_owner.as_deref() } else { new_owner };
378
379            match target_futex.wait(expected_state, effective_owner, clean_deadline) {
380                // The deadline expired while we were sleeping.
381                Err(zx::Status::TIMED_OUT) => {
382                    if clean_deadline < deadline {
383                        starnix_stack::clean_stack();
384                        clean_deadline = deadline;
385                    } else {
386                        return Err(WakeReason::DeadlineExpired);
387                    }
388                }
389                // The value changed before we went to sleep, or we woke up; the next iteration
390                // re-loads the active futex and state via `load_state()`.
391                Err(zx::Status::BAD_STATE) | Ok(()) => {}
392                Err(e) => panic!("Unexpected error from zx_futex_wait: {e}"),
393            }
394        }
395    }
396
397    /// Wake up the event normally.
398    ///
399    /// If this function completes while no wait cycle is active (before `begin_wait` /
400    /// `begin_wait_requeueable` or after the [`EventWaitGuard`] is dropped), this notification is
401    /// ignored; when an event is reused, an in-flight call racing with cycle turnover may wake the
402    /// next wait cycle. Calling this function repeatedly within a cycle has no additional effect.
403    /// If both `notify` and `interrupt` are called, the state observed by `block_until` is a race.
404    pub fn notify(&self) {
405        self.wake(NOTIFIED);
406    }
407
408    /// Wake up the event because of an interruption.
409    ///
410    /// If this function completes while no wait cycle is active (before `begin_wait` /
411    /// `begin_wait_requeueable` or after the [`EventWaitGuard`] is dropped), this interruption is
412    /// ignored; when an event is reused, an in-flight call racing with cycle turnover may interrupt
413    /// the next wait cycle. Calling this function repeatedly within a cycle has no additional
414    /// effect. If both `notify` and `interrupt` are called, the state observed by `block_until` is
415    /// a race.
416    pub fn interrupt(&self) {
417        self.wake(INTERRUPTED);
418    }
419
420    fn wake(&self, state: i32) {
421        // Check `self.futex` in Shared cache state before attempting `compare_exchange` to avoid
422        // unnecessary exclusive cache-line invalidations when the event is already READY,
423        // NOTIFIED, or INTERRUPTED (see <https://marabos.nl/atomics/hardware.html#failing-compare-exchange>).
424        //
425        // When `assign_new_owner()` transitions `futex` to `REQUEUED`, there is a brief window
426        // before `requeue_target` transitions to `WAITING` (or while `reset()` transitions back to
427        // `READY`), so we yield and re-check `load_state()` until the transition settles.
428        loop {
429            let LoadedState { futex, state: current_state, is_requeued } = self.load_state();
430            match current_state {
431                NOTIFIED | INTERRUPTED => return,
432                READY if !is_requeued => return,
433                READY => std::thread::yield_now(),
434                WAITING | WAITING_REQUEUEABLE => {
435                    if futex
436                        .compare_exchange(
437                            current_state,
438                            state,
439                            Ordering::Release,
440                            Ordering::Acquire,
441                        )
442                        .is_ok()
443                    {
444                        futex.wake_all();
445                        return;
446                    }
447                }
448                other => panic!("Unexpected event state: {other}"),
449            }
450        }
451    }
452}
453
454#[cfg(test)]
455mod test {
456    use super::*;
457    use std::sync::atomic::AtomicBool;
458
459    #[test]
460    fn test_wait_block_and_notify() {
461        let event = InterruptibleEvent::new();
462
463        let guard = event.begin_wait();
464
465        let other_event = Arc::clone(&event);
466        let thread = std::thread::spawn(move || {
467            other_event.notify();
468        });
469
470        guard.block_until(None, zx::MonotonicInstant::INFINITE).expect("failed to be notified");
471        thread.join().expect("failed to join thread");
472    }
473
474    #[test]
475    fn test_wait_block_and_interrupt() {
476        let event = InterruptibleEvent::new();
477
478        let guard = event.begin_wait();
479
480        let other_event = Arc::clone(&event);
481        let thread = std::thread::spawn(move || {
482            other_event.interrupt();
483        });
484
485        let result = guard.block_until(None, zx::MonotonicInstant::INFINITE);
486        assert_eq!(result, Err(WakeReason::Interrupted));
487        thread.join().expect("failed to join thread");
488    }
489
490    #[test]
491    fn test_wait_block_and_timeout() {
492        let event = InterruptibleEvent::new();
493
494        let guard = event.begin_wait();
495        let result = guard
496            .block_until(None, zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(20)));
497        assert_eq!(result, Err(WakeReason::DeadlineExpired));
498    }
499
500    #[test]
501    fn futex_ownership_is_transferred() {
502        let event = Arc::new(InterruptibleEvent::new());
503
504        let (root_thread_handle, root_thread_koid) = fuchsia_runtime::with_thread_self(|thread| {
505            (thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(), thread.koid().unwrap())
506        });
507
508        let event_for_blocked_thread = event.clone();
509
510        let blocked_thread = std::thread::spawn(move || {
511            let event = event_for_blocked_thread;
512            let guard = event.begin_wait();
513            guard.block_until(Some(&root_thread_handle), zx::MonotonicInstant::INFINITE).unwrap();
514        });
515
516        // Wait for the correct owner to appear.
517        // TODO(b/502692311): Replace this polling loop if it starts timing out.
518        while event.get_owner() != Some(root_thread_koid) {
519            std::thread::sleep(std::time::Duration::from_millis(100));
520        }
521
522        event.notify();
523        blocked_thread.join().unwrap();
524    }
525
526    #[test]
527    fn stale_pi_owner_is_noop() {
528        let mut new_owner = None;
529        std::thread::scope(|s| {
530            s.spawn(|| {
531                new_owner = Some(
532                    fuchsia_runtime::with_thread_self(|thread| {
533                        thread.duplicate_handle(zx::Rights::SAME_RIGHTS)
534                    })
535                    .unwrap(),
536                );
537            });
538        });
539        let new_owner = new_owner.unwrap();
540
541        let event = InterruptibleEvent::new();
542        let guard = event.begin_wait();
543        let result = guard.block_until(
544            Some(&new_owner),
545            zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(20)),
546        );
547        assert_eq!(result, Err(WakeReason::DeadlineExpired));
548    }
549
550    #[test]
551    fn futex_ownership_is_transferred_via_requeue() {
552        let event = Arc::new(InterruptibleEvent::new());
553
554        let (root_thread_handle, root_thread_koid) = fuchsia_runtime::with_thread_self(|thread| {
555            (
556                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
557                thread.koid().unwrap(),
558            )
559        });
560
561        let event_for_blocked_thread = event.clone();
562
563        let (tx, rx) = std::sync::mpsc::channel();
564        let blocked_thread = std::thread::spawn(move || {
565            let (thread_handle, _) = fuchsia_runtime::with_thread_self(|thread| {
566                (thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(), thread.koid().unwrap())
567            });
568            tx.send(thread_handle).unwrap();
569            let event = event_for_blocked_thread;
570            let guard = event.begin_wait_requeueable();
571            guard.block_until(None, zx::MonotonicInstant::INFINITE).unwrap();
572        });
573
574        // Wait until the thread starts sleeping on the primary futex in the kernel.
575        let blocked_thread_handle = rx.recv().unwrap();
576        while blocked_thread_handle.info().unwrap().state
577            != zx::ThreadState::Blocked(zx::ThreadBlockType::Futex)
578        {
579            std::thread::sleep(std::time::Duration::from_millis(10));
580        }
581
582        // Dynamically assign PI ownership to root_thread_handle.
583        event.assign_new_owner(&root_thread_handle).unwrap();
584
585        // Wait for the correct requeue owner to appear.
586        while event.get_owner() != Some(root_thread_koid) {
587            std::thread::sleep(std::time::Duration::from_millis(50));
588        }
589
590        event.notify();
591        blocked_thread.join().unwrap();
592    }
593
594    #[test]
595    fn concurrent_wait_wake_assign_new_owner_stress_test() {
596        let root_thread_handle = fuchsia_runtime::with_thread_self(|thread| {
597            Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap())
598        });
599
600        for _ in 0..200 {
601            let event = Arc::new(InterruptibleEvent::new());
602            let barrier = Arc::new(std::sync::Barrier::new(2));
603
604            let event_waiter = event.clone();
605            let event_assign = event.clone();
606            let event_waker = event.clone();
607            let barrier_assign = barrier.clone();
608            let barrier_waker = barrier.clone();
609            let thread_handle = Arc::clone(&root_thread_handle);
610
611            let waiter = std::thread::spawn(move || {
612                let guard = event_waiter.begin_wait_requeueable();
613                let _ = guard.block_until(None, zx::MonotonicInstant::INFINITE);
614            });
615
616            while event.futex.load(Ordering::Relaxed) != WAITING_REQUEUEABLE {
617                std::thread::yield_now();
618            }
619
620            let assigner = std::thread::spawn(move || {
621                barrier_assign.wait();
622                let _ = event_assign.assign_new_owner(&thread_handle);
623            });
624
625            let waker = std::thread::spawn(move || {
626                barrier_waker.wait();
627                event_waker.notify();
628            });
629
630            assigner.join().unwrap();
631            waker.join().unwrap();
632            waiter.join().unwrap();
633        }
634    }
635
636    #[test]
637    fn futex_ownership_is_transferred_when_requeued_before_block() {
638        let event = InterruptibleEvent::new();
639
640        let (root_thread_handle, root_thread_koid) = fuchsia_runtime::with_thread_self(|thread| {
641            (
642                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
643                thread.koid().unwrap(),
644            )
645        });
646
647        let event_for_blocked_thread = Arc::clone(&event);
648        let (ready_tx, ready_rx) = std::sync::mpsc::channel();
649        let (go_tx, go_rx) = std::sync::mpsc::channel();
650        let blocked_thread = std::thread::spawn(move || {
651            let guard = event_for_blocked_thread.begin_wait_requeueable();
652            ready_tx.send(()).unwrap();
653            go_rx.recv().unwrap();
654            guard.block_until(None, zx::MonotonicInstant::INFINITE).unwrap();
655        });
656
657        // Call `assign_new_owner` after `begin_wait_requeueable()`, before `block_until()`.
658        ready_rx.recv().unwrap();
659        event.assign_new_owner(&root_thread_handle).unwrap();
660        go_tx.send(()).unwrap();
661
662        while event.get_owner() != Some(root_thread_koid) {
663            std::thread::sleep(std::time::Duration::from_millis(5));
664        }
665
666        event.notify();
667        blocked_thread.join().unwrap();
668    }
669
670    #[test]
671    fn test_wait_clean_stack_then_notify() {
672        let event = InterruptibleEvent::new();
673        let guard = event.begin_wait();
674
675        let other_event = Arc::clone(&event);
676        let thread = std::thread::spawn(move || {
677            std::thread::sleep(std::time::Duration::from_millis(30));
678            other_event.notify();
679        });
680
681        guard
682            .block_until_with_clean_delay(
683                None,
684                zx::MonotonicInstant::INFINITE,
685                zx::MonotonicDuration::from_millis(10),
686            )
687            .expect("failed to be notified after clean_stack");
688        thread.join().expect("failed to join thread");
689    }
690
691    #[test]
692    fn test_wait_clean_stack_then_timeout() {
693        let event = InterruptibleEvent::new();
694        let guard = event.begin_wait();
695        let deadline = zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(30));
696
697        let result = guard.block_until_with_clean_delay(
698            None,
699            deadline,
700            zx::MonotonicDuration::from_millis(10),
701        );
702        assert_eq!(result, Err(WakeReason::DeadlineExpired));
703        assert!(zx::MonotonicInstant::get() >= deadline);
704    }
705
706    #[test]
707    fn futex_requeue_owner_is_preserved_across_clean_stack() {
708        let event = InterruptibleEvent::new();
709
710        let (root_thread_handle, root_thread_koid) = fuchsia_runtime::with_thread_self(|thread| {
711            (
712                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
713                thread.koid().unwrap(),
714            )
715        });
716
717        let event_for_blocked_thread = Arc::clone(&event);
718        let (ready_tx, ready_rx) = std::sync::mpsc::channel();
719        let (go_tx, go_rx) = std::sync::mpsc::channel();
720        let blocked_thread = std::thread::spawn(move || {
721            let thread_handle = fuchsia_runtime::with_thread_self(|thread| {
722                thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()
723            });
724            let guard = event_for_blocked_thread.begin_wait_requeueable();
725            ready_tx.send(thread_handle).unwrap();
726            go_rx.recv().unwrap();
727            guard
728                .block_until_with_clean_delay(
729                    None,
730                    zx::MonotonicInstant::INFINITE,
731                    zx::MonotonicDuration::from_millis(25),
732                )
733                .unwrap();
734        });
735
736        // Assign PI ownership after `begin_wait_requeueable()` before `blocked_thread` enters
737        // `block_until_with_clean_delay`, so the 25ms `clean_deadline` cannot expire before
738        // `assign_new_owner` runs even on a loaded bot.
739        let blocked_thread_handle = ready_rx.recv().unwrap();
740        event.assign_new_owner(&root_thread_handle).unwrap();
741        go_tx.send(()).unwrap();
742
743        while event.get_owner() != Some(root_thread_koid) {
744            std::thread::sleep(std::time::Duration::from_millis(5));
745        }
746
747        // Sleep past the 25ms clean_deadline so the blocked thread wakes, calls `clean_stack()`,
748        // and re-waits on `requeue_target`.
749        std::thread::sleep(std::time::Duration::from_millis(50));
750        while blocked_thread_handle.info().unwrap().state
751            != zx::ThreadState::Blocked(zx::ThreadBlockType::Futex)
752        {
753            std::thread::sleep(std::time::Duration::from_millis(5));
754        }
755        assert_eq!(event.get_owner(), Some(root_thread_koid));
756
757        event.notify();
758        blocked_thread.join().unwrap();
759    }
760
761    #[test]
762    fn test_stack_allocated_wait_notify_and_interrupt() {
763        let event = InterruptibleEvent::default();
764        let guard = event.begin_wait();
765        std::thread::scope(|s| {
766            s.spawn(|| {
767                event.notify();
768            });
769            guard.block_until(None, zx::MonotonicInstant::INFINITE).expect("failed to be notified");
770        });
771
772        let guard = event.begin_wait();
773        std::thread::scope(|s| {
774            s.spawn(|| {
775                event.interrupt();
776            });
777            let result = guard.block_until(None, zx::MonotonicInstant::INFINITE);
778            assert_eq!(result, Err(WakeReason::Interrupted));
779        });
780    }
781
782    #[test]
783    fn test_is_waiting_during_requeue_transition() {
784        let event = InterruptibleEvent::new();
785        assert!(!event.is_waiting());
786
787        let guard = event.begin_wait_requeueable();
788        assert!(event.is_waiting());
789
790        // Simulate the mid-transition window in `assign_new_owner` where `futex` has transitioned
791        // to `REQUEUED` while `requeue_target` is still `READY`.
792        event.futex.store(REQUEUED, Ordering::Release);
793        assert_eq!(event.requeue_target.load(Ordering::Relaxed), READY);
794        assert!(event.is_waiting());
795
796        drop(guard);
797        assert!(!event.is_waiting());
798
799        let guard = event.begin_wait();
800        event.notify();
801        guard.block_until(None, zx::MonotonicInstant::INFINITE).unwrap();
802        assert!(!event.is_waiting());
803    }
804
805    #[test]
806    fn test_dropping_wait_guard_resets_event() {
807        let event = InterruptibleEvent::new();
808        assert!(!event.is_waiting());
809        {
810            let _guard = event.begin_wait();
811            assert!(event.is_waiting());
812        }
813        assert!(!event.is_waiting());
814
815        let guard = event.begin_wait();
816        event.notify();
817        guard.block_until(None, zx::MonotonicInstant::INFINITE).unwrap();
818        assert!(!event.is_waiting());
819    }
820
821    #[test]
822    fn test_dropping_requeued_wait_guard_releases_owner() {
823        let event = InterruptibleEvent::new();
824        let owner = fuchsia_runtime::with_thread_self(|thread| {
825            Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap())
826        });
827        assert_eq!(Arc::strong_count(&owner), 1);
828
829        {
830            let _guard = event.begin_wait_requeueable();
831            event.assign_new_owner(&owner).unwrap();
832            assert!(event.is_waiting());
833            assert_eq!(Arc::strong_count(&owner), 2);
834        }
835
836        assert!(!event.is_waiting());
837        assert_eq!(Arc::strong_count(&owner), 1);
838        assert_eq!(event.get_owner(), None);
839
840        let guard = event.begin_wait();
841        event.notify();
842        guard.block_until(None, zx::MonotonicInstant::INFINITE).unwrap();
843        assert!(!event.is_waiting());
844    }
845
846    #[test]
847    fn test_notify_before_block_resets_event_for_reuse() {
848        let event = InterruptibleEvent::new();
849
850        let guard = event.begin_wait();
851        event.notify();
852        assert!(!event.is_waiting());
853        guard.block_until(None, zx::MonotonicInstant::INFINITE).unwrap();
854        assert!(!event.is_waiting());
855
856        let guard = event.begin_wait();
857        event.notify();
858        guard.block_until(None, zx::MonotonicInstant::INFINITE).unwrap();
859        assert!(!event.is_waiting());
860    }
861
862    #[test]
863    fn test_reuse_after_requeued_block() {
864        const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5);
865
866        let event = InterruptibleEvent::new();
867        let (owner, owner_koid) = fuchsia_runtime::with_thread_self(|thread| {
868            (
869                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
870                thread.koid().unwrap(),
871            )
872        });
873        assert_eq!(Arc::strong_count(&owner), 1);
874
875        // Cycle 1: Real requeued block woken by `notify()`.
876        let event_for_waiter = Arc::clone(&event);
877        let (thread_tx, thread_rx) = std::sync::mpsc::channel();
878        let waiter = std::thread::spawn(move || {
879            let thread_handle = fuchsia_runtime::with_thread_self(|thread| {
880                thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()
881            });
882            let guard = event_for_waiter.begin_wait_requeueable();
883            thread_tx.send(thread_handle).unwrap();
884            guard.block_until(None, zx::MonotonicInstant::INFINITE)
885        });
886
887        let waiter_handle = thread_rx.recv().unwrap();
888        while waiter_handle.info().unwrap().state
889            != zx::ThreadState::Blocked(zx::ThreadBlockType::Futex)
890        {
891            std::thread::sleep(POLL_INTERVAL);
892        }
893
894        event.assign_new_owner(&owner).unwrap();
895        assert_eq!(Arc::strong_count(&owner), 2);
896        assert_eq!(event.get_owner(), Some(owner_koid));
897
898        event.notify();
899        assert_eq!(waiter.join().unwrap(), Ok(()));
900        assert!(!event.is_waiting());
901        assert_eq!(Arc::strong_count(&owner), 1);
902        assert_eq!(event.get_owner(), None);
903
904        // Cycle 2: Reuse the same event for a second real requeued block woken by `interrupt()`.
905        let event_for_waiter = Arc::clone(&event);
906        let (thread_tx, thread_rx) = std::sync::mpsc::channel();
907        let waiter = std::thread::spawn(move || {
908            let thread_handle = fuchsia_runtime::with_thread_self(|thread| {
909                thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()
910            });
911            let guard = event_for_waiter.begin_wait_requeueable();
912            thread_tx.send(thread_handle).unwrap();
913            guard.block_until(None, zx::MonotonicInstant::INFINITE)
914        });
915
916        let waiter_handle = thread_rx.recv().unwrap();
917        while waiter_handle.info().unwrap().state
918            != zx::ThreadState::Blocked(zx::ThreadBlockType::Futex)
919        {
920            std::thread::sleep(POLL_INTERVAL);
921        }
922
923        event.assign_new_owner(&owner).unwrap();
924        assert_eq!(Arc::strong_count(&owner), 2);
925        assert_eq!(event.get_owner(), Some(owner_koid));
926
927        event.interrupt();
928        assert_eq!(waiter.join().unwrap(), Err(WakeReason::Interrupted));
929        assert!(!event.is_waiting());
930        assert_eq!(Arc::strong_count(&owner), 1);
931        assert_eq!(event.get_owner(), None);
932    }
933
934    #[test]
935    fn test_reuse_after_timeout_and_interrupt() {
936        const SHORT_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(20);
937
938        let event = InterruptibleEvent::new();
939
940        // Spawn a distinct live thread to serve as a valid PI owner while the main thread blocks.
941        let (owner_tx, owner_rx) = std::sync::mpsc::channel();
942        let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>();
943        let owner_thread = std::thread::spawn(move || {
944            let handle = fuchsia_runtime::with_thread_self(|thread| {
945                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap())
946            });
947            owner_tx.send(handle).unwrap();
948            let _ = stop_rx.recv();
949        });
950        let owner = owner_rx.recv().unwrap();
951        assert_eq!(Arc::strong_count(&owner), 1);
952
953        // Cycle 1: Timeout on standard wait.
954        let guard = event.begin_wait();
955        assert_eq!(
956            guard.block_until(None, zx::MonotonicInstant::after(SHORT_TIMEOUT)),
957            Err(WakeReason::DeadlineExpired)
958        );
959        assert!(!event.is_waiting());
960
961        // Cycle 2: Timeout on requeued wait releases `requeue_owner`.
962        let guard = event.begin_wait_requeueable();
963        event.assign_new_owner(&owner).unwrap();
964        assert_eq!(Arc::strong_count(&owner), 2);
965        assert_eq!(
966            guard.block_until(None, zx::MonotonicInstant::after(SHORT_TIMEOUT)),
967            Err(WakeReason::DeadlineExpired)
968        );
969        assert!(!event.is_waiting());
970        assert_eq!(Arc::strong_count(&owner), 1);
971        assert_eq!(event.get_owner(), None);
972
973        // Cycle 3: Interruption.
974        let guard = event.begin_wait();
975        let other_event = Arc::clone(&event);
976        let interrupter = std::thread::spawn(move || {
977            other_event.interrupt();
978        });
979        assert_eq!(
980            guard.block_until(None, zx::MonotonicInstant::INFINITE),
981            Err(WakeReason::Interrupted)
982        );
983        interrupter.join().unwrap();
984        assert!(!event.is_waiting());
985
986        // Cycle 4: Normal notification after prior timeouts and interruption.
987        let guard = event.begin_wait();
988        let other_event = Arc::clone(&event);
989        let notifier = std::thread::spawn(move || {
990            other_event.notify();
991        });
992        assert_eq!(guard.block_until(None, zx::MonotonicInstant::INFINITE), Ok(()));
993        notifier.join().unwrap();
994        assert!(!event.is_waiting());
995
996        drop(stop_tx);
997        owner_thread.join().unwrap();
998    }
999
1000    #[test]
1001    fn test_concurrent_notify_during_reuse() {
1002        const REUSE_ITERATIONS: usize = 500;
1003
1004        let event = InterruptibleEvent::new();
1005        let done = AtomicBool::new(false);
1006        std::thread::scope(|scope| {
1007            let notifier_event = &event;
1008            let done_ref = &done;
1009            scope.spawn(move || {
1010                while !done_ref.load(Ordering::Relaxed) {
1011                    notifier_event.notify();
1012                }
1013            });
1014            scopeguard::defer! {
1015                done.store(true, Ordering::Relaxed);
1016            }
1017            for i in 0..REUSE_ITERATIONS {
1018                let guard = event.begin_wait();
1019                if i % 4 == 0 {
1020                    drop(guard);
1021                } else {
1022                    assert_eq!(guard.block_until(None, zx::MonotonicInstant::INFINITE), Ok(()));
1023                }
1024            }
1025        });
1026        assert!(!event.is_waiting());
1027    }
1028
1029    #[test]
1030    fn test_concurrent_interrupt_and_requeue_during_reuse() {
1031        const REUSE_ITERATIONS: usize = 500;
1032
1033        let event = InterruptibleEvent::new();
1034        let (owner_tx, owner_rx) = std::sync::mpsc::channel();
1035        let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>();
1036        let owner_thread = std::thread::spawn(move || {
1037            let handle = fuchsia_runtime::with_thread_self(|thread| {
1038                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap())
1039            });
1040            owner_tx.send(handle).unwrap();
1041            let _ = stop_rx.recv();
1042        });
1043        let owner = owner_rx.recv().unwrap();
1044        assert_eq!(Arc::strong_count(&owner), 1);
1045
1046        let done = AtomicBool::new(false);
1047        std::thread::scope(|scope| {
1048            let interrupter_event = &event;
1049            let assigner_event = &event;
1050            let owner_ref = &owner;
1051            let done_ref = &done;
1052            scope.spawn(move || {
1053                while !done_ref.load(Ordering::Relaxed) {
1054                    interrupter_event.interrupt();
1055                }
1056            });
1057            scope.spawn(move || {
1058                while !done_ref.load(Ordering::Relaxed) {
1059                    let _ = assigner_event.assign_new_owner(owner_ref);
1060                }
1061            });
1062            scopeguard::defer! {
1063                done.store(true, Ordering::Relaxed);
1064            }
1065            for i in 0..REUSE_ITERATIONS {
1066                let guard =
1067                    if i % 2 == 0 { event.begin_wait_requeueable() } else { event.begin_wait() };
1068                if i % 4 == 0 {
1069                    drop(guard);
1070                } else {
1071                    assert_eq!(
1072                        guard.block_until(None, zx::MonotonicInstant::INFINITE),
1073                        Err(WakeReason::Interrupted)
1074                    );
1075                }
1076            }
1077        });
1078
1079        assert!(!event.is_waiting());
1080        assert_eq!(Arc::strong_count(&owner), 1);
1081        assert_eq!(event.get_owner(), None);
1082
1083        drop(stop_tx);
1084        owner_thread.join().unwrap();
1085    }
1086
1087    #[test]
1088    fn test_non_requeueable_wait_rejects_assign_new_owner() {
1089        const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5);
1090
1091        let event = InterruptibleEvent::new();
1092        let (known_owner, known_owner_koid) = fuchsia_runtime::with_thread_self(|thread| {
1093            (
1094                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1095                thread.koid().unwrap(),
1096            )
1097        });
1098
1099        let (other_owner_tx, other_owner_rx) = std::sync::mpsc::channel();
1100        let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>();
1101        let other_owner_thread = std::thread::spawn(move || {
1102            let handle = fuchsia_runtime::with_thread_self(|thread| {
1103                Arc::new(thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap())
1104            });
1105            other_owner_tx.send(handle).unwrap();
1106            let _ = stop_rx.recv();
1107        });
1108        let other_owner = other_owner_rx.recv().unwrap();
1109
1110        // Cycle 1: Start and drop a requeueable wait so we also verify requeue eligibility does not
1111        // persist into a subsequent `begin_wait()` cycle.
1112        drop(event.begin_wait_requeueable());
1113
1114        // Cycle 2: Standard `begin_wait()` with a known PI owner rejects `assign_new_owner` and
1115        // preserves `known_owner`.
1116        std::thread::scope(|s| {
1117            let (tx, rx) = std::sync::mpsc::channel();
1118            let event_ref = &event;
1119            let known_owner_ref = &known_owner;
1120            s.spawn(move || {
1121                let guard = event_ref.begin_wait();
1122                let waiter_handle = fuchsia_runtime::with_thread_self(|thread| {
1123                    thread.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()
1124                });
1125                tx.send(waiter_handle).unwrap();
1126                guard.block_until(Some(known_owner_ref), zx::MonotonicInstant::INFINITE).unwrap();
1127            });
1128            scopeguard::defer! {
1129                event.notify();
1130            }
1131
1132            let waiter_handle = rx.recv().unwrap();
1133            while waiter_handle.info().unwrap().state
1134                != zx::ThreadState::Blocked(zx::ThreadBlockType::Futex)
1135            {
1136                std::thread::sleep(POLL_INTERVAL);
1137            }
1138            assert_eq!(event.get_owner(), Some(known_owner_koid));
1139
1140            assert_eq!(event.assign_new_owner(&other_owner), Err(zx::Status::BAD_STATE));
1141            assert_eq!(Arc::strong_count(&other_owner), 1);
1142            assert_eq!(event.get_owner(), Some(known_owner_koid));
1143        });
1144
1145        drop(stop_tx);
1146        other_owner_thread.join().unwrap();
1147    }
1148}