连接组件

本文档演示了如何使用 capability 将组件连接在一起 以及供父级组件管理其子级的其他工具。

概念

在继续阅读本指南之前,您应该了解以下概念:

* 组件框架会组建 命名空间 针对 组件 组件声明 那个 描述 功能 该组件需要 函数。该组件向其他组件提供的功能会组合到 一 公开目录 ,了解所有最新动态。 * 每个组件都会收到一个服务器端的句柄, Directory 频道称为 传出目录 ,了解所有最新动态。组件的程序 使用户可通过该目录检测到其提供的任何功能。 * 在运行时, 组件实例树 它描述了 组件实例 ,了解所有最新动态。组件实例 树上的功能路由统称为 用作 组件拓扑 ,了解所有最新动态。 * 父组件在其自身中以静态方式声明子组件。 组件清单 也可以使用 组件集合 ,了解所有最新动态。集合是 用于存储可能在运行时创建和销毁的动态子项的容器 使用 fuchsia.component.Realm 框架协议。

如需详细了解这些概念,请参阅大区功能

通过路由连接功能

组件通过功能相互交互。提供的功能 您需要在该组件的清单中声明某个组件 可供其他人使用,通过 exposeoffer 路由到父级/子组件 声明。使用该功能的其他组件也需要声明 在其清单中使用相应字段为了让其程序能够使用 在运行时,必须将所使用的功能路由到该组件,即 由家长提供或由孩子提供。

功能路由是指由组件执行的递归过程 通过跟踪各个路由,以标识服务组件, 请参阅清单文件说明的步骤在以下情况下,系统会启动功能路由:

  • 组件的程序会在其 命名空间 ,了解所有最新动态。
  • 组件的程序会在另一个组件的 公开目录 ,了解所有最新动态。
  • 开发者调用 ffx component route
  • 启动依赖于其备份的解析器或运行程序的组件 解析器或运行程序功能尚未路由。

路由是延迟执行,而功能可以配置为 由父级或子级提供(直接或通过进一步的 委托),则调用 路由操作。实际上,这意味着 除非 。

提供 capability 实现

提供功能的组件必须在其 使用 capabilities 声明的组件清单。

请参阅以下示例,其中声明了 FIDL 协议功能

{
    include: [
        "inspect/client.shard.cml",
        "syslog/client.shard.cml",
    ],

    // Information about the program to run.
    program: {
        // Use the built-in ELF runner.
        runner: "elf",

        // The binary to run for this component.
        binary: "bin/echo_server_rust",
    },

    // Capabilities provided by this component.
    capabilities: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
    expose: [
        {
            protocol: "fidl.examples.routing.echo.Echo",
            from: "self",
        },
    ],
}

在运行时,服务器组件通过 使用 fuchsia.io 在其传出目录中提供 协议。生成的 FIDL 绑定会封装此句柄并启用提供程序 要开始接收传入请求,请执行以下操作:

Rust

// Wrap protocol requests being served.
enum IncomingRequest {
    Echo(EchoRequestStream),
}

#[fuchsia::main(logging = false)]
async fn main() -> Result<(), anyhow::Error> {
    let mut service_fs = ServiceFs::new_local();

    // Initialize inspect
    component::health().set_starting_up();
    let _inspect_server_task = inspect_runtime::publish(
        component::inspector(),
        inspect_runtime::PublishOptions::default(),
    );

    // Serve the Echo protocol
    service_fs.dir("svc").add_fidl_service(IncomingRequest::Echo);
    service_fs.take_and_serve_directory_handle().context("failed to serve outgoing namespace")?;

    // Component is serving and ready to handle incoming requests
    component::health().set_ok();

    // Attach request handler for incoming requests
    service_fs
        .for_each_concurrent(None, |request: IncomingRequest| async move {
            match request {
                IncomingRequest::Echo(stream) => handle_echo_request(stream).await,
            }
        })
        .await;

    Ok(())
}

// Handler for incoming service requests
async fn handle_echo_request(mut stream: EchoRequestStream) {
    while let Some(event) = stream.try_next().await.expect("failed to serve echo service") {
        let EchoRequest::EchoString { value, responder } = event;
        responder.send(value.as_ref().map(|s| &**s)).expect("failed to send echo response");
    }
}

C++

