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

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

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

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

摘要

我們建議進行下列變更:

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

提振精神

「介面」一詞附帶的情境包袱包括方法多載、建構函式和解構函式、做為訊息接收者的物件模型等。

不過,FIDL 的目標較為簡單,旨在描述兩個對等點之間的「通訊協定」,也就是一組可交換的訊息。

我們在 FIDL API 的開頭就明確指出這一點,例如「雖然語法類似於物件導向介面的定義,但設計考量更接近網路通訊協定,而非物件系統。」當面臨引入更多「類似物件導向」功能的選項時,我們選擇避開 (例如最近在 RFC-0020:序數雜湊中關於多載的評論)。

我們希望在語言中更清楚區分這兩者,並建議將關鍵字 interface 替換為關鍵字 protocol,藉此變更語法。

此外,借用繼承語法所隱含的「is-a」關係並不健全,會導致不正確的預期。(為求明確,FIDL 不提供這類繼承語意,但語法會建議這麼做)。詳情請參閱「『Is A』關係是否會造成危害」一節。

設計

這項提案導入正式語意,用於描述程序互動和通訊協定。

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

目前,JSON IR 不會呈現繼承關係,因此繫結作者無法利用這項資訊。因此,除了改善說明文件外,我們預期這項新指南對產生的繫結程式碼不會有太大影響。

這項提案不會變更傳輸格式。

這項提案不會變更 JSON IR,但我們預計在日後進行更大幅度的變更時,加入金鑰重新命名。

通訊協定模型

Zircon 通道不需要為其攜帶的酬載指定特定結構定義。FIDL 以這個原始物件為基礎,並限制管道只能傳送特定通訊協定。這樣一來,FIDL 就能為管道的兩端賦予意義和名稱。一個稱為「用戶端」,另一個稱為「伺服器」。

我們的模型將「通訊協定」描述為「一組導向互動」,並可選擇加入「墓誌銘」。我們將「工作階段」定義為用戶端與伺服器之間使用通訊協定進行通訊的特定執行個體。

方向可以是「從用戶端到伺服器」或「從伺服器到用戶端」。

互動會從要求開始,並視需要要求回覆。我們通常會使用「fire and forget」或「one way」來表示無回應的互動,並使用「call」來表示預期回應的要求。

要求和回應都是「訊息」,以標頭表示,後面接著 struct 的酬載,即要求或回應的「引數」。

目前,我們限制伺服器對用戶端訊息的回應。簡單來說,「事件啟動後便不須控制」。

墓誌銘是伺服器與用戶端之間的互動,用於結束工作階段。詳情請參閱 RFC-0053:Epitaphs。

這個模型不包含更複雜的互動,例如 TCP 的 SYN/SYN-ACK/ACK 三向握手。我們認為這超出範圍,且未來對模型進行調整時,也不太可能涵蓋這項內容。

組合模型

現在,通訊協定可以定義互動,也可以擴充一或多個通訊協定。產生的通訊協定 (「組合通訊協定」) 會包含所有直接定義的互動,並繼承前置項 (直接或間接) 定義的所有互動。

舉例來說,Child 通訊協定的定義如下:

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

會包含 Method1、Method2 和 Method3 這三種互動。

不過,Method1 和 Method2 是透過組合結構或直接在 Child 中定義,並不會傳送至特定語言的後端,也就是說,這不會在 JSON IR 中表示。

「Is A」關係有害

由於通訊協定可以雙向傳送要求,因此建立子類型關係時需要更加謹慎。因此,我們不允許通訊協定與其擴充的通訊協定具有「is a」關係。

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

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

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

相反地,我們將尋求支援特定通訊協定註解,例如「僅限用戶端與伺服器互動」,以支援並允許「是」關係。發生這種情況時,這類關係會傳送至 JSON IR,供後端使用。

目前對「Is A」關係的依賴

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

AudioCapturer 和 AudioRenderer 都不會定義事件,也就是說,這些純粹是「用戶端對伺服器通訊協定」,屬於單向通訊協定。(StreamSource 定義了兩個事件,但這裡我們特別討論每個通訊協定自己的定義。)

因此,如果用戶端知道如何與 StreamBufferSet 或 StreamSource (分別為 StreamBufferSet 或 StreamSink) 互動,則也可以與 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。目前,這些是手動多工處理,並依賴序數調度來排序。

我們可能會在 compose 區塊中使用「->」語法,以便稍後反向多工。另一種做法是,只有在擴充功能包含反向通訊協定時,才需要明確指定方向。這樣的好處是目前不會引入任何方向語法,因為我們會延後推出包含反向通訊協定的擴充功能。

我們允許將 compose 區塊放在通訊協定定義中的任何位置,也允許使用多個 compose 區塊。我們也可以只使用一個區塊,並要求這個區塊必須位於頂端。我們選擇開放,並依賴自動格式設定和/或樣式指南來提供建議,而不是將強制執行機制納入語言本身。

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@)

原因:看起來不像繼承,重複使用現有關鍵字來表示名稱已納入範圍。較不易與方法宣告或「屬性2」混淆。

範例:

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

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

替代關鍵字

「compose」關鍵字的替代方案:

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

既有技術和參考資料

沒有特定原因。

Cap'n Proto 的介面支援繼承,包括多重繼承 (採用 mixin 樣式)。


  1. 方法導入序數雜湊,加上未來提案中預計將方法序數從 32 位元提升至 64 位元的變更,可能會使這種遠端中斷 (在實際情況下) 不存在,屆時將重新審視 FragileBase 的使用。↩

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