8000 OTP 28: Spawning an escript as a port crashes on Windows. · Issue #9872 · erlang/otp · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
OTP 28: Spawning an escript as a port crashes on Windows. #9872
Closed
@yoshi-monster

Description

@yoshi-monster

Describe the bug

Since OTP-28, it is no longer possible to spawn an erlang process as a child process on Windows, as long as standard error is inherited. It seems like the new tty tries to set some console mode flags on the handles, which are not valid for this call when the process is run as a child process.

To Reproduce

Run the following code as escript echo.erl on Windows, using OTP-28:

`echo.erl`
-module(echo).
-export([main/0, main/1]).
-mode(compile).

main() -> main([]).

main([]) -> parent();
main(["child"]) -> child().

parent() ->
    Escript = os:find_executable("escript"),
    Port = open_port({spawn_executable, Escript}, 
                     [{args, [escript:script_name(), "child"]}, {line, 1024}, hide]),
    
    port_command(Port, "hello world\n"),
    port_command(Port, []),
    
    parent_loop(Port).

parent_loop(Port) ->
    receive
        {Port, {data, {eol, Response}}} ->
            io:format("Parent received: ~s~n", [Response]),
            parent_loop(Port);
        {Port, {exit_status, Status}} ->
            io:format("Child exited with status: ~p~n", [Status]),
            port_close(Port);
        {Port, closed} ->
            io:format("Port closed~n")
    after 5000 ->
        io:format("Timeout - closing port~n"),
        port_close(Port)
    end.

child() ->
    case io:get_line("") of
        eof -> ok;
        Line ->
            io:format("~s~n", [string:uppercase(string:trim(Line))]),
            child()
    end.

Expected behaviour

The parent process is able to communicate with the child process using stdin and stdout, while stderr is inherited and forwarded to the console. The child process does not crash on startup. The behaviour is the same as using OTP-27.

Observed behaviour

The child process crashes with the following crash report:

Crash report
=ERROR REPORT==== 24-May-2025::13:32:42.490000 ===
Error in process <0.52.0> with exit value:
{{case_clause,{error,{'SetConsoleMode','The handle is invalid.\r\n'}}},
 [{prim_tty,init_term,1,[{file,"prim_tty.erl"},{line,283}]},
  {standard_error,server,0,[{file,"standard_error.erl"},{line,72}]}]}

=SUPERVISOR REPORT==== 24-May-2025::13:32:42.490000 ===
    supervisor: {local,standard_error_sup}
    errorContext: child_terminated
    reason: {{case_clause,
                 {error,{'SetConsoleMode','The handle is invalid.\r\n'}}},
             [{prim_tty,init_term,1,[{file,"prim_tty.erl"},{line,283}]},
              {standard_error,server,0,
                  [{file,"standard_error.erl"},{line,72}]}]}
    offender: [{pid,<0.52.0>},{mod,standard_error}]

=ERROR REPORT==== 24-May-2025::13:32:42.504000 ===
** Generic server standard_error_sup terminating
** Last message in was {'EXIT',<0.52.0>,
                        {{case_clause,
                          {error,
                           {'SetConsoleMode','The handle is invalid.\r\n'}}},
                         [{prim_tty,init_term,1,
                           [{file,"prim_tty.erl"},{line,283}]},
                          {standard_error,server,0,
                           [{file,"standard_error.erl"},{line,72}]}]}}
** When Server state == {state,standard_error,undefined,<0.52.0>,
                               {local,standard_error_sup}}
** Reason for termination ==
** {{case_clause,{error,{'SetConsoleMode','The handle is invalid.\r\n'}}},
    [{prim_tty,init_term,1,[{file,"prim_tty.erl"},{line,283}]},
     {standard_error,server,0,[{file,"standard_error.erl"},{line,72}]}]}