```cpp // 传入服务请求的处理程序 class EchoImplementation : public fidl::examples::routing::echo::Echo { 公开 void EchoString(fidl::StringPtr value, EchoStringCallback callback) 替换 { callback(value);} fidl::examples::routing::echo::Echo_EventSender* event_sender_; };

int main(int argc, const char** argv) { async::Loop loop(&amp;kAsyncLoopConfigAttachToCurrentThread); auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory(); // 初始化检查器 inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{}); inspector.Health().StartingUp(); // 提供 Echo 协议 EchoImplementation echo_instance; fidl::Binding&lt;fidl::examples::routing::echo::Echo&gt;binding(&amp;echo_instance); echo_instance.event_sender_ = &binding.events(); fidl::InterfaceRequestHandler&lt;fidl::examples::routing::echo::Echo&gt;处理程序 = [&amp;](fidl::InterfaceRequest&lt;fidl::examples::routing::echo::Echo&gt; request) { binding.Bind(std::move(request)); }; context-&gt;outgoing()-&gt;AddPublicService(std::move(handler)); // 组件正在提供服务,并已准备好处理传入请求 inspector.Health().Ok(); returnLoop.Run(); } ```

连接到路由功能

客户端组件声明它们可能在其组件中请求的功能 包含 use 声明的清单。

请参阅以下示例,了解使用 FIDL 的客户端组件清单 协议:

{
    include: [
        // Enable logging on stdout
        "syslog/client.shard.cml",
    ],

    // Information about the program to run.
    program: {
        // Use the built-in ELF runner.
        runner: "elf",

        // The binary to run for this component.
        binary: "bin/echo_client_rust",

        // Program arguments
        args: [ "Hello Fuchsia!" ],
    },


    // Capabilities used by this component.
    use: [
        { protocol: "fidl.examples.routing.echo.Echo" },
    ],
}

在运行时,客户端组件使用 fuchsia.io 协议,以获取功能 由其他组件提供Fuchsia 组件库支持 生成的 FIDL 绑定,来提供通过 频道:

Rust

#[fuchsia::main]
async fn main() -> Result<(), anyhow::Error> {
    // Parse arguments, removing binary name
    let mut args: Vec<String> = std::env::args().collect();
    args.remove(0);

    // Connect to FIDL protocol
    let echo = connect_to_protocol::<EchoMarker>().expect("error connecting to echo");

    // Send messages over FIDL interface
    for message in args {
        let out = echo.echo_string(Some(&message)).await.expect("echo_string failed");
        tracing::info!("Server response: {}", out.as_ref().expect("echo_string got empty result"));
    }

    Ok(())
}

C++

int main(int argc, const char* argv[], char* envp[]) {
  // Set tags for logging.
  fuchsia_logging::LogSettingsBuilder builder;
  builder.WithTags({"echo_client"}).BuildAndInitialize();

  // Connect to FIDL protocol
  fidl::examples::routing::echo::EchoSyncPtr echo_proxy;
  auto context = sys::ComponentContext::Create();
  context->svc()->Connect(echo_proxy.NewRequest());

  // Send messages over FIDL interface for each argument
  fidl::StringPtr response = nullptr;
  for (int i = 1; i < argc; i++) {
    ZX_ASSERT(echo_proxy->EchoString(argv[i], &response) == ZX_OK);
    if (!response.has_value()) {
      FX_LOG_KV(INFO, "echo_string got empty result");
    } else {
      FX_LOG_KV(INFO, "Server response", FX_KV("response", response->c_str()));
    }
  }

  return 0;
}

组件的父级负责向组件路由所有必要资源, 功能。

将一些已使用的功能标记为可选

组件运行并非必须使用其使用的所有功能 成功。有时,如果存在 功能缺失,其存在只能启用一些 替代行为

使组件框架能够理解组件的哪些功能 以及哪些功能对于组件是可选的,请使用 availability 字段。

use: [
    {
        // It is ok if this protocol is unavailable
        protocol: "fuchsia.examples.Echo1",
        availability: "optional",
    },
    {
        // This protocol MUST be provided for the component to function correctly.
        protocol: "fuchsia.examples.Echo2",
        availability: "required",
    },
]

如果组件具有 required,请对 capability 使用声明(默认设置) 但其父项以 optional 的形式提供功能,则 静态功能分析器将产生错误,并且 则始终会失败。

使用可选功能

如果组件的父级对如下功能的 offer 设置了一项功能: availability: "optional",则该功能在运行时可能不可用。

组件的 命名空间 会出现 该功能是否可用任何尝试打开以下路径的路径 该功能会导致向 Directory.Open() 提供句柄 以 ZX_ERR_NOT_FOUND 长叹号结束的通话。

使用 libc 方法(如 open()stat())将返回 ENOENT

