Linux – Erlang: How do I make a connected external OS process die automatically when the Erlang process crashes?

Erlang: How do I make a connected external OS process die automatically when the Erlang process crashes?… here is a solution to the problem.

Erlang: How do I make a connected external OS process die automatically when the Erlang process crashes?

I’m using the Erlang port to read the output of a Linux process. I want the Linux process to terminate automatically when the Erlang process to which I connect terminates. Judging by the documentation, it seems to me that this should happen automatically, but it is not.

Minimal example. Put it in the file 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.

Then, in the erl shell:

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

The process starts and prints some data received from the Linux “watch” command every 2 seconds.

Then, I let the Erlang process crash :

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)

The Erlang process terminates as expected, data from “watch” stops appearing, but the watch process is still running in the background, as shown in the Linux (not erl) terminal

fuxoft@frantisek:~$ pidof watch
1880127

In my real scenario, instead of using the “watch” command, I use a process that outputs data but does not accept any input. When my connected Erlang process crashes, how can I make it disappear automatically? I could do this using the Erlang supervisor and manually issuing the “kill” command when the Erlang process crashes, but I think this could be done much easier and cleaner.

Solution

open_port function creates a port() and links it to the calling process. If the owning process terminates, port() shuts down.

To communicate with externally generated commands, Erlang creates several pipes that bind (bind) to the external process’s stdin and stdout (file descriptor) by default.

When Port is closed, the pipes that connect it to external processes are broken, so attempting to read or write to them will give you a SIGPIPE/EPIPE.

You can detect this from an external process when writing or reading the FD and exiting the process.

For example, using your current code, you can use proplists:get_value(os_pid, erlang:port_info(Port)) to retrieve the external process OS PID. If you strace it, you’ll see:

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

Port and SIGPIPE in Erlang

It seems that while SIGPIPE’s default action is to terminate the process, Erlang sets it to ignore signals (so child processes inherit this configuration).

If you cannot modify the external process code to detect EPIPE, you can use this c wrapper to reset the operation:

<pre class=”lang-c prettyprint-override”>#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 [...]]] and open_port

Related Problems and Solutions