RFC-0121:元件生命週期事件

RFC-0121:元件生命週期事件
狀態已接受
區域
  • 元件架構
說明

這項 RFC 正式確立了元件架構的事件功能。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-05-26
審查日期 (年-月-日)2021-08-11

摘要

本文記錄了有關元件事件的設計考量、原則和決策,包括概念、資訊清單語法和 FIDL 通訊協定。

以下許多決策都是在 2020 年初制定及實施。 此外,我們也提出其他概念,以解決先前決策的缺點和複雜性。許多功能都建議加入允許清單,以免在我們努力將這些功能遷移至更完善的機制時,這些功能超出目前的使用情境。

本文件涵蓋下列元件架構 API:

  • CML:用於編寫元件資訊清單的語言
  • fuchsia.sys2/events.fidl:目前 Component Events FIDL API 所在的 API 介面。目標是將所有事件 API 升級至 fuchsia.component/events.fidl,並在 SDK 中提供這些 API。

提振精神

元件管理工具會在內部處理元件的生命週期,不會向元件公開這項資訊。部分具備特殊權限的用戶端 (例如診斷、測試、元件監管員、偵錯工具) 需要深入瞭解元件生命週期,才能執行工作。

為向這些具備特殊權限的元件公開這類資訊,我們推出了元件事件解決方案。當元件執行個體發生生命週期轉換時,元件管理員會調度所有這些事件。在本文中提及的某些位置,功能路徑 API 仍處於開放狀態,以便進一步設計,讓元件自行公開及提供自訂事件。

設計

本節將說明元件事件的現行設計和 API 修訂內容,並強調現行設計並非長期設計,以及可設計及實作的未來功能。

元件事件串流功能

元件事件會以「事件串流」功能為模型。每個事件串流能力都代表拓撲中的單一元件或元件子樹狀結構。撰寫本文時,事件會視為個別事件功能 (而非事件串流)。發生生命週期轉換時,元件管理工具會代表元件發出事件。

與任何能力一樣,事件串流可以進行路徑設定。事件串流的狀態如下:

  • 架構公開/提供的項目:事件是指公開或提供事件的元件。

  • 用於父項/子項:元件可以監聽從子項或父項傳送給它的事件。元件的父項可以直接使用子項的生命週期事件串流,不必由子項從架構明確公開事件。

來自上述根目錄的事件串流

根領域可使用/提供「父項」的事件串流。您可能會想,如果根領域是拓樸的根,那麼根領域的父項是什麼?在元件管理員術語中,元件管理員提供給根領域的功能,據說來自「根以上」。

從「高於根層級」到根層級領域提供的事件串流,範圍涵蓋整個元件執行個體拓撲。這些事件串流在路由時,可以縮減範圍,只參照拓撲的子樹狀結構。這與目錄功能可做為整體或子目錄 (目錄的路由宣告中的 subdir 鍵) 路由傳送的方式類似。如要監聽整個領域範圍的事件,唯一方法是取得源自根目錄的範圍縮減事件串流能力。涵蓋整個領域樹狀結構的事件屬於私密/敏感事件,會打破封裝界線,因此我們會從根目錄上方明確地轉送這些事件,透過靜態路由提供存取控制。

請參考以下範例:

下方顯示的聲明樹狀圖

// root.cml
{
  offer: [
    {
      event_stream: "started",
      from: "parent",
      to: "#core",
      scope: "#core"
    },
  ]
}

// core.cml
{
  offer: [
    {
      event_stream: "started",
      from: "parent",
      to: "#test_manager",
      scope: "#test_manager"
    },
  ]
}

// test_manager.cml
{
  offer: [
    {
      event_stream: "started",
      from: "parent",
      to: [ "#archivist", "#tests" ]
      scope: "#tests",
    }
  ]
}

// tests.cml
{
  offer: [
    {
      event_stream: "started",
      from: "parent",
      to: [ "#test-12345" ]
      scope: "#test-12345",
    }
  ]
}