路由功能

组件只能访问传送给它们的功能。功能 可以源自组件拓扑中的任何位置,但前提是功能有效 以功能中的以下声明链形式存在 provider:

  • expose:将 capability 路由到组件的父级。
  • offer:将功能向下路由到组件的其中一个 子女。

如需将功能提供程序与请求这些功能的组件连接起来, 执行以下操作:

  1. 向功能提供程序组件添加 offerexpose 声明:

    {
        include: [
            "inspect/client.shard.cml",
            "syslog/client.shard.cml",
        ],
    
        // Information about the program to run.
        program: {
            // Use the built-in ELF runner.
            runner: "elf",
    
            // The binary to run for this component.
            binary: "bin/echo_server_rust",
        },
    
        // Capabilities provided by this component.
        capabilities: [
            { protocol: "fidl.examples.routing.echo.Echo" },
        ],
        expose: [
            {
                protocol: "fidl.examples.routing.echo.Echo",
                from: "self",
            },
        ],
    }
    
  2. 对于组件实例树中的每个中间组件,添加 额外添加 exposeoffer 声明,直至达到 包含 use 声明的组件:

    {
        // Two children: a server and client.
        children: [
            {
                name: "echo_server",
                url: "#meta/echo_server.cm",
            },
            {
                name: "echo_client",
                url: "#meta/echo_client.cm",
            },
        ],
        offer: [
            // Route Echo protocol from server to client.
            {
                protocol: "fidl.examples.routing.echo.Echo",
                from: "#echo_server",
                to: "#echo_client",
            },
    
            // Route diagnostics protocols to both children.
            {
                protocol: [
                    "fuchsia.inspect.InspectSink",
                    "fuchsia.logger.LogSink",
                ],
                from: "parent",
                to: [
                    "#echo_client",
                    "#echo_server",
                ],
            },
        ],
    }
    

可选依赖项

如果某个组件依赖于某项功能,则最多 该组件的父级来确定该组件是否接收该功能 。提供某项功能时,组件可以设置 availability 字段 更改为 optionalrequiredsame_as_target。每个值都有 以下语义:

  • optional:优惠的目标必须声明其处理 则缺少此功能,具体方法是将其 use 声明标记为 optional。 如果目标无法执行此操作(也就是说,目标的可用性是 required),那么路由该功能将会导致 错误。
  • required:目标必须获得此功能。如果优惠来源为 parent 且组件的父级(目标的祖父级)提供了此属性 作为可选功能,那么路由该功能将会导致错误 因为父级无法保证该功能的可用性。
  • same_as_target:此功能的可用性取决于 目标的期望。如果目标依赖于此 功能,那么此优惠也是可选的。如果目标的 依赖此 capability,则此优惠也将 必填字段。
offer: [
    {
        // child-1 MUST receive the protocol 'fuchsia.logger.LogSink'.
        protocol: "fuchsia.logger.LogSink",
        to: "#child-1",
        from: "#child-2",
        availability: "required",
    },
    {
        // child-1 MUST be able to handle the absence of the protocol
        // 'fuchsia.tracing.provider.Registry'.
        protocol: "fuchsia.tracing.provider.Registry",
        to: "#child-1",
        from: "parent",
        availability: "optional",
    },
    {
        // child-1 decides if it must receive the protocol
        // 'fuchsia.posix.socket.Provider', or if it must be able to support its
        // absence.
        protocol: "fuchsia.posix.socket.Provider",
        to: "#child-1",
        from: "parent",
        availability: "same_as_target",
    },
]

与 use 声明一样,availability 字段可以省略,其中 则默认为 required

过渡依赖项

通过组件框架,组件可以软过渡到使用和 提供可选和必需的功能。在过渡期 可用性标记,即使用功能的组件,不会导致审查 验证错误,无论父项是必需项、可选项还是与父项相同 目标。请注意,尽管此字段的存在是为了启用软过渡,组件 最终应确定是可选还是必需。

要使用此功能,子组件会将其可用性标记为 "transitional":

use: [
    {
        // It is ok if this protocol is offered as "required" or "optional"
        protocol: "fuchsia.examples.Echo",
        availability: "transitional",
    },
]

管理子组件

组件可以从组件拓扑中的任何位置相互交互 但前提是它们之间存在有效的功能路由。 还有一些其他方法可让父组件 直接子女。

以下示例组件声明了一个名为 lifecycle 的静态子级 以及一个名为 echo 的集合,您可以在其中创建其他子组件 在运行时:

{
    // ...

    // Statically declared child components
    children: [
        {
            name: "lifecycle",
            url: "lifecycle#meta/default.cm",
        },
    ],

    // Collection to hold dynamically created child components
    collections: [
        {
            name: "echo",
            durability: "transient",
        },
    ],

    // Capabilities required by this component
    use: [
        {
            protocol: "fuchsia.component.Binder",
            from: "#lifecycle",
        },
        {
            protocol: "fuchsia.component.Realm",
            from: "framework",
        },
    ],

    // Capabilities required by child components
    offer: [
        {
            protocol: [
                "fuchsia.inspect.InspectSink",
                "fuchsia.logger.LogSink",
            ],
            from: "parent",
            to: [
                "#echo",
                "#lifecycle",
            ],
        },
    ],
}

请注意,集合的行为类似于父级中的静态子实例 您可以为其命名并提供特定的功能 。集合中的所有子组件均可访问这组功能 提供给它。

启动子组件

组件框架提供 fuchsia.component.Binder 父组件的协议,用于显式启动可能不会公开 任何其他功能由于此功能由框架提供, 子组件只需通过其组件清单公开该组件即可:

{
    // ...

    // Capabilities exposed from this component to parent.
    expose: [
        {
            // Expose this protocol so that parent component can start it
            // by binding to this capability.
            protocol: "fuchsia.component.Binder",
            from: "framework",
        },
    ],
}

创建动态子级

要在运行时创建新的子组件,请使用 fuchsia.component.Realm 协议,用于创建 现有集合调用 CreateChild 方法:

  • CollectionRef:描述集合,其中 系统就会添加该组件
  • Child:组件声明,包括其名称和 组件网址。

Rust

use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker};
use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode};

// ...

// Use the fuchsia.component.Realm protocol to create a dynamic
// child instance in the collection.
async fn create_dynamic_child() {
    let realm = client::connect_to_protocol::<RealmMarker>()
        .expect("failed to connect to fuchsia.component.Realm");

    let collection_ref = CollectionRef { name: String::from("echo") };
    let child_decl = Child {
        name: Some(String::from("lifecycle_dynamic")),
        url: Some(String::from("echo_server#meta/default.cm")),
        startup: Some(StartupMode::Lazy),
        ..Default::default()
    };

    realm
        .create_child(&collection_ref, &child_decl, CreateChildArgs::default())
        .await
        .expect("create_child failed")
        .expect("failed to create child");
}

C++

#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>

// ...

// Use the fuchsia.component.Realm protocol to create a dynamic
// child instance in the collection.
void CreateDynamicChild() {
  fuchsia::component::decl::CollectionRef collection_ref = {
      .name = "echo",
  };
  fuchsia::component::decl::Child child_decl;
  child_decl.set_name("lifecycle_dynamic");
  child_decl.set_url("echo_server#meta/default.cm");
  child_decl.set_startup(fuchsia::component::decl::StartupMode::LAZY);

  realm_proxy_->CreateChild(std::move(collection_ref), std::move(child_decl),
                            fuchsia::component::CreateChildArgs(),
                            [&](fuchsia::component::Realm_CreateChild_Result result) {
                              ZX_ASSERT(!result.is_err());
                              FX_LOGS(INFO) << "Dynamic child instance created.";

                              ConnectDynamicChild();
                            });
}

关联到子 capability

由于在构建时并不知道动态组件的父项, 无法在 组件清单。

如需连接动态子实例公开的 capability,请执行以下操作:

  1. 使用 fuchsia.component.Realm 协议打开孩子的 公开目录调用 OpenExposedDir 方法(其中包含子组件的名称和集合名称):

    Rust

    use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker};
    use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode};
    
    // ...
    // Connect to the fidl.examples.routing.echo capability exposed by the child
    // instance, and send a request.
    async fn connect_dynamic_child(message: String) {
        // Open the child's exposed directory
        let exposed_dir = client::open_childs_exposed_directory(
            String::from("lifecycle_dynamic"),
            Some(String::from("echo")),
        )
        .await
        .expect("failed to open exposed directory");
    
        // ...
    }
    

    C++

    #include <fuchsia/component/cpp/fidl.h>
    #include <fuchsia/component/decl/cpp/fidl.h>
    
    // ...
    
    // Use the fuchsia.component.Realm protocol to open the exposed directory of
    // the dynamic child instance.
    void ConnectDynamicChild() {
      fuchsia::component::decl::ChildRef child_ref = {
          .name = "lifecycle_dynamic",
          .collection = "echo",
      };
    
      fidl::InterfaceHandle<fuchsia::io::Directory> exposed_dir;
      realm_proxy_->OpenExposedDir(
          child_ref, exposed_dir.NewRequest(),
          [this, exposed_dir = std::move(exposed_dir)](
              fuchsia::component::Realm_OpenExposedDir_Result result) mutable {
            ZX_ASSERT(!result.is_err());
            std::shared_ptr<sys::ServiceDirectory> svc = std::make_shared<sys::ServiceDirectory>(
                sys::ServiceDirectory(std::move(exposed_dir)));
    
            SendEchoRequest(svc);
          });
    }
    
  2. 使用公开目录连接到子级公开的 capability 处理为根:

    Rust

    // Access the fidl.examples.routing.echo capability provided there
    let echo = client::connect_to_protocol_at_dir_root::<EchoMarker>(&exposed_dir)
        .expect("failed to connect to fidl.examples.routing.echo");
    
    let response = echo
        .echo_string(Some(&message))
        .await
        .expect("echo_string failed")
        .expect("echo_string got empty result");
    info!("Server response: {}", response);
    

    C++

    // Connect to the fidl.examples.routing.echo capability exposed by the child's
    // service directory.
    void SendEchoRequest(std::shared_ptr<sys::ServiceDirectory> svc_directory) {
      // Connect to the protocol inside the child's exposed directory
      svc_directory->Connect(echo_proxy_.NewRequest());
    
      // Send a protocol request
      echo_proxy_->EchoString(message_, [&](fidl::StringPtr response) {
        FX_LOGS(INFO) << "Server response: " << response;
        DestroyDynamicChild();
      });
    }
    

销毁动态子级

当不再需要动态子级时,请使用 使用 fuchsia.component.Realm 协议销毁组件 实例。使用DestroyChild ChildRef,表示集合中的子项。

Rust

use fidl_fuchsia_component::{BinderMarker, CreateChildArgs, RealmMarker};
use fidl_fuchsia_component_decl::{Child, ChildRef, CollectionRef, StartupMode};

// ...

// Use the fuchsia.component.Realm protocol to destroy the dynamic
// child instance running in the collection.
async fn destroy_dynamic_child() {
    let realm = client::connect_to_protocol::<RealmMarker>()
        .expect("failed to connect to fuchsia.component.Realm");

    let child_ref = ChildRef {
        name: String::from("lifecycle_dynamic"),
        collection: Some(String::from("echo")),
    };

    realm
        .destroy_child(&child_ref)
        .await
        .expect("destroy_child failed")
        .expect("failed to destroy child");
}

C++

#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/component/decl/cpp/fidl.h>

// ...

// Use the fuchsia.component.Realm protocol to destroy the dynamic
// child instance running in the collection.
void DestroyDynamicChild() {
  fuchsia::component::decl::ChildRef child_ref = {
      .name = "lifecycle_dynamic",
      .collection = "echo",
  };

  realm_proxy_->DestroyChild(std::move(child_ref),
                             [&](fuchsia::component::Realm_DestroyChild_Result result) {
                               ZX_ASSERT(!result.is_err());
                               FX_LOGS(INFO) << "Dynamic child instance destroyed.";

                               // Terminate the loop
                               loop_->Quit();
                             });
}

如果组件当前正在运行,这会导致其停止运行。要处理此问题,请执行以下操作: 事件,请参阅监听停止事件

控制组件生命周期

组件框架提供了用于修改各种组件并与之交互的功能 组件生命周期的各个部分。

如需详细了解生命周期概念,请参阅 组件生命周期

生命周期通知

ELF 运行程序会使用 fuchsia.process.lifecycle.Lifecycle 协议。

