| RFC-0031:类型化墓志铭 | |
|---|---|
| 状态 | 已拒绝 |
| 区域 |
|
| 说明 | 用于指明墓志铭类型的能力和语法。 |
| Gerrit 更改 | |
| 作者 | |
| 提交日期(年-月-日) | 2019-02-05 |
| 审核日期(年-月-日) | 2021-04-07 |
拒绝理由
此提案因与服务发现的交互效果不佳而被拒绝,在较小程度上,也因估计的实现复杂性而被拒绝。
与服务发现的互动
Fuchsia 上非常常见的模式是通过 fuchsia.io/Directory.Open 调用按名称请求特定协议。我们称之为服务发现。
在服务发现期间,客户端会与实现 fuchsia.io/Directory 协议的服务器进行交互。当该服务器收到服务发现请求时,它会找到所请求的相应服务,并将通道的服务器端转移到所请求的服务。这意味着,客户端先与一个服务器(支持 fuchsia.io/Directory)互动,然后与另一个服务器(支持所请求的服务)互动。
遗憾的是,服务发现对墓志铭施加了严格的限制。如果发生故障导致发送讣文,客户端无法判断是哪个对等方发送了讣文 - 是支持 fuchsia.io/Directory 的服务器,还是所请求的服务?
实际上,服务发现可能包含的服务器数量远不止上述两个。因此,墓志铭必须非常通用,不能包含特定于网域的详细信息。从本质上讲,epitaphs 必须满足所有可发现协议的最小公分母,因此会受到影响。
作为此提案的一部分,为了解决此限制,我们讨论了让参与服务发现的所有协议组成一个 fuchsia/IsDiscoverable 协议。此协议将定义一个带类型的墓志铭:
protocol IsDiscoverable {
epitaph zx.status;
};
fuchsia/IsDiscoverable 的任何子级都无法定义自定义墓志铭,因此类型系统可以正确捕获此限制。具体来说,提案的这一部分:
对于一个协议(包括任何和所有组合协议,以递归方式),最多只能有一个墓志铭类型声明。我们专门防止出现两个具有相同类型的语义等效的墓志铭类型声明。
不过,我们认为让所有可发现的协议都构成这个新的 fuchsia/IsDiscoverable 是不可行的。例如,由于服务发现是动态的,因此无法提供静态强制执行。
与请求流水线的互动
请求流水线模式可以看作是服务发现模式的泛化,并且对墓志铭施加了类似的严格限制。
实现复杂性
虽然 FIDL 功能具有复杂性(语言规则、JSON IR 的扩展、绑定代码、生成的代码、人体工程学),但类型化墓志铭在复杂性方面相当高,而实用性却相当低。这种权衡取舍并没有让我们欣喜若狂。
接下来该怎么做?
此部分代表作者 (pascallouis@google.com) 的观点。
引入墓志铭时,既定目标是“提供有关客户端与服务器连接为何关闭的指示”。
墓志铭未能达到此目标,其用途微乎其微。依赖于墓志铭的协议也可以使用自定义事件,后者没有上述任何缺点,也不会因 FIDL 背后的低级第一原则而受到任何载荷限制。
墓志铭的一项好处是协议的“干净终止”,其中服务器基本上可以确保客户端对等方将从通道解除绑定,并且不会发出后续请求。
有人提议引入适用于事件的 terminal 修饰符,以便在不失去“干净终止”属性的情况下,从使用墓志铭转向使用自定义事件。借助终端事件,库作者可以随意定义事件的载荷。为了支持这一点,我们将扩展有线格式,以分配事务性标头的一个标志来指示协议终止。收到标记为“终端”的消息后,除了正常处理之外,客户端还会终止连接。如果客户端不知道该事件(在复杂的请求流水线中很可能发生这种情况),则客户端可以理解事务性标头,但会舍弃载荷。
摘要
我们建议:
- 用于指明协议上墓志铭类型的新语法(我们不会更改墓志铭的默认类型
zx.status); - 一种向绑定公开墓志类型的方法,以便在代码生成期间适当利用此信息;
- 补充组合模型,以指定每个协议的墓志类型都是唯一的,并且通过组合继承。
我们希望使用墓志铭类型:要么在“叶”协议(即由自身定义或组合其他协议的协议)上使用;要么在“组合树”中使用一次,并将墓志铭类型放在“顶部”或“根”协议上。
设计初衷
总结:我们喜欢类型。类型很好。让我们使用更多类型。
语法和错误类型
在 RFC-0053:墓志铭中,我们引入了墓志铭的概念,即“一种允许服务器在关闭连接之前发送消息的机制,用于指示连接关闭的原因”。墓志铭包含错误状态,目前固定为 int32。在审核墓志铭时,我们选择将类型固定为 int32 以与 zx.status 叠加,目的是在未来将“用户错误”与“协议错误”合并。
在 RFC-0060:错误处理中,我们引入了用于指定错误类型的语法,特别指出“错误类型必须是 int32、uint32 或其中一种类型的枚举”。我们希望允许对墓志铭的错误进行类型化,并选择与错误处理相匹配的表示形式。
在 RFC-023:协议的组合模型中,我们引入了用于声明协议和组合协议的新语法。我们在此处为墓志铭提出的语法遵循了这种风格。
我们认为,所有这些先前的判决在方向上都与命题 (1) 和 (2) 一致。
包含墓志铭的组合模型
命题 (3) 有两个方面,即每个协议的墓志铭类型的唯一性以及组合下的行为。
墓志铭的语义与特殊事件的语义类似,因此每个协议的响应类型都是唯一的。
组合下的行为遵循类似的思路,确认墓志铭的类型可以组合。我们在下文中讨论了一种替代定义及其缺点。
通过允许墓志铭类型进行组合,我们引入了潜在的远程中断场景。假设协议 ChildWithEpitaph 构成协议 FarawayParent,并将其墓志铭类型定义为 SomeSpecificErrorCode 枚举。如果 FarawayParent 事后决定指定墓志铭类型,则系统会禁止进行组合,并且 ChildWithEpitaph 的编译会失败。
替代方案:墓志铭不构成作品
另一种方法是考虑只有已定义的消息(方法和事件)才能组合到其他协议中。在此替代模型中,如果父协议定义了墓志铭类型,则此类型将独立于子协议可能单独的墓志铭类型。例如,我们允许以下定义:
protocol Parent {
epitaph ParentErrorCode;
};
protocol Child {
compose Parent;
epitaph ChildErrorCode;
};
由于我们不提供任何协议之间的关系(例如,没有包含关系,没有可演化性规则),因此该替代模型具有一定的优势。在需要同时进行墓志铭输入和撰写的情况下,它能提供更大的自由度。
不过,我们计划在不久的将来定义协议之间的关系,因此应根据此设计目标来权衡此选择。例如,如果我们引入正式的子集关系(“是一种”),那么当一个协议组合了另一个协议,而这两个协议都定义了不兼容的墓志类型时,这些协议会立即无法通过提交测试:期望一种墓志类型的客户端将无法处理另一种墓志类型。
因此,我们认为,今天在墓志铭类型的使用方面采取限制性措施是更好的选择,这样才能为明天的扩展留出空间。
设计
语法
我们扩展了语法,以允许在协议声明中包含 epitaph 节:
protocol SomeProtocol {
ExampleMethod(...) -> (...);
epitaph SomeErrorCode;
};
墓志铭 stanza 在语法上与 compose stanza 类似,并且也遵循为错误规范选择的语法。
形式上,语法修改如下:
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 或其枚举。
因此,更改墓志铭类型(可能从默认类型更改为指定类型)不会修改 ABI 兼容性。
不过,更改墓志铭类型很可能会导致源代码级重大变更。绑定作者可能会破坏源代码兼容性。我们认为这不会造成问题,因为现在墓志铭并不常用。
JSON IR
我们向 definition/interface 对象添加了一个 definitions/type 类型的成员墓志铭。
例如,我们可能会有:
{
"name": "example/SomeProtocol",
"epitaph": {
"kind": "primitive",
"subtype": "uint32"
},
"methods": [
{
"ordinal": 296942602,
墓志铭类型始终存在于接口声明中,如果未另行指定,则设置为默认值 zx.status。
撰写墓志铭
将一个协议组合到另一个协议中时,墓志铭的类型会沿用下来。例如,使用以下协议:
protocol Parent {
epitaph SomeErrorCode;
};
protocol Child {
compose Parent;
};
Parent 和 Child 的最终墓志类型均为 SomeErrorCode。
对于一个协议(包括任何和所有组合协议,以递归方式),墓志铭类型声明不得超过一个。我们专门禁止使用相同类型声明两个在语义上等效的墓志铭类型。
此示例无效,应无法编译:
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 文件都无法在协议声明中包含“墓志铭类型构造函数”段落。
JSON IR:向后兼容允许额外键的非严格解析器,因为我们正在向所有接口声明添加“墓志铭”键。
性能
对性能没有影响。
安全
没有影响,或者由于额外的类型安全而略有积极影响。
测试
fidlc 中的单元测试和绑定生成测试。
缺点、替代方案和未知因素
不适用
在先技术和参考资料
(如文本中所述)。