Skip to main content

starnix_sync/
port_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 std::sync::LazyLock;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8/// A [`PortEvent`] is interested only in events originating from within the
9/// process (see [`PortEvent.futex`] for more details), and the waiter is
10/// may be notified up.
11const FUTEX_WAITING: i32 = 0;
12/// A [`PortEvent`] is interested only in events originating from within the
13/// process (see [`PortEvent.futex`] for more details), and the waiter is
14/// has been notified of an event.
15const FUTEX_NOTIFIED: i32 = 1;
16/// A [`PortEvent`] is interested only in events originating from within the
17/// process (see [`PortEvent.futex`] for more details), and the waiter is
18/// has been notified of an interrupt.
19const FUTEX_INTERRUPTED: i32 = 2;
20/// A [`PortEvent`] is interested in events originating from outside of
21/// process (see [`PortEvent.futex`] for more details). The waiter's `zx::Port`
22/// should be used instead of the Futex.
23const FUTEX_USE_PORT: i32 = 3;
24
25/// Specifies the ordering for atomics accessed by both the "notifier" and
26/// "notifee" (the waiter).
27///
28/// Relaxed ordering because the [`PortEvent`] does not provide synchronization
29/// between the "notifier" and the "notifee". If a notifiee needs synchronization,
30/// it needs to perform that synchronization itself.
31///
32/// See [`PortEvent.wait`] for more details.
33const ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE: Ordering = Ordering::Relaxed;
34
35/// A wrapper around a [`zx::Port`] that optimizes for the case where events are
36/// signaled within a process.
37///
38/// This object will prefer to use a Futex for notifications/interrupts but will
39/// fallback to a `zx::Port` when the port is subscribed for events on an object
40/// with [`PortEvent.object_wait_async`].
41///
42/// Note that the `PortEvent` does not provide any synchronization between a
43/// notifier (caller of [`PortEvent.notify`]) and a notifiee/waiter (caller of
44/// [`PortEvent.wait`].
45#[derive(Debug)]
46pub struct PortEvent {
47    /// The Futex used to wake up a thread when this waiter is waiting for
48    /// events that don't depend on a `zx::Port`.
49    ///
50    /// This mode is subject to spurious wakeups.
51    /// See more in https://fuchsia.dev/reference/syscalls/futex_wait?hl=en#spurious_wakeups
52    futex: zx::Futex,
53    /// The underlying Zircon port that the waiter waits on when it is
54    /// interested in events that cross process boundaries.
55    ///
56    /// Lazily allocated to optimize for the case where waiters are interested
57    /// only in events triggered within a process.
58    port: LazyLock<zx::Port>,
59    /// Indicates whether a user packet is sitting in the `zx::Port` to wake up
60    /// waiter after handling user events.
61    has_pending_user_packet: AtomicBool,
62}
63
64/// The kind of notification.
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub enum NotifyKind {
67    Regular,
68    Interrupt,
69}
70
71/// The result of a call to [`PortEvent.wait`].
72#[derive(Debug, Eq, PartialEq)]
73pub enum PortWaitResult {
74    /// Signals asserted on an object.
75    Signal { key: u64, observed: zx::Signals },
76    /// A notification to wake up waiters.
77    Notification { kind: NotifyKind },
78    /// Wait timed out.
79    TimedOut,
80}
81
82impl PortWaitResult {
83    const NOTIFY_REGULAR: Self = Self::Notification { kind: NotifyKind::Regular };
84    const NOTIFY_INTERRUPT: Self = Self::Notification { kind: NotifyKind::Interrupt };
85}
86
87impl PortEvent {
88    /// Returns a new `PortEvent`.
89    pub fn new() -> Self {
90        Self {
91            futex: zx::Futex::new(FUTEX_WAITING),
92            port: LazyLock::new(zx::Port::create),
93            has_pending_user_packet: Default::default(),
94        }
95    }
96
97    /// Wait for an event to occur, or the deadline has been reached.
98    pub fn wait(&self, deadline: zx::MonotonicInstant) -> PortWaitResult {
99        self.wait_with_clean_delay(deadline, crate::STACK_CLEAN_DELAY)
100    }
101
102    fn wait_with_clean_delay(
103        &self,
104        deadline: zx::MonotonicInstant,
105        clean_delay: zx::MonotonicDuration,
106    ) -> PortWaitResult {
107        let mut clean_deadline: Option<zx::MonotonicInstant> = None;
108        let mut state = self.futex.load(ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE);
109        loop {
110            match state {
111                FUTEX_WAITING => {
112                    // A deadline of ZERO (or earlier) only checks for a pending notification, which
113                    // the load above already did: zx_futex_wait() could only time out or observe a
114                    // state change that the next wait observes as well, so skip the syscall.
115                    if deadline <= zx::MonotonicInstant::ZERO {
116                        return PortWaitResult::TimedOut;
117                    }
118                    let wait_deadline = *clean_deadline.get_or_insert_with(|| {
119                        crate::initial_clean_deadline(deadline, clean_delay)
120                    });
121                    match self.futex.wait(FUTEX_WAITING, None, wait_deadline) {
122                        Ok(()) | Err(zx::Status::BAD_STATE) => {
123                            state =
124                                self.futex.load(ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE);
125                        }
126                        Err(zx::Status::TIMED_OUT) => {
127                            if wait_deadline < deadline {
128                                starnix_stack::clean_stack();
129                                clean_deadline = Some(deadline);
130                                state = self
131                                    .futex
132                                    .load(ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE);
133                            } else {
134                                return PortWaitResult::TimedOut;
135                            }
136                        }
137                        Err(e) => panic!("Unexpected error from zx_futex_wait: {e}"),
138                    }
139                }
140                FUTEX_NOTIFIED | FUTEX_INTERRUPTED => {
141                    match self.futex.compare_exchange(
142                        state,
143                        FUTEX_WAITING,
144                        ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE,
145                        ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE,
146                    ) {
147                        Ok(new_state) => {
148                            debug_assert_eq!(new_state, state);
149                            return if new_state == FUTEX_INTERRUPTED {
150                                PortWaitResult::NOTIFY_INTERRUPT
151                            } else {
152                                PortWaitResult::NOTIFY_REGULAR
153                            };
154                        }
155                        Err(new_state) => {
156                            debug_assert_ne!(new_state, state);
157                            state = new_state;
158                        }
159                    }
160                }
161                FUTEX_USE_PORT => {
162                    break;
163                }
164                state => unreachable!("unexpected value = {state}"),
165            }
166        }
167
168        let mut clean_deadline =
169            clean_deadline.unwrap_or_else(|| crate::initial_clean_deadline(deadline, clean_delay));
170        let packet = loop {
171            match self.port.wait(clean_deadline) {
172                Ok(packet) => break Ok(packet),
173                Err(zx::Status::TIMED_OUT) if clean_deadline < deadline => {
174                    starnix_stack::clean_stack();
175                    clean_deadline = deadline;
176                }
177                Err(e) => break Err(e),
178            }
179        };
180        match packet {
181            Ok(packet) => match packet.status() {
182                zx::sys::ZX_OK => {
183                    match packet.contents() {
184                        zx::PacketContents::SignalOne(sigpkt) => PortWaitResult::Signal {
185                            key: packet.key(),
186                            observed: sigpkt.observed(),
187                        },
188                        zx::PacketContents::User(_) => {
189                            // User packet w/ OK status is only used to wake up
190                            // the waiter after handling process-internal events.
191                            //
192                            // Note that we can be woken up even when we will
193                            // not handle any user events. This is because right
194                            // after we set `has_pending_user_packet` to `false`,
195                            // another thread can immediately queue a new user
196                            // event and set `has_pending_user_packet` to `true`.
197                            // However, that event will be handled by us (by the
198                            // caller when this method returns) as if the event
199                            // was enqueued before we received this user packet.
200                            // Once the caller handles all the current user events,
201                            // we end up with no remaining user events but a user
202                            // packet sitting in the `zx::Port`.
203                            assert!(
204                                self.has_pending_user_packet
205                                    .swap(false, ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE)
206                            );
207
208                            PortWaitResult::NOTIFY_REGULAR
209                        }
210                        _contents => panic!("unexpected packet = {:?}", packet),
211                    }
212                }
213                zx::sys::ZX_ERR_CANCELED => PortWaitResult::NOTIFY_INTERRUPT,
214                status => {
215                    panic!("Unexpected status in port wait {}", status);
216                }
217            },
218            Err(zx::Status::TIMED_OUT) => PortWaitResult::TimedOut,
219            Err(e) => panic!("Unexpected error from port_wait: {e}"),
220        }
221    }
222
223    /// Subscribe for signals on an object.
224    pub fn object_wait_async(
225        &self,
226        handle: &dyn zx::AsHandleRef,
227        key: u64,
228        signals: zx::Signals,
229        opts: zx::WaitAsyncOpts,
230    ) -> Result<(), zx::Status> {
231        match self.futex.swap(FUTEX_USE_PORT, ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE) {
232            FUTEX_WAITING => {
233                self.futex.wake_all();
234            }
235            state @ (FUTEX_NOTIFIED | FUTEX_INTERRUPTED) => {
236                self.queue_user_packet_data(if state == FUTEX_INTERRUPTED {
237                    NotifyKind::Interrupt
238                } else {
239                    NotifyKind::Regular
240                })
241            }
242            FUTEX_USE_PORT => {}
243            v => unreachable!("unexpected value = {v}"),
244        }
245
246        handle.as_handle_ref().wait_async(&self.port, key, signals, opts)
247    }
248
249    /// Cancels async port notifications on an object.
250    pub fn cancel(&self, key: u64) {
251        let _: Result<(), zx::Status> = self.port.cancel(key);
252    }
253
254    /// Queue a packet to the underlying Zircon port, which will cause the
255    /// waiter to wake up.
256    ///
257    /// This method should only be called when the waiter is interested in
258    /// events that may originate from outside of the process.
259    fn queue_user_packet_data(&self, kind: NotifyKind) {
260        let status = match kind {
261            NotifyKind::Interrupt => zx::sys::ZX_ERR_CANCELED,
262            NotifyKind::Regular => {
263                if self
264                    .has_pending_user_packet
265                    .swap(true, ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE)
266                {
267                    return;
268                }
269
270                zx::sys::ZX_OK
271            }
272        };
273
274        let packet = zx::Packet::from_user_packet(0, status, zx::UserPacket::default());
275        self.port.queue(&packet).unwrap()
276    }
277
278    /// Marks the port as ready to handle a notification (or an interrupt) and
279    /// wakes up any blocked waiters.
280    pub fn notify(&self, kind: NotifyKind) {
281        let futex_val = match kind {
282            NotifyKind::Interrupt => FUTEX_INTERRUPTED,
283            NotifyKind::Regular => FUTEX_NOTIFIED,
284        };
285
286        match self.futex.compare_exchange(
287            FUTEX_WAITING,
288            futex_val,
289            ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE,
290            ORDERING_FOR_ATOMICS_BETWEEN_NOTIFIER_AND_NOTIFEE,
291        ) {
292            Ok(observed) => {
293                debug_assert_eq!(observed, FUTEX_WAITING);
294                self.futex.wake_all();
295            }
296            Err(observed) => match observed {
297                FUTEX_WAITING => unreachable!("this should have passed"),
298                FUTEX_NOTIFIED | FUTEX_INTERRUPTED => {}
299                FUTEX_USE_PORT => {
300                    self.queue_user_packet_data(kind);
301                }
302                observed => unreachable!("unexpected value = {observed}"),
303            },
304        }
305    }
306}
307
308#[cfg(test)]
309mod test {
310    use std::sync::Arc;
311
312    use test_case::test_case;
313
314    use super::*;
315
316    #[test]
317    fn test_signal_and_wait_block() {
318        const KEY: u64 = 1;
319        const ASSERTED_SIGNAL: zx::Signals = zx::Signals::USER_0;
320
321        let event = Arc::new(PortEvent::new());
322        let object = zx::Event::create();
323        assert_eq!(
324            event.object_wait_async(&object, KEY, ASSERTED_SIGNAL, zx::WaitAsyncOpts::empty()),
325            Ok(())
326        );
327
328        let event_clone = event.clone();
329        let thread = std::thread::spawn(move || {
330            assert_eq!(
331                event_clone.wait(zx::MonotonicInstant::INFINITE),
332                PortWaitResult::Signal { key: KEY, observed: ASSERTED_SIGNAL }
333            );
334        });
335
336        object.signal(/*clear_mask=*/ zx::Signals::NONE, /*set_mask=*/ ASSERTED_SIGNAL).unwrap();
337        thread.join().expect("join thread");
338    }
339
340    #[test]
341    fn test_signal_then_wait_nonblock() {
342        const KEY: u64 = 2;
343        const ASSERTED_SIGNAL: zx::Signals = zx::Signals::USER_1;
344
345        let event = PortEvent::new();
346        let object = zx::Event::create();
347        assert_eq!(
348            event.object_wait_async(&object, KEY, ASSERTED_SIGNAL, zx::WaitAsyncOpts::empty()),
349            Ok(())
350        );
351        object.signal(/*clear_mask=*/ zx::Signals::NONE, /*set_mask=*/ ASSERTED_SIGNAL).unwrap();
352
353        assert_eq!(
354            event.wait(zx::MonotonicInstant::INFINITE_PAST),
355            PortWaitResult::Signal { key: KEY, observed: ASSERTED_SIGNAL }
356        );
357    }
358
359    #[test]
360    fn test_signal_then_cancel_then_wait() {
361        const KEY: u64 = 3;
362        const ASSERTED_SIGNAL: zx::Signals = zx::Signals::USER_2;
363
364        let event = PortEvent::new();
365        let object = zx::Event::create();
366
367        assert_eq!(
368            event.object_wait_async(&object, KEY, ASSERTED_SIGNAL, zx::WaitAsyncOpts::empty()),
369            Ok(())
370        );
371        object.signal(/*clear_mask=*/ zx::Signals::NONE, /*set_mask=*/ ASSERTED_SIGNAL).unwrap();
372
373        event.cancel(KEY);
374        assert_eq!(event.wait(zx::MonotonicInstant::INFINITE_PAST), PortWaitResult::TimedOut);
375    }
376
377    #[test_case(NotifyKind::Interrupt, true; "interrupt with object")]
378    #[test_case(NotifyKind::Regular, true; "not interrupt with object")]
379    #[test_case(NotifyKind::Interrupt, false; "interrupt without object")]
380    #[test_case(NotifyKind::Regular, false; "not interrupt without object")]
381    fn test_notify_and_wait_block(kind: NotifyKind, with_object: bool) {
382        const KEY: u64 = 4;
383
384        let event = Arc::new(PortEvent::new());
385        let object = zx::Event::create();
386        if with_object {
387            assert_eq!(
388                event.object_wait_async(
389                    &object,
390                    KEY,
391                    zx::Signals::USER_3,
392                    zx::WaitAsyncOpts::empty()
393                ),
394                Ok(())
395            );
396        }
397
398        let event_clone = event.clone();
399        let thread = std::thread::spawn(move || {
400            assert_eq!(
401                event_clone.wait(zx::MonotonicInstant::INFINITE),
402                PortWaitResult::Notification { kind }
403            );
404        });
405
406        event.notify(kind);
407        thread.join().expect("join thread");
408    }
409
410    #[test_case(NotifyKind::Interrupt, true; "interrupt with object")]
411    #[test_case(NotifyKind::Regular, true; "not interrupt with object")]
412    #[test_case(NotifyKind::Interrupt, false; "interrupt without object")]
413    #[test_case(NotifyKind::Regular, false; "not interrupt without object")]
414    fn test_notify_then_wait_nonblock(kind: NotifyKind, with_object: bool) {
415        const KEY: u64 = 5;
416
417        let event = PortEvent::new();
418        let object = zx::Event::create();
419        if with_object {
420            assert_eq!(
421                event.object_wait_async(
422                    &object,
423                    KEY,
424                    zx::Signals::USER_4,
425                    zx::WaitAsyncOpts::empty()
426                ),
427                Ok(())
428            );
429        }
430
431        event.notify(kind);
432        assert_eq!(
433            event.wait(zx::MonotonicInstant::INFINITE_PAST),
434            PortWaitResult::Notification { kind }
435        );
436    }
437
438    #[test_case(true, zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(100)); "blocking with object")]
439    #[test_case(false, zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(100)); "blocking without object")]
440    #[test_case(true, zx::MonotonicInstant::INFINITE_PAST; "non blocking with object")]
441    #[test_case(false, zx::MonotonicInstant::INFINITE_PAST; "non blocking without object")]
442    #[test_case(true, zx::MonotonicInstant::ZERO; "zero deadline with object")]
443    #[test_case(false, zx::MonotonicInstant::ZERO; "zero deadline without object")]
444    fn test_wait_timeout(with_object: bool, deadline: zx::MonotonicInstant) {
445        const KEY: u64 = 6;
446        const ASSERTED_SIGNAL: zx::Signals = zx::Signals::USER_5;
447
448        let event = PortEvent::new();
449        let object = zx::Event::create();
450
451        if with_object {
452            assert_eq!(
453                event.object_wait_async(&object, KEY, ASSERTED_SIGNAL, zx::WaitAsyncOpts::empty()),
454                Ok(())
455            );
456        }
457        assert_eq!(event.wait(deadline), PortWaitResult::TimedOut);
458    }
459
460    #[test_case(NotifyKind::Interrupt, true; "interrupt with object")]
461    #[test_case(NotifyKind::Regular, true; "not interrupt with object")]
462    #[test_case(NotifyKind::Interrupt, false; "interrupt without object")]
463    #[test_case(NotifyKind::Regular, false; "not interrupt without object")]
464    fn test_zero_deadline_timeout_then_notify(kind: NotifyKind, with_object: bool) {
465        const KEY: u64 = 9;
466
467        let event = PortEvent::new();
468        let object = zx::Event::create();
469        if with_object {
470            assert_eq!(
471                event.object_wait_async(
472                    &object,
473                    KEY,
474                    zx::Signals::USER_4,
475                    zx::WaitAsyncOpts::empty()
476                ),
477                Ok(())
478            );
479        }
480
481        assert_eq!(event.wait(zx::MonotonicInstant::ZERO), PortWaitResult::TimedOut);
482        event.notify(kind);
483        assert_eq!(event.wait(zx::MonotonicInstant::ZERO), PortWaitResult::Notification { kind });
484        assert_eq!(event.wait(zx::MonotonicInstant::ZERO), PortWaitResult::TimedOut);
485    }
486
487    #[test_case(true; "with object")]
488    #[test_case(false; "without object")]
489    fn test_wait_clean_stack_then_notify(with_object: bool) {
490        const KEY: u64 = 7;
491
492        let event = Arc::new(PortEvent::new());
493        let object = zx::Event::create();
494        if with_object {
495            assert_eq!(
496                event.object_wait_async(
497                    &object,
498                    KEY,
499                    zx::Signals::USER_6,
500                    zx::WaitAsyncOpts::empty()
501                ),
502                Ok(())
503            );
504        }
505
506        let event_clone = Arc::clone(&event);
507        let thread = std::thread::spawn(move || {
508            assert_eq!(
509                event_clone.wait_with_clean_delay(
510                    zx::MonotonicInstant::INFINITE,
511                    zx::MonotonicDuration::from_millis(10),
512                ),
513                PortWaitResult::NOTIFY_REGULAR
514            );
515        });
516
517        std::thread::sleep(std::time::Duration::from_millis(30));
518        event.notify(NotifyKind::Regular);
519        thread.join().expect("join thread");
520    }
521
522    #[test_case(true; "with object")]
523    #[test_case(false; "without object")]
524    fn test_wait_clean_stack_then_timeout(with_object: bool) {
525        const KEY: u64 = 8;
526
527        let event = PortEvent::new();
528        let object = zx::Event::create();
529        if with_object {
530            assert_eq!(
531                event.object_wait_async(
532                    &object,
533                    KEY,
534                    zx::Signals::USER_7,
535                    zx::WaitAsyncOpts::empty()
536                ),
537                Ok(())
538            );
539        }
540
541        let deadline = zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(30));
542        assert_eq!(
543            event.wait_with_clean_delay(deadline, zx::MonotonicDuration::from_millis(10)),
544            PortWaitResult::TimedOut
545        );
546        assert!(zx::MonotonicInstant::get() >= deadline);
547    }
548}