// test-12345.cml
{
  offer: [
    {
      event_stream: "started",
      from: "framework",
      scope: "#bar",
      to: "#foo"
    }
  ],
  use: [
    {
      event_stream: "started",
      from: "parent"
    },
    {
      event_stream: "stopped",
      from: "framework",
      scope: "#bar",
    }
  ]
}

// foo.cml
{
  use: [
    {
      event_stream: "started",
      from: "parent"
    }
  ]
}

// archivist.cml
{
  use: [
    {
      event_stream: "started",
      from: "parent",
    }
  ]
}

在這個例子中:

  • foo 可以取得 barstarted 事件,前提是 test-12345 會將事件路徑傳送至 foo

  • archivist 可以取得 tests 底下所有元件的 started 事件,前提是 started 是從 test_manager 提供,而 test_manager 是從 core 取得,core 是從 root 取得,root 是從 above root 取得,且在過程中經過範圍縮減。

  • test-12345 (測試根) 可以取得所有子項元件的啟動事件,原因與 archivist 相同。不過,與可從 tests 取得所有事件的封存者不同,由於 tests 已將事件範圍縮小為 test-12345,因此只能取得與測試相關的事件。

這個範例顯示目前事件的核心用途之一。檔案管理員可以獨立觀察各項測試的內部情況。此外,每項測試都可以取得測試中所有元件的事件,或是特定元件的事件。

合併事件串流

您可以將相同類型的事件串流合併為單一串流。 舉例來說,在上述範例中,#test-12345 可以將 foobarstopped 提供給其他元件,做為單一能力。該元件隨後會取得 stoppedbarstopped 事件。foo

// core.cml
offer: [
  {
    event_stream: "stopped",
    from: [ "#netstack", "#supervisor" ],
    to: "#someone",
  }
]

目前無法自行曝光/提供活動。不過,這裡仍有成長空間,可讓事件公開/提供自行調度的自訂事件。

活動模式

撰寫本文時,事件可以非同步或同步方式擷取。目標是保留非同步事件,並完全淘汰同步事件。

同步處理事件可讓訂閱者在處理事件時封鎖元件管理員,然後繼續作業。這項功能最初是為了測試而推出,同時也考量到偵錯工具。

自此之後,我們發現一般來說,使用非同步事件即可編寫使用同步事件的測試。因此,我們已努力完全淘汰同步事件,但元件管理員內部測試仍會用到。據信同步事件在偵錯工具 (例如 step 或 zxdb) 中會很有用,但屆時我們會發明適合偵錯工具需求的解決方案。

提案是允許使用同步事件的測試,並逐步淘汰同步事件。

事件類型

撰寫本文時,我們有兩類事件:

  • 生命週期事件。這些事件會反映元件執行個體的生命週期變化,並由管理這類資訊的元件管理工具發出。
  • 已淘汰的事件。這些事件不會反映元件執行個體生命週期的變化,我們正努力完全移除這些事件,改用更合適的解決方案。

生命週期事件類型如下:

  • Discovered:這是元件生命週期的第一階段。動態子項建立時會分派,靜態子項的父項解析時會分派,元件管理員啟動時則會分派給根。
  • Resolved:系統首次成功解析執行個體的宣告。
  • Started:根據元件管理員的資料,這個執行個體已啟動。 不過,如果這是可執行的元件,執行元件還需要執行其他工作才能啟動元件。元件開始執行,但可能尚未開始執行程式碼。
  • Stopped:執行個體已成功停止。這個事件必須在「已毀損」之前發生。
  • Destroyed:執行個體已開始銷毀。此時執行個體已停止運作。執行個體仍存在於父項領域中,但很快就會移除。
  • Purged:執行個體已成功終止。執行個體已停止,且不再存在於上層領域中。

