1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Copyright 2021 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.

pub mod component_controller;

use {
    async_trait::async_trait, fidl::endpoints::ServerEnd, fidl::prelude::*,
    fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_runner as fcrunner,
    fuchsia_async as fasync, futures::stream::StreamExt, thiserror::Error, tracing::warn,
};

/// 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>,
    );
}

/// 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 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 {
        if let Some(Ok(request)) = request_stream.next().await {
            match request {
                fcrunner::ComponentControllerRequest::Stop { control_handle }
                | fcrunner::ComponentControllerRequest::Kill { control_handle } => {
                    control_handle.shutdown();
                }
            }
        }
    })
    .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>,
    ) {
        let resolved_url = runner::get_resolved_url(&start_info).unwrap_or(String::new());
        if let Err(e) = self.client.start(start_info, server_end) {
            warn!(url=%resolved_url, error=%e, "Failed to call runner to start component");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use fidl::endpoints::{self, Proxy};

    #[fuchsia::test]
    async fn test_null_runner() {
        let null_runner = NullRunner {};
        let (client, server) = endpoints::create_endpoints::<fcrunner::ComponentControllerMarker>();
        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");
    }
}