Signal handling: CRuby-compatible SignalException conversion + SIG_DFL re-raise #3623
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Rust | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| branches: [master] | |
| jobs: | |
| amd64: | |
| runs-on: ubuntu-latest | |
| env: | |
| CARGO_TERM_COLOR: always | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "4.0.2" | |
| - uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| toolchain: nightly | |
| - uses: taiki-e/install-action@cargo-llvm-cov | |
| - uses: taiki-e/install-action@nextest | |
| - name: Clone optcarrot | |
| run: git clone https://github.com/mame/optcarrot.git ../optcarrot | |
| - name: Clone ruby/spec and mspec (pinned) | |
| # Pinned to fixed commits so upstream ruby/spec changes can't break CI. | |
| # `bin/test` runs these specs under `set -e`, so a single newly added | |
| # upstream spec that monoruby doesn't pass yet would fail the whole run. | |
| # Bump these SHAs deliberately when adopting newer specs. | |
| env: | |
| SPEC_REV: baf5738ada1709afdbcf222c7609d3379b9d211d | |
| MSPEC_REV: dffcdf7d00612c13465983224203e457a30cf124 | |
| run: | | |
| git init ../spec | |
| git -C ../spec fetch --depth 1 https://github.com/ruby/spec.git "$SPEC_REV" | |
| git -C ../spec checkout FETCH_HEAD | |
| git init ../mspec | |
| git -C ../mspec fetch --depth 1 https://github.com/ruby/mspec.git "$MSPEC_REV" | |
| git -C ../mspec checkout FETCH_HEAD | |
| - name: Clone ruby-bench | |
| run: git clone --depth 1 https://github.com/ruby/ruby-bench.git ../ruby-bench | |
| - name: Install ruby-bench gem dependencies | |
| # bin/ruby-bench-diff requires `erubi`. The other benchmarks | |
| # only use stdlib gems, so a single explicit install is enough. | |
| run: gem install erubi --no-document | |
| - name: Test | |
| run: bin/test | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v5 | |
| # Coverage upload is a non-essential, externally-dependent step: the | |
| # Codecov CLI download/GPG-signature verification fails intermittently | |
| # on Codecov's side (e.g. "Could not verify signature. No public key"), | |
| # which must not fail the whole CI run when the tests themselves pass. | |
| continue-on-error: true | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos | |
| files: lcov.info | |
| fail_ci_if_error: false | |
| darwin: | |
| # Native Apple Silicon (arm64-apple-darwin) runner. build.rs sets the `jit` | |
| # cfg on arm64 (only `jit_x86` is x86-only), so this builds and runs the | |
| # AArch64 JIT (AsmIR→A64 backend, asmir/compile_stub.rs) — instructions not | |
| # yet ported bail to the VM. It runs the same `bin/test` as the Linux | |
| # `amd64` job (portable head/yjit handling makes it BSD/macOS-safe), so | |
| # the arm64 VM + JIT get the full check scope (unit tests, benchmark diffs, | |
| # optcarrot, ruby-bench, ruby/spec). | |
| # | |
| # SKIP_COV=1: this job does NOT collect coverage. On the GitHub M1 runner, | |
| # llvm-cov coverage instrumentation itself trips a latent VM-layer memory | |
| # bug (a value is freed mid-flight -> String INVALID(255) -> SIGABRT). It is | |
| # independent of the JIT (`--no-jit` crashes the same way) and does not | |
| # reproduce on x86-64, qemu-aarch64, or a local Apple Silicon Mac — so it is | |
| # specific to native-M1 + coverage instrumentation and is tracked | |
| # separately. The Linux `amd64` job owns the Codecov upload. | |
| runs-on: macos-latest | |
| # Backstop: if a hung attempt somehow escapes the per-attempt 25-min cap on | |
| # the Test step below, GitHub cancels the job cleanly here (2 attempts × | |
| # 25 min + setup/clone overhead) rather than letting the runner die with | |
| # "lost communication". | |
| timeout-minutes: 70 | |
| env: | |
| CARGO_TERM_COLOR: always | |
| SKIP_COV: "1" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| ruby-version: "4.0.2" | |
| - name: Install bigdecimal (no longer a default gem since Ruby 3.4) | |
| run: gem install bigdecimal --no-document | |
| - name: Install ruby-bench gem dependencies | |
| # bin/ruby-bench-diff requires `erubi`; matches the `coverage` job. | |
| run: gem install erubi --no-document | |
| - name: Install libffi + pkg-config | |
| # The aarch64-macOS dep block in monoruby/Cargo.toml builds libffi-sys | |
| # with the `system` feature; libffi is keg-only so pkg-config needs its | |
| # prefix on PKG_CONFIG_PATH to find it. | |
| run: | | |
| brew install libffi pkg-config | |
| echo "PKG_CONFIG_PATH=$(brew --prefix libffi)/lib/pkgconfig" >> "$GITHUB_ENV" | |
| - uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| toolchain: nightly | |
| - uses: taiki-e/install-action@nextest | |
| - name: Clone optcarrot | |
| run: git clone https://github.com/mame/optcarrot.git ../optcarrot | |
| - name: Clone ruby/spec and mspec (pinned) | |
| # Pinned to fixed commits so upstream ruby/spec changes can't break CI. | |
| # Keep these SHAs in sync with the `amd64` job above; bump deliberately. | |
| env: | |
| SPEC_REV: baf5738ada1709afdbcf222c7609d3379b9d211d | |
| MSPEC_REV: dffcdf7d00612c13465983224203e457a30cf124 | |
| run: | | |
| git init ../spec | |
| git -C ../spec fetch --depth 1 https://github.com/ruby/spec.git "$SPEC_REV" | |
| git -C ../spec checkout FETCH_HEAD | |
| git init ../mspec | |
| git -C ../mspec fetch --depth 1 https://github.com/ruby/mspec.git "$MSPEC_REV" | |
| git -C ../mspec checkout FETCH_HEAD | |
| - name: Clone ruby-bench | |
| run: git clone --depth 1 https://github.com/ruby/ruby-bench.git ../ruby-bench | |
| - name: Test (VM + AArch64 JIT, arm64-apple-darwin) | |
| # The arm64 JIT/runtime intermittently (~a few % of runs) hangs by | |
| # exhausting memory: the macOS runner OOM-dies ("loses communication") | |
| # before any stack can be captured, and on-demand reproduction under | |
| # instrumentation has so far failed (nextest ×5 and full bin/test ×8 | |
| # both ran clean). Two guards: | |
| # * retry — cap each attempt at 25 min and retry once, so a hang fails | |
| # fast (normal pass ~11 min) and the rerun recovers automatically; and | |
| # * a background memory watchdog that NAMES the culprit the next time | |
| # the hang strikes a real run: it polls every monoruby process's RSS | |
| # and, when one exceeds ~4 GB (or the parallel sum exceeds ~5 GB), | |
| # logs that process's command line (the nextest test / benchmark / | |
| # spec it is running), `sample`s its stack, and kills it before the | |
| # runner dies. It only acts above 4 GB, so normal runs are | |
| # unaffected and the retry recovers the killed attempt. | |
| # The manual .github/workflows/darwin-hang-debug.yml drives the same | |
| # watchdog in a loop for active reproduction. | |
| uses: nick-fields/retry@v3 | |
| with: | |
| timeout_minutes: 25 | |
| max_attempts: 2 | |
| command: | | |
| RSS_LIMIT_KB=4000000 | |
| SUM_LIMIT_KB=5000000 | |
| ( while true; do | |
| snap=$(ps -Ao pid,rss,comm | awk 'NR>1 { n=split($3,a,"/"); b=a[n]; if (b ~ /^monoruby/) print $1, $2 }') | |
| if [ -n "$snap" ]; then | |
| sum=$(echo "$snap" | awk '{s+=$2} END{print s+0}') | |
| set -- $(echo "$snap" | awk '{if($2>m){m=$2;p=$1}} END{print p+0, m+0}') | |
| if [ "${2:-0}" -gt "$RSS_LIMIT_KB" ] || [ "${sum:-0}" -gt "$SUM_LIMIT_KB" ]; then | |
| echo "==================== MEMORY WATCHDOG fired @ $(date -u +%H:%M:%S) ====================" | |
| echo "sum_rss_kb=$sum top_pid=$1 top_rss_kb=$2 (caps: per-proc=$RSS_LIMIT_KB sum=$SUM_LIMIT_KB)" | |
| ps -Ao pid,rss,command | awk 'NR>1 { n=split($3,a,"/"); b=a[n]; if (b ~ /^monoruby/) print }' | |
| echo "---- CULPRIT command line (pid $1) ----"; ps -p "$1" -o command= 2>/dev/null || true | |
| echo "---- sample pid $1 (2s) ----"; /usr/bin/sample "$1" 2 2>&1 | sed -n '1,160p' || true | |
| echo "---- killing pid $1 ----"; kill -9 "$1" 2>/dev/null || true | |
| fi | |
| fi | |
| sleep 4 | |
| done ) & | |
| WATCHDOG_PID=$! | |
| trap 'kill "$WATCHDOG_PID" 2>/dev/null || true' EXIT | |
| bin/test | |