Project

General

Profile

Bug #16342

macOS + Ruby 2.6.0 以降で拡張ライブラリ内での waitpid() コールに失敗する

Added by watson1978 (Shizuo Fujita) about 1 month ago. Updated 31 minutes ago.

Status:
Rejected
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-dev:50858]

Description

再現環境

  • Ruby 2.6.0 以降
  • macOS 10.15 (試した環境)

問題の説明

macOS 上で Ruby 2.6.0 以降を使用した際に、拡張ライブラリ内で fork()/exec() で外部コマンドを実行したあと waitpid() をコールすると失敗するようになりました。
私がメンテナンスしている RMagick 利用している ImageMagick が PDF を処理する際にこのような処理を行っており、macOS + Ruby 2.6.0 以降で処理に失敗するようになり困っております。
https://github.com/rmagick/rmagick/issues/483

https://github.com/Watson1978/rmagick-issue483 に再現コードを用意しており、以下の様に実行していただくと macOS + Ruby 2.6.0 以降でエラー終了します。

$ ./setup.sh
$ ruby sample.rb
$ ruby sample.rb
Ruby v2.6.0
Error waitpid(): Interrupted system call
$ ./setup.sh
$ ruby ./sample.rb
Ruby v2.6.5
Error waitpid(): Interrupted system call

Ruby 2.5.x 以下ですと期待通りに WIFEXITED のパスを通り正常に終了します。

$ ./setup.sh
$ ruby sample.rb
Ruby v2.5.7
(1) WIFEXITED : status = 0
$ ./setup.sh
$ ruby sample.rb
Ruby v2.3.8
(1) WIFEXITED : status = 0

確認した限り、macOS 上だけでこの現象が発生します。

再現コード

https://github.com/Watson1978/rmagick-issue483 のレポジトリに以下と同様の再現コードをコミットしており、すぐに確認できるかと思います。

#include <ruby.h>
#include <unistd.h>

VALUE
sample_test(VALUE self)
{
    int status = 0;
    int child_status = 0;
    pid_t child_pid = fork();


    if (child_pid == 0) {
        char *const args[] = {
            "/bin/sleep",
            "1",
            NULL
        };
        status = execvp(args[0], args);
        exit(0);
    }
    else {
        pid_t pid;

        pid = waitpid(child_pid, &child_status, 0);
        if (pid == -1) {
            status = -1;
            perror("Error waitpid()");
        }
        else {
            if (WIFEXITED(child_status)) {
                // Expected path
                status = WEXITSTATUS(child_status);
                printf("(1) WIFEXITED : status = %d\n", status);
            }
            else if (WIFSIGNALED(child_status)) {
                status = -1;
                printf("(2) WIFSIGNALED : status = %d\n", status);
            }
        }
    }


    return INT2FIX(status);
}

void Init_sample(void)
{
    VALUE rb_cSample = rb_define_class("Sample", rb_cObject);
    rb_define_method(rb_cSample, "test", sample_test, 0);
}

History

Updated by mame (Yusuke Endoh) about 1 month ago

まったく試さずにエラーメッセージだけ見て回答してますが、waitpidがシグナルで割り込まれてEINTRで終わっているのだと思います。2.5で動いてたのは、たぶん偶然じゃないかと。

https://abrakatabura.hatenablog.com/entry/2015/04/11/081256

のようにwhileしてみると良さそうな気がします。

Updated by watson1978 (Shizuo Fujita) about 1 month ago

書き忘れていたのですが、 https://github.com/ruby/ruby/commit/48b6bd74e2febde095ac85d818e94c0e58677647 の変更を境にエラーになるようになり、それ以前ですと問題なく動いておりました。

Updated by mame (Yusuke Endoh) 31 minutes ago

  • Status changed from Open to Rejected

頻度の問題で、2.6.0 以前でもこの問題は起きえます。多少人為的ですが、irbで↓のようにやれば Interrupted system call になりました。

irb(main):001:0> RUBY_VERSION
=> "2.5.7"
irb(main):002:0> trap(:WINCH) { }
=> "SYSTEM_DEFAULT"
irb(main):003:0> load "sample.rb" # ロード開始直後にターミナルをリサイズする
Ruby v2.5.7
Error waitpid(): Interrupted system call
=> true

ということで、少なくともその再現コードの waitpid(2) の使い方には問題があると思います。

私もタイマースレッド削除の変更をちゃんと理解してないんで大きなことはいえないんですが、調査してパッチを貰えれば検討できるかもしれません。(なんかのシグナルにSA_RESTARTを設定すればいいのかもしれないですが、他にも影響ありそう)

Also available in: Atom PDF