=CRASH REPORT==== 24-May-2025::13:32:42.504000 ===
  crasher:
    initial call: supervisor_bridge:standard_error/1
    pid: <0.51.0>
    registered_name: standard_error_sup
    exception exit: {{case_clause,
                         {error,
                             {'SetConsoleMode','The handle is invalid.\r\n'}}},
                     [{prim_tty,init_term,1,
                          [{file,"prim_tty.erl"},{line,283}]},
                      {standard_error,server,0,
                          [{file,"standard_error.erl"},{line,72}]}]}
      in function  gen_server:handle_common_reply/5 (gen_server.erl:2562)
    ancestors: [kernel_sup,<0.47.0>]
    message_queue_len: 0
    messages: []
    links: [<0.49.0>]
    dictionary: []
    trap_exit: true
    status: running
    heap_size: 610
    stack_size: 29
    reductions: 792
  neighbours:

=ERROR REPORT==== 24-May-2025::13:32:42.601000 ===
** State machine user_drv terminating
** When server state  = {undefined,undefined}
** Reason for termination = error:{badmatch,{error,arguments}}
** Callback modules = [user_drv]
** Callback mode = state_functions
** Stacktrace =
**  [{user_drv,init_standard_error,2,[{file,"user_drv.erl"},{line,212}]},
     {user_drv,init,1,[{file,"user_drv.erl"},{line,188}]},
     {gen_statem,init_it,6,[{file,"gen_statem.erl"},{line,3323}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,333}]}]

=CRASH REPORT==== 24-May-2025::13:32:42.601000 ===
  crasher:
    initial call: user_drv:init/1
    pid: <0.69.0>
    registered_name: []
    exception error: no match of right hand side value {error,arguments}
      in function  user_drv:init_standard_error/2 (user_drv.erl:212)
      in call from user_drv:init/1 (user_drv.erl:188)
      in call from gen_statem:init_it/6 (gen_statem.erl:3323)
    ancestors: [<0.68.0>,kernel_sup,<0.47.0>]
    message_queue_len: 0
    messages: []
    links: [<0.70.0>,<0.71.0>]
    dictionary: []
    trap_exit: true
    status: running
    heap_size: 610
    stack_size: 29
    reductions: 511
  neighbours:
    neighbour:
      pid: <0.71.0>
      registered_name: user_drv_reader
      initial call: prim_tty:reader/1
      current_function: {prim_tty,reader_loop,2}
      ancestors: [user_drv,<0.68.0>,kernel_sup,<0.47.0>]
      message_queue_len: 0
      links: [<0.69.0>]
      trap_exit: false
      status: waiting
      heap_size: 233
      stack_size: 11
      reductions: 36
      current_stacktrace: [{prim_tty,reader_loop,2,[{file,"prim_tty.erl"},{line,553}]},
                  {proc_lib,init_p_do_apply,3,
                            [{file,"proc_lib.erl"},{line,333}]}]
    neighbour:
      pid: <0.70.0>
      registered_name: user_drv_writer
      initial call: prim_tty:writer/1
      current_function: {prim_tty,writer_loop,2}
      ancestors: [user_drv,<0.68.0>,kernel_sup,<0.47.0>]
      message_queue_len: 0
      links: [<0.69.0>]
      trap_exit: false
      status: waiting
      heap_size: 233
      stack_size: 7
      reductions: 24
      current_stacktrace: [{prim_tty,writer_loop,2,[{file,"prim_tty.erl"},{line,652}]},
                  {proc_lib,init_p_do_apply,3,
                            [{file,"proc_lib.erl"},{line,333}]}]

Affected versions
OTP-28.0

Additional context
We've ran into this issue in the Gleam compiler (see gleam-lang/gleam#4619) - the compiler uses Rusts std::process module to spawn an escript as a child process to handle compilation of Erlang and Elixir files to bytecode. Similar to the escript above, it tries to capture stdin and stdout, while "inheriting" the stderr handle to pass all Erlang and Elixir compilation errors to the user.

In Rust, using a pipe instead of inheriting stderr and spawning a thread that continuously reads from it fixes the issue; in Erlang, a workaround is to pass stderr_to_stdout. I've also tried some combinations of -noshell and -noinput, which did not seem to make a difference.

Metadata

Metadata

Assignees

Labels

bugIssue is reported as a bugteam:VMAssigned to OTP team VM

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions

    0