如需在子组件中监听停止通知,请执行以下操作:

  1. 在组件中订阅生命周期事件 manifest:

    Rust

    // Information about the program to run.
    program: {
        // Use the built-in ELF runner.
        runner: "elf",
    
        // The binary to run for this component.
        binary: "bin/lifecycle_example_rust",
    
        // Subscribe to component lifecycle events
        lifecycle: { stop_event: "notify" },
    },
    

    C++

    // Information about the program to run.
    program: {
        // Use the built-in ELF runner.
        runner: "elf",
    
        // The binary to run for this component.
        binary: "bin/lifecycle_example_cpp",
    
        // Subscribe to component lifecycle events
        lifecycle: { stop_event: "notify" },
    },
    
  2. 使用 runner:

    Rust

    use fidl_fuchsia_process_lifecycle::{LifecycleRequest, LifecycleRequestStream};
    
    // ...
    
    #[fuchsia::main(logging_tags = ["lifecycle", "example"])]
    async fn main() {
        // Take the lifecycle handle provided by the runner
        match fuchsia_runtime::take_startup_handle(HandleInfo::new(HandleType::Lifecycle, 0)) {
            Some(lifecycle_handle) => {
                info!("Lifecycle channel received.");
                // Begin listening for lifecycle requests on this channel
                let x: fuchsia_zircon::Channel = lifecycle_handle.into();
                let async_x = AsyncChannel::from(fuchsia_async::Channel::from_channel(x));
                let mut req_stream = LifecycleRequestStream::from_channel(async_x);
                info!("Awaiting request to close...");
                if let Some(request) =
                    req_stream.try_next().await.expect("Failure receiving lifecycle FIDL message")
                {
                    match request {
                        LifecycleRequest::Stop { control_handle: c } => {
                            info!("Received request to stop. Shutting down.");
                            c.shutdown();
                            process::exit(0);
                        }
                    }
                }
    
                // We only arrive here if the lifecycle channel closed without
                // first sending the shutdown event, which is unexpected.
                process::abort();
            }
            None => {
                // We did not receive a lifecycle channel, exit abnormally.
                error!("No lifecycle channel received, exiting.");
                process::abort();
            }
        }
    }
    

    C++

    #include <fuchsia/process/lifecycle/cpp/fidl.h>
    
    // ...
    
    // Implementation of the fuchsia.process.lifecycle FIDL protocol
    class LifecycleHandler : public fuchsia::process::lifecycle::Lifecycle {
     public:
      explicit LifecycleHandler(async::Loop* loop) : loop_(loop) {
        // Get the PA_LIFECYCLE handle, and instantiate the channel with it
        zx::channel channel = zx::channel(zx_take_startup_handle(PA_LIFECYCLE));
        // Bind to the channel and start listening for events
        bindings_.AddBinding(
            this, fidl::InterfaceRequest<fuchsia::process::lifecycle::Lifecycle>(std::move(channel)),
            loop_->dispatcher());
        FX_LOGS(INFO) << "Lifecycle channel received.";
      }
    
      // This is the Stop event we must override - see the pure virtual function we
      // need to override at the declaration of fuchsia::process::lifecycle::Lifecycle
      void Stop() override {
        FX_LOGS(INFO) << "Received request to stop. Shutting down.";
        // Shut down our loop - it's important to call Shutdown() here vs. Quit()
        loop_->Shutdown();
        // Close the binding
        bindings_.CloseAll();
      }
    
     private:
      async::Loop* loop_;
      fidl::BindingSet<fuchsia::process::lifecycle::Lifecycle> bindings_;
    };
    

从父级开始

利用组件清单,您可以将子级标记为 eager,这会使组件框架隐式 从父级开始