以及下列已淘汰的事件類型:

  • Running:元件管理員會為訂閱時已執行的所有元件合成這個事件。這個事件衍生自 started,但適用於在監聽器訂閱事件前啟動 (且未停止) 的元件。最終目標是建立元件架構查詢 API,瞭解正在執行的內容。因此,我們打算將這個事件加入許可清單,只允許其用戶端 Archivist 存取,並在日後使用新的 API 移除 running

  • Capability Routed:這個事件是為了在測試中使用而導入。我們最近已從使用該項目的測試中完全移除,目前正在進行全面移除作業。

  • Capability Requested:這項事件是暫時解決方案,可為 fuchsia.logger/LogSink 連線提供元件歸因。自此之後,這項技術也用於為 fuchsia.debugdata/Publisher 連線提供歸因。這項功能原本就不是長期解決方案。由於事件系統僅適用於具備特殊權限的元件,因此使用元件事件建構這項功能是低投入的做法。大家瞭解,如果用途增加,總有一天會需要發明更標準化的解決方案。目前的計畫是完全移除這個事件,同時將其加入允許清單,只開放給以下用戶端使用:Archivist、偵錯資料和測試管理員 (適用於偵錯資料)。

  • Directory Ready:這個事件的用途是提供解決方案,將元件公開的 out/diagnostics 目錄提供給 Archivist,以檢查資料匯總。診斷團隊計畫設計 VMO 支援的記錄檔。由於記錄必須在元件開始提供診斷目錄前就可供使用,因此這種方法已不適用。我們將設計新的解決方案,提供檢查和記錄 VMO 給 Archivist,確保即使元件尚未啟動非同步迴圈,記錄仍可供使用。目前提案是將這項事件加入允許清單,只允許擁有者 (封存者) 存取,並逐步完全移除。

路由 CML 語法

使用
{
    use: [
        {
            event_stream: [
                "running",
                "started",
                "stopped",
            ],
            from: "parent",
            mode: "async",
            path: "/my_stream"
        },
    ]
}

使用聲明包含:

  • event_stream:單一事件名稱或事件名稱清單。
  • from:能力來源。允許的值:父項或子項參照。不得使用架構或自身產生的事件。
  • path:提供事件串流的路徑。這項設定為選用。當元件的傳入命名空間包含服務檔案時,該檔案會具有元件管理工具服務的指定名稱 (請參閱「使用」)。fuchsia.component.EventStream如果未提供,元件可以使用 EventSource 在特定時間點開始取用事件。
  • scope:從父項使用事件時,scope 可用於將事件向下參照至特定子項範圍,否則事件會攜帶來自父項的範圍。
  • mode:預設為 async。如前所述,事件模式只會是非同步。因此,在完全淘汰模式之前,只有允許清單中的測試可以使用「同步」模式。這個欄位最終會完全消失。
  • filter:目前僅用於 DiagnosticsReadyCapabilityRequested。如前所述,這些事件將完全移除,因此不會提供篩選條件的詳細資料,因為這與非診斷開發人員無關。

您也可以使用來自不同來源的事件。在下列範例中,元件會取得單一事件串流,其中包含父項提供的 startedstopped 事件範圍,以及子項 #child 啟動時的 started

