Skip to content

Port can fail to deliver exit_status when stderr_to_stdout is used #10411

@jcpetruzza

Description

@jcpetruzza

Describe the bug
In an scenario like this:

BEAM1 --[open_port stderr_to_stdout]--> BEAM2 --[open_port use_stdio]--> SOME_PROCESS

It is possible for BEAM2 to finish and yet BEAM1 will not receive the exit_status message, unless SOME_PROCESS is also killed

To Reproduce
Here is a small repro:

-module(repro).
-export([start/0, start/1]).

start() ->
    start([]).

start([]) ->
    io:format("[PARENT] Starting child~n"),
    ErlBin = filename:join([code:root_dir(), "bin", "erl"]),
    Port = open_port({spawn_executable, ErlBin}, [
        {args, ["-noshell", "-run", atom_to_list(?MODULE), ?FUNCTION_NAME, "child"]},
        use_stdio,
        stderr_to_stdout,
        exit_status
    ]),
    parent_loop(Port);

start(["child"]) ->
    io:format("[CHILD] open 'sleep infinity' port~n"),
    SleepBin = os:find_executable("sleep"),
    Port = open_port({spawn_executable, SleepBin}, [
        {args, ["infinity"]},
        use_stdio,
        exit_status
    ]),
    io:format("[CHILD] SLEEP PROCESS INFO ~p~n", [erlang:port_info(Port, os_pid)]),
    io:format("[CHILD] EXIT~n"),
    erlang:halt(42).

parent_loop(Port) ->
    receive
        {Port, {exit_status, N}} ->
            io:format("[PARENT] Child exited with status ~p. Exiting~n", [N]),
            erlang:halt(0);
        {Port, {data, Data}} ->
            io:format("~ts", [Data]),
            parent_loop(Port);
        M ->
            io:format("UNEXPECTED ~p", [M]),
            parent_loop(Port)
    end.

Run it with:

$ erlc repro.erl && erl -noshell -run repro

If one comments out the stderr_to_stdout line, things work as expected

Expected behavior
The expected behaviour is that the program ends. Instead it hangs waiting to receive the exit_status:

$ erlc repro.erl && erl -noshell -run repro
[PARENT] Starting child
[CHILD] open 'sleep infinity' port
[CHILD] SLEEP PROCESS INFO {os_pid,974596}
[CHILD] EXIT
<HANGS>

Interestingly, if one kills the "sleep process pid" (kill 974596). The program finishes, with the correct exit status

[PARENT] Child exited with status 42. Exiting

As mentioned above, removing stderr_to_stdout makes it work as expected

Affected versions
Tried it on OTP 27 and OTP 28, with the same behaviour

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIssue is reported as a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions