blob: 40772426f8932eac0995ac7892c1d584d80649c4 [file] [log] [blame]
// 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.
use {
anyhow::Error, async_trait::async_trait, clonable_error::ClonableError,
fidl::endpoints::ServerEnd, fidl_fuchsia_component as fcomponent,
fidl_fuchsia_component_runner as fcrunner, fuchsia_async as fasync, fuchsia_zircon as zx,
futures::stream::TryStreamExt, thiserror::Error,
};
/// Executes a component instance.
/// TODO: The runner should return a trait object to allow the component instance to be stopped,
/// binding to services, and observing abnormal termination. In other words, a wrapper that
/// encapsulates fcrunner::ComponentController FIDL interfacing concerns.
/// TODO: Consider defining an internal representation for `fcrunner::ComponentStartInfo` so as to
/// further isolate the `Model` from FIDL interfacting concerns.
#[async_trait]
pub trait Runner: Sync + Send {
#[must_use]
async fn start(
&self,
start_info: fcrunner::ComponentStartInfo,
server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
);
}
/// Errors produced by `Runner`.
#[derive(Debug, Error, Clone)]
pub enum RunnerError {
#[error("invalid arguments provided for component with url \"{}\": {}", url, err)]
InvalidArgs {
url: String,
#[source]
err: ClonableError,
},
#[error("unable to load component with url \"{}\": {}", url, err)]
ComponentLoadError {
url: String,
#[source]
err: ClonableError,
},
#[error("runner failed to launch component with url \"{}\": {}", url, err)]
ComponentLaunchError {
url: String,
#[source]
err: ClonableError,
},
#[error("failed to populate the runtime directory: {}", err)]
ComponentRuntimeDirectoryError {
#[source]
err: ClonableError,
},
#[error("failed to connect to the runner: {}", err)]
RunnerConnectionError {
#[source]
err: ClonableError,
},
#[error("remote runners unsupported")]
Unsupported,
}
impl RunnerError {
pub fn invalid_args(url: impl Into<String>, err: impl Into<Error>) -> RunnerError {
RunnerError::InvalidArgs { url: url.into(), err: err.into().into() }
}
pub fn component_load_error(url: impl Into<String>, err: impl Into<Error>) -> RunnerError {
RunnerError::ComponentLoadError { url: url.into(), err: err.into().into() }
}
pub fn component_launch_error(url: impl Into<String>, err: impl Into<Error>) -> RunnerError {
RunnerError::ComponentLaunchError { url: url.into(), err: err.into().into() }
}
pub fn component_runtime_directory_error(err: impl Into<Error>) -> RunnerError {
RunnerError::ComponentRuntimeDirectoryError { err: err.into().into() }
}
pub fn runner_connection_error(err: impl Into<Error>) -> RunnerError {
RunnerError::RunnerConnectionError { err: err.into().into() }
}
/// Convert this error into its approximate `fuchsia.component.Error` equivalent.
pub fn as_fidl_error(&self) -> fcomponent::Error {
match self {
RunnerError::InvalidArgs { .. } => fcomponent::Error::InvalidArguments,
RunnerError::ComponentLoadError { .. } => fcomponent::Error::InstanceCannotStart,
RunnerError::ComponentLaunchError { .. } => fcomponent::Error::InstanceCannotStart,
RunnerError::ComponentRuntimeDirectoryError { .. } => fcomponent::Error::Internal,
RunnerError::RunnerConnectionError { .. } => fcomponent::Error::Internal,
RunnerError::Unsupported { .. } => fcomponent::Error::Unsupported,
}
}
/// Convert this error into its approximate `zx::Status` equivalent.
pub fn as_zx_status(&self) -> zx::Status {
match self {
RunnerError::InvalidArgs { .. } => zx::Status::INVALID_ARGS,
RunnerError::ComponentLoadError { .. } => zx::Status::UNAVAILABLE,
RunnerError::ComponentLaunchError { .. } => zx::Status::UNAVAILABLE,
RunnerError::ComponentRuntimeDirectoryError { .. } => zx::Status::INTERNAL,
RunnerError::RunnerConnectionError { .. } => zx::Status::INTERNAL,
RunnerError::Unsupported { .. } => zx::Status::NOT_SUPPORTED,
}
}
}
/// A null runner for components without a runtime environment.
///
/// Such environments, even though they don't execute any code, can still be
/// used by other components to bind to, which in turn may trigger further
/// bindings to its children.
pub(super) struct NullRunner {}
#[async_trait]
impl Runner for NullRunner {
async fn start(
&self,
_start_info: fcrunner::ComponentStartInfo,
server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
) {
spawn_null_controller_server(
server_end
.into_stream()
.expect("NullRunner failed to convert server channel into request stream"),
);
}
}
/// Spawn an async execution context which takes ownership of `server_end`
/// and holds on to it until a stop or kill request is received.
fn spawn_null_controller_server(mut request_stream: fcrunner::ComponentControllerRequestStream) {
// Listen to the ComponentController server end and exit after the first
// one, as this is the contract we have implemented so far. Exiting will
// cause our handle to the channel to drop and close the channel.
fasync::Task::spawn(async move {
while let Ok(Some(request)) = request_stream.try_next().await {
match request {
fcrunner::ComponentControllerRequest::Stop { control_handle: c } => {
c.shutdown();
break;
}
fcrunner::ComponentControllerRequest::Kill { control_handle: c } => {
c.shutdown();
break;
}
}
}
})
.detach();
}
/// Wrapper for converting fcomponent::Error into the anyhow::Error type.
#[derive(Debug, Clone, Error)]
pub struct RemoteRunnerError(pub fcomponent::Error);
impl std::fmt::Display for RemoteRunnerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Use the Debug formatter for Display.
use std::fmt::Debug;
self.0.fmt(f)
}
}
impl std::convert::From<fcomponent::Error> for RemoteRunnerError {
fn from(error: fcomponent::Error) -> RemoteRunnerError {
RemoteRunnerError(error)
}
}
/// A runner provided by another component.
pub struct RemoteRunner {
client: fcrunner::ComponentRunnerProxy,
}
impl RemoteRunner {
pub fn new(client: fcrunner::ComponentRunnerProxy) -> RemoteRunner {
RemoteRunner { client }
}
}
#[async_trait]
impl Runner for RemoteRunner {
async fn start(
&self,
start_info: fcrunner::ComponentStartInfo,
server_end: ServerEnd<fcrunner::ComponentControllerMarker>,
) {
self.client.start(start_info, server_end).unwrap();
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::{self, Proxy};
#[fuchsia_async::run_singlethreaded(test)]
async fn test_null_runner() {
let null_runner = NullRunner {};
let (client, server) =
endpoints::create_endpoints::<fcrunner::ComponentControllerMarker>().unwrap();
null_runner
.start(
fcrunner::ComponentStartInfo {
resolved_url: None,
program: None,
ns: None,
outgoing_dir: None,
runtime_dir: None,
..fcrunner::ComponentStartInfo::EMPTY
},
server,
)
.await;
let proxy = client.into_proxy().expect("failed converting to proxy");
proxy.stop().expect("failed to send message to null runner");
proxy.on_closed().await.expect("failed waiting for channel to close");
}
}