RFC-0023:通訊協定的組合模型

RFC-0023:通訊協定的組合模型
狀態已接受
區域
  • FIDL
說明

關鍵字介面已由關鍵字通訊協定取代。擴充通訊協定已明確指出組合模型,其中一個通訊協定可定義為一組訊息,並由一或多個其他通訊協定增強。用於通訊協定擴充功能的語法已從類似繼承的語法,變更為類似 mixin 的語法。

作者
提交日期 (年-月-日)2018-12-10
審查日期 (年-月-日)2019-01-09

摘要

我們建議做出以下變更:

  • 關鍵字「interface」已改為「protocol」。(我們會在本文件的其餘部分使用「通訊協定」一詞)。
  • 擴充通訊協定可用來表示組合模型,其中一個通訊協定可定義為一組訊息,並由一或多個其他通訊協定擴充。
  • 用於通訊協定擴充功能的語法已從類似於繼承的語法,變更為類似於混合的語法。
  • 當繫結作者以目標語言表示組合通訊協定時,必須避免使用子類別 (例如「is-a」階層、繼承、子類型)。

提振精神

介面一詞所帶來的上下文負擔,包括方法超載、建構函式和解構函式、物件模型做為訊息收件者等等。

不過,FIDL 的目標較為保守,旨在描述兩個同級裝置之間的通訊協定,也就是一組可交換的訊息。

我們在開始介紹 FIDL API 時,就會清楚說明這一點,例如指出「雖然語法類似物件導向介面的定義,但設計考量更類似於網路通訊協定,而非物件系統。」當我們有更多「類似以物件為導向」的功能可供選擇時,我們會避免採用這些功能 (例如最近在 RFC-0020:序號雜湊 的註解中,討論過載)。

我們希望這項差異在語言中更為明確,因此建議您將關鍵字 interface 替換為關鍵字 protocol,以便變更語法。

此外,借用繼承語法的「is-a」關係是不健全的,且會導致錯誤的預期。(為求明確,雖然 FIDL 並未提供這類繼承語意,但語法會建議這麼做)。詳情請參閱「是否為有害關係」一節。

設計

這項提案會引入正式語意,以便說明程序互動和通訊協定。

這項提案會變更 FIDL 原始碼語言,以便釐清通訊協定擴充功能的語意,並為繫結作者提供新的指引。

目前,繼承關係並未在 JSON IR 中呈現,因此無法由繫結作者加以利用。因此,除了改善文件之外,我們預計這項新指南對產生的繫結程式碼修改方式影響不大。

這項提案不會變更線路格式。

這項提案不會變更 JSON IR,但我們預期會在日後的重大變更中納入重命名項目。

通訊協定的模型

Zircon 管道不需要為所載酬載提供特定結構定義。FIDL 則是建立在這個原始類型之上,並限制管道只能傳輸特定通訊協定。如此一來,FIDL 就會為管道的兩端提供意義和名稱。我們將其中一個稱為「用戶端」,另一個稱為「伺服器」

我們的模型將通訊協定描述為一組導向交互作用,並提供選用的碑文。我們稱工作階段為用戶端與伺服器之間使用通訊協定進行通訊的特定例項。

方向可以是從用戶端到伺服器,或從伺服器到用戶端

互動會從要求開始,並可能需要回應。我們通常會使用「fire and forget」或「one way」一詞來表示無回應的互動,而「call」一詞則是指需要回應的要求。

要求和回應都是訊息,以標頭表示,後面接著結構體的酬載,也就是要求或回應的引數

目前,我們限制伺服器對用戶端訊息不含回應。簡單來說,事件只會觸發即棄用。

epitaph 是指結束工作階段的伺服器對用戶端互動。詳情請參閱 RFC-0053:墓誌銘

這個模型沒有更複雜的互動,例如 TCP 的 SYN/SYN-ACK/ACK 三方握手。我們認為這超出範圍,日後改良模型時也不太可能納入這項功能。

組合模型

目前,通訊協定可定義互動,也能擴充一或多個通訊協定。產生的通訊協定 (「組合通訊協定」) 會直接載入所有互動,並繼承其前置詞 (直接或間接) 定義的所有互動。

例如,Child 通訊協定定義如下:

protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child { compose Parent1; compose Parent 2; Method3(); };

會包含所有三個互動:Method1Method2Method3

不過,無論 Method1Method2 是在 Child 中定義為組合結果,還是直接定義,都不會傳送至特定語言的後端,也就是說,這不會在 JSON IR 中顯示。

是否屬於有害的「是 A」關係

