RFC-0023:协议的组合模型

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

关键字接口已被关键字协议取代。扩展协议已明确定义为表示组合模型,其中一个协议可以定义为一组消息,由一个或多个其他协议增强。用于协议扩展的语法已从类似于继承的语法更改为类似于混入的语法。

作者
提交日期(年-月-日)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 便为通道的两端赋予了意义和名称。我们将其中一个称为客户端,另一个称为服务器

我们的模型将协议描述为一组有向互动,其中包含可选的墓志铭。我们将客户端和服务器之间使用协议进行通信的特定实例称为 session

方向可以是从客户端到服务器,也可以是从服务器到客户端

互动始于请求,并且可以选择是否需要响应。对于无响应的互动,我们通常使用“火花式”或“单向”一词,对于需要响应的请求,我们通常使用“调用”一词。

请求和响应都是消息,以标头的形式表示,后跟结构体有效载荷,即请求或响应的参数

目前,我们限制服务器向客户端发送的消息不含响应。 简单来说,“事件只是触发即弃”。

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 中表示。

“是”关系被视为有害

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

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

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

如果我们允许将传输协议 Child 的通道视为 Parent(即“ChildParent”关系),则会使客户端接收无法处理的 Event。如需查看具体示例,请参阅下一部分

我们将支持特定的协议注解(例如“仅客户端与服务器交互”),以支持和允许“是”关系。在这种情况下,此类关系会传递到 JSON IR,以供后端使用。

目前依赖于“Is A”关系

我们来看一个具体的示例,fuchsia.media 库会将各种协议组合在一起。具体而言:

AudioCapturerAudioRenderer 均不会定义事件,也就是说,它们完全是“客户端到服务器协议” - 它们是单向的。(StreamSource 定义了两个事件,但我们在这里专门讨论的是每种协议自己的定义。)

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

不过,如果向任一接口添加事件,此“是”关系将不复存在。假设客户端正在与 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();
};

注意:“接口”关键字表示每个方法都必须有实现,而“协议”关键字表示包含该接口的合规协议和接口的要求。例如,我们不一定会预期 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 具有支持继承的接口,包括多重继承(混入式)。


  1. 为方法引入序数哈希,再加上未来的提案中将方法序数从 32 位更改为 64 位的预期更改,可能会使这种远程断点不存在(在实际操作中),届时我们将重新考虑使用 FragileBase。 

  2. 属性:一个假想的 FIDL 扩展,用于促进观察 / 数据绑定。粗略地说,这些绑定会生成用于访问、修改和/或观察接口公开的值的方法。