use: [
  {
    event_stream: [
      {
        name: [ "started", "stopped ],
        from: "parent",
      },
      {
        name: "started",
        from: "framework",
        scope: "#child",
      }
    ]
  }
]
優惠
{
    offer: [
        {
            event_stream: "started",
            from: [ "#child_a", "parent", ],
            to: "#archivist",
            as: "started_foo"
        },
    ]
}

優惠聲明包含:

  • event_stream:單一事件名稱或事件名稱清單。
  • scope:當架構提供事件時,範圍可讓您定義事件所屬的子項 (或子項陣列)。如果未指定範圍,範圍就是「self」,表示事件與元件本身有關。如果事件是由父項提供,範圍可將事件範圍縮小至子項範圍。
  • from:一或多個能力來源。如果提供多個來源,系統會將這些來源視為已合併。允許的值:架構、父項或子項參照。不允許提供自己的商品。
  • to:提供能力的子項參照。
  • as:能力的新名稱 (重新命名)。只有在提供單一事件串流名稱時,才能提供這項資訊。
公開
{
    expose: [
        {
            event_stream: "started",
            from: "framework",
            scope: [ "#child", "#other_child" ],
            as: "foo_started"
        },
    ]
}

公開聲明包含:

  • event_stream:單一事件名稱或事件名稱清單。
  • scope:從 framework 公開事件時,必須提供範圍,並允許定義事件所屬的子項 (或子項陣列)。
  • from:一或多個能力來源。如果提供多個來源,系統會將這些來源視為已合併。允許的值:父項或子項參照。不得公開自己的活動。
  • as:能力的新名稱 (重新命名)。只有在提供單一事件串流名稱時,才能提供這項資訊。

EventSource 通訊協定

EventSource 是一種通訊協定,可讓元件例項訂閱事件串流。這是任何元件都能從 framework 使用的內建能力,可接收明確傳送給元件的事件。

靜態事件串流

靜態事件串流可透過通訊協定,在傳入的 /events 目錄中取得事件。與透過 EventSource.Subscribe 執行的訂閱不同,事件可以緩衝,並在抵達時觸發元件的啟動

元件會在某個路徑宣告 use 事件串流,而元件管理工具會在指定路徑的傳入命名空間中建立項目,該項目是由元件管理工具提供服務 (請參閱「使用方式」一節)。fuchsia.component.EventStream

系統會在內部緩衝處理事件,直到執行 GetNext 呼叫為止。這個緩衝區的大小會在實作期間決定 (可能是大型緩衝區、下一批資料的最大大小或其他大小),並清楚記錄在 FIDL API 和事件文件中。如果緩衝事件數量超過定義的數量 (因為取用者速度太慢),元件管理員就會捨棄事件並關閉連線。元件隨後即可在準備好接收事件時重新連線。這是 Zircon 缺乏流量控制的解決方法。我們希望避免管道溢位,如果反轉提供 EventStream 通訊協定的對象 (元件而非元件管理員),就無法避免溢位,而且我們不希望在元件管理員中使用過多的記憶體。如果用戶端運作正常,並以穩定的速率取用事件,就不必擔心會遺漏事件。如果用戶端消耗事件的速度過慢,系統會通知用戶端事件已遭捨棄。這會透過串流中的事件發生,而使用者會從中取用資料。以下是執行這項通知的幾種替代方法:

  • 同類型的事件,但錯誤酬載會指出有多少這類型的事件遭到捨棄。
  • fuchsia.component.EventErrorPayload」底下的特殊欄位:dropped。這個欄位會包含 EventType 和一個數字,表示已捨棄的該類型事件數量。
  • 通訊協定中的 FIDL 事件,用戶端會在事件遭到捨棄時收到,其中會指定捨棄的事件數量和類型。不過,這個替代方案可能會引入另一個 DoS 向量,因為管道可能會填滿事件。

FIDL API

撰寫本文時,事件的取用通訊協定定義於 fuchsia.sys2/events.fidl。這項提案引進了幾項變更,目的是將通訊協定和結構升級至 fuchsia.component,並在 SDK 中提供這些項目:

元件管理工具會提供 EventStream,而非元件

這表示變更 EventSource#Subscribe 以接受要求:

[Discoverable]
protocol EventSource {
    Subscribe(vector<EventSubscription> events, request<EventStream> stream)
        -> () error fuchsia.component.Error;
};

EventStream.GetNext 提供一批事件

如先前章節所述,由於元件管理工具現在會提供通訊協定並批次處理事件,因此新的串流通訊協定會在 GetNext 上傳回一批事件,而非單一事件。

protocol EventStream {
    GetNext() -> vec<Event>;
};

TakeStaticEventStream」已停用

如上一節所述,為了將事件串流直接放在傳入的命名空間中,我們移除了 EventSource.TakeStaticEventStream 方法。

更嚴格的 EventEventResult

撰寫本文時,這些資料類型都是資料表。不過,這些資料的定義非常明確,我們預計不會再新增資料。為改善人體工學,我們將移除選填欄位的處理方式,並將這些欄位設為 struct 和非彈性聯集:

struct Event {
    EventHeader header;
    EventResult event_result;
};


union EventResult {
    1: EventPayload payload;
    2: EventError error;
};

實作

大部分的設計都已實作。在 SDK 中公開這個 API 前,我們還需要實作一些位元。請特別注意以下幾點:

  • 允許的事件:directory readyrunningcapability requested
  • 將靜態事件串流放在傳入的命名空間中,並移除 EventStream.TakeStaticEventStream 方法。
  • 自今天起,我們會轉送 CML 更新 events,並希望轉送 event streams,以便在轉送中合併。
  • 提供從「根目錄上方」到根目錄的整個拓撲結構相關事件,並在事件路由宣告中實作 scope 關鍵字,允許拓撲結構中的子樹狀結構有事件。
  • 公開活動。撰寫本文時,我們僅支援 offeruse
  • 在來源中,事件現在是指單一元件,但從「根層級以上」提供給根領域的事件除外。事件在經過路徑設定後可以匯總,因此可能參照多個元件。

效能

事件系統是以元件管理員實作的現有內部掛鉤系統為基礎建構而成,因此將事件分派給感興趣的其他元件,對效能的影響微乎其微。事實上,由於現在如果元件只對單一元件的事件感興趣,而不是整個子樹狀結構,現在可以只分派該元件的事件,而不是整個子樹狀結構,因此減少了相關的系統呼叫次數,效能也獲得提升。

人體工學

這項 RFC 導入了事件人體工學的改善項目:

  • 如果元件只對單一元件事件感興趣,現在可以在 CML 中表示,無須編寫程式碼來篩選。
  • 所有元件現在都提供 EventSource 通訊協定做為內建架構能力,因此不需要明確路由傳送。
  • 系統會改為傳送單一能力 event stream,而不是傳送 events 和消耗 event streams
  • Event streams 可以在轉送時合併,減少開發人員需要在 CML 中編寫的轉送陳述式數量。

回溯相容性

這項變更不會破壞相容性。元件管理員層級 (以及根、核心、啟動領域) 會進行樹狀結構內微不足道的重構,但用戶端元件不會。此外,由於事件現在只會與單一元件相關,因此使用事件的測試 (全都樹狀結構內) 也會重構。

安全性和隱私權注意事項

這項提案維持了 2020 年安全性和隱私權審查中討論的安全屬性。先前,由於事件與整個子樹狀結構有關,因此系統會明確地將 EventSource 通訊協定路由傳送至該子樹狀結構。現在這個通訊協定是內建能力,事件則與特定元件有關。系統會明確地將整個子樹狀結構的事件路由傳送出去,並執行靜態驗證,確保非具備權限的元件不會收到這些事件。

測試

所有事件功能和語意都必須在元件管理員中進行整合測試。

說明文件

如要更新說明文件,請前往「Event Capabilities」和「Component Manifests」頁面。

缺點、替代方案和未知事項

從元件向架構提供 EventSink 能力

根據這個概念,元件會向架構公開通訊協定 RealmEventSink。架構接著會開啟這項能力,並在事件發生時將事件推送至其中。這項提案缺少定義元件從哪個範圍取得事件的方法,也沒有明確的路徑可限制這類範圍,或靜態驗證元件是否未從拓撲的一部分取得事件,而該部分不應具有可見度。根據目前的設計,您可以靜態表示要取得特定元件集 (適用於一般用途) 或拓撲中的整個子樹狀結構 (適用於生產環境和測試中的診斷用途) 的事件。

用於公開和提供路由宣告的自我來源

expose/offer 事件,self 則保留為開放區域,供日後設計元件可能公開及自行調度的自訂事件。

既有技術和參考資料

如要瞭解事件的目前狀態,請參閱「事件功能」和「事件 FIDL 定義」。