| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! The "Action" concept represents an asynchronous activity on a component that should eventually |
| //! complete. |
| //! |
| //! Actions decouple the "what" of what needs to happen to a component from the "how". Several |
| //! client APIs may induce operations on a component's state that complete asynchronously. These |
| //! operations could depend on each other in various ways. |
| //! |
| //! A key property of actions is idempotency. If two equal actions are registered on a component, |
| //! the work for that action is performed only once. This means that two distinct call sites can |
| //! register the same action, and be guaranteed the work is not repeated. |
| //! |
| //! Here are a couple examples: |
| //! - A `Shutdown` FIDL call must shut down every component instance in the tree, in |
| //! dependency order. For this to happen every component must shut down, but not before its |
| //! downstream dependencies have shut down. |
| //! - A `Realm.DestroyChild` FIDL call returns right after a child component is destroyed. |
| //! However, in order to actually delete the child, a sequence of events must happen: |
| //! * All instances in the component must be shut down (see above) |
| //! * The component instance's persistent storage must be erased, if any. |
| //! * The component's parent must remove it as a child. |
| //! |
| //! Note the interdependencies here -- destroying a component also requires shutdown, for example. |
| //! |
| //! These processes could be implemented through a chain of futures in the vicinity of the API |
| //! call. However, this doesn't scale well, because it requires distributed state handling and is |
| //! prone to races. Actions solve this problem by allowing client code to just specify the actions |
| //! that need to eventually be fulfilled. The actual business logic to perform the actions can be |
| //! implemented by the component itself in a coordinated manner. |
| //! |
| //! `DestroyChild()` is an example of how this can work. For simplicity, suppose it's called on a |
| //! component with no children of its own. This might cause a chain of events like the following: |
| //! |
| //! - Before it returns, the `DestroyChild` FIDL handler registers the `DeleteChild` action on the |
| //! parent component for child being destroyed. |
| //! - This results in a call to `Action::handle` for the component. In response to |
| //! `DestroyChild`, `Action::handle()` spawns a future that sets a `Destroy` action on the child. |
| //! Note that `Action::handle()` is not async, it always spawns any work that might block |
| //! in a future. |
| //! - `Action::handle()` is called on the child. In response to `Destroy`, it sets a `Shutdown` |
| //! action on itself (the component instance must be stopped before it is destroyed). |
| //! - `Action::handle()` is called on the child again, in response to `Shutdown`. It turns out the |
| //! instance is still running, so the `Shutdown` future tells the instance to stop. When this |
| //! completes, the `Shutdown` action is finished. |
| //! - The future that was spawned for `Destroy` is notified that `Shutdown` completes, so it cleans |
| //! up the instance's resources and finishes the `Destroy` action. |
| //! - When the work for `Destroy` completes, the future spawned for `DestroyChild` deletes the |
| //! child and marks `DestroyChild` finished, which will notify the client that the action is |
| //! complete. |
| |
| mod destroy; |
| mod discover; |
| pub mod resolve; |
| mod set; |
| pub mod shutdown; |
| pub mod start; |
| mod stop; |
| mod unresolve; |
| |
| // Re-export the actions |
| pub use { |
| destroy::DestroyAction, discover::DiscoverAction, resolve::ResolveAction, |
| shutdown::ShutdownAction, shutdown::ShutdownType, start::StartAction, stop::StopAction, |
| unresolve::UnresolveAction, |
| }; |
| |
| use { |
| crate::model::component::{ComponentInstance, WeakComponentInstance}, |
| async_trait::async_trait, |
| cm_util::AbortHandle, |
| errors::ActionError, |
| futures::{ |
| channel::oneshot, |
| future::{BoxFuture, FutureExt, Shared}, |
| task::{Context, Poll}, |
| Future, |
| }, |
| std::fmt::Debug, |
| std::hash::Hash, |
| std::pin::Pin, |
| std::sync::{Arc, Mutex}, |
| }; |
| |
| /// A action on a component that must eventually be fulfilled. |
| #[async_trait] |
| pub trait Action: Send + Sync + 'static { |
| /// Run the action. |
| async fn handle(self, component: Arc<ComponentInstance>) -> Result<(), ActionError>; |
| |
| /// `key` identifies the action. |
| fn key(&self) -> ActionKey; |
| |
| /// If the action supports cooperative cancellation, return a handle for this purpose. |
| /// |
| /// The action may monitor the handle and bail early when it is safe to do so. |
| fn abort_handle(&self) -> Option<AbortHandle> { |
| None |
| } |
| } |
| |
| /// A key that uniquely identifies an action. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
| pub enum ActionKey { |
| Discover, |
| Resolve, |
| Unresolve, |
| Start, |
| Stop, |
| Shutdown, |
| Destroy, |
| } |
| |
| /// A future bound to a particular action that completes when that action completes. |
| /// |
| /// Cloning this type will not duplicate the action, but generate another future that waits on the |
| /// same action. |
| #[derive(Debug, Clone)] |
| pub struct ActionNotifier { |
| fut: Shared<BoxFuture<'static, Result<(), ActionError>>>, |
| } |
| |
| impl ActionNotifier { |
| /// Instantiate an `ActionNotifier` that will complete when a message is received on |
| /// `receiver`. |
| pub fn new(receiver: oneshot::Receiver<Result<(), ActionError>>) -> Self { |
| Self { |
| fut: receiver |
| .map(|res| res.expect("ActionNotifier sender was unexpectedly closed")) |
| .boxed() |
| .shared(), |
| } |
| } |
| |
| /// Returns the number of references that exist to the shared future in this notifier. Returns |
| /// None if the future has completed. |
| #[cfg(test)] |
| pub fn get_reference_count(&self) -> Option<usize> { |
| self.fut.strong_count() |
| } |
| } |
| |
| impl Future for ActionNotifier { |
| type Output = Result<(), ActionError>; |
| fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
| let fut = Pin::new(&mut self.fut); |
| fut.poll(cx) |
| } |
| } |
| |
| pub struct ActionsManager { |
| action_set: Arc<Mutex<set::ActionSet>>, |
| component: WeakComponentInstance, |
| } |
| |
| impl ActionsManager { |
| pub fn new() -> Self { |
| Self { |
| action_set: Arc::new(Mutex::new(set::ActionSet::new())), |
| component: WeakComponentInstance::invalid(), |
| } |
| } |
| |
| /// Sets the component reference against which actions on this component will be run. If an |
| /// action is registered on this component before this function is called, it will panic. |
| pub fn set_component_reference(&mut self, component: WeakComponentInstance) { |
| self.component = component; |
| } |
| |
| pub async fn contains(&self, key: ActionKey) -> bool { |
| self.action_set.lock().unwrap().contains(key) |
| } |
| |
| /// Registers an action in the set, returning when the action is finished (which may represent |
| /// a task that's already running for this action). |
| pub async fn register<A>( |
| component: Arc<ComponentInstance>, |
| action: A, |
| ) -> Result<(), ActionError> |
| where |
| A: Action, |
| { |
| let rx = { |
| let mut actions = component.lock_actions().await; |
| actions.register_no_wait(action).await |
| }; |
| rx.await |
| } |
| |
| /// Registers an action in the set, but does not wait for it to complete, instead returning a |
| /// future that can be used to wait on the task. This function is a no-op if the task is |
| /// already registered. |
| /// |
| /// REQUIRES: `self` is the `ActionSet` contained in `component`. |
| pub async fn register_no_wait<A>(&mut self, action: A) -> ActionNotifier |
| where |
| A: Action, |
| { |
| let component = |
| self.component.upgrade().expect("tried to register action on nonexistent component"); |
| set::ActionSet::register_no_wait(self.action_set.clone(), &component, action) |
| } |
| |
| /// Returns a future that waits for the given action to complete, if one exists. |
| pub async fn wait(&self, action_key: ActionKey) -> Option<ActionNotifier> { |
| self.action_set.lock().unwrap().wait(action_key) |
| } |
| } |
| |
| #[cfg(test)] |
| pub(crate) mod test_utils { |
| use { |
| super::*, |
| crate::model::component::instance::InstanceState, |
| moniker::{ChildName, MonikerBase}, |
| routing::component_instance::ComponentInstanceInterface, |
| }; |
| |
| /// A mock action, which can be scheduled on a component. The mock action will wait until it |
| /// receives a message over a given oneshot, and then use the received value as the return |
| /// value for the action. |
| /// |
| /// Note that the action coordinator will panic if an action returns `Ok(())` and does not |
| /// change the component's state into the expected target state of that action key, and mock |
| /// actions are incapable of changing the component's state. |
| pub struct MockAction { |
| key: ActionKey, |
| completion_waiter: oneshot::Receiver<Result<(), ActionError>>, |
| } |
| |
| #[async_trait] |
| impl Action for MockAction { |
| async fn handle(self, _component: Arc<ComponentInstance>) -> Result<(), ActionError> { |
| match self.completion_waiter.await { |
| // If we successfully received a value, return it. |
| Ok(result) => result, |
| |
| // If we fail to receive a value, then we never will. Let's leave this action as |
| // pending indefinitely. |
| Err(_) => std::future::pending().await, |
| } |
| } |
| |
| fn key(&self) -> ActionKey { |
| self.key |
| } |
| |
| fn abort_handle(&self) -> Option<AbortHandle> { |
| None |
| } |
| } |
| |
| impl MockAction { |
| pub fn new(key: ActionKey) -> (oneshot::Sender<Result<(), ActionError>>, Self) { |
| let (sender, completion_waiter) = oneshot::channel(); |
| (sender, Self { key, completion_waiter }) |
| } |
| } |
| |
| /// Verifies that a child component is deleted by checking its InstanceState and verifying that |
| /// it does not exist in the InstanceState of its parent. Assumes the parent is not destroyed |
| /// yet. |
| pub async fn is_child_deleted(parent: &ComponentInstance, child: &ComponentInstance) -> bool { |
| let instanced_moniker = |
| child.instanced_moniker().leaf().expect("Root component cannot be destroyed"); |
| |
| // Verify the parent-child relationship |
| assert_eq!( |
| parent.instanced_moniker().child(instanced_moniker.clone()), |
| *child.instanced_moniker() |
| ); |
| |
| let parent_state = parent.lock_state().await; |
| let parent_resolved_state = parent_state.get_resolved_state().expect("not resolved"); |
| |
| let child_state = child.lock_state().await; |
| let found_child = parent_resolved_state.get_child(child.child_moniker().unwrap()); |
| |
| found_child.is_none() && matches!(*child_state, InstanceState::Destroyed) |
| } |
| |
| pub async fn is_stopped(component: &ComponentInstance, moniker: &ChildName) -> bool { |
| if let Some(resolved_state) = component.lock_state().await.get_resolved_state() { |
| if let Some(child) = resolved_state.get_child(moniker) { |
| return !child.is_started().await; |
| } |
| } |
| false |
| } |
| |
| pub async fn is_destroyed(component: &ComponentInstance) -> bool { |
| let state = component.lock_state().await; |
| matches!(*state, InstanceState::Destroyed) |
| } |
| |
| pub async fn is_resolved(component: &ComponentInstance) -> bool { |
| component.lock_state().await.get_resolved_state().is_some() |
| } |
| |
| pub async fn is_discovered(component: &ComponentInstance) -> bool { |
| let state = component.lock_state().await; |
| matches!(*state, InstanceState::Unresolved(_)) |
| } |
| |
| pub async fn is_shutdown(component: &ComponentInstance) -> bool { |
| let state = component.lock_state().await; |
| matches!(*state, InstanceState::Shutdown(_, _)) |
| } |
| } |