連結元件

本文件說明如何使用相關功能和父項元件管理子項的其他工具,將元件連結在一起。

概念

在繼續閱讀本指南之前,請先瞭解以下概念:

  • capabilities
  • Directory元件的程式可以透過此目錄提供任何功能。
  • 集合是動態子項的容器,可在執行階段使用 fuchsia.component.Realm 架構通訊協定建立及刪除。

如要進一步瞭解這些概念,請參閱「運作範圍」和「功能」。

透過轉送連結功能

元件會透過功能相互互動。元件提供的功能必須在該元件的資訊清單中宣告,以提供這些功能供他人使用,同時透過 exposeoffer 宣告轉送至父項/子項元件。其他使用該能力的元件也必須在資訊清單中宣告相關用途。為了讓程式在執行階段使用能力,所用能力必須轉送至該元件,無論是由父項提供或由子項公開。

功能轉送是指元件管理員執行的週期性程序,可按照資訊清單檔案中說明的個別轉送步驟,識別服務元件。功能轉送會在以下情況啟動:

  • 開發人員叫用 ffx component route
  • 啟動依附於解析器或執行器的元件,其備份解析器或執行元件功能尚未轉送。

延遲執行轉送:雖然能力可以由父項或子項設定成由父項或子項提供 (直接或間接透過進一步委派),但啟動轉送作業時,目標元件可能尚未獲得解析。基本上,這表示在嘗試轉送之前,可能無法得知從要求元件到最終服務元件的完整路徑。

提供能力實作

提供能力的元件必須使用 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++

// Handler for incoming service requests
class EchoImplementation : public fidl::examples::routing::echo::Echo {
 public:
  void EchoString(fidl::StringPtr value, EchoStringCallback callback) override { callback(value); }
  fidl::examples::routing::echo::Echo_EventSender* event_sender_;
};

int main(int argc, const char** argv) {
  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
  auto context = sys::ComponentContext::CreateAndServeOutgoingDirectory();

  // Initialize inspect
  inspect::ComponentInspector inspector(loop.dispatcher(), inspect::PublishOptions{});
  inspector.Health().StartingUp();

  // Serve the Echo protocol
  EchoImplementation echo_instance;
  fidl::Binding<fidl::examples::routing::echo::Echo> binding(&echo_instance);
  echo_instance.event_sender_ = &binding.events();
  fidl::InterfaceRequestHandler<fidl::examples::routing::echo::Echo> handler =
      [&](fidl::InterfaceRequest<fidl::examples::routing::echo::Echo> request) {
        binding.Bind(std::move(request));
      };
  context->outgoing()->AddPublicService(std::move(handler));

  // Component is serving and ready to handle incoming requests
  inspector.Health().Ok();

  return loop.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::SetTags({"echo_client"});

  // 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_SLOG(INFO, "echo_string got empty result");
    } else {
      FX_SLOG(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 使用宣告 (預設值),但其父項提供 optional 能力,則能力將會產生錯誤,且在執行階段一律無法嘗試連線。

使用選用功能

如果元件的父項已offer功能與 availability: "optional",則該能力可能無法在執行階段使用。

若嘗試開啟該能力的路徑,提供給給 Directory.Open() 的呼叫的控制代碼都會使用 ZX_ERR_NOT_FOUND 詞曲關閉。

使用 open()stat()libc 方法會傳回 ENOENT

路徑功能

元件只能存取轉送到這些元件的功能。有效的功能路徑可做為有效能力路徑的鏈結,從能力供應器提供給任何消費者的鏈結中,即可從元件拓撲中的任何位置產生功能:

  • expose:將能力轉送至元件的父項。
  • 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:這項能力的可用性取決於目標的期望。如果目標對這項能力有選用依附元件,您也可以選擇提供這項方案。如果目標對這項能力有必要依附元件,您也必須提供這項優惠。
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",
    },
]

和使用宣告一樣,availability 欄位可省略,如果其預設為 required

精簡依附元件

元件架構可讓元件柔軟轉換為使用,並同時提供選用及必要功能。使用過渡性可用性標記時,使用某項能力的元件不會造成 Scrutiny 驗證錯誤,無論父項是否必要、選用或與目標相同。請注意,雖然這個欄位可用來啟用軟轉換,但元件最終應取決於選用或必要屬性。

如要使用這項功能,子項元件會將可用性標示為「轉換」:

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();
                            });
}

連結子項功能

由於建構時無法得知動態元件的父項,因此其已公開的功能不能在元件資訊清單中表示的能力路徑命名。

