Skip to main content

vfs/
temp_clone.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 fuchsia_sync::{Condvar, Mutex};
6use std::cell::UnsafeCell;
7use std::collections::hash_map::Entry;
8use std::collections::{HashMap, VecDeque};
9use std::marker::PhantomData;
10use std::mem::ManuallyDrop;
11use std::ops::Deref;
12use std::sync::{Arc, OnceLock, Weak};
13
14#[cfg(not(target_os = "fuchsia"))]
15use fuchsia_async::emulated_handle::zx_handle_t;
16#[cfg(target_os = "fuchsia")]
17use zx::sys::zx_handle_t;
18
19/// A wrapper around zircon handles that allows them to be temporarily cloned. These temporary
20/// clones can be used with `unblock` below which requires callbacks with static lifetime.  This is
21/// similar to Arc<T>, except that whilst there are no clones, there is no memory overhead, and
22/// there's no performance overhead to use them just as you would without the wrapper, except for a
23/// small overhead when they are dropped. The wrapper ensures that the handle is only dropped when
24/// there are no references.
25pub struct TempClonable<T: fidl::AsHandleRef>(ManuallyDrop<T>);
26
27impl<T: fidl::AsHandleRef> TempClonable<T> {
28    /// Returns a new handle that can be temporarily cloned.
29    pub fn new(handle: T) -> Self {
30        Self(ManuallyDrop::new(handle))
31    }
32}
33
34impl<T: fidl::AsHandleRef> Deref for TempClonable<T> {
35    type Target = T;
36
37    fn deref(&self) -> &T {
38        &self.0
39    }
40}
41
42impl<T: fidl::AsHandleRef> TempClonable<T> {
43    /// Creates a temporary clone of the handle. The clone should only exist temporarily.
44    ///
45    /// # Panics
46    ///
47    /// Panics if the handle is invalid.
48    pub fn temp_clone(&self) -> TempClone<T> {
49        assert!(!self.as_handle_ref().is_invalid());
50        let mut clones = clones().lock();
51        let raw_handle = self.0.as_handle_ref().raw_handle();
52        TempClone {
53            handle: match clones.entry(raw_handle) {
54                Entry::Occupied(mut o) => {
55                    if let Some(clone) = o.get().upgrade() {
56                        clone
57                    } else {
58                        // The last strong reference was dropped but the entry hasn't been removed
59                        // yet. This must be racing with `TempHandle::drop`. Replace the
60                        // `TempHandle`.
61                        let clone =
62                            Arc::new(TempHandle { raw_handle, tombstone: UnsafeCell::new(false) });
63                        *o.get_mut() = Arc::downgrade(&clone);
64                        clone
65                    }
66                }
67                Entry::Vacant(v) => {
68                    let clone =
69                        Arc::new(TempHandle { raw_handle, tombstone: UnsafeCell::new(false) });
70                    v.insert(Arc::downgrade(&clone));
71                    clone
72                }
73            },
74            marker: PhantomData,
75        }
76    }
77}
78
79impl<T: fidl::AsHandleRef> Drop for TempClonable<T> {
80    fn drop(&mut self) {
81        if let Some(handle) =
82            clones().lock().remove(&self.0.as_handle_ref().raw_handle()).and_then(|c| c.upgrade())
83        {
84            // There are still some temporary clones alive, so mark the handle with a tombstone.
85
86            // SAFETY: This is the only unsafe place where we access `tombstone`. We're are holding
87            // the clones lock which ensures no other thread is concurrently accessing it, but it
88            // wouldn't normally happen anyway because it would mean there were multiple
89            // TempClonable instances wrapping the same handle, which shouldn't happen.
90            unsafe { *handle.tombstone.get() = true };
91            return;
92        }
93
94        // SAFETY: There are no temporary clones, so we can drop the handle now. No more clones can
95        // be made and it should be clear we meet the safety requirements of ManuallyDrop.
96        unsafe { ManuallyDrop::drop(&mut self.0) }
97    }
98}
99
100type Clones = Mutex<HashMap<zx_handle_t, Weak<TempHandle>>>;
101
102/// Returns the global instance which keeps track of temporary clones.
103fn clones() -> &'static Clones {
104    static CLONES: OnceLock<Clones> = OnceLock::new();
105    CLONES.get_or_init(|| Mutex::new(HashMap::new()))
106}
107
108pub struct TempClone<T> {
109    handle: Arc<TempHandle>,
110    marker: PhantomData<T>,
111}
112
113impl<T> Deref for TempClone<T> {
114    type Target = T;
115
116    fn deref(&self) -> &T {
117        // SAFETY: T is repr(transparent) and stores zx_handle_t.
118        unsafe { std::mem::transmute::<&zx_handle_t, &T>(&self.handle.raw_handle) }
119    }
120}
121
122struct TempHandle {
123    raw_handle: zx_handle_t,
124    tombstone: UnsafeCell<bool>,
125}
126
127unsafe impl Send for TempHandle {}
128unsafe impl Sync for TempHandle {}
129
130impl Drop for TempHandle {
131    fn drop(&mut self) {
132        if *self.tombstone.get_mut() {
133            // SAFETY: The primary handle has been dropped and it is our job to clean up the
134            // handle. There are no memory safety issues here.
135            unsafe { fidl::NullableHandle::from_raw(self.raw_handle) };
136        } else {
137            if let Entry::Occupied(o) = clones().lock().entry(self.raw_handle) {
138                // There's a small window where another TempHandle could have been inserted, so
139                // before removing this entry, check for a match.
140                if std::ptr::eq(o.get().as_ptr(), self) {
141                    o.remove_entry();
142                }
143            }
144        }
145    }
146}
147
148/// This is similar to fuchsia-async's unblock except that it used a fixed size thread pool which
149/// has the advantage of not making traces difficult to decipher because of many threads being
150/// spawned.
151pub async fn unblock<T: 'static + Send>(f: impl FnOnce() -> T + Send + 'static) -> T {
152    const NUM_THREADS: u8 = 2;
153
154    struct State {
155        queue: Mutex<VecDeque<Box<dyn FnOnce() + Send + 'static>>>,
156        cvar: Condvar,
157    }
158
159    static STATE: OnceLock<State> = OnceLock::new();
160
161    let mut start_threads = false;
162    let state = STATE.get_or_init(|| {
163        start_threads = true;
164        State { queue: Mutex::new(VecDeque::new()), cvar: Condvar::new() }
165    });
166
167    if start_threads {
168        for _ in 0..NUM_THREADS {
169            std::thread::spawn(|| {
170                loop {
171                    let item = {
172                        let mut queue = state.queue.lock();
173                        loop {
174                            if let Some(item) = queue.pop_front() {
175                                break item;
176                            }
177                            state.cvar.wait(&mut queue);
178                        }
179                    };
180                    item();
181                }
182            });
183        }
184    }
185
186    let (tx, rx) = futures::channel::oneshot::channel();
187    state.queue.lock().push_back(Box::new(move || {
188        let _ = tx.send(f());
189    }));
190    state.cvar.notify_one();
191
192    rx.await.unwrap()
193}
194
195#[cfg(target_os = "fuchsia")]
196#[cfg(test)]
197mod tests {
198    use super::{TempClonable, clones};
199
200    use std::sync::Arc;
201
202    #[test]
203    fn test_temp_clone() {
204        let parent_vmo = zx::Vmo::create(100).expect("create failed");
205
206        {
207            let temp_clone = {
208                let vmo = TempClonable::new(
209                    parent_vmo
210                        .create_child(zx::VmoChildOptions::REFERENCE, 0, 0)
211                        .expect("create_child failed"),
212                );
213
214                vmo.write(b"foo", 0).expect("write failed");
215                {
216                    // Create and read from a temporary clone.
217                    let temp_clone2 = vmo.temp_clone();
218                    assert_eq!(
219                        &temp_clone2.read_to_vec::<u8>(0, 3).expect("read_to_vec failed"),
220                        b"foo"
221                    );
222                }
223
224                // We should still be able to read from the primary handle.
225                assert_eq!(&vmo.read_to_vec::<u8>(0, 3).expect("read_to_vec failed"), b"foo");
226
227                // Create another vmo which should get cleaned up when the primary handle is
228                // dropped.
229                let vmo2 = TempClonable::new(
230                    parent_vmo
231                        .create_child(zx::VmoChildOptions::REFERENCE, 0, 0)
232                        .expect("create_child failed"),
233                );
234                // Create and immediately drop a temporary clone.
235                vmo2.temp_clone();
236
237                // Take another clone that will get dropped after we take the clone below.
238                let _clone1 = vmo.temp_clone();
239
240                // And return another clone.
241                vmo.temp_clone()
242            };
243
244            // The primary handle has been dropped, but we should still be able to
245            // read via temp_clone.
246            assert_eq!(&temp_clone.read_to_vec::<u8>(0, 3).expect("read_to_vec failed"), b"foo");
247        }
248
249        // Make sure that all the VMOs got properly cleaned up.
250        parent_vmo
251            .wait_one(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::INFINITE)
252            .expect("wait for zero children failed");
253        assert_eq!(parent_vmo.info().expect("info failed").num_children, 0);
254        assert!(clones().lock().is_empty());
255    }
256
257    #[test]
258    fn test_race() {
259        let parent_vmo = zx::Vmo::create(100).expect("create failed");
260
261        {
262            let vmo = Arc::new(TempClonable::new(
263                parent_vmo
264                    .create_child(zx::VmoChildOptions::REFERENCE, 0, 0)
265                    .expect("create_child failed"),
266            ));
267            vmo.write(b"foo", 0).expect("write failed");
268
269            let vmo_clone = vmo.clone();
270
271            let t1 = std::thread::spawn(move || {
272                for _ in 0..1000 {
273                    assert_eq!(
274                        &vmo.temp_clone().read_to_vec::<u8>(0, 3).expect("read_to_vec failed"),
275                        b"foo"
276                    );
277                }
278            });
279
280            let t2 = std::thread::spawn(move || {
281                for _ in 0..1000 {
282                    assert_eq!(
283                        &vmo_clone
284                            .temp_clone()
285                            .read_to_vec::<u8>(0, 3)
286                            .expect("read_to_vec failed"),
287                        b"foo"
288                    );
289                }
290            });
291
292            let _ = t1.join();
293            let _ = t2.join();
294        }
295
296        // Make sure that all the VMOs got properly cleaned up.
297        parent_vmo
298            .wait_one(zx::Signals::VMO_ZERO_CHILDREN, zx::MonotonicInstant::INFINITE)
299            .expect("wait for zero children failed");
300        assert_eq!(parent_vmo.info().expect("info failed").num_children, 0);
301        assert!(clones().lock().is_empty());
302    }
303}