如果即时子级因任何原因(例如缺少组件)而无法启动, 组件管理器表现出以下行为:

  • 如果父组件不是根组件,则父组件将开始,但 与其绑定的组件将观察到连接断开(就像 其他失败的绑定)。
  • 如果父组件是根组件,组件管理器将会崩溃,并显示 错误消息,例如:

    [component_manager] ERROR: protocol `fuchsia.component.CoreBinder` was not available for target `startup`:
        failed to resolve `fuchsia-pkg://fuchsia.com/your_component#meta/your_component.cm`:
        package not found: remote resolver responded with PackageNotFound
        For more, run `ffx component doctor `startup`
    

标记为 eager 的组件如果不存在,可能会导致系统崩溃 如果它们的祖先实体也被标记为 eager(直到根组件)。这是 因为许多构建配置都会创建包含 一部分可用组件。为避免出现这种问题,请声明这些 组件使用核心领域分片来确保它们 已从测试 build 和产品映像中安全排除。

eager 组件也应位于同一软件包集中 因为此组件会与它的父项同时启动, 。通常,eager 组件应位于产品的基础软件包中 。

如需确定您的软件包是否在基础软件包集中,请运行以下命令 命令:

fx list-packages --verbose my-package

此命令会输出包含匹配软件包的软件包集的列表 找到。例如,system-update-checker 位于 baseuniverse 中 软件包集:

$ fx list-packages --verbose system-update-checker
system-update-checker [base universe]

您还可以使用 --base 查看基础软件包集中的所有软件包 选项:

fx list-packages --base

终止时重新启动

利用组件清单,您可以控制 使用 on_terminate 构建组件。具有 “reboot-on-terminate”如果存在以下情况,则系统将正常重新启动: 组件因任何原因终止(包括成功退出)。

如需启用此功能,请执行以下操作:

  1. 在父级的组件清单中将子级标记为 on_terminate: reboot

    // core.cml
    {
        children: [
            ...
            {
                name: "system-update-checker",
                url: "fuchsia-pkg://fuchsia.com/system-update-checker#meta/system-update-checker.cm",
                startup: "eager",
                on_terminate: "reboot",
            },
        ],
    }
    
  2. 将该组件的名称添加到组件管理器的安全政策许可名单中 在 //src/security/policy/component_manager_policy.json5:

    // //src/security/policy/component_manager_policy.json5
    {
        security_policy: {
            ...
            child_policy: {
                reboot_on_terminate: [
                    ...
                    "/core/system-update-checker",
                ],
            },
        },
    }
    

错误

信号机制

当发出请求的客户端通过执行 针对其命名空间的 Directory.Open() 请求,或针对 该 公开目录 ,它会通过服务器端传递 zircon 对象的句柄,发送后会提供相应功能。错误 将导致该句柄的对象以书写形式关闭。墓碑 载荷始终是 Zircon 状态代码。

由于路由是延迟且异步的,因此此消息可能会随时到达 。

注意:一旦路由成功,服务组件还可以关闭 将对象替换为自己选择的状态代码。客户端无法做到这一点 来辨别对象是由组件管理器还是由 组件或其后委托给其他方。

类似 libc 的调用(例如 open())也使用相同的错误信号机制。

如需查看实际示例,请参阅问题排查部分。

错误状态代码

组件管理器可能会发送以下错误代码来指示 路由操作:

  • ZX_ERR_NOT_FOUND:无法针对以下其中一项进行路由: 原因如下: <ph type="x-smartling-placeholder">
      </ph>
    • 此配置中未提供可选功能。
    • 路由路径上的任何一个组件出现配置错误。
    • 服务组件与此版本的 Fuchsia 不兼容。
    • 服务组件的程序中存在的错误。
    • 任何解析器运行程序中存在错误 所需的全部设置
  • ZX_ERR_ACCESS_DENIED:无法路由功能,因为 不允许请求组件访问该组件。例如:
    • 该功能的政策许可名单已存在,但 不包含发出请求的组件。
    • 发出请求的组件所请求的权限高于所提供的权限 (即,请求对以只读方式提供的目录进行读/写)。
  • ZX_ERR_TIMED_OUT:其中一个路由步骤超时。
  • ZX_ERR_INTERNAL:组件管理器本身遇到了意外错误。 这表明平台存在 bug。

NOT_FOUNDACCESS_DENIEDINTERNAL 错误也可重现 但前提是平台上没有更新任何软件。软件更新 都会改变功能的路线,并可能影响 该功能的可用性

路由错误语义的原则

  • 最小性:因为错误信号路径在组件之间共享 与服务组件相关联后,组件管理器会将大部分 供服务组件使用的错误空间。
  • 客户端视角:路由取决于多个单个用户 子操作都可能因各种原因而失败,包括错误 而对于其他组件作者,错误语义是针对 发出请求的客户端和发出请求的客户端的需求例如: 中间组件作者的 user-error 仍会返回 NOT_FOUND(适用于发出请求的客户端)。

问题排查

本部分包含您在尝试use和 通过建议的解决方案连接到组件的功能。

功能路由失败

组件管理器会执行功能路由,以找到 指定功能的来源之后,您的组件尝试访问 功能。如果路由中的某个组件清单,则路由可能会失败 路径配置不正确。例如,offerexpose 声明 路径中的某个部分或 链无法解析。

执行以下操作,检查路由故障是否是导致通道关闭的原因:

  • 使用 ffx component route 检查组件的路由。这个 可以与组件的名称或组件的 网址。例如:

    # check with the moniker
    ffx component route /core/ffx-laboratory:echo_realm/echo_client
    
    # check with a partial URL
    ffx component route echo_client.cm
    
  • 使用 ffx log 检查组件日志,查找以 Failed to route 开头的消息(说明路由链出现故障的位置)。例如:

    [echo_client][][W] protocol `fidl.examples.routing.echo.Echo` was not available for target `/core/ffx-laboratory:echo_realm/echo_client`:
        `fidl.examples.routing.echo.Echo` was not offered to `/core/ffx-laboratory:echo_realm/echo_client` by parent
        For more, run `ffx component doctor /core/ffx-laboratory:echo_realm/echo_client`
    
  • 检查已关闭频道上的墓记。墓碑 针对最常见的路由故障设置的值为 ZX_ERR_NOT_FOUND

    [echo_client][][I] Connecting to Echo protocol failed with error
    "A FIDL client's channel to the service fidl.examples.routing.echo.Echo was closed: NOT_FOUND"
    

    如需了解详情,请参阅路由错误

如需查看失败功能路由的独立示例,请参阅 //examples/components/routing_failed

收到路由错误

当功能路由失败时,底层 FIDL 通道会关闭。FIDL 协议 如果通道已关闭,绑定将返回错误状态。考虑使用 示例:

Rust

let echo = connect_to_protocol::<EchoMarker>()
    .context("Failed to connect to echo service")?;
let res = echo.echo_string(Some("Hippos rule!")).await;
match res {
    Ok(_) => { info!("Call succeeded!"); }
    Err(fidl::Error::ClientChannelClosed { status, service_name } => { 
        error!("Channel to service {} was closed with status: {}", service_name, status); 
    } 
    Err(e) => {
        error!("Unexpected error: {}", e);
    }
};

C++

fuchsia::examples::EchoPtr echo_proxy;
auto context = sys::ComponentContext::Create();
context->svc()->Connect(echo_proxy.NewRequest());

// Sets an error handler that will be called if an error causes the underlying 
// channel to be closed. 
echo_proxy.set_error_handler([&loop](zx_status_t status) { 
  printf("Channel was closed with status: %d\n", status); 
  // ... 
}); 

echo_proxy->EchoString("Hippos rule!", [&](std::string response) {
  // ...
});

若要确定渠道关闭的根本原因,您可以查看 可选的 epitaph。要检索 如果是封闭式渠道,请执行以下操作:

Rust

let stream = echo.take_event_stream();
match stream.next().await {
    Some(Err(fidl::Error::ClientChannelClosed { status, .. })) => {
        info!("Channel was closed with epitaph: {}", status);
    }
    Some(m) => {
        info!("Received message other than epitaph or peer closed: {:?}", m);
    }
    None => {
        info!("Component failed to start or channel was closed by server");
    }
}

C++

echo_proxy.set_error_handler([&loop](zx_status_t status) {
  // If an Epitaph was present on the channel, its error value will be passed as
  // the parameter.
  printf("Channel was closed with epitaph: %d\n", status);
});

组件未能启动

如果功能路由成功,但存在一个问题,则可能会遇到错误 发生解析或启动组件的问题。错误消息的形式 依赖于组件运行程序

  • 对于 ELF 运行程序,请使用 ffx log --filter component_manager 检查组件管理器日志。查找以 Failed to start component 开头的邮件。例如:

    [component_manager] WARN: Failed to start component
    `fuchsia-pkg://fuchsia.com/components-routing-failed-example#meta/echo_server_bad.cm`:
    unable to load component with url "fuchsia-pkg://fuchsia.com/components-routing-failed-example#meta/echo_server_bad.cm":
    error loading executable: "reading object at \"bin/routing_failed_echo_server_oops\" failed:
    A FIDL client's channel to the service fuchsia.io.File was closed: PEER_CLOSED"
    
  • 对于其他运行程序,请查看运行程序组件的日志。您 为此,可以运行以下命令:

    ffx log --tags runner-name
    

如需解决此问题,请验证以下内容:

  • 您的组件清单中的 program 声明为 配置正确例如,验证二进制文件的路径是否拼写正确 正确。
  • 二进制文件本身以及启动组件所需的所有其他资源 包含在软件包中。

有关因配置错误而无法启动的组件示例 组件清单,请参见 //examples/components/routing_failed

组件终止或关闭了频道

如果您已验证路由成功且组件已启动 则表示您可能会遇到来源组件关闭的问题 频道本身这可能发生在组件运行时,也可能是 组件终止的副作用

如果该组件因崩溃而终止,您可以查找崩溃报告 在包含转储中的组件名称的 ffx log 中:

[33860.645][klog][klog][I] crashsvc: exception received, processing
[33860.645][klog][klog][I] <== fatal : process echo_client.cm[21090] thread initial-thread[21092]
<stack trace follows...>

如果来源组件自行关闭了频道,以下提示可帮助您 排查问题:

  • 如需了解错误消息,请参阅来源组件的日志
  • 使用 ffx debug fidl 检查 FIDL 连接流量, fidlcat 表示错误或意外行为。