Skip to main content

vfs/
execution_scope.rs

1// Copyright 2019 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//! Values of this type represent "execution scopes" used by the library to give fine grained
6//! control of the lifetimes of the tasks associated with particular connections.  When a new
7//! connection is attached to a pseudo directory tree, an execution scope is provided.  This scope
8//! is then used to start any tasks related to this connection.  All connections opened as a result
9//! of operations on this first connection will also use the same scope, as well as any tasks
10//! related to those connections.
11//!
12//! This way, it is possible to control the lifetime of a group of connections.  All connections
13//! and their tasks can be shutdown by calling `shutdown` method on the scope that is hosting them.
14//! Scope will also shutdown all the tasks when it goes out of scope.
15//!
16//! Implementation wise, execution scope is just a proxy, that forwards all the tasks to an actual
17//! executor, provided as an instance of a [`futures::task::Spawn`] trait.
18
19use crate::token_registry::TokenRegistry;
20
21use fuchsia_async::{JoinHandle, Scope, ScopeHandle, SpawnableFuture};
22use fuchsia_sync::{MappedMutexGuard, Mutex, MutexGuard};
23use futures::Future;
24use futures::task::{self, Poll};
25use std::future::poll_fn;
26use std::pin::Pin;
27use std::sync::{Arc, Weak};
28use std::task::Context;
29
30#[cfg(target_os = "fuchsia")]
31use fuchsia_async::EHandle;
32
33pub use fuchsia_async::scope::ScopeActiveGuard as ActiveGuard;
34
35pub type SpawnError = task::SpawnError;
36
37/// An execution scope that is hosting tasks for a group of connections.  See the module level
38/// documentation for details.
39///
40/// Actual execution will be delegated to an "upstream" executor - something that implements
41/// [`futures::task::Spawn`].  In a sense, this is somewhat of an analog of a multithreaded capable
42/// [`futures::stream::FuturesUnordered`], but this some additional functionality specific to the
43/// vfs library.
44///
45/// Use [`ExecutionScope::new()`] or [`ExecutionScope::build()`] to construct new
46/// `ExecutionScope`es.
47#[derive(Clone)]
48pub struct ExecutionScope {
49    executor: Arc<Executor>,
50
51    /// The FDomain client, which lets us communicate with the Fuchsia device
52    /// where the actual handles are.
53    #[cfg(feature = "fdomain")]
54    client: Arc<flex_client::Client>,
55}
56
57struct Executor {
58    token_registry: TokenRegistry,
59    scope: Mutex<Option<Scope>>,
60}
61
62impl ExecutionScope {
63    /// Constructs an execution scope.  Use [`ExecutionScope::build()`] if you want to specify
64    /// parameters.
65    pub fn new(#[cfg(feature = "fdomain")] client: Arc<flex_client::Client>) -> Self {
66        Self::build().new(
67            #[cfg(feature = "fdomain")]
68            client,
69        )
70    }
71
72    /// Return the domain for handle creation for operations in this execution scope.
73    #[cfg(feature = "fdomain")]
74    pub fn domain(&self) -> Arc<flex_client::Client> {
75        Arc::clone(&self.client)
76    }
77
78    /// Return the domain for handle creation for operations in this execution scope.
79    #[cfg(not(feature = "fdomain"))]
80    pub fn domain(&self) -> fidl::endpoints::ZirconClient {
81        fidl::endpoints::ZirconClient
82    }
83
84    /// Constructs a new execution scope builder, wrapping the specified executor and optionally
85    /// accepting additional parameters.  Run [`ExecutionScopeParams::new()`] to get an actual
86    /// [`ExecutionScope`] object.
87    pub fn build() -> ExecutionScopeParams {
88        ExecutionScopeParams::default()
89    }
90
91    pub fn as_weak(&self) -> WeakExecutionScope {
92        WeakExecutionScope {
93            executor: Arc::downgrade(&self.executor),
94            #[cfg(feature = "fdomain")]
95            client: Arc::downgrade(&self.client),
96        }
97    }
98
99    /// Sends a `task` to be executed in this execution scope.  This is very similar to
100    /// [`futures::task::Spawn::spawn_obj()`] with a minor difference that `self` reference is not
101    /// exclusive.
102    ///
103    /// If the task needs to prevent itself from being shutdown, then it should use the
104    /// `try_active_guard` function below.
105    ///
106    /// For the "vfs" library it is more convenient that this method allows non-exclusive
107    /// access.  And as the implementation is employing internal mutability there are no downsides.
108    /// This way `ExecutionScope` can actually also implement [`futures::task::Spawn`] - it just was
109    /// not necessary for now.
110    pub fn spawn(&self, task: impl Future<Output = ()> + Send + 'static) -> JoinHandle<()> {
111        self.executor.scope().spawn(task)
112    }
113
114    /// Sends a `task` to be executed in this execution scope on the current thread.
115    /// This is useful for spawning `!Send` tasks when running on a single-threaded executor.
116    ///
117    /// # Panics
118    ///
119    /// Panics if this execution scope is run on a multi-threaded executor (`SendExecutor`).
120    pub fn spawn_local(&self, task: impl Future<Output = ()> + 'static) -> JoinHandle<()> {
121        self.executor.scope().spawn_local(task)
122    }
123
124    /// Returns a task that can be spawned later.  The task can also be polled before spawning.
125    pub fn new_task(self, task: impl Future<Output = ()> + Send + 'static) -> Task {
126        Task(self.executor, SpawnableFuture::new(task))
127    }
128
129    pub fn token_registry(&self) -> &TokenRegistry {
130        &self.executor.token_registry
131    }
132
133    pub fn shutdown(&self) {
134        self.executor.shutdown();
135    }
136
137    /// Forcibly shut down the executor without respecting the active guards.
138    pub fn force_shutdown(&self) {
139        let _ = self.executor.scope().clone().abort();
140    }
141
142    /// Restores the executor so that it is no longer in the shut-down state.  Any tasks
143    /// that are still running will continue to run after calling this.
144    pub fn resurrect(&self) {
145        // After setting the scope to None, a new scope will be created the next time `spawn` is
146        // called.
147        *self.executor.scope.lock() = None;
148    }
149
150    /// Wait for all tasks to complete and for there to be no guards.
151    pub async fn wait(&self) {
152        let scope = self.executor.scope().clone();
153        scope.on_no_tasks_and_guards().await;
154    }
155
156    /// Prevents the executor from shutting down whilst the guard is held. Returns None if the
157    /// executor is shutting down.
158    pub fn try_active_guard(&self) -> Option<ActiveGuard> {
159        self.executor.scope().active_guard()
160    }
161}
162
163impl PartialEq for ExecutionScope {
164    fn eq(&self, other: &Self) -> bool {
165        Arc::as_ptr(&self.executor) == Arc::as_ptr(&other.executor)
166    }
167}
168
169impl Eq for ExecutionScope {}
170
171impl std::fmt::Debug for ExecutionScope {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        f.write_fmt(format_args!("ExecutionScope {:?}", Arc::as_ptr(&self.executor)))
174    }
175}
176
177#[derive(Default)]
178pub struct ExecutionScopeParams {
179    #[cfg(target_os = "fuchsia")]
180    async_executor: Option<EHandle>,
181}
182
183impl ExecutionScopeParams {
184    #[cfg(target_os = "fuchsia")]
185    pub fn executor(mut self, value: EHandle) -> Self {
186        assert!(self.async_executor.is_none(), "`executor` is already set");
187        self.async_executor = Some(value);
188        self
189    }
190
191    pub fn new(
192        self,
193        #[cfg(feature = "fdomain")] client: Arc<flex_client::Client>,
194    ) -> ExecutionScope {
195        ExecutionScope {
196            executor: Arc::new(Executor {
197                token_registry: TokenRegistry::new(),
198                #[cfg(target_os = "fuchsia")]
199                scope: self.async_executor.map_or_else(
200                    || Mutex::new(None),
201                    |e| Mutex::new(Some(e.global_scope().new_child())),
202                ),
203                #[cfg(not(target_os = "fuchsia"))]
204                scope: Mutex::new(None),
205            }),
206            #[cfg(feature = "fdomain")]
207            client,
208        }
209    }
210}
211
212/// Holds a weak reference to the internal `ExecutionScope`, and can spawn futures on it as long as
213/// the reference is still valid.
214#[derive(Clone)]
215pub struct WeakExecutionScope {
216    executor: Weak<Executor>,
217    #[cfg(feature = "fdomain")]
218    client: Weak<flex_client::Client>,
219}
220
221impl WeakExecutionScope {
222    /// Adds a task to the referenced [`ExecutionScope`]. The task is dropped if there are no more
223    /// strong references to the original task group.
224    pub fn spawn(&self, task: impl Future<Output = ()> + Send + 'static) {
225        let executor = self.executor.upgrade();
226        if let Some(executor) = executor {
227            _ = executor.scope().spawn(task)
228        }
229    }
230
231    /// Return the domain for handle creation for operations in this execution scope.
232    #[cfg(feature = "fdomain")]
233    pub fn domain(&self) -> Option<Arc<flex_client::Client>> {
234        self.client.upgrade()
235    }
236
237    #[cfg(not(feature = "fdomain"))]
238    pub fn domain(&self) -> Option<fidl::endpoints::ZirconClient> {
239        Some(fidl::endpoints::ZirconClient)
240    }
241}
242
243impl Executor {
244    fn scope(&self) -> MappedMutexGuard<'_, Scope> {
245        // We lazily initialize the executor rather than at construction time as there are currently
246        // a few tests that create the ExecutionScope before the async executor has been initialized
247        // (which means we cannot call EHandle::local()).
248        MutexGuard::map(self.scope.lock(), |s| {
249            s.get_or_insert_with(|| {
250                #[cfg(target_os = "fuchsia")]
251                return Scope::global().new_child();
252                #[cfg(not(target_os = "fuchsia"))]
253                return Scope::new();
254            })
255        })
256    }
257
258    fn shutdown(&self) {
259        if let Some(scope) = &*self.scope.lock() {
260            scope.wake_all_with_active_guard();
261            let _ = ScopeHandle::clone(&*scope).cancel();
262        }
263    }
264}
265
266impl Drop for Executor {
267    fn drop(&mut self) {
268        self.shutdown();
269        // We must detach the scope, because otherwise all the tasks will be aborted and the active
270        // guards will be ignored.
271        if let Some(scope) = self.scope.get_mut().take() {
272            scope.detach();
273        }
274    }
275}
276
277/// Yields to the executor, providing an opportunity for other futures to run.
278pub async fn yield_to_executor() {
279    let mut done = false;
280    poll_fn(|cx| {
281        if done {
282            Poll::Ready(())
283        } else {
284            done = true;
285            cx.waker().wake_by_ref();
286            Poll::Pending
287        }
288    })
289    .await;
290}
291
292pub struct Task(Arc<Executor>, SpawnableFuture<'static, ()>);
293
294impl Task {
295    /// Spawns the task on the scope.
296    pub fn spawn(self) {
297        self.0.scope().spawn(self.1);
298    }
299}
300
301impl Future for Task {
302    type Output = ();
303
304    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
305        Pin::new(&mut &mut self.1).poll(cx)
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::{ExecutionScope, yield_to_executor};
312
313    use fuchsia_async::{TestExecutor, Timer};
314    use futures::Future;
315    use futures::channel::oneshot;
316    use std::pin::pin;
317    use std::sync::Arc;
318    #[cfg(target_os = "fuchsia")]
319    use std::sync::atomic::{AtomicBool, Ordering};
320    #[cfg(target_os = "fuchsia")]
321    use std::task::Poll;
322    use std::time::Duration;
323
324    #[cfg(target_os = "fuchsia")]
325    fn run_test<GetTest, GetTestRes>(get_test: GetTest)
326    where
327        GetTest: FnOnce(ExecutionScope) -> GetTestRes,
328        GetTestRes: Future<Output = ()>,
329    {
330        let mut exec = TestExecutor::new();
331
332        #[cfg(feature = "fdomain")]
333        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
334        #[cfg(not(feature = "fdomain"))]
335        let scope = crate::execution_scope::ExecutionScope::new();
336
337        let test = get_test(scope);
338
339        assert_eq!(
340            exec.run_until_stalled(&mut pin!(test)),
341            Poll::Ready(()),
342            "Test did not complete"
343        );
344    }
345
346    #[cfg(not(target_os = "fuchsia"))]
347    fn run_test<GetTest, GetTestRes>(get_test: GetTest)
348    where
349        GetTest: FnOnce(ExecutionScope) -> GetTestRes,
350        GetTestRes: Future<Output = ()>,
351    {
352        use fuchsia_async::TimeoutExt;
353        let mut exec = TestExecutor::new();
354
355        #[cfg(feature = "fdomain")]
356        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
357        #[cfg(not(feature = "fdomain"))]
358        let scope = crate::execution_scope::ExecutionScope::new();
359
360        // This isn't a perfect equivalent to the target version, but Tokio
361        // doesn't have run_until_stalled and it sounds like it's
362        // architecturally impossible.
363        let test =
364            get_test(scope).on_stalled(Duration::from_secs(30), || panic!("Test did not complete"));
365
366        exec.run_singlethreaded(&mut pin!(test));
367    }
368
369    #[test]
370    fn simple() {
371        run_test(|scope| {
372            async move {
373                let (sender, receiver) = oneshot::channel();
374                let (counters, task) = mocks::ImmediateTask::new(sender);
375
376                scope.spawn(task);
377
378                // Make sure our task had a chance to execute.
379                receiver.await.unwrap();
380
381                assert_eq!(counters.drop_call(), 1);
382                assert_eq!(counters.poll_call(), 1);
383            }
384        });
385    }
386
387    #[test]
388    fn simple_drop() {
389        run_test(|scope| {
390            async move {
391                let (poll_sender, poll_receiver) = oneshot::channel();
392                let (processing_done_sender, processing_done_receiver) = oneshot::channel();
393                let (drop_sender, drop_receiver) = oneshot::channel();
394                let (counters, task) =
395                    mocks::ControlledTask::new(poll_sender, processing_done_receiver, drop_sender);
396
397                scope.spawn(task);
398
399                poll_receiver.await.unwrap();
400
401                processing_done_sender.send(()).unwrap();
402
403                scope.shutdown();
404
405                drop_receiver.await.unwrap();
406
407                // poll might be called one or two times depending on the order in which the
408                // executor decides to poll the two tasks (this one and the one we spawned).
409                let poll_count = counters.poll_call();
410                assert!(poll_count >= 1, "poll was not called");
411
412                assert_eq!(counters.drop_call(), 1);
413            }
414        });
415    }
416
417    #[test]
418    fn test_wait_waits_for_tasks_to_finish() {
419        let mut executor = TestExecutor::new();
420        #[cfg(feature = "fdomain")]
421        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
422        #[cfg(not(feature = "fdomain"))]
423        let scope = crate::execution_scope::ExecutionScope::new();
424        executor.run_singlethreaded(async {
425            let (poll_sender, poll_receiver) = oneshot::channel();
426            let (processing_done_sender, processing_done_receiver) = oneshot::channel();
427            let (drop_sender, _drop_receiver) = oneshot::channel();
428            let (_, task) =
429                mocks::ControlledTask::new(poll_sender, processing_done_receiver, drop_sender);
430
431            scope.spawn(task);
432
433            poll_receiver.await.unwrap();
434
435            // We test that wait is working correctly by concurrently waiting and telling the
436            // task to complete, and making sure that the order is correct.
437            let done = fuchsia_sync::Mutex::new(false);
438            futures::join!(
439                async {
440                    scope.wait().await;
441                    assert_eq!(*done.lock(), true);
442                },
443                async {
444                    // This is a Turing halting problem so the sleep is justified.
445                    Timer::new(Duration::from_millis(100)).await;
446                    *done.lock() = true;
447                    processing_done_sender.send(()).unwrap();
448                }
449            );
450        });
451    }
452
453    #[cfg(target_os = "fuchsia")]
454    #[fuchsia::test]
455    async fn test_shutdown_waits_for_channels() {
456        use fuchsia_async as fasync;
457
458        #[cfg(feature = "fdomain")]
459        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
460        #[cfg(not(feature = "fdomain"))]
461        let scope = crate::execution_scope::ExecutionScope::new();
462        let (rx, tx) = zx::Channel::create();
463        let received_msg = Arc::new(AtomicBool::new(false));
464        let (sender, receiver) = futures::channel::oneshot::channel();
465        {
466            let received_msg = received_msg.clone();
467            scope.spawn(async move {
468                let mut msg_buf = zx::MessageBuf::new();
469                msg_buf.ensure_capacity_bytes(64);
470                let _ = sender.send(());
471                let _ = fasync::Channel::from_channel(rx).recv_msg(&mut msg_buf).await;
472                received_msg.store(true, Ordering::Relaxed);
473            });
474        }
475        // Wait until the spawned future has been polled once.
476        let _ = receiver.await;
477
478        tx.write(b"hello", &mut []).expect("write failed");
479        scope.shutdown();
480        scope.wait().await;
481        assert!(received_msg.load(Ordering::Relaxed));
482    }
483
484    #[fuchsia::test]
485    async fn test_force_shutdown() {
486        #[cfg(feature = "fdomain")]
487        let scope = crate::execution_scope::ExecutionScope::new(flex_local::local_client_empty());
488        #[cfg(not(feature = "fdomain"))]
489        let scope = crate::execution_scope::ExecutionScope::new();
490        let scope_clone = scope.clone();
491        let ref_count = Arc::new(());
492        let ref_count_clone = ref_count.clone();
493
494        // Spawn a task that holds a reference.  When the task is dropped the reference will get
495        // dropped with it.
496        scope.spawn(async move {
497            let _ref_count_clone = ref_count_clone;
498
499            // Hold an active guard so that only a forced shutdown will work.
500            let _guard = scope_clone.try_active_guard().unwrap();
501
502            let _: () = std::future::pending().await;
503        });
504
505        scope.force_shutdown();
506        scope.wait().await;
507
508        // The task should have been dropped leaving us with the only reference.
509        assert_eq!(Arc::strong_count(&ref_count), 1);
510
511        // Test resurrection...
512        scope.resurrect();
513
514        let ref_count_clone = ref_count.clone();
515        scope.spawn(async move {
516            // Yield so that if the executor is in the shutdown state, it will kill this task.
517            yield_to_executor().await;
518
519            // Take another reference count so that we can check we got here below.
520            let _ref_count = ref_count_clone.clone();
521
522            let _: () = std::future::pending().await;
523        });
524
525        while Arc::strong_count(&ref_count) != 3 {
526            yield_to_executor().await;
527        }
528
529        // Yield some more just to be sure the task isn't killed.
530        for _ in 0..5 {
531            yield_to_executor().await;
532            assert_eq!(Arc::strong_count(&ref_count), 3);
533        }
534    }
535
536    mod mocks {
537        use futures::Future;
538        use futures::channel::oneshot;
539        use futures::task::{Context, Poll};
540        use std::pin::Pin;
541        use std::sync::Arc;
542        use std::sync::atomic::{AtomicUsize, Ordering};
543
544        pub(super) struct TaskCounters {
545            poll_call_count: Arc<AtomicUsize>,
546            drop_call_count: Arc<AtomicUsize>,
547        }
548
549        impl TaskCounters {
550            fn new() -> (Arc<AtomicUsize>, Arc<AtomicUsize>, Self) {
551                let poll_call_count = Arc::new(AtomicUsize::new(0));
552                let drop_call_count = Arc::new(AtomicUsize::new(0));
553
554                (
555                    poll_call_count.clone(),
556                    drop_call_count.clone(),
557                    Self { poll_call_count, drop_call_count },
558                )
559            }
560
561            pub(super) fn poll_call(&self) -> usize {
562                self.poll_call_count.load(Ordering::Relaxed)
563            }
564
565            pub(super) fn drop_call(&self) -> usize {
566                self.drop_call_count.load(Ordering::Relaxed)
567            }
568        }
569
570        pub(super) struct ImmediateTask {
571            poll_call_count: Arc<AtomicUsize>,
572            drop_call_count: Arc<AtomicUsize>,
573            done_sender: Option<oneshot::Sender<()>>,
574        }
575
576        impl ImmediateTask {
577            pub(super) fn new(done_sender: oneshot::Sender<()>) -> (TaskCounters, Self) {
578                let (poll_call_count, drop_call_count, counters) = TaskCounters::new();
579                (
580                    counters,
581                    Self { poll_call_count, drop_call_count, done_sender: Some(done_sender) },
582                )
583            }
584        }
585
586        impl Future for ImmediateTask {
587            type Output = ();
588
589            fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
590                self.poll_call_count.fetch_add(1, Ordering::Relaxed);
591
592                if let Some(sender) = self.done_sender.take() {
593                    sender.send(()).unwrap();
594                }
595
596                Poll::Ready(())
597            }
598        }
599
600        impl Drop for ImmediateTask {
601            fn drop(&mut self) {
602                self.drop_call_count.fetch_add(1, Ordering::Relaxed);
603            }
604        }
605
606        impl Unpin for ImmediateTask {}
607
608        pub(super) struct ControlledTask {
609            poll_call_count: Arc<AtomicUsize>,
610            drop_call_count: Arc<AtomicUsize>,
611
612            drop_sender: Option<oneshot::Sender<()>>,
613            future: Pin<Box<dyn Future<Output = ()> + Send>>,
614        }
615
616        impl ControlledTask {
617            pub(super) fn new(
618                poll_sender: oneshot::Sender<()>,
619                processing_complete: oneshot::Receiver<()>,
620                drop_sender: oneshot::Sender<()>,
621            ) -> (TaskCounters, Self) {
622                let (poll_call_count, drop_call_count, counters) = TaskCounters::new();
623                (
624                    counters,
625                    Self {
626                        poll_call_count,
627                        drop_call_count,
628                        drop_sender: Some(drop_sender),
629                        future: Box::pin(async move {
630                            poll_sender.send(()).unwrap();
631                            processing_complete.await.unwrap();
632                        }),
633                    },
634                )
635            }
636        }
637
638        impl Future for ControlledTask {
639            type Output = ();
640
641            fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
642                self.poll_call_count.fetch_add(1, Ordering::Relaxed);
643                self.future.as_mut().poll(cx)
644            }
645        }
646
647        impl Drop for ControlledTask {
648            fn drop(&mut self) {
649                self.drop_call_count.fetch_add(1, Ordering::Relaxed);
650                self.drop_sender.take().unwrap().send(()).unwrap();
651            }
652        }
653    }
654}