RFC-0031:有格式的墓志铭 | |
---|---|
状态 | 已拒绝 |
区域 |
|
说明 | 用于指明墓志铭类型的功能和语法。 |
Gerrit 更改 | |
作者 | |
提交日期(年-月-日) | 2019-02-05 |
审核日期(年-月-日) | 2021-04-07 |
拒绝理由
由于该方案与服务发现的互动不佳,以及预计实现复杂性较高,因此被拒绝。
与服务发现互动
在 Fuchsia 上,一种非常常见的模式是通过 fuchsia.io/Directory.Open
调用按名称请求特定协议。我们将其称为服务发现。在服务发现期间,客户端会与实现 fuchsia.io/Directory
协议的服务器交互。当该服务器收到服务发现请求时,它会查找请求的适当服务,并将其通道的服务器端转移到请求的服务。这意味着,客户端会先与一个服务器(支持 fuchsia.io/Directory
)交互,然后再与另一个服务器(支持请求的服务)交互。
遗憾的是,服务发现对 epitaph 施加了严格的限制。如果失败导致发送了挽歌,客户端无法确定是哪个对等方发出了挽歌,是支持 fuchsia.io/Directory
的服务器,还是请求的服务?
实际上,服务发现不仅仅包含上述两个服务器,还可以包含更多服务器。因此,墓志铭必须非常笼统,不能包含特定于领域的详细信息。从本质上讲,墓志铭必须满足所有可检测协议的最小公分母。
在此提案中,为了解决此限制,我们讨论了让所有参与服务发现的协议组成 fuchsia/IsDiscoverable
协议。此协议将定义一个类型化的墓志铭:
protocol IsDiscoverable {
epitaph zx.status;
};
fuchsia/IsDiscoverable
的任何子项都无法定义自定义墓志铭,从而正确捕获类型系统中的限制。具体而言,此部分应:
协议(包括任何和所有复合协议,以递归方式)不得有多个 epitaph 类型声明。我们会专门阻止两个具有相同类型且在语义上等效的墓志铭类型声明。
不过,我们认为让所有可检测到的协议组成这个新的 fuchsia/IsDiscoverable
是不可行的。例如,由于服务发现是动态的,因此无法提供静态强制执行。
与请求流水线的互动
请求流水线模式可视为服务发现模式的泛化,并对 epitaph 施加类似的严格限制。
实现复杂性
虽然 FIDL 功能具有复杂性(语言规则、对 JSON IR 的扩展、绑定代码、生成的代码、人体工学),但类型化 epitaph 在复杂性谱系中处于相当高的水平,并且其实用性非常低。我们对这种权衡并不完全满意。
接下来该怎么做?
此部分代表作者 (pascallouis@google.com) 的观点。
引入墓志铭时,声明的目标是“指明客户端与服务器之间的连接为何关闭”。
墓志铭未能达到这一目标,其实用性不大。 已经开始依赖于 epitaph 的协议也可以使用自定义事件,自定义事件没有上述任何缺点,也不会受到 FIDL 背后低级第一原则所强加的任何载荷限制。
墓志铭的一个好处是,协议的“正常终止”:服务器几乎可以肯定客户端对等方会与通道解绑,并会避免发出后续请求。
我们提出了引入适用于事件的 terminal
修饰符的想法,以便从使用墓志铭转为使用自定义事件,同时不会丢失此“干净终止”属性。借助终端事件,库作者可以随意定义事件的载荷。为此,我们将扩展线程格式,以分配一个事务标头标志,用于指示协议终止。收到标记为终端的消息后,客户端除了正常处理外,还会终止连接。如果客户端不知道该事件(在复杂的请求流水线情况下很可能),客户端可以理解事务标头,而将载荷舍弃。
摘要
我们建议:
- 用于指明协议中墓志铭类型的新语法(我们不会更改墓志铭的默认类型
zx.status
); - 一种向绑定公开墓志铭的类型的方法,以便在代码生成期间适当地利用这些信息;
- 补充组合模型,以指定墓志铭的类型因协议而异,并且通过组合传递。
我们希望在以下位置使用 epitaph 类型:在“叶”协议(即由自身定义或组合其他协议的协议)上;或在“组合树”中使用一次,将 epitaph 类型放置在“顶部”或“根”协议上。
设计初衷
要点:我们喜欢类型。类型很棒。让我们添加更多类型。
语法和错误类型
在 RFC-0053:Epitaphs 中,我们引入了 Epitaph 的概念,“一种机制,允许服务器在关闭连接之前发送一条消息,以指明关闭连接的原因”。epitaph 包含一个错误状态,目前固定为 int32。在审核 epitaph 时,我们选择将类型固定为 int32,以便与 zx.status 叠加,并计划日后将“用户错误”与“协议错误”合并。
在 RFC-0060:错误处理中,我们引入了用于明确指定错误类型的语法,并特别指出“错误类型必须为 int32、uint32 或这两种类型之一的枚举”。我们希望允许输入墓志铭的错误,并选择与错误处理方式匹配的表示法。
在 RFC-023:协议的组合模型中,我们引入了用于声明协议和组合协议的新语法。我们在此为墓志铭建议的语法遵循该样式。
我们认为所有这些先前的决定都与命题 (1) 和 (2) 大致一致。
带有墓志铭的组合模型
命题 (3) 有两个方面,即每种协议的墓志铭类型的唯一性,以及在组合下的行为。
墓志铭的语义与特殊事件的语义类似,因此响应类型因协议而异。
组合下的行为遵循类似的思路,确认了墓志铭的类型可以组合。下文介绍了另一种定义及其缺点。
允许墓志铭类型进行组合,会在远程场景中引入潜在的破坏。假设协议 ChildWithEpitaph
组成了协议 FarawayParent
,并将其 epitaph 类型定义为 SomeSpecificErrorCode
枚举。如果 FarawayParent
事后决定指定墓志铭类型,则系统会禁止该组合,并且 ChildWithEpitaph
的编译将会失败。
替代方案:墓志铭不合成
另一种方法是,假定只有已定义的消息(方法和事件)才能组合到其他协议中。在此替代模型中,如果父级协议定义了 epitaph 类型,则此类型将独立于子协议可能的单独 epitaph 类型。例如,我们允许以下定义:
protocol Parent {
epitaph ParentErrorCode;
};
protocol Child {
compose Parent;
epitaph ChildErrorCode;
};
由于我们未在协议之间提供任何关系(例如,没有包含子协议,没有可扩展性规则),因此该替代模型具有一些优点。在同时使用墓志铭打字和组合的情况下,这为您提供了更大的自由度。
不过,我们有意在近期定义协议之间的关系,因此应根据这一设计目标权衡此选择。例如,如果我们引入了正式的包含关系(“是”),如果某个协议组合了另一个协议,而这两个协议都定义了不兼容的墓志铭类型,则这些协议会立即在提交测试中失败:预期一种墓志铭类型的客户端无法处理另一种墓志铭类型。
因此,我们认为,现在对墓志铭类型的使用方式进行限制,以便将来能够扩展使用范围,是更好的选择。
设计
语法
我们扩展了语法,以允许在协议声明中使用墓志铭诗节:
protocol SomeProtocol {
ExampleMethod(...) -> (...);
epitaph SomeErrorCode;
};
墓志铭诗节在语法上与 compose 诗节类似,并且也遵循为错误规范选择的语法。
形式上,语法会修改为:
protocol-declaration = ( attribute-list ) , "protocol" , IDENTIFIER ,
"{" , ( protocol-member , ";" )* , "}" ;
protocol-member = ...
| "epitaph" type-constructor ; [NOTE]
NOTE: The epitaph stanza allows the more liberal type-constructor in the
grammar, but the compiler limits this to int32, uint32, or enum thereof. There
may be only one epitaph stanza per protocol definition.
ABI 和源代码兼容性
在引入墓志铭时,我们将类型固定为 int32,并希望将其限制为 32 位。在引入错误语法时,我们确认了将错误代码固定为 32 位。
在这里,我们将保留此选择,并将墓志铭类型限制为 int32、uint32 或其枚举。
因此,更改 epitaph 类型(可能从默认类型更改为指定类型)不会修改 ABI 兼容性。
不过,更改墓志铭类型很可能是源代码级的破坏性更改。绑定作者可能会破坏源代码兼容性。我们预计这不会成为问题,因为墓志铭目前并不广泛使用。
JSON IR
我们向 definition/interface
对象添加一个类型为 definitions/type
的成员 epitaph。
例如,我们可能会:
{
"name": "example/SomeProtocol",
"epitaph": {
"kind": "primitive",
"subtype": "uint32"
},
"methods": [
{
"ordinal": 296942602,
epitaph 类型始终存在于接口声明中,如果未另行指定,则设置为默认的 zx.status
。
撰写墓志铭
将一个协议组合到另一个协议中时,墓志铭的类型会保留。例如,使用以下协议:
protocol Parent {
epitaph SomeErrorCode;
};
protocol Child {
compose Parent;
};
Parent
和 Child
的最终墓志铭类型均为 SomeErrorCode
。
协议(包括所有组合协议,以递归方式)只能有一个 epitaph 类型声明。我们会专门阻止两个具有相同类型且在语义上等效的墓志铭类型声明。
以下示例无效,应该会编译失败:
protocol Parent1 {
epitaph SomeErrorCode1;
};
protocol Parent2 {
epitaph SomeErrorCode2;
};
protocol Child {
compose Parent1;
compose Parent2;
};
以下示例也无效,并且应该无法编译:
protocol Parent {
epitaph SomeErrorCode;
};
protocol Child {
compose Parent;
epitaph SomeErrorCode;
};
实施策略
(由 FIDL 团队在审核后确定。这项变更与之前的许多变更没有太大不同。)
工效学设计
不适用
文档和示例
至少:
向后兼容性
FIDL 源代码:由于我们扩展了语言语法,因此完全向后兼容。在此更改之前,任何 FIDL 文件都不能在协议声明中包含“epitaph type-constructor;”诗节。
JSON IR:与允许额外键的非严格解析器向后兼容,因为我们会向所有接口声明添加“epitaph”键。
性能
对性能没有影响。
安全
没有影响,或者由于增加了类型安全性而略有积极影响。
测试
fidlc 中的单元测试和绑定生成测试。
缺点、替代方案和未知
不适用
在先技术和参考文档
(如文中所述。)