RFC-0057:預設無句柄 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 我們建議預設禁止在 FIDL 類型宣告中使用句柄,並新增關鍵字資源,用於標示可包含句柄或其他資源類型的類型。新增或移除資源修飾符可能會造成來源中斷的變更。 |
作者 | |
提交日期 (年-月-日) | 2020-01-16 |
審查日期 (年-月-日) | 2020-01-23 |
「媽媽,看!不用手就能開門了!」
摘要
我們建議預設禁止在 FIDL 類型宣告中使用句柄,並新增關鍵字 resource
1,用於標示可包含句柄或其他資源類型的類型。新增或移除 resource
修飾符可能會導致來源中斷的變更。
提振精神
FIDL 的獨特功能是支援Zircon 句柄。控點是記憶體中的 32 位元整數,但會以特殊方式處理:必須移動而非複製,且必須關閉,以免資源外洩。當您考慮只適用於沒有句柄的純資料的功能時,這種特殊處理方式會導致問題。雖然 FIDL 繫結可以根據句柄的存在情況有條件地啟用程式碼,但這麼做並不理想,因為這會違反可進展性保證。舉例來說,在資料表中新增欄位通常是安全的,但新增句柄欄位會導致來源中斷,不僅是該資料表,也包括所有會間接包含該欄位的類型。這會讓繫結變得保守,即使程式庫作者從未打算加入,也一律假設型別可能包含句柄。
為了配合手把,我們做出了妥協:
- 在 Dart 中,將 FIDL 實作為 JSON 編碼的努力遭到反對,因為這只適用於沒有句柄的類型,會影響可進化性。最終使用
MaxHandles
屬性建構,但這是暫時性解決方案,因為該屬性只適用於最外層型別,而非所有可透過該型別間接存取的型別。 - 在 Rust 中,首次將句柄新增至類型會導致來源中斷,因為該類型將不再衍生
Clone
特徵。(正確複製句柄需要叫用 zx_handle_duplicate 系統呼叫,但這可能會失敗)。 - 協定體系的 Rust 繫結會透過可變動參照取得 FIDL 物件,並將句柄設為零,而不是明確取得擁有權,因此之後可以重複使用沒有句柄的物件。
如果我們要求程式庫作者指出類型是否可能包含句柄,以及變更指示是否會導致來源中斷,則所有這些情況都能以更安全、更符合人體工學的方式處理。
設計
術語
FIDL 類型可能是值類型或資源類型。資源類型包括:
handle
和handle<H>
,其中H
是句柄子類型P
和request<P>
,其中P
是通訊協定的名稱- 使用
resource
修飾符宣告的結構體、表格或聯集 - 參照資源類型的型別別名
- 包裝資源類型的新類型 RFC-0052
T?
,其中T
是不可為空值的資源類型array<T>
和vector<T>
,其中T
是資源類型
所有其他類型都是值類型。
正確使用 resource
修飾符時,值類型絕不會包含句柄,但資源類型可能會 (現在或未來) 包含句柄。
語言
新的修飾符 resource
可套用至結構體、資料表和聯集宣告。
如果沒有 resource
,宣告不得包含資源類型。FIDL 編譯器必須驗證這項資訊。只需檢查直接欄位:如果 A 包含 B,且兩者都未標示為資源,而 B 包含句柄,則編譯作業會因 B 而失敗,且無需針對 A 間接包含句柄的情況,另外顯示錯誤訊息。
使用 resource
時,宣告可以包含資源類型。即使新宣告的類型不含資源,也視為資源類型。
原則上,語言可在 newtype 宣告上允許 resource
(RFC-0052)。不過,資源新類型包裝值類型並沒有實際用途,因此新類型會從包裝的類型隱含繼承值/資源狀態。
文法
這項提案會修改 FIDL 文法中的一項規則:
declaration-modifiers = "strict" | "resource" ;
JSON IR
這項提案會在 "struct_declarations"
、"table_declarations"
和 "union_declarations"
陣列中的所有物件中,新增一個具有布林值的鍵 "resource"
。
請注意,此鍵與 "max_handles"
並非重複。值類型必須將 max_handles
設為零,但資源類型可以有任意數量的 max_handles
,因為這會反映宣告的實際內容 (而非程式庫作者允許句柄的意圖)。
繫結
這項提案不包含繫結的特定變更。不過,這可讓 FIDL 繫結作者 (包括 FIDL 團隊) 解決「動機」一節中討論的問題。以下是這個 FTP 可實現的功能,但接受時並非必要:
- 實作 JSON 序列化和值類型序列化功能 (或更可能的是 FIDL 文字格式,而非 JSON,如 RFC-0058 所述)。
- 針對值/資源類型,為 C++
Clone()
方法使用不同的類型簽名,以強調只有資源複製作業可能失敗。 - 讓 Rust 通訊協定將值類型引數設為
&T
,將資源類型引數設為T
,而非同時使用&mut T
,並只變更資源類型。
API 評量標準
API 評分標準應提供使用 resource
的時間點指南。以下列舉一些簡單的案例:
- 沒有資源類型的結構體不應標示為
resource
,因為結構體並未設計為可擴充 (在日後新增句柄,在大多數情況下會破壞 ABI)。 - 沒有資源類型的嚴格表格或聯集不應標示為
resource
,因為嚴格性已表示修改其欄位會造成來源變更。
它也應處理最初沒有句柄的彈性資料表和聯集。舉例來說,我們可能會根據程式庫的用途、程式庫的使用率、使用語言中造成來源變更的預期成本,以及其他因素,建議您選擇某個版本。
導入策略
高階實作步驟如下:
- 在 fidlc 中剖析
resource
關鍵字。 - 將現有的 FIDL 程式庫遷移至
resource
(詳情請參閱「未知」)。 - 使用測試驗證 fidlc 中的值/資源類型規則。
- 將
resource
標記儲存在 JSON IR 中,並在 fidlgen 中公開。
人體工學
這項提案會引入新概念,因此會使 FIDL 變得更加複雜。與其他 FIDL 結構體 (例如「結構體」和「通訊協定」) 不同,新使用者不太可能猜到「資源」的意思,因此需要從說明文件中學習。
這項提案是否讓 FIDL 語言更符合人體工學,仍有待商榷。這有助於吸引開發人員注意含有句柄的宣告,尤其是當實際句柄值隱藏在巢狀結構中時。任何瀏覽程式庫的使用者都會立即看到結構體會攜帶句柄,而非只有資料。另一方面,如果要擔心是否要使用 resource
並輸入關鍵字,可能會讓使用者感到不便。將一個宣告從值變更為資源,可能會產生連鎖效應,導致許多類型都必須變成資源 (雖然這可能會帶來好處,因為否則會顯示為來源損壞)。
由於 FIDL 繫結功能有所改善,因此增加的複雜性是合理的。您可以自由為值類型和資源類型提供不同的 API,讓繫結更安全、更符合人體工學。如需這些改善項目的範例,請參閱「繫結」。
說明文件和範例
您必須完成下列工作:
- 更新所有涉及句柄的文件,以便視需要使用
resource
。 - 更新 FIDL 語言規格,說明
resource
修飾符。 - 在 FIDL 教學課程中提及
resource
。應有簡短的註解說明所有輔助鍵 (即strict
和resource
)。 - 提供指引,說明不含句柄的新類型是否應為資源。
- 繫結充分利用值/資源的差異後,請更新相關文件,指出值類型和資源類型提供的 API 之間的差異,並提供 (如有可能) 兩者之間的轉換操作說明。
回溯相容性
這項提案不會影響 ABI 相容性。
修訂 (2021 年 7 月)。在實作期間,我們發現此提案與 RFC-0033:處理不明欄位和嚴格性的互動中,出現了邊緣情況。部分繫結會在解碼資料表和彈性聯集時儲存不明成員;如果不明成員包含句柄,則無法為值類型執行此操作,因此在這種情況下,解碼作業一定會失敗。詳情請參閱相容性指南。
修訂 (2021 年 10 月)。在 RFC-0137:在 Fidl 中捨棄不明資料 後,繫結項目就不會再儲存不明資料,因此不會再有邊緣情況。因此,值/資源的區別不會影響 ABI 相容性。
新增或移除 resource
修飾符既不相容於來源,也無法轉換,2 在 RFC-0024 的定義上。繫結可明確允許為兩個型別產生不相容的 API,這兩個型別的差異僅在修飾符存在與否,實際上,在新增/移除修飾符之前和之後編譯的程式碼可能無法相容。如要以來源相容的方式從 resource
轉換,資料庫作者必須建立新的類型和方法,而非變更現有類型和方法。
一旦繫結作者開始善用值/資源區別,我們就會重新考慮這項決定。要求可轉換的路徑 (例如使用具有 [Transitional]
屬性的中間階段) 可能會很有幫助。一開始,這項功能的限制不明確:可能會過於嚴格,破壞這項提案要啟用的潛在 API 改善功能。
成效
這項提案對建構效能影響不大:FIDL 編譯器會稍微增加一些工作,以便剖析新關鍵字並驗證其用途。這不會對 IPC 效能造成直接影響。如果繫結使用值/資源區別來建立 API,並且不鼓勵不必要的複製作業,則某些語言可能會略為改善。舉例來說,您不必複製值類型物件,即可多次傳送該物件。
安全性
這項提案不會直接影響安全性,但可讓繫結提供更安全的 API。舉例來說,C++ 可以針對資源類型,使用 [[nodiscard]]
強制處理 Clone()
的錯誤,或是透過移動來使用資源類型方法引數,以免之後誤用經過變異的物件。這類變更可避免錯誤,包括安全性錯誤。
測試
我們會透過以下方式測試這項功能:
- 在 fidlc 中新增測試,針對剖析和驗證程式碼路徑進行測試。這些測試應執行各種情況,其中標示為
resource
(或非) 的宣告無法符合資源類型 (或值類型) 的定義。 - 除了修正需要
resource
的現有宣告外,也請在金本中加入一些資源類型宣告。 - 更新 fidl-changes 測試套件,以示範從值類型轉換至資源類型,以及從資源類型轉換至值類型的步驟。
缺點、替代方案和未知事項
這項提案會引入新的關鍵字,導致語言變得更複雜。關鍵字過多可能會造成問題;「嚴格的資源聯合」這個詞有點難懂。
本提案以兩種方式削弱 FIDL 可調整性保證:
- 在此之前,在類型中新增句柄並不會造成來源中斷的變更。這項行為現在已明確允許,且預期會發生 (除非類型已標示為
resource
,以便在需要新增句柄時使用)。 - 在此之前,您可以宣告類型,並預期未來會 (1) 為其新增句柄,以及 (2) 將其納入任何其他類型的欄位。因此,程式庫作者必須一開始就選擇 (1) 或 (2)。
這項提案有兩個主要替代方案:
- 不採取任何行動。允許在任何地方使用句柄,並接受在新增或移除句柄時,繫結必須保留來源相容性的事實。
- 預設允許句柄。與這項提案類似,但假設宣告是預設資源類型,且需要
value
關鍵字才能禁止宣告中的資源類型。
「動機」和「人體工學」部分則說明不採取任何行動的缺點。至於其他替代方案,經驗證明大多數訊息不含句柄,因此在通訊協定中傳遞句柄時,必須謹慎處理並事先規劃。換句話說,值類型是常見的情況,而新增句柄的功能可能不如想像中實用。這表示不允許使用句柄是更好的預設值。
這項提案主要可讓使用 FIDL 繫結的最終開發人員受惠,但其缺點則適用於設計 API 的程式庫作者。這項權衡符合 Fuchsia API 委員會章程,該章程將終端開發人員列為優先,而非 API 設計人員和實作人員。
我們建議您使用另一種做法:將句柄當做參照。這項功能不會禁止值類型中的句柄,而是會將句柄表示為參照,以解決值/資源問題。複製包含句柄的結構體,只會對同一個句柄建立另一個參照。您可以在 C++ 中使用 shared_ptr
完成這項操作,而且不必新增 resource
關鍵字,就能大幅簡化程序。不過,這項做法也有難處:
- 所有繫結都需要記錄機制,確保只有在最後一個參照消失時,才會關閉句柄。但在某些語言中,這可能會很困難。
- 將句柄傳送至其他程序後,所有其他參照都會變為無效,就像懸掛的指標一樣。將句柄視為一般值的便利性,意味著在這些情況下,編譯時間的安全性會降低。
- 由於這項變更涉及變更所有句柄的類型,因此很可能會對所有語言造成重大變更。要順利轉換,需要付出大量心力。
這項提案仍有幾個未解決的問題:
- 我們應該如何遷移現有的 FIDL 程式庫?使用
resource
標示所有現有宣告是安全的做法,但不會反映程式庫作者的意圖。只標記最基本類型 (即包含句柄的類型) 即可,但假設沒有句柄的類型永遠不會包含任何句柄,這可能會過於激進。 - 如果採用一般資料類型,這項功能會如何與其互動?舉例來說,如果我們定義
Pair<A, B>
類型,在A
或B
為資源類型時,該類型在邏輯上應為資源類型,而非必須註解Pair
本身。是否有其他情況,建議您推斷類型是否為資源?
既有技術與參考資料
這項提案的目標,是讓您在變更類型的值/資源狀態時,允許發生來源變更。RFC-0024 與此目標相關,因為它為 FIDL 建立了來源相容性標準。它也提到了句柄的問題,導致在 Rust 中使用 Clone
特徵變得困難,而這項提案解決了這個問題。
我們不清楚其他 IPC 系統是否能解決這個問題 (區分可能含有句柄或系統資源的類型)。不過,在程式設計語言中,以「感染」所有用途網站的方式註解類型,是常見的做法。舉例來說,JavaScript、Python 和 Rust 中的非同步函式都有這種行為,Haskell 中的 IO 單元也一樣。