[#120465] [Ruby master Bug#20998] rb_str_locktmp() changes flags of frozen strings and string literals — "Eregon (Benoit Daloze) via ruby-core" <ruby-core@...>

Issue #20998 has been reported by Eregon (Benoit Daloze).

17 messages 2025/01/03

[#120469] [Ruby master Feature#21000] A way to avoid loading constant required by a type check — "Dan0042 (Daniel DeLorme) via ruby-core" <ruby-core@...>

Issue #21000 has been reported by Dan0042 (Daniel DeLorme).

13 messages 2025/01/03

[#120488] [Ruby master Feature#21005] Update the source location method to include line start/stop and column start/stop details — "bkuhlmann (Brooke Kuhlmann) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMDA1IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGJrdWhsbWFubiAoQnJvb2tlIEt1aGxt

16 messages 2025/01/05

[#120580] [Ruby master Bug#21021] "try to mark T_NONE object" with 3.4.1 — "Benoit_Tigeot (Benoit Tigeot) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMDIxIGhhcyBiZWVuIHJlcG9ydGVkIGJ5IEJlbm9pdF9UaWdlb3QgKEJlbm9pdCBU

28 messages 2025/01/09

[#120601] [Ruby master Bug#21024] Ruby including <cstdbool> generates compilation warning with GCC 15, header is deprecated in C++17, — "jprokop (Jarek Prokop) via ruby-core" <ruby-core@...>

Issue #21024 has been reported by jprokop (Jarek Prokop).

7 messages 2025/01/10

[#120617] [Ruby master Feature#21028] Method for finding why an object isn't Ractor shareable — "tenderlovemaking (Aaron Patterson) via ruby-core" <ruby-core@...>

Issue #21028 has been reported by tenderlovemaking (Aaron Patterson).

7 messages 2025/01/11

[#120618] [Ruby master Bug#21029] Prism behavior for `defined? (;x)` differs — "qnighy (Masaki Hara) via ruby-core" <ruby-core@...>

Issue #21029 has been reported by qnighy (Masaki Hara).

12 messages 2025/01/12

[#120619] [Ruby master Bug#21030] Bug: #step with Range<ActiveSupport::Duration> behavior broken on Ruby 3.4.1 — "johnnyshields (Johnny Shields) via ruby-core" <ruby-core@...>

Issue #21030 has been reported by johnnyshields (Johnny Shields).

11 messages 2025/01/12

[#120628] [Ruby master Bug#21031] Incompatibility with prism and parse.y when eval'ing unnamed forwarding variables — "ksss (Yuki Kurihara) via ruby-core" <ruby-core@...>

Issue #21031 has been reported by ksss (Yuki Kurihara).

8 messages 2025/01/13

[#120637] [Ruby master Bug#21032] `Module#autoload?` is slow when `$LOAD_PATH` contains a relative path — "byroot (Jean Boussier) via ruby-core" <ruby-core@...>

Issue #21032 has been reported by byroot (Jean Boussier).

9 messages 2025/01/13

[#120643] [Ruby master Feature#21033] Allow lambdas that don't access `self` to be Ractor shareable — "tenderlovemaking (Aaron Patterson) via ruby-core" <ruby-core@...>

Issue #21033 has been reported by tenderlovemaking (Aaron Patterson).

18 messages 2025/01/13

[#120650] [Ruby master Bug#21034] try to mark T_NONE object error after upgrading to 3.4.1 — "travisbell (Travis Bell) via ruby-core" <ruby-core@...>

Issue #21034 has been reported by travisbell (Travis Bell).

17 messages 2025/01/14

[#120657] [Ruby master Misc#21035] Clarify or redefine Module#autoload? and Module#const_defined? — "fxn (Xavier Noria) via ruby-core" <ruby-core@...>

Issue #21035 has been reported by fxn (Xavier Noria).

28 messages 2025/01/14

[#120694] [Ruby master Bug#21039] Ractor.make_shareable breaks block semantics (seeing updated captured variables) of existing blocks — "Eregon (Benoit Daloze) via ruby-core" <ruby-core@...>

Issue #21039 has been reported by Eregon (Benoit Daloze).

26 messages 2025/01/15

[#120738] [Ruby master Bug#21048] [Prism] rescue in modifier form with condition behaves differently — "Earlopain (Earlopain _) via ruby-core" <ruby-core@...>

Issue #21048 has been reported by Earlopain (Earlopain _).

7 messages 2025/01/19

[#120774] [Ruby master Bug#21087] "try to mark T_NONE object" error in ActiveRecord with 3.4.1 upgrade — "p8 (Petrik de Heus) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMDg3IGhhcyBiZWVuIHJlcG9ydGVkIGJ5IHA4IChQZXRyaWsgZGUgSGV1cykuDQoN

6 messages 2025/01/23

[#120787] [Ruby master Bug#21088] TCPSocket.new raises Socket::ResolutionError instead of Errno::ECONNREFUSED for hosts defined in /etc/hosts — "dmlary (David Lary) via ruby-core" <ruby-core@...>

Issue #21088 has been reported by dmlary (David Lary).

9 messages 2025/01/24

[#120811] [Ruby master Bug#21095] Prefer `uname -n` over `hostname` in tests. — "ioquatix (Samuel Williams) via ruby-core" <ruby-core@...>

Issue #21095 has been reported by ioquatix (Samuel Williams).

10 messages 2025/01/28

[#120819] [Ruby master Bug#21097] `x = a rescue b in c` and `def f = a rescue b in c` parsed differently between parse.y and prism — "tompng (tomoya ishida) via ruby-core" <ruby-core@...>

Issue #21097 has been reported by tompng (tomoya ishida).

12 messages 2025/01/29

[#120840] [Ruby master Misc#21100] DevMeeting before or after RubyKaigi2025 — "ko1 (Koichi Sasada) via ruby-core" <ruby-core@...>

SXNzdWUgIzIxMTAwIGhhcyBiZWVuIHJlcG9ydGVkIGJ5IGtvMSAoS29pY2hpIFNhc2FkYSkuDQoN

9 messages 2025/01/30

[ruby-core:120798] [Ruby master Bug#20682] Slave PTY output is lost after a child process exits in macOS

From: "nobu (Nobuyoshi Nakada) via ruby-core" <ruby-core@...>
Date: 2025-01-27 08:17:51 UTC
List: ruby-core #120798
Issue #20682 has been updated by nobu (Nobuyoshi Nakada).


ono-max (Naoto Ono) wrote in #note-4:
> This program hangs in `Process.wait(pid)`.
> 
> ```ruby
> require 'pty'
> _, _, pid = PTY.spawn('ruby', '-e', 'puts "a"; puts "b"')
> Process.waitpid(pid)
> ```

Now I tried this, and it exited immediately, on arm64 macOS 14.7.2 23H311.

However, the code in the description still returns `nil`.

> ```ruby
> require 'pty'
> 
> r, w, pid = PTY.spawn('ruby', '-e', 'puts "a"')
> sleep 3
> puts r.gets #=> Returns nil
> ```


----------------------------------------
Bug #20682: Slave PTY output is lost after a child process exits in macOS
https://bugs.ruby-lang.org/issues/20682#change-111665

* Author: ono-max (Naoto Ono)
* Status: Open
* Backport: 3.1: UNKNOWN, 3.2: UNKNOWN, 3.3: UNKNOWN
----------------------------------------
According to Launchable, the following PTY tests are flaky only on macOS.

https://app.launchableinc.com/organizations/ruby/workspaces/ruby/data/test-paths/file%3Dtest%2Ftest_pty.rb%23%23%23class%3DTestPTY%23%23%23testcase%3Dtest_spawn_without_block
https://app.launchableinc.com/organizations/ruby/workspaces/ruby/data/test-paths/file%3Dtest%2Ftest_pty.rb%23%23%23class%3DTestPTY%23%23%23testcase%3Dtest_spawn_with_block
https://app.launchableinc.com/organizations/ruby/workspaces/ruby/data/test-paths/file%3Dtest%2Ftest_pty.rb%23%23%23class%3DTestPTY%23%23%23testcase%3Dtest_commandline
https://app.launchableinc.com/organizations/ruby/workspaces/ruby/data/test-paths/file%3Dtest%2Ftest_pty.rb%23%23%23class%3DTestPTY%23%23%23testcase%3Dtest_argv0

It's because the slave PTY output is lost after a child process exits in macOS. Here is the code to reproduce the problem.
When I remove `sleep 3` from the code, "a" is returned.

```
require 'pty'

r, w, pid = PTY.spawn('ruby', '-e', 'puts "a"')
sleep 3
puts r.gets #=> Returns nil
```

Based on my investigation, this issue happens in the macOS side and it's almost same as https://github.com/pexpect/pexpect/issues/662.
The cause is described as follows in the ticket:

> // NOTE[macOS-S_CTTYREF]: On macOS, after a forkpty(), if the pty slave (child)
// is closed before the pty master (parent) reads, the pty's buffer is cleared
// thus the master (parent) reads nothing. This can happen if the child exits
// before the parent has a chance to call master.read().
//
// This issue has been reported to Apple, but has not been resolved:
// https://developer.apple.com/forums/thread/663632
//
// Work around this issue by opening /dev/tty then closing it. This ultimately
// causes the child's exit() to flush the slave pty's output buffer in a
// blocking way. This fixes the problem on macOS 13.2 in my testing.
//
// Here's how the workaround works in detail:
//
// If we open /dev/tty, it sets the S_CTTYREF flag on the process. This flag
// remains set if we close the /dev/tty file descriptor.
// https://github.com/apple-oss-distributions/xnu/blob/aca3beaa3dfbd42498b42c5e5ce20a938e6554e5/bsd/kern/tty_tty.c#L128
// Additionally, opening /dev/tty retains a reference to the pty slave.
// https://github.com/apple-oss-distributions/xnu/blob/aca3beaa3dfbd42498b42c5e5ce20a938e6554e5/bsd/kern/tty_tty.c#L147
//
// When the child process exits:
//
// 1. All open file descriptors (including stdin/stdout/stderr which are the pty
//    slave) are closed. This does *not* drain unread pty slave output.
//    * If S_CTTYREF was set, closing the file descriptors does not close the
//      last reference to the pty slave, so no cleanup happens yet.
//    * NOTE[macOS-pty-close-loss]: If S_CTTYREF was not set, closing the file
//      descriptors drops the last reference to the pty slave. Unread data is
//      dropped.
//
// 2. If the S_CTTYREF flag is set on the child process, the controlling
//    terminal (pty slave) is closed. XNU's ptsclose() ultimately calls
//    ttywait().
//    https://github.com/apple-oss-distributions/xnu/blob/aca3beaa3dfbd42498b42c5e5ce20a938e6554e5/bsd/kern/kern_exit.c#L2272
//    * ttywait() is the same as ioctl(slave, TIOCDRAIN); it blocks waiting for
//      output to be received.
//      https://github.com/apple-oss-distributions/xnu/blob/aca3beaa3dfbd42498b42c5e5ce20a938e6554e5/bsd/kern/tty.c#L1129-L1130
//    * NOTE[macOS-pty-waitpid-hang]: Because of the blocking ttywait(), the
//      process is in an exiting (but not zombie) state. waitpid() will hang.
//
//    * NOTE[macOS-pty-close-loss]: If the S_CTTYREF flag is not set on the
//      child process, ttywait() is not called, thus the pty slave does not
//      block waiting for the output to be received, and the output is dropped.
//      A well-behaving parent will use a poll() loop anyway, so this isn't a
//      problem. (It does make quick tests annoying to write though.)
//
// Demonstration of NOTE[macOS-pty-close-loss] (S_CTTYREF is not set before
// exit):
//
//     // On macOS, this program should report 'data = ""', demonstrating that
//     // writes are lost.
//
//     #include <stdlib.h>
//     #include <errno.h>
//     #include <stdio.h>
//     #include <string.h>
//     #include <unistd.h>
//     #include <util.h>
//
//     int main() {
//       int tty_fd;
//       pid_t pid = forkpty(&tty_fd, /*name=*/NULL, /*termp=*/NULL,
//                           /*winp=*/NULL);
//       if (pid == -1) { perror("forkpty"); abort(); }
//
//       if (pid == 0) {
//         // Child.
//         (void)write(STDOUT_FILENO, "y", 1);
//         exit(0);
//       } else {
//         // Parent.
//
//         // Cause the child to write() then exit(). exit() will drop written
//         // data.
//         sleep(1);
//
//         char buffer[10];
//         ssize_t rc = read(tty_fd, buffer, sizeof(buffer));
//         if (rc < 0) { perror("read"); abort(); }
//         fprintf(stderr, "data = \"%.*s\"\n", (int)rc, buffer);
//       }
//
//       return 0;
//     }
//
// Demonstration of NOTE[macOS-pty-waitpid-hang] (S_CTTYREF is set before exit):
//
//     // On macOS, this program should hang, demonstrating that the child
//     // process doesn't finish exiting.
//     //
//     // During the hang, observe that the child is in an exiting state ("E"):
//     //
//     //     $ ps -e -o pid,stat | grep 20125
//     //     20125 ?Es
//
//     #include <errno.h>
//     #include <fcntl.h>
//     #include <stdio.h>
//     #include <stdlib.h>
//     #include <string.h>
//     #include <unistd.h>
//     #include <util.h>
//
//     int main() {
//       int tty_fd;
//       pid_t pid = forkpty(&tty_fd, /*name=*/NULL, /*termp=*/NULL,
//                           /*winp=*/NULL);
//       if (pid == -1) { perror("forkpty"); abort(); }
//
//       if (pid == 0) {
//         // Child.
//         close(open("/dev/tty", O_WRONLY));
//         (void)write(STDOUT_FILENO, "y", 1);
//         exit(0);
//       } else {
//         // Parent.
//
//         fprintf(stderr, "child PID: %d\n", pid);
//
//         // This will hang because, despite the child being is an exiting
//         // state, the child is waiting for us to read().
//         pid_t rc = waitpid(pid, NULL, 0);
//         if (rc < 0) { perror("waitpid"); abort(); }
//       }
//
//       return 0;
//     }


In Ruby, PTY is implemented with [fork()](https://github.com/ruby/ruby/blob/master/process.c#L1706) and [posix_openpt()](https://github.com/ruby/ruby/blob/master/ext/pty/pty.c#L329) in macOS. I could reproduce the problem in the following script.


```
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int main() {
    int master_fd, slave_fd;
    pid_t child_pid;
    char *slave_name;

    // Open a master pseudo-terminal
    master_fd = posix_openpt(O_RDWR | O_NOCTTY);
    if (master_fd == -1) {
        perror("posix_openpt");
        exit(1);
    }

    // Grant access to the slave pseudo-terminal
    if (grantpt(master_fd) == -1) {
        perror("grantpt");
        exit(1);
    }

    // Unlock the slave pseudo-terminal
    if (unlockpt(master_fd) == -1) {
        perror("unlockpt");
        exit(1);
    }

    // Get the name of the slave pseudo-terminal
    slave_name = ptsname(master_fd);
    if (slave_name == NULL) {
        perror("ptsname");
        exit(1);
    }

    // Fork a child process
    child_pid = fork();
    if (child_pid == -1) {
        perror("fork");
        exit(1);
    } else if (child_pid == 0) {
        // Child process

        // Open the slave pseudo-terminal
        slave_fd = open(slave_name, O_RDWR);
        if (slave_fd == -1) {
            perror("open");
            exit(1);
        }

        // Create a new session and process group
        if (setsid() == -1) {
            perror("setsid");
            exit(1);
        }

        // Set the controlling terminal for the child process
        if (ioctl(slave_fd, TIOCSCTTY, NULL) == -1) {
            perror("ioctl");
            exit(1);
        }

        // Duplicate the slave file descriptor to stdin, stdout, and stderr
        if (dup2(slave_fd, STDIN_FILENO) == -1) {
            perror("dup2");
            exit(1);
        }
        if (dup2(slave_fd, STDOUT_FILENO) == -1) {
            perror("dup2");
            exit(1);
        }
        if (dup2(slave_fd, STDERR_FILENO) == -1) {
            perror("dup2");
            exit(1);
        }
        // close(open("/dev/tty", O_WRONLY));

        // Close the original slave file descriptor
        close(slave_fd);

        // Execute a shell or other program
        (void)write(STDOUT_FILENO, "y", 1);
        exit(1);
    } else {
        sleep(5);
        char buffer[10];
        ssize_t rc = read(master_fd, buffer, sizeof(buffer));
        if (rc < 0)
        {
            perror("read");
            abort();
        }
        fprintf(stderr, "data = \"%.*s\"\n", (int)rc, buffer);
        // Clean up
        close(master_fd);
    }

    return 0;
}
```





-- 
https://bugs.ruby-lang.org/
 ______________________________________________
 ruby-core mailing list -- ruby-core@ml.ruby-lang.org
 To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
 ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/


In This Thread

Prev Next