Erlang:如何使连接的外部操作系统进程在控制Erlang进程崩溃时自动死亡?

os8fio9y  于 2022-12-08  发布在  Erlang
关注(0)|答案(1)|浏览(164)

我正在使用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”命令来做到这一点,但我认为这可以更简单、更干净地完成。

u0njafvf

u0njafvf1#

The open_port function creates a port() and links it to the calling process. If the owning process dies, the port() closes.
In order to communicate with the externally spawned command, Erlang creates several pipes, which are by default tied to the stdin and stdout (file descriptors) of the external process. Anything that the external process writes through the stdout will arrive as a message to the owning process.
When the Port is closed, the pipes attaching it to the external process are broken, and so trying to read or write to them will give you a SIGPIPE/EPIPE.
You can detect that from your external process when writing or reading from the FDs and exiting the process then.
E.g.: With your current code, you can retrieve the external process OS pid with proplists:get_value(os_pid, erlang:port_info(Port)) . If you strace it, you will see:

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

SIGPIPE in ports and Erlang

It seems that although the default action for SIGPIPE is to terminate the process, Erlang sets it to ignore the signal (and thus the children processes inherit this configuration).
If you're unable to modify the external process code to detect the EPIPE, you can use this c wrapper to reset the action:

#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]));
}

just compile it and run it as wrapper path-to-executable [arg1 [arg2 [...]]] with open_port

相关问题