由於通訊協定可在兩個方向傳送要求,因此建立子類型關係時必須更加小心。因此,我們不允許協定與其擴充的協定建立「is a」關係。

舉例來說,假設我們有以下兩個通訊協定:

protocol Parent { Method(); };
protocol Child { ->Event(...); };

如果我們允許將載有通訊協定 Child 的管道視為 Parent (即「ChildParent」關係),我們會讓用戶端接收 Event,但用戶端無法處理 Event。如需具體範例,請參閱下一節

相反地,我們會支援特定的通訊協定註解,例如「僅限用戶端與伺服器互動」,以便支援及允許「is a」關係。發生這種情況時,這些關係會傳送至 JSON IR,以便後端使用。

依賴「Is A」關係

舉例來說,fuchsia.media 程式庫會將各種通訊協定組合在一起。請特別注意以下幾點:

AudioCapturerAudioRenderer 都不會定義事件,也就是說,這些都是純粹的「用戶端對伺服器通訊協定」,也就是單向通訊。(StreamSource 定義了兩個事件,但我們在此特別討論各個通訊協定的定義)。

因此,如果用戶端知道如何與 StreamBufferSetStreamSource 互動 (分別為 StreamBufferSetStreamSink),則也能與 AudioCapturer (以及 AudioRenderer) 互動,也就是說,用戶端會直接忽略公開的額外方法。我們可以在此定義預期的「is a」關係。

不過,如果要將事件新增至任一介面,這項「is a」關係就會消失。假設用戶端正在與 StreamBufferSet 互動,而這其實是伺服器端的 AudioRenderer。如果 AudioRenderer 觸發事件,會發生什麼事?那麼客戶會如何處理?

由於我們 (目前) 無法在 fidlc 中提供這項區別,因此我們確認不支援「is a」關係。這項提案基本上是釐清現況。

如同 fuchsia.media 的情況,如果作者知道某些關係是正確的,就可以根據需求調整繫結 (例如使用轉換等)。

在後續提案中,我們預計會引入屬性或新關鍵字,以便擷取這個方向性限制,並根據此限制,在繫結中提供「is a」關係。在提出這類提案之前,我們無法提供更完善的 FIDL 工具鍊支援。

語法變更

在設計階段,我們提出了幾種不同的替代方案,請參閱下方的缺點、替代方案和未知事項

使用接受的語法的擴充通訊協定如下所示:

protocol Parent1 {
  Method1OfParent1();
  Method2OfParent1();
};

protocol Parent2 {
  Method1OfParent2();
  Method2OfParent2();
};

protocol Child {
  compose Parent1;
  compose Parent2;
  Method1OfChild();
  Method2OfChild();
};

正式的文法變更如下:

declaration = const-declaration | enum-declaration | protocol-declaration |
              struct-declaration | union-declaration | table-declaration ;

protocol-declaration = ( attribute-list ) , "protocol" , IDENTIFIER ,
                       "{" , ( method-or-compose-declaration , ";" )*  , "}";

method-or-compose = method-declaration | compose-declaration ;

method-declaration = ( ordinal , ":" ) , method-parameters ;

method-parameters = IDENTIFIER , parameter-list , ( "->" , parameter-list )
                     | "->" , IDENTIFIER , parameter-list ;

compose-declaration = "compose", compound-identifier ;

您只能提及一個組合通訊協定。

可能的擴充功能

我們預計後續提案將進一步允許伺服器與用戶端的互動不必要求回應,進而啟用管道上的多重通訊協定,甚至可在相反的順序中啟用。舉例來說,coordinator.fidl 定義了兩個指令回應通訊協定,一個是從 devmgr -> devhost,另一個是從 devhost -> devmgr。目前,這些項目是手動混合,並依序調度,以便分辨哪個項目為哪個項目。

我們可能會在組合區塊中使用「->" 語法,以便日後在相反方向引入 muxing。另一個做法是,只有在擴充功能包含反向通訊協定時才要求明確的方向,這樣就能避免在目前不導入任何方向語法,因為我們會將使用反向通訊協定的擴充功能延後。

我們允許將組合區塊放在通訊協定定義的任何位置,也允許使用多個組合區塊。我們也可以只使用一個區塊,並要求該區塊位於頂端。我們選擇採用開放式做法,並依據自動格式和/或樣式指南提供建議,而非在語言本身中強制執行。

JSON IR

我們不會在本次變更中變更 JSON IR。

相反地,我們會將「interface_declarations」鍵重新命名為「protocol_declarations」,並納入更大規模的變更。這項較大的變更需要採用多步驟做法,將結構定義版本從 0.0.1 提升至 0.0.2,並提供後端適應的過渡期。

