Skip to content

Race condition with pty_echo on macOS #193

@wojtekmach

Description

@wojtekmach

I noticed the following surprising behavior on macOS, the input is echoed back even though pty_echo is not enabled:

1> exec:start(), {ok, P, I} = exec:run("tr a-z A-Z", [stdin, stdout, pty]), exec:send(I, <<"hello\n">>), timer:sleep(100), flush().
Shell got {stdout,55327,<<"hello\r\n">>}
Shell got {stdout,55327,<<"HELLO\r\n">>}
ok

On Linux, I get the expected output line only:

1> exec:start(), {ok, P, I} = exec:run("tr a-z A-Z", [stdin, stdout, pty]), exec:send(I, <<"hello\n">>), timer:sleep(100), flush().
Shell got {stdout,55327,<<"HELLO\r\n">>}
ok

It looks like pty difference between OSes and a race condition where the child hasn't disabled echo before parent started writing. This workaround seems to fix it on my machine:

diff --git a/c_src/exec_impl.cpp b/c_src/exec_impl.cpp
index 7cf8643..3551c5b 100644
--- a/c_src/exec_impl.cpp
+++ b/c_src/exec_impl.cpp
@@ -662,6 +662,13 @@ pid_t start_child(CmdOptions& op, std::string& error)

     DEBUG(debug > 1, "Spawned child pid %d", pid);

+    #ifdef __APPLE__
+    if (op.pty()) {
+       // Give child a moment to set up PTY on macOS
+       usleep(1000);
+    }
+    #endif
+
     // Either the parent or the child could use setpgid() to change
     // the process group ID of the child. However, because the scheduling
     // of the parent and child is indeterminate after a fork(), we can’t

I think this is similar in spirit to #38 that pty support hasn't been thoroughly tested on macOS.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions