如何从受监督的工作进程中触发 Elixir 主管树终止

Posted

技术标签:

【中文标题】如何从受监督的工作进程中触发 Elixir 主管树终止【英文标题】:How to trigger elixir supervisor tree termination from a supervised worker process 【发布时间】:2019-04-17 07:02:55 【问题描述】:

我正在尝试从受监督的工作进程中终止整个监督树。这是我的监督树:

                   +--------------------------+
                   |                          |
          +--------+ Sup1: Dynamic Supervisor +---------+
          |        |                          |         |
          |        +-------------+------------+         |
          |                      |                      |
          |                      |                      |
          v                      v                      v

+------------------+   +------------------+  +------------------+
|                  |   |                  |  |                  |
| Job1: Supervisor |   | Job2: Supervisor |  | Job3: Supervisor |
|                  |   |                  |  |                  |
+------------------+   +-+-------- +---+--+  +------------------+
                         |             |
                         |             |
                         |             |
                         |             |
                         v             v

             +-------------------+  +--------------+
             |                   |  |              |
             | Progress Monitor: |  | Work: Worker |
             |       Worker      |  |              |
             |                   |  +--------------+
             +-------------------+

流程生命周期:

    Job 通过以下方式启动:DynamicSupervisor.start_child(__MODULE__, spec) 每个作业也是一个监督树:1 个主管(重启策略 - one_for_one)-> 2 个工人 Progress Monitorworker 知道给定工作何时完成 工作完成后,Progress Monitor worker 尝试终止整个工作监督树,方法是调用:DynamicSupervisor.terminate_child(__MODULE__, pid) Progress Monitor 预计会在 terminate 回调中执行清理步骤 - 它正在捕获退出信号

问题和观察:

    DynamicSupervisor.terminate_child 是一个阻塞调用,这意味着它也等待所有子进程终止,包括调用进程 - Progress Monitor Progress Monitor 处于死锁状态,无法终止。父supervisor发送:kill信号,不会触发terminate回调

快速解决方法:

    Progress Monitor worker 异步调用DynamicSupervisor.terminate_child

    spawn(fn -> DynamicSupervisor.terminate_child(__MODULE__, pid) end)

    Sup1: Dynamic Supervisor定义关闭策略:

    shutdown: 5_000

    它将最多等待 5 秒等待作业监督树终止,然后发送shutdown 退出信号。这将确保为Progress Monitor 进程调用terminate 回调。

对他们俩都不满意。

问题:

    如何从工作进程触发监督树终止并避免死锁? 如果从工作人员处终止监督树不是最佳做法,那么推荐的方法是什么? 有什么建议可以重新设计监督树以使优雅终止更容易吗?

【问题讨论】:

在我看来Progress Monitor 是不必要的。你能解释为什么Worker 不能简单地做它的事情然后以normal 的原因终止吗?看来您可以为此目的使用 Task.Supervisor 【参考方案1】:

只需在异步任务Task.async(fn -> Process.exit(Sup1, :shutdown) end) 中调用它,它将终止 Sup1,并且所有子进程都将关闭

编辑:

如果您需要更漂亮的解决方案,这取决于您还需要什么。在大多数情况下,我会创建 Bootstrapper 工作程序来进行初始化和其他一些事情。您可以轻松添加其他功能。

所以考虑到上面,粗略地说,我会在上面添加一个层(AppSupervisor),另一个 DynamicSupervisor 以便它可以启动 Bootstrapper 并将self() 传递给它(或以本地名称注册它以避免这种注入)。之后,在启动时,Bootstrap worker 将启动 Sup1(你的动态主管)并等待其他消息,例如:terminate_sup1 将关闭 Sup1 进程。稍后,在下面的一些工作人员中,您可以通过将 :terminate_sup1 消息投射到引导程序来关闭 Sup1。当另一条消息发送到引导工作程序时,还有一个允许您重新启动 Sup1 的门。

此外,如果您只需要关闭 Sup1,只需使用 Task。但是,如果您需要控制,则将其放入应该可以控制它的单个工作进程中,无论何时启动或关闭。

【讨论】:

以上是关于如何从受监督的工作进程中触发 Elixir 主管树终止的主要内容,如果未能解决你的问题,请参考以下文章

Elixir - 受监督的进程似乎会阻止程序执行

如何在 Elixir 中建模主管树

如何在 Elixir 主管中引用之前启动的进程

Erlang - Elixir:啥是监督树?

每个主管有多少工人?

Elixir 中的动态主管规范