RFC-0023:协议的组合模型

RFC-0023:协议的组合模型
状态已接受
区域
  • FIDL
说明

关键字界面已替换为关键字协议。扩展协议已明确为表示组合模型,其中一个协议可以定义为一组消息,并通过一个或多个其他协议进行扩充。用于协议扩展的语法从类似于继承的语法更改为类似于 mixin 的语法。

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

摘要

我们建议进行以下更改:

  • 关键字 interface 已替换为关键字 protocol。(在本文档的其余部分中,我们将使用“协议”一词。)
  • 明确了扩展协议是指组合模型,其中一个协议可以定义为一组消息,并由一个或多个其他协议进行扩充。
  • 协议扩展所用的语法从类似于继承的语法更改为类似于 mixin 的语法。
  • 在目标语言中表示组合协议时,绑定作者必须避免子集化(例如“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”(调用)来表示需要响应的请求。

请求和响应都是消息,表示为标头,后跟结构体的载荷,即请求或响应的实参

目前,我们限制服务器到客户端的消息不得包含响应。 简而言之,“事件即发即弃”。

墓志铭是一种服务器到客户端的互动,用于结束会话。如需了解详情,请参阅 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 中表示出来。

“Is A”关系被认为是有害的

由于协议可以双向传送请求,因此在存在子类型关系时需要更加谨慎。因此,我们不允许协议与其扩展的协议之间存在“is a”关系。

例如,假设我们有以下两种协议:

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

如果我们允许将携带协议 Child 的渠道视为 Parent(即“ChildParent”关系),那么客户端可能会收到 Event,但无法处理该协议。 如需查看具体示例,请参阅下一部分

相反,我们将支持特定的协议注释(例如“仅限客户端到服务器的互动”),以支持和允许“是”关系。如果发生这种情况,此类关系将传递到 JSON IR 以供后端使用。

目前对“Is A”关系的依赖

以一个具体示例为例,fuchsia.media 库将各种协议组合在一起。具体而言:

AudioCapturerAudioRenderer 均未定义事件,也就是说,它们纯粹是“客户端到服务器协议”,是单向的。(StreamSource 定义了两个事件,但这里我们专门讨论每个协议自身的定义。)

因此,如果客户端知道如何与 StreamBufferSetStreamSource(分别是 StreamBufferSetStreamSink)交互,那么它也可以与 AudioCapturer(和 AudioRenderer)交互,也就是说,客户端只会忽略公开的额外方法。 在这里,我们可以按预期定义“is a”关系。

不过,如果向任一接口添加事件,这种“是一种”关系将不再存在。假设客户端正在与 StreamBufferSet 实际上是服务器端的 AudioRenderer。如果 AudioRenderer 触发了事件,会发生什么情况? 相应客户会如何处理?

由于我们(目前)无法在 fidlc 中提供这种区分,因此我们确认不支持任何“是”关系。此提案实际上明确了现状。

fuchsia.media 示例中一样,知道某些关系为真的作者可以根据自己的需求调整绑定(使用强制转换等)。

在后续提案中,我们预计会引入属性或新关键字来捕获这种方向性限制,并在此基础上在绑定中提供“是”关系。在提出此类提案之前,我们无法作为 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@)

原因:看起来不像继承,而是重用现有关键字来指示纳入范围的名称。 不太可能与方法声明或“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 具有支持继承的接口,包括多重继承(采用 mixin 样式)。


  1. 引入方法 Ordinal Hashing,并计划在未来的提案中将方法序号从 32 位提升到 64 位,这可能会使这种远距离的破坏(实际上)不存在,届时将重新考虑使用 FragileBase。 

  2. 属性:一种假设的 FIDL 扩展,用于简化观测 / 数据绑定。从广义上讲,绑定会生成用于访问、修改和/或观察接口公开的值的方法。