在元件處於閒置狀態時停止元件

元件通常無法隨時運作。大部分元件都是 所以往往在等待下一個 FIDL 訊息 。然而,這些元件會佔用記憶體。本指南適用於 調整元件,使其停止自願,並在閒置時釋出資源 閒置中。

總覽

以下是期望的做法:

  • 變更元件的程式碼後,程式碼即可決定 停止。您的元件將保留其狀態,並在此之前處理 停止。保留這類資料稱為「託管」

  • 用戶端存取元件時,不會發現您的元件已停止。 以這種方式停止元件並不會中斷這些元件與 元件。

  • Fuchsia 提供的程式庫可讓您監控 FIDL 連線及 傳出目錄連線變為閒置狀態,然後重新 來處理這種情況

  • 元件架構為元件提供 API,可用於儲存帳號代碼和資料 並在下次執行時擷取這些物件 (通常是在控制代碼之後 或提出新的能力要求時以下將詳細說明其運作方式 後續章節將介紹

  • Fuchsia 快照和 Cobalt 資訊主頁含有實用的生命週期指標,

哪些要素適合使用?

建議您查看具有這些特性的元件:

  • 尖峰流量。元件可以啟動並處理這些流量 工作完成後返回停止。啟動和更新路徑中的大量元件 只有在這些時段才需要 ,但其他情況卻曾坐在浪費 RAM,例如:core/system-update/system-updater

  • 狀態不是「太有狀態」。您可以在元件停止前保留狀態。於 因此我們可以撰寫程式碼,保留所有重要狀態實務上 在節省記憶體與保存複雜度之間取得平衡 所需狀態

  • 高記憶體用量:使用 ffx profile memory 查看元件的記憶體用量。例如,它顯示 console-launcher.cm 系統使用 732 KiB 的私人記憶體私人記憶體僅適用於記憶體 該元件參照,因此我們保證至少能釋出該元件 停止執行該元件詳情請見 測量記憶體用量

    Process name:         console-launcher.cm
    Process koid:         2222
    Private:              732 KiB
    PSS:                  1.26 MiB (Proportional Set Size)
    Total:                3.07 MiB (Private + Shared unscaled)
    

http-client.cm 是不會保留的 狀態,而且只會用於指標和當機情形 上傳。因此,我們已調整設定,在設定閒置時停止。

已知限制

  • 檢查:如果您的元件透過檢查來發布診斷資訊, 這些資訊就會在元件停止後捨棄。 https://fxbug.dev/339076913 追蹤保留情形 並在元件停止後檢查資料

  • Hing-gets:如果您的元件是造成當機的伺服器或用戶端 FIDL 方法則難以保留連線,因為 您無法針對 FIDL 繫結的內容儲存及還原相關資訊 。您可以將 FIDL 方法轉換為事件,且 單向確認

  • 目錄:如果您的元件提供目錄通訊協定,系統會 難以保留連線,因為系統通常會為 由 VFS 程式庫提供。VFS 程式庫目前並未公開復原的方法 基礎頻道和相關狀態 (例如搜尋指標)。

您可以視需要提供足夠的理由,藉此支援上述所有功能。你可能會與 以及您的用途

偵測閒置狀態

停止閒置元件的第一步是強化該元件的程式碼 得知閒置狀態,表示:

  • FIDL 連線閒置中:元件通常會宣告多個 FIDL 通訊協定功能和用戶端 。這些連線不應有要求 注意力元件

  • 外寄目錄處於閒置狀態:元件會提供傳出目錄, 發布了對外功能在此找不到您 表示對這個元件發出的能力要求,因此不應 連至傳出目錄, component_manager

  • 其他背景商業邏輯:例如某個元件 網路要求以回應 FIDL 方法,我們可能不會 除非該網路要求已完成,否則請將該元件視為閒置。 要求中途停止該元件可能不安全。

我們透過 Rust 程式庫偵測各種情況的閒置狀態。 https://fxbug.dev/332342122 追蹤相同的 提供的功能

偵測閒置的 FIDL 連線

您可以使用 detect_stall::until_stalled 轉換 Rust FIDL 要求串流會意外解除 FIDL 端點的繫結狀態, 連線閒置超過指定的逾時時間。您必須將元件新增到 查看瀏覽權限清單:src/lib/detect-stall/BUILD.gn參閱 API 說明文件 並進行各種測試以下是 http-client.cm 使用此功能的方式:

async fn loader_server(
    stream: net_http::LoaderRequestStream,
    idle_timeout: fasync::Duration,
) -> Result<(), anyhow::Error> {
    // Transforms `stream` into another stream yielding the same messages,
    // but may complete prematurely when idle.
    let (stream, unbind_if_stalled) = detect_stall::until_stalled(stream, idle_timeout);

    // Handle the `stream` as per normal.
    stream.for_each_concurrent(None, |message| {
        // Match on `message`...
    }).await?;

    // The `unbind_if_stalled` future will resolve if the stream was idle
    // for `idle_timeout` or if the stream finished. If the stream was idle,
    // it will resolve with the unbound server endpoint.
    //
    // If the connection did not close or receive new messages within the
    // timeout, send it over to component manager to wait for it on our behalf.
    if let Ok(Some(server_end)) = unbind_if_stalled.await {
        // Escrow the `server_end`...
    }
}

偵測閒置的傳出目錄

您可以使用 fuchsia_component::server::ServiceFs::until_stalled 方法, 將 ServiceFs 轉換成解除傳出目錄伺服器的繫結 端點 (如果檔案系統沒有工作的話)。使用 API 以及說明文件和測試以下是 http-client.cm 使用此功能的方式:

#[fuchsia::main]
pub async fn main() -> Result<(), anyhow::Error> {
    // Initialize a `ServiceFs` and add services as per normal.
    let mut fs = ServiceFs::new();
    let _: &mut ServiceFsDir<'_, _> = fs
        .take_and_serve_directory_handle()?
        .dir("svc")
        .add_fidl_service(HttpServices::Loader);

    // Chain `.until_stalled()` before calling `.for_each_concurrent()`.
    // This wraps each item in the `ServiceFs` stream into an enum of either
    // a capability request, or an `Item::Stalled` message containing the
    // outgoing directory server endpoint if the filesystem became idle.
    fs.until_stalled(idle_timeout)
        .for_each_concurrent(None, |item| async {
            match item {
                Item::Request(services, _active_guard) => {
                    let HttpServices::Loader(stream) = services;
                    loader_server(stream, idle_timeout).await;
                }
                Item::Stalled(outgoing_directory) => {
                    // Escrow the `outgoing_directory`...
                }
            }
        })
        .await;
}

等待其他背景商業邏輯

ServiceFs 產生 Item::Stalled 訊息。如果您有一點背景 可避免元件停止,但 ServiceFs 已成為 同時處於閒置狀態,且已提前解除傳出目錄繫結 端點如要處理這些情況,您可以防止 ServiceFs 進入閒置狀態ServiceFs 產生的 Item::Request 包含 ActiveGuard。只要主動守衛在範圍內, ServiceFs 不會處於閒置狀態,且會繼續產生能力要求,因為 來讓他們進入

同樣地,您可以建立 ExecutionScope, 與處理 FIDL 連線相關的背景工作 ExecutionScope::wait()等待作業完成。舉例來說, 直到那之前,http-client.cm 中的 loader_server 函式才會傳回 背景工作完成,這樣 active_guard 就會保存在 Item::Request 範圍內,導致 ServiceFs 無法停止。

由 Escrow 控制法和狀態授予架構

一旦連線處於閒置狀態,且程式庫會提供未繫結的伺服器 下一步就是託管這些帳號代碼,也就是將帳號代碼傳送到 以便安全維護

無狀態通訊協定

部分 FIDL 連線沒有狀態。每個要求的作用相同 透過相同或不同的連線傳送 您可以為這些連線執行下列步驟:

  • 如果尚未在元件資訊清單中宣告能力,請先宣告。您可能需要 宣告能力,如果這個通訊協定連線是衍生自其他 連線,而且通常不會從傳出目錄提供。

  • 在宣告能力時新增 delivery: "on_readable"。您必須新增 並將元件設為 delivery_type 瀏覽權限清單 tools/cmc/build/restricted_features/BUILD.gn。架構 然後監控伺服器端點的可讀訊號 建立新的連線要求,將伺服器端點連線至供應商 元件。範例:

    capabilities: [
        {
            protocol: "fuchsia.net.http.Loader",
            delivery: "on_readable",
        },
    ],
    
  • self 新增使用宣告,讓程式能力 會從傳入的命名空間對該命名空間進行連線您可以在 /escrow 目錄,以便與其他函式 元件。範例:

    {
        protocol: "fuchsia.net.http.Loader",
        from: "self",
        path: "/escrow/fuchsia.net.http.Loader",
    },
    
  • 從傳入的命名空間連線至能力,並傳遞未繫結 伺服器端點:detect_stalled::until_stalled

    if let Ok(Some(server_end)) = unbind_if_stalled.await {
        // This will open `/escrow/fuchsia.net.http.Loader` and pass the server
        // endpoint obtained from the idle FIDL connection.
        fuchsia_component::client::connect_channel_to_protocol_at::<net_http::LoaderMarker>(
            server_end.into(),
            "/escrow",
        )?;
    }
    

總體來說,這代表元件架構會監控閒置連線 然後將功能傳回元件 這種做法如果元件已停止,系統會啟動元件。

傳出目錄

我們必須使用其他 API 來託管主要傳出目錄連線 (也就是 Item::StalledServiceFs 傳回的回應),因為該伺服器 端點是其他所有連線都會傳送至 元件。對於 ELF 元件,您可以將傳出目錄傳送至 透過 fuchsia.process.lifecycle/Lifecycle.OnEscrow FIDL 事件建立架構:

  • lifecycle: { stop_event: "notify" } 新增至元件 .cml

    program: {
        runner: "elf",
        binary: "bin/http_client",
        lifecycle: { stop_event: "notify" },
    },
    
  • 使用生命週期編號控制代碼,將其轉換成 FIDL 要求串流,然後 使用 send_on_escrow 傳送事件:

    let lifecycle =
        fuchsia_runtime::take_startup_handle(HandleInfo::new(HandleType::Lifecycle, 0)).unwrap();
    let lifecycle: zx::Channel = lifecycle.into();
    let lifecycle: ServerEnd<flifecycle::LifecycleMarker> = lifecycle.into();
    let (mut lifecycle_request_stream, lifecycle_control_handle) =
        lifecycle.into_stream_and_control_handle().unwrap();
    
    // Later, when `ServiceFs` has stalled and we have an `outgoing_dir`.
    let outgoing_dir = Some(outgoing_dir);
    lifecycle_control_handle
        .send_on_escrow(flifecycle::LifecycleOnEscrowRequest { outgoing_dir, ..Default::default() })
        .unwrap();
    

    元件傳送 OnEscrow 事件後,就無法 監控更多能力要求因此,系統應該會立即結束遊戲。 下次執行時,您的元件會回傳啟動資訊 先前執行時傳出的同一個 outgoing_dir 控制代碼。

    請參閱 http-client,瞭解上述所有項目的排列方式。

有狀態通訊協定和其他重要狀態

fuchsia.process.lifecycle/Lifecycle.OnEscrow 事件需要另一個引數 具備 escrowed_dictionary client_end:fuchsia.component.sandbox.DictionaryDictionary 物件的參照。字典: 可能存放資料或功能的鍵/值對應。

  • 您可以使用 fuchsia.component.sandbox.Factory 建立 Dictionary 並在 Factory 通訊協定上呼叫 CreateDictionary

    use: [
        {
            protocol: "fuchsia.component.sandbox.Factory",
            from: "framework",
        }
    ]
    
    let factory =
        fuchsia_component::client::connect_to_protocol::<
            fidl_fuchsia_component_sandbox::FactoryMarker
        >().unwrap();
    let dictionary = factory.create_dictionary().await?;
    
  • 若要將一些資料 (例如位元組向量) 新增至 Dictionary,請呼叫 Dictionary FIDL 連線上的 Insert。詳情請參閱 fuchsia.component.sandbox FIDL 程式庫說明文件 其他方法:

    let bytes = vec![...];
    let data = fidl_fuchsia_component_sandbox::Data::Bytes(bytes);
    let dictionary = dictionary.into_proxy().unwrap();
    dictionary
        .insert(
            "my_data",
            fidl_fuchsia_component_sandbox::Capability::Data(data)
        )
        .await??;
    
  • 退出前,在 send_on_escrow 中傳送 Dictionary 用戶端端點:

    lifecycle
        .control_handle()
        .send_on_escrow(flifecycle::LifecycleOnEscrowRequest {
            outgoing_dir: Some(outgoing_dir),
            escrowed_dictionary: Some(dictionary.into_channel().unwrap().into_zx_channel().into()),
            ..Default::default()
        })?;
    
  • 下次啟動時,您可以從新創公司控制代碼取得這個字典:

    if let Some(dictionary) = fuchsia_runtime::take_startup_handle(
        HandleInfo::new(HandleType::EscrowedDictionary, 0)
    ) {
        let dictionary = dictionary.into_proxy()?;
        let capability = dictionary.get("my_data").await??;
        match capability {
            fidl_fuchsia_component_sandbox::Capability::Data(
                fidl_fuchsia_component_sandbox::Data::Bytes(data)
            ) => {
                // Do something with the data...
            },
            capability @ _ => warn!("unexpected {capability:?}"),
        }
    }
    

Dictionary 物件支援多種項目資料類型。如果您的 元件的狀態小於 fuchsia.component.sandbox/MAX_DATA_LENGTH 可以考慮儲存 fuchsia.component.sandbox/Data 項目 可容納位元組向量

我想等待管道可讀取

停止之前,如果想讓元件架構 等到管道內容清晰可讀後,再將管道轉達給 元件,您可以使用相同的 delivery: "on_readable" 技術。這個 一般化成元件不會公開的 FIDL 通訊協定,例如 服務成員甚至支援不採用 FIDL 通訊協定的管道。阿斯 例如,假設您的元件擁有 Zircon 例外狀況管道 指示架構先等可讀取的管道 元件,您可以宣告下列 .cml

capabilities: [
    {
        protocol: "exception_channel",
        delivery: "on_readable",
        path: "/escrow/exception_channel",
    },
],
use: [
    {
        protocol: "exception_channel",
        from: "self",
        path: "/escrow/exception_channel",
    }
]

請注意,exception_channel 能力並未公開。這項能力 用於元件本身元件可能會開啟 /escrow/exception_channel 與要等待的管道連線。建立管道後 讀取架構時,架構會在傳出時/escrow/exception_channel開啟 目錄,如有需要,請啟動元件。總而言之,您可以宣告 功能,並使用 self 中的控點向 component_manager 託管控制代碼。

如需其他類型的資料,請與元件架構團隊聯絡 觸發事件,例如等待自訂信號或等待計時器。

測試

建議您加強現有的整合測試,以便同時測試 元件可自行停止並重新啟動,而不會中斷 FIDL 連線。 如果您已有整合測試來啟動元件並傳送 FIDL 要求收到 FIDL 要求,您可以使用元件事件比對器來驗證 在沒有任何訊息時,元件就會停止。詳情請參閱 http-client 測試會列出實際操作範例。

到達網頁和指標

如要為特定產品最佳化這項元件, 您可以為元件新增結構化設定,以控制是否顯示 閒置逾時。

元件架構會記錄元件的開始和停止時間長度 並上傳至 Cobalt您可能會在 資訊主頁,微調閒置逾時。

當擷取到意見回饋快照時,如 欄位,初始和最新元件執行作業的時間戳記將為 可在選取器「<component_manager>:root/lifecycle/early」和 <component_manager>:root/lifecycle/late。你也可以找出 事件記錄,協助您調查是否發生錯誤 以錯誤的方式停止元件