作者
Maria Scott <maria-12648430(at)hnc-agency(dot)org> , Jan Uhlig <juhlig(at)hnc-agency(dot)org>
状态
最终版/24.0 在 OTP 24 版本中实现
类型
标准跟踪
创建时间
2021-03-04
Erlang 版本
OTP-24.0
发布历史
2021-03-08, 2021-03-17, 2021-03-23, 2021-03-31, https://github.com/erlang/otp/pull/4521

EEP 56: 由重要子进程终止触发的自动 supervisor 关闭 #

摘要 #

此 EEP 引入了一种基于特定标记的重要子进程的终止来自动终止 supervisor 的方法。

本文档基于 OTP-PR 4521 中的讨论。

动机 #

supervisor 下的子进程通常代表一个工作单元,即一组协作进程,而不仅仅是单个进程。此类工作单元 supervisor(在本文档中称为组 supervisor)通常通过 simple_one_for_one supervisor 托管,并通过 simple_one_for_one supervisor 根据需要启动。

然而,在撰写本文时,一旦它们所代表的工作单元完成其工作并且相应的子进程已终止,就没有一个好的、规范的方法来停止此类组 supervisor,这意味着组 supervisor 将会闲置并永远存在,除非以某种方式手动停止。

这个问题已经在应用程序中以各种方式得到解决,但没有一种方式可以被认为是真正好的、直接的或规范的

  • 将组 supervisor 的 Pid 传递给负责关闭该 supervisor 的子进程。然后通过向组 supervisor 发送退出信号来实现关闭。虽然记录了适当的退出信号,但它不是用于此目的的,并且随意发送退出信号可能是危险的。
  • 将组 supervisor 的 Pid 和其顶层 supervisor 的 Pid 传递给负责关闭该 supervisor 的子进程。然后通过告诉顶层 supervisor 通过 supervisor:terminate_child/2 终止组 supervisor 来实现关闭。由于这是一个阻塞调用,因此必须为此生成一个进程。此外,这将导致顶层 supervisor 被阻塞,直到组 supervisor 关闭,因此它在此之前不会接受其他请求。

以上两种方法都存在一个问题,即负责关闭的子进程必须了解其周围环境,即…

  • … 它首先位于(组)supervisor 下。在上述第二种方法中,甚至这个组 supervisor 又位于另一个 supervisor 下。而且,相反地,无法独立运行此进程(例如,用于测试),即没有顶层的 supervisor 层。
  • … 它可能存在同级进程,并且通过关闭 supervisor 来导致它们的关闭是安全的。从程序员的角度来看,仅仅通过查看 supervisor 实现,并不明显某个子进程可能会导致 supervisor 和所有子进程的关闭,因此存在意外的可能。

可以通过拥有一个专门的监督子进程来监视其他子进程并根据它们的行为采取行动来解决这个问题。但是,这需要大量的样板代码来完成更适合在 supervisor 中完成的任务。此外,还存在监督进程必须保持其监视的子进程列表更新的问题,以防任何子进程被重新启动,可以通过在启动时启用子进程向其注册(为此它们又必须知道监督进程的 pid),或者向 supervisor 请求。

另一种常用的方法是使负责关闭组 supervisor 的子进程成为永久进程,并将 supervisor 的重启强度设置为 0。这样做的一个缺点是,如果子进程异常退出但 *可以*重新启动,它将不会重新启动,而是会导致 supervisor 关闭。这种方法的另一个缺点是,即使关闭是有意的,它也会产生错误消息(崩溃报告)。

最后但并非最不重要的是,有些人已经采取了克隆 OTP supervisor 并根据他们的需要进行自定义的方法,原因在此处和其他地方概述。

原理 #

此 EEP 提供了一种通过引入一种方法来通过新的子进程规范标志将特定子进程标记为 significant,以及一种方法来配置 supervisor 以根据重要子进程的退出自动关闭来缓解动机中概述的问题。

为了保持向后兼容性,新的标志将只能在子进程规范和 supervisor 标志的映射形式中使用,并且出于相同的原因,新标志的默认值被选择为,在它们不存在的情况下,supervisor 的行为与迄今为止的行为相同。

新的子进程规范标志名为 significant,可能的值为 truefalse,其中 false 为默认值。

新的 supervisor 标志名为 auto_shutdown,可能的值为 neverany_significantall_significant,其中 never 为默认值。

如果 supervisor auto_shutdown 标志设置为 never,则不允许子进程规范标志 significanttruenever 值和对 significant 值的限制旨在作为一种安全措施,以防止意外的自动关闭,例如由于通过 supervisor:start_child/2 稍后添加的重要子进程的退出而导致的自动关闭。由于此类子进程的规范不会出现在 supervisor:init/1 回调代码中,而是在其他地方,因此调试此类无法解释的 supervisor 关闭可能会很困难。

否则,当重要子进程 自行 退出时,将应用以下规则

  • 如果 transient 子进程异常退出,将会重新启动(不会导致 supervisor 关闭)。如果它正常退出…

    • 如果 supervisor auto_shutdown 标志为 any_significant,则 supervisor 将关闭
    • 如果 supervisor auto_shutdown 标志为 all_significant,则如果该子进程是最后一个活动的重要子进程,则 supervisor 将关闭
  • temporary 子进程将永远不会重新启动。如果它正常或异常退出,则应用与 transient 子进程相同的规则,关于 supervisor auto_shutdown 标志。

如果重启类型为 permanent,则不允许 significant 标志为 true,因为此组合没有意义。

明确地说,上述规则仅适用于 重要 子进程 自行 退出时,也就是说,不是通过 supervisor:terminate_child/2 手动终止时,不是当其他非重要子进程退出时,也不是在 one_for_allrest_for_one 策略中因同级进程死亡而被终止时。

此处提出的方法也可以通过将 所有 子进程标记为 significant 并将 supervisor auto_shutdown 标志设置为 all_significant 来达到“空时关闭”的效果。

值得一提的是,simple_one_for_one 策略构成了一种特殊情况,因为它只能有一个适用于所有子进程的子进程规范。这意味着 所有 子进程都是重要的,或者 没有 子进程是重要的。

注意事项 #

one_for_allrest_for_one supervisor 中使用临时的重要子进程可能会导致边缘情况,其中预期的自动关闭不会发生。临时子进程不会重新启动,即使它们的终止是由同级进程死亡引起的。另一方面,当重要子进程因同级进程死亡而被终止时,不会触发 supervisor 的自动关闭。因此,如果临时的重要子进程因同级进程死亡而被终止,则旨在自动关闭其 supervisor 的临时重要子进程将会丢失。

向后兼容性 #

本文档中提出的更改没有引入不兼容的更改,因为新的子进程规范和 supervisor 标志是可选的,并且默认为导致当前行为的值。此外,动机中概述的所有当前解决方法仍然有效。

尽管提出的更改是向后兼容的,但是除非采取适当的措施,否则使用此增强功能的应用程序在较旧的 OTP 版本中编译时可能不兼容。使用较旧 OTP 版本编译的此类应用程序将泄漏进程,因为它依赖于自动 supervisor 关闭来删除其监控树中未使用的部分,而这种关闭不会发生。如果实施者期望使用重要子进程行为的应用程序使用早于其出现的 OTP 版本进行编译,则应自行决定是否解决此问题。

实现 #

可以在 OTP-PR 4638 中找到将更新以反映本文档状态的参考实现。

版权 #

本文档已置于公共领域。