在距離上發生中斷,以及使用 [FragileBase]

這項提案不會影響遠端中斷的可能性狀態,因此我們重申,對於任何要擴充的通訊協定,都應使用 [FragileBase]1

綁定作者指南

  • 在目標語言中表示組合通訊協定時,繫結必須避免包含子類別 (例如「is-a」階層、繼承、子類型)。
  • 接收不明序數應為錯誤。繫結應將此錯誤提報為「不明的序號錯誤」,並關閉管道。

導入策略

三個步驟:

  1. 新增語法支援
  2. 將所有 FIDL 檔案轉換為使用新語法的檔案。
  3. 停止支援舊語法。

人體工學

這項變更可讓 FIDL 更容易理解,請參閱「動機」一節。這項異動可能無法讓 FIDL 更容易理解,但可避免日後產生誤解,以及預期不一致的情況。

說明文件和範例

我們需要更新語言、文法、評分標準和其他類似文件。

回溯相容性

這項變更會破壞目前使用繼承功能的 FIDL 檔案與原始碼的相容性。如實作一節所述,我們會分階段推出新語法、遷移所有 FIDL 檔案,然後移除對舊語法的支援。

這項變更不會變更 FIDL 線路格式,因此是具備回溯相容性的 ABI 變更。

成效

不會影響效能。

安全性

我們可能可以利用更嚴格的類型語意,來保護管道或觀察管道。這並非本提案的目標,也不會導致現狀惡化,甚至可以說會改善現狀。

測試

您可以完全透過單元測試,在 fidlc 層級測試這項變更。

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

以下各節將記錄設計階段提出的替代語法。

其他語法 (pascallouis@)

範例

protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child {
  compose {
    -> Parent1();
    -> Parent2();
  };
  Method1OfChild();
}

附註:這是原先建議的語法。compose 區塊似乎不自然,且與現有語言相差太多。這讓編寫多個通訊協定成為首選做法,而編寫單一通訊協定則會讓程式碼變得冗長。我們也不清楚是否允許多個 compose 區塊,以及這會產生什麼結果。最後,我們選擇在通訊協定中移除方向性「->」指標,改為在日後推出這項功能,並搭配多向多路復用 (如果考慮推出這類功能的話)。

替代語法 (jeremymanson@)

原因:為了清楚說明我們預期要實作的一系列方法,與定義通訊協定的一系列方法之間的差異:

範例

protocol Parent1 {
  Method1OfParent1();
  Method2OfParent1();
};

protocol Parent2 {
  Method1OfParent2();
  Method2OfParent2();
};

interface Child {
  compose {
    -> Parent1();
    -> Parent2();
  };
  Method1OfChild();
};

注意:「interface」關鍵字表示每個方法都必須有實作項目,「protocol」關鍵字則表示符合通訊協定和整合通訊協定的介面所需的條件。舉例來說,我們不一定會預期 StreamSource 會有自己的實作項目。這可讓我們進一步避免實作繼承,因為不會發生任何繼承。您無法將介面組合到另一個介面。

替代語法:類似 Go 的介面組合 (proppy@)

原因:不像是繼承,而是熟悉 Golang 語法,用於介面嵌入

範例

protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child {
    Parent1;
    Parent2;
    Method3();
};

附註:Go 語言規格,適用於介面和嵌入。

其他語法:使用宣告 (jeffbrown@)

原因:不像是繼承,而是重複使用現有關鍵字,用來表示將名稱帶入範圍。不太可能與方法宣告或「property2」混淆。

範例

protocol Parent1 { Method1(); };
protocol Parent2 { Method2(); };
protocol Child {
    using Parent1;
    using Parent2;
    Method3();
};

附註:FIDL、C++、Rust 和其他語言中的先例。

替代關鍵字

compose」關鍵字的替代方案:

  • extends (pascallouis@)
  • contains (smklein@)

既有技術與參考資料

沒有具體的內容。

Cap'n Proto 提供支援繼承的介面,包括多重繼承 (以混合模組的形式)。


  1. 在方法中導入序數雜湊,再加上在未來提案中將方法序數從 32 位元變更為 64 位元,這麼一來,這種距離的損毀情況 (在實際情況下) 就可能不存在,屆時我們也會重新審視 FragileBase 的用法。 

  2. 屬性:假設的 FIDL 擴充功能,可協助觀察 / 資料繫結。大致來說,繫結會產生方法,用於存取、修改和/或觀察介面公開的值。