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

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

這個 RFC 會標準化元件架構的事件功能。

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

摘要

本文件擷取了針對元件事件所做的設計解構、原則和決策:概念、資訊清單語法和 FIDL 通訊協定。

以下所述的多項決策已於 2020 年初制定並實施。其他概念在先前的決策中可以解決缺點和複雜性的問題。我們建議許多功能都納入許可清單,以免在現有用途外使用這些功能,同時設法將這些功能遷移至更完善的機制。

本文件範圍涵蓋以下元件架構 API:

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

提振精神

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

其中的元件事件是向這些具有特殊權限的元件公開此類資訊的解決方案。發生元件執行個體的生命週期轉換時,元件管理員會分派這些事件。功能 Route 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 取得 started 事件的 bar 事件,test-12345因為將其轉送至該事件。

  • archivist 可為 tests 底下所有元件取得 started 事件,因為 test_manager 是從 core 取得該事件的 started,而該函式是從 above root 取得,且在設定範圍內處於縮減狀態。root

  • test-12345 是測試根,可對其下所有元件啟動事件,但 archivist 有機會這麼做。然而,與封存者 (可從 tests 取得所有事件) 不同,由於 teststest-12345 的事件範圍縮小,因此只能取得測試的相關事件。

這個範例顯示了目前事件的核心用途之一。該組織能夠以獨立的方式觀察每項測試內部的情況。此外,每項測試都能取得測試中所有元件的事件,或特定特定元件的事件。

合併事件串流

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

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

目前不允許自行公開或提供活動。然而,這裡仍有成長空間,可讓事件公開/提供事件本身分派的自訂事件。

事件模式

在撰寫本文件時,您可以透過非同步或同步方式擷取事件。意圖是「只」擁有非同步事件,並完全淘汰同步處理事件。

同步取用事件可讓訂閱者在處理事件時封鎖元件管理員,然後繼續作業。這項功能在初期進行測試,同時考量偵錯工具。

從那時起,我們已瞭解使用同步處理事件的測試大致上可以使用非同步事件。因此,開發人員需要設法完全消除同步處理事件,但元件管理員內部測試中仍有幾項用途。我們認為同步事件在偵錯工具 (例如步驟或 zxdb) 中會很實用,但屆時我們將開發出能準確滿足偵錯工具需求的解決方案。

本提案是將使用同步處理事件的測試加入許可清單,並徹底刪除同步處理事件。

事件類型

在撰寫本文件時,我們會有兩個事件類別:

  • 生命週期事件。這些事件會反映元件執行個體生命週期中的變更,並由管理這類資訊的元件管理員發出。
  • 已淘汰的事件。這些事件不會反映元件執行個體生命週期中的變更,我們正設法將其完全移除,改為使用更合適的解決方案。

我們的生命週期事件類型如下:

  • Discovered:這是元件生命週期的第一個階段。在動態子項建立時分派給靜態子項,在父項解決時分派給靜態子項,在元件管理員啟動時分派到根子項。
  • Resolved:執行個體宣告成功首次解析。
  • Started:這個執行個體已啟動 (根據元件管理員)。不過,如果是可執行元件,執行器就必須進一步完成元件啟動作業。元件即將開始執行,但尚未開始執行程式碼。
  • Stopped:已成功停止執行個體。這個事件必須在刪除前發生。
  • Destroyed:已開始刪除執行個體。此時執行個體已停止。執行個體仍存在於父項領域中,但不久後就會移除。
  • Purged:已成功刪除執行個體。執行個體已停止,並不再存在於父項領域中。

下列已淘汰的事件類型:

  • Running:這個事件會由元件管理員針對訂閱當下執行的所有元件合成。此事件衍生自 started,但如果是在監聽事件監聽器之前啟動 (且未停止) 的元件。最後,我們的目標是讓 Component Framework Query API 瞭解執行中的元件,因此,我們打算將這個事件加入許可清單給這個活動唯一的用戶端「架構師」,日後再使用新的 API 並移除 running

  • Capability Routed:這是用於測試的事件。這項功能最近已從使用這項工具中移除,我們也正在著手完全移除。

  • Capability Requested:此事件是暫時性的解決方案,可為 fuchsia.logger/LogSink 連線提供元件歸因。從此開始,這些資料也可用於提供 fuchsia.debugdata/Publisher 連線的歸因資訊。這並不是長期可行的解決辦法由於事件系統僅適用於具有特殊權限的元件,因此使用元件事件建構這項功能是一種低承諾度的方法。事實證明,如果用途不斷成長,時間就能發動更標準化的解決方案。目前的計畫是徹底移除這個事件,同時將事件加入許可清單,將其加入許可清單的用戶端:Archivist、偵錯資料,以及測試管理工具 (針對偵錯資料)。

  • Directory Ready:這個事件想做為解決方案,用於提供元件公開的 out/diagnostics 目錄供 Archivis 檢查,以便檢查資料匯總作業。診斷團隊已計劃設計受 VMO 支援的記錄。由於在元件開始提供診斷目錄之前,必須先取得記錄,因此這個方法已過時。將 VMO 提供檢查和記錄 VMO 的新解決方案將能保證在元件啟動非同步迴圈之前,甚至可以取得記錄。目前提案是將這個事件加入許可清單,讓該事件僅開放給其擁有的使用者 (架構師),並努力完全移除。

轉送 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:透過架構提供事件時,範圍允許定義事件相關子項 (或子項陣列)。如未指定範圍,則範圍是本身,這表示事件與元件本身有關。從父項提供事件時,範圍允許將事件縮小為子項範圍。
  • 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 中提供這些 API:

元件管理員會提供 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 通訊協定會明確轉送,因為事件與整個子樹狀結構有關。這個通訊協定現在是內建能力,事件則與特定元件有關。整個子樹狀結構的事件會明確轉送,並可執行靜態驗證,以確保無特殊權限的元件不會收到這些事件。

測試

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

說明文件

如需更新說明文件,請前往事件功能元件資訊清單頁面。

缺點、替代方案和未知

從元件提供 EventSink 能力到架構

在這個想法中,元件會將通訊協定 RealmEventSink 公開給架構。架構會接著開啟這項能力,並在發生事件時將其推送到中。本提案無法定義元件可取得事件的範圍,而且沒有明確的路徑限制這類範圍,或是能夠靜態驗證元件是否無法從不應有瀏覽權限的拓撲中取得事件。在目前的設計下,您可以透過靜態方式表達特定元件組合 (適用於一般用途) 或拓撲中的整個子樹狀結構 (針對實際測試和測試中的診斷用途)。

公開聲明與提供轉送聲明的自我來源

selfexpose/offer 事件會保留為開放區域,方便日後針對元件公開及分派的自訂事件設計工作。

先前的圖片和參考資料

如需事件目前狀態的說明文件,請參閱事件功能事件 FIDL 定義