如何連接動態子項執行個體所公開的功能:

  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. 使用公開目錄控制代碼做為根目錄,連線至子項公開的功能:

    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 通訊協定刪除元件執行個體。使用代表集合內的子項的 ChildRef 呼叫 DestroyChild 方法。

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. 在元件資訊清單中訂閱生命週期事件

    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. 使用執行元件提供的啟動控制代碼來註冊生命週期處理常式:

    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: Required protocol `fuchsia.component.CoreBinder` was not
    available for target component `/startup`:
    failed to resolve "fuchsia-pkg://fuchsia.com/your_component#meta/your_component.cm":
    package not found: remote resolver responded with PackageNotFound
    

如果標記為 eager 的元件也在根元件上加上 eager,則該元件若不存在,可能會導致系統當機。這點非常重要,因為許多建構設定會建立包含可用元件子集的系統映像檔。如要避免這個問題,請使用核心領域資料分割宣告這些元件,確保能夠安全從測試版本和產品映像檔中排除。

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 控管元件的終止政策。如果元件的元件設定「在終止時重新啟動」政策,當元件因任何原因終止 (包括成功退出) 時,系統即可順利重新啟動。

如要啟用這項功能,請按照下列步驟操作:

  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 狀態碼。

由於轉送為延遲和非同步,因此在轉送作業啟動後,這則訊息隨時可能會送達。

注意:一旦轉送「成功」,供應元件也可以關閉相同的物件,並提供自選的狀態碼。用戶端無法辨別物件是否由元件管理員、服務元件或之後委任對象關閉。

類似的錯誤信號機制也適用於類似 libc 的呼叫,例如 open()

如需實際範例,請參閱「疑難排解」一節。

錯誤狀態碼

元件管理員可能會傳送下列錯誤代碼,藉此表示轉送作業失敗:

  • ZX_ERR_NOT_FOUND:無法轉送能力,原因如下:
    • 此設定未提供選用能力。
    • 路徑路徑上任何元件中的設定錯誤。
    • 放送元件與這個 Fuchsia 版本不相容。
    • 供應元件程式的錯誤。
    • 完成轉送作業涉及的任何解析器執行器錯誤。
  • ZX_ERR_ACCESS_DENIED:要求元件無法存取,因此無法轉送能力。例如:
    • 具有能力的政策許可清單,但沒有要求元件。
    • 要求元件要求的權限大於所提供的內容 (即要求在以唯讀模式提供的目錄上進行讀取/寫入)。
  • ZX_ERR_TIMED_OUT:其中一個轉送步驟逾時。
  • ZX_ERR_INTERNAL:元件管理員本身發生未預期的錯誤,表示平台中的錯誤。

只要平台上沒有任何軟體更新,NOT_FOUNDACCESS_DENIEDINTERNAL 錯誤都會針對相同能力重現。即使只有單一元件,軟體更新也會變更能力路徑,也可能影響該能力的可用性。

轉送錯誤語意的原則

  • 最少性:由於錯誤信號路徑會在元件管理員和服務元件之間共用,因此元件管理員會留下大部分的錯誤空間供服務元件使用。
  • 用戶端觀點:雖然轉送取決於許多個別子作業,這些子作業可能會因各種原因而失敗,包括其他元件作者的錯誤,則錯誤語意會專為要求用戶端和要求用戶端的需求而設。例如,在中繼元件作者部分發生的使用者錯誤,仍會針對要求的用戶端傳回 NOT_FOUND

疑難排解

本節包含您在嘗試 use 以及透過建議的解決方案連線至元件功能時,可能會遇到的常見問題。

能力轉送失敗時,基礎 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) {
  // ...
});

如要判斷管道關閉的根本原因,您可以檢查管道上的選用事件。如要在封閉式管道上擷取全景,請執行下列步驟:

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

功能轉送失敗

一旦元件嘗試存取能力,元件管理員就會執行「功能轉送」,找出特定能力的來源。如果轉送路徑中的其中一個元件資訊清單設定錯誤,轉送作業可能會失敗。例如,路徑中的某些元件缺少 offerexpose 宣告,或者鏈結中的其中一個元件無法解析。

請按照下列步驟操作,確認轉送失敗是否為管道關閉的原因:

  • 請使用 ffx log 查看元件記錄,找出開頭為 Failed to route 的訊息,瞭解轉送鏈失敗的原因。例如:

    [echo_client][][W] Required protocol
    `fidl.examples.routing.echo.Echo` was not available for target component
    `/core/ffx-laboratory:echo_realm/echo_client`:
    A `use from parent` declaration was found at `/core/ffx-laboratory:echo_realm/echo_client`
    for `fidl.examples.routing.echo.Echo`, but no matching `offer` declaration was found in the parent
    
  • 確認已關閉頻道的重要內容。 最常轉送失敗的錯誤設定為 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

元件無法啟動

如果能力轉送成功,但解析或啟動元件時發生問題,您可能會遇到錯誤。錯誤訊息的形式視元件執行器而定:

  • 如果是 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 (anonymous) 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 檢查與 fidlcat 的 FIDL 連線流量,確認是否有錯誤或非預期的行為。