Erlang:控制Erlang进程崩溃时如何使连接的外部OS进程自动死亡?

Posted

技术标签:

【中文标题】Erlang:控制Erlang进程崩溃时如何使连接的外部OS进程自动死亡?【英文标题】:Erlang: How to make connected external OS process automatically die when controlling Erlang process crashes? 【发布时间】:2021-08-14 15:30:27 【问题描述】:

我正在使用 Erlang 端口读取 Linux 进程的输出。我希望在我连接的 Erlang 进程死亡时自动终止 Linux 进程。从文档来看,在我看来这应该自动发生,但事实并非如此。

最小的例子。把它放在文件 test.erl 中:

-module(test).
-export([start/0, spawn/0]).

start() ->
    Pid = spawn_link(?MODULE, spawn, []),
    register(test, Pid).

spawn() ->
    Port = open_port(spawn, "watch date",[stream, exit_status]),
    loop([port, Port]).

loop(State) ->
    receive
        die ->
            error("died");
        Any ->
            io:fwrite("Received: ~p~n", [Any]),
            loop(State)
    end.

然后,在 erl shell 中:

1> c(test).
ok,test
2> test:start().
true

该进程启动并每 2 秒打印从 Linux“watch”命令接收到的一些数据。

然后,我让 Erlang 进程崩溃:

3> test ! die.
=ERROR REPORT==== 26-May-2021::13:24:01.057065 ===
Error in process <0.95.0> with exit value:
"died",[test,loop,1,[file,"test.erl",line,15]]

** exception exit: "died"
     in function  test:loop/1 (test.erl, line 15)

Erlang 进程按预期终止,“watch”中的数据停止出现,但 watch 进程仍继续在后台运行,如 Linux(不是 erl)终端所示:

fuxoft@frantisek:~$ pidof watch
1880127

在我的现实生活场景中,我没有使用“watch”命令,而是使用其他输出数据且不接受任何输入的进程。当我连接的 Erlang 进程崩溃时,如何让它自动死亡?我可以使用 Erlang 主管执行此操作,并在 Erlang 进程崩溃时手动发出“kill”命令,但我认为这可以更轻松、更清洁。

【问题讨论】:

【参考方案1】:

open_port 函数创建一个port() 并将其链接到调用进程。如果拥有进程终止,port() 将关闭。

为了与外部生成的命令进行通信,Erlang 创建了几个管道,默认情况下这些管道与外部进程的stdinstdout(文件描述符)相关联。外部进程通过标准输出写入的任何内容都将作为消息到达所属进程。

Port 关闭时,将其连接到外部进程的管道被破坏,因此尝试读取或写入它们会给您一个 SIGPIPE/EPIPE。

您可以在从 FD 写入或读取时从外部进程中检测到这一点,然后退出进程。

例如:使用您当前的代码,您可以使用proplists:get_value(os_pid, erlang:port_info(Port)) 检索外部进程操作系统 pid。如果你strace它,你会看到:

write(1, ..., 38) = -1 EPIPE (Broken pipe)
--- SIGPIPE si_signo=SIGPIPE, si_code=SI_USER, si_pid=31297, si_uid=1001 ---

端口和 Erlang 中的 SIGPIPE

似乎虽然 SIGPIPE 的默认操作是终止进程,但 Erlang 将其设置为忽略信号(因此子进程继承此配置)。

如果您无法修改外部进程代码来检测 EPIPE,您可以使用此 c 包装器来重置操作:

#include <unistd.h>
#include <signal.h>

int main(int argc, char* argv[]) 
    if (signal(SIGPIPE, SIG_DFL) == SIG_ERR)
        return 1;
    if (argc < 2)
        return 2;
    execv(argv[1], &(argv[1]));

只需编译它并以wrapper path-to-executable [arg1 [arg2 [...]]]open_port 运行它

【讨论】:

我不确定是我不理解你还是你不理解我,但我不明白这对我有什么帮助。首先,外部进程不是我写的,我也没有它的源代码,所以我不能“从用于与 Erlang 通信的 FD 中写入或读取”。其次,当我的 Erlang 进程在开发过程中崩溃时,它会关闭我的整个应用程序(它的所有进程都已链接),因此我无法检索外部进程 pid 并对其进行处理——我的整个应用程序已经关闭。我知道我可以为此编写额外的独立 Erlang 进程,但我认为有更简单的解决方案。 @fuxoft 我已经编辑了答案,希望现在更清楚。如果无法打印时外部进程没有退出,您可能需要为其编写一个包装器。从操作系统的角度来看,它已尽其所能(关闭将其绑定到 Erlang 的文件描述符),由外部进程来识别它并在不再需要它时优雅地退出。在 linux 中,您可以使用 prctl 调用在父进程死亡时终止外部进程(但这只有在整个 erlang 节点随端口关闭时才有效,这可能是也可能不是您的意图),并且您会需要来源。 对不起,我还是一头雾水。你的回答是“不,你想要的不能在 Erlang 中完成,你必须为此编写一个 OS 包装器”?在这种情况下,编写一个额外的 Erlang 进程(不链接到所有其他进程)来监视我的所有端口并在它们断开连接时杀死操作系统进程似乎更简单。 @fuxoft 或多或少,是的。这种方法(从 erlang 监控)的问题是,如果外部进程没有检测到 EPIPE,它们可能会在节点突然崩溃的情况下继续存在。 @fuxoft 我对此进行了深入研究,并添加了一个非常简单的 c 包装器,它将重置信号操作,然后将 execv 关闭。

以上是关于Erlang:控制Erlang进程崩溃时如何使连接的外部OS进程自动死亡?的主要内容,如果未能解决你的问题,请参考以下文章

如何使erlang与mysql连接?

Erlang,如何在控制台中打印每个值?

Erlang - 将每个“erlang 进程”映射到新的内核线程

Erlang 心跳

Erlang 监控多个进程

Erlang 作为后端进程