Skip to main content

fuchsia_sync/
completion.rs

1// Copyright 2026 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
5//! A lightweight completion event primitive wrapping `libsync`'s `sync_completion_t`.
6
7use std::time::Duration;
8use zx::sys;
9
10unsafe extern "C" {
11    fn sync_completion_wait(
12        completion: *const sys::zx_futex_t,
13        timeout: sys::zx_duration_mono_t,
14    ) -> sys::zx_status_t;
15    fn sync_completion_wait_deadline(
16        completion: *const sys::zx_futex_t,
17        deadline: sys::zx_instant_mono_t,
18    ) -> sys::zx_status_t;
19    fn sync_completion_signal(completion: *const sys::zx_futex_t);
20    fn sync_completion_reset(completion: *const sys::zx_futex_t);
21    fn sync_completion_signaled(completion: *const sys::zx_futex_t) -> bool;
22}
23
24const SYNC_COMPLETION_INIT: i32 = 0;
25
26/// A lightweight in-process completion event.
27///
28/// Conceptually, a completion has an internal state of either unsignaled or signaled. Threads
29/// may wait on the completion until it achieves the signaled state, or alter its state.
30///
31/// On Fuchsia, this wraps `sync_completion_t` from `zircon/system/ulib/sync`.
32#[repr(transparent)]
33pub struct Completion(sys::zx_futex_t);
34
35// SAFETY: `Completion` wraps `sys::zx_futex_t` (`AtomicI32`), which is safe to send
36// across threads because futex operations operate on process-wide atomic memory.
37unsafe impl Send for Completion {}
38
39// SAFETY: All operations on `Completion` synchronize via atomic operations and
40// kernel futexes with acquire/release semantics, making concurrent access sound.
41unsafe impl Sync for Completion {}
42
43impl Completion {
44    /// Creates a new completion in the unsignaled state.
45    pub const fn new() -> Self {
46        Self(sys::zx_futex_t::new(SYNC_COMPLETION_INIT))
47    }
48
49    #[inline]
50    fn as_futex_ptr(&self) -> *const sys::zx_futex_t {
51        std::ptr::addr_of!(self.0)
52    }
53
54    /// Blocks the current thread until the completion is signaled.
55    pub fn wait(&self) {
56        let signaled = self.wait_until(zx::MonotonicInstant::INFINITE);
57        assert!(signaled, "sync_completion_wait with infinite deadline failed");
58    }
59
60    /// Blocks the current thread until the completion is signaled or the timeout expires.
61    /// Returns `true` if the completion was signaled, or `false` if it timed out.
62    pub fn wait_for(&self, timeout: Duration) -> bool {
63        let nanos = zx::MonotonicDuration::from(timeout).into_nanos();
64        // SAFETY: `self.as_futex_ptr()` points to a valid, aligned `zx_futex_t`
65        // whose memory remains valid for the duration of `&self`.
66        let status = unsafe { sync_completion_wait(self.as_futex_ptr(), nanos) };
67        status == sys::ZX_OK
68    }
69
70    /// Blocks the current thread until the completion is signaled or the deadline is reached.
71    /// Returns `true` if the completion was signaled, or `false` if it timed out.
72    pub fn wait_until(&self, deadline: zx::MonotonicInstant) -> bool {
73        // SAFETY: `self.as_futex_ptr()` points to a valid, aligned `zx_futex_t`
74        // whose memory remains valid for the duration of `&self`.
75        let status =
76            unsafe { sync_completion_wait_deadline(self.as_futex_ptr(), deadline.into_nanos()) };
77        status == sys::ZX_OK
78    }
79
80    /// Signals the completion, awakening all waiting threads.
81    pub fn signal(&self) {
82        // SAFETY: `self.as_futex_ptr()` points to a valid, aligned `zx_futex_t`
83        // whose memory remains valid for the duration of `&self`.
84        unsafe {
85            sync_completion_signal(self.as_futex_ptr());
86        }
87    }
88
89    /// Resets the completion to the unsignaled state.
90    pub fn reset(&self) {
91        // SAFETY: `self.as_futex_ptr()` points to a valid, aligned `zx_futex_t`
92        // whose memory remains valid for the duration of `&self`.
93        unsafe {
94            sync_completion_reset(self.as_futex_ptr());
95        }
96    }
97
98    /// Returns `true` if the completion has been signaled.
99    pub fn is_signaled(&self) -> bool {
100        // SAFETY: `self.as_futex_ptr()` points to a valid, aligned `zx_futex_t`
101        // whose memory remains valid for the duration of `&self`.
102        unsafe { sync_completion_signaled(self.as_futex_ptr()) }
103    }
104}
105
106impl Default for Completion {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use std::sync::Arc;
116    use std::time::Duration;
117
118    #[test]
119    fn test_completion_signal_then_wait() {
120        let c = Completion::new();
121        assert!(!c.is_signaled());
122        c.signal();
123        assert!(c.is_signaled());
124        c.wait();
125        assert!(c.is_signaled());
126    }
127
128    #[test]
129    fn test_completion_wait_then_signal() {
130        let c = Arc::new(Completion::new());
131        let c_clone = c.clone();
132        let handle = std::thread::spawn(move || {
133            std::thread::sleep(Duration::from_millis(10));
134            c_clone.signal();
135        });
136        c.wait();
137        handle.join().unwrap();
138        assert!(c.is_signaled());
139    }
140
141    #[test]
142    fn test_completion_reset() {
143        let c = Completion::new();
144        c.signal();
145        assert!(c.is_signaled());
146        c.reset();
147        assert!(!c.is_signaled());
148    }
149
150    #[test]
151    fn test_completion_multiple_waiters() {
152        let c = Arc::new(Completion::new());
153        let mut handles = vec![];
154        for _ in 0..5 {
155            let c_clone = c.clone();
156            handles.push(std::thread::spawn(move || {
157                c_clone.wait();
158            }));
159        }
160        std::thread::sleep(Duration::from_millis(10));
161        c.signal();
162        for handle in handles {
163            handle.join().unwrap();
164        }
165    }
166
167    #[test]
168    fn test_completion_wait_for() {
169        let c = Completion::new();
170        assert!(!c.wait_for(Duration::from_millis(10)));
171
172        c.signal();
173        assert!(c.wait_for(Duration::from_millis(10)));
174    }
175
176    #[test]
177    fn test_completion_wait_for_signal() {
178        let c = Arc::new(Completion::new());
179        let c_clone = c.clone();
180        let handle = std::thread::spawn(move || {
181            std::thread::sleep(Duration::from_millis(10));
182            c_clone.signal();
183        });
184        assert!(c.wait_for(Duration::from_secs(5)));
185        handle.join().unwrap();
186        assert!(c.is_signaled());
187    }
188
189    #[test]
190    fn test_completion_wait_until() {
191        let c = Completion::new();
192        assert!(!c.wait_until(zx::MonotonicInstant::after(zx::MonotonicDuration::from_millis(10))));
193
194        c.signal();
195        assert!(c.wait_until(zx::MonotonicInstant::from_nanos(0)));
196    }
197
198    #[test]
199    fn test_completion_wait_until_signal() {
200        let c = Arc::new(Completion::new());
201        let c_clone = c.clone();
202        let handle = std::thread::spawn(move || {
203            std::thread::sleep(Duration::from_millis(10));
204            c_clone.signal();
205        });
206        assert!(c.wait_until(zx::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(5))));
207        handle.join().unwrap();
208        assert!(c.is_signaled());
209    }
210}