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 不提供此类继承语义,但语法中提供了同样的效果。) 如需了解详情,请参阅“是 A”关系被视为有害的部分。

设计

此方案引入了正式语义来描述进程交互和协议。

此方案更改了 FIDL 源语言,以阐明协议扩展的语义,并为绑定作者提供了新的指导。

目前,继承关系无法在 JSON IR 中表示,因此绑定创建者无法使用继承关系。因此,除了改进的文档外,我们预计此新指南对生成的绑定代码修改方式做出的更改应该微乎其微。

此方案不会更改电汇格式。

此方案不会更改 JSON IR,但我们预计会在以后的更大更改中包含键重命名。

协议模型

Zircon 信道对于它们携带的载荷不需要特定的架构。FIDL 基于此原语构建,并限制信道携带特定协议。这样的话,FIDL 会为通道的两端指定含义和名称。 我们将其中一个称为客户端,另一个称为服务器

我们的模型将协议描述为一组有向交互,并附带可选的 epitaph(epitaph)。我们使用协议将“会话”称为客户端和服务器之间通信的特定实例。

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

互动从请求开始,可以根据需要选择响应。 我们通常使用“触发后不理”或“单向”这一术语来表示无响应的互动,而“调用”这一术语则表示期望响应的请求。

请求和响应都是以标头表示的消息,后跟结构体的载荷(请求或响应的参数)。

目前,我们限制服务器到客户端的消息具有响应。简单地说,“事件只是触发,不会忘记”。

epitaph 是指用于结束会话的服务器到客户端的互动。有关详情,请参阅 RFC-0053: Epitaphs

此模型缺少一些更复杂的交互,例如 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。如需查看具体示例,请参阅下一部分

相反,我们希望支持特定的协议注解(例如“仅客户端到服务器交互”),以支持并允许“是”关系。发生这种情况时,此类关系会传递到 JSON IR 以供后端使用。

如今依赖“Is A”关系

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

AudioCapturerAudioRenderer 均未定义事件,即这些事件纯粹是“客户端到服务器协议”,它们是单向的。 (StreamSource 定义了两个事件,但我们在这里具体讨论的是每个协议自己的定义。)

因此,如果客户端知道如何与 StreamBufferSetStreamSource(分别为 StreamBufferSetStreamSink)交互,那么它还可以与 AudioCapturer(分别为 AudioRenderer)进行交互,即客户端会直接忽略提供的额外方法。在这里,我们可以像定义一样定义“是”关系。

但是,如果将事件添加到任一接口,这种“是”关系将不复存在。假设某个客户端正在与 StreamBufferSet 交互,后者实际上是服务器端的 AudioRenderer。如果 AudioRenderer 触发了事件,会发生什么情况? 该客户会如何处理?

由于我们尚不能在 fidlc 中提供这种区别,因此我们确认不支持“为”关系。该提案从根本上阐明了现状。

fuchsia.media 案例一样,知道某些关系为 true 的作者可以将绑定转换为其需求(使用类型转换等)。

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

替代语法:使用声明 (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 具有支持继承的接口,包括多重继承(采用 mixins 样式)。


  1. 针对方法引入序数哈希,再加上未来方案中预期将方法序数从 32 位提升到 64 位的变更,很可能在一定距离上无法实现此中断(实际上),并且以后需要重新考虑对 FragileBase 的使用。 

  2. 属性:有助于进行观察 / 数据绑定的假设 FIDL 扩展。简单来说,这些绑定将生成用于访问、修改和/或观察接口公开的值的方法。