[ruby-dev:52210] [Ruby Bug#21989] Fiber initialization failure on WASI/ruby.wasm raises TypeError instead of FiberError
Issue #21989 has been reported by ledsun (shigeru nakajima). ---------------------------------------- Bug #21989: Fiber initialization failure on WASI/ruby.wasm raises TypeError instead of FiberError https://bugs.ruby-lang.org/issues/21989 * Author: ledsun (shigeru nakajima) * Status: Open * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ## Summary When building Ruby for WASM and using it via `ruby.wasm`, if Fiber initialization fails, the following error may occur: ```text TypeError: wrong argument type false (expected Class) ``` instead of the expected `FiberError`. This seems to happen because `fiber_pool_expand()` can call: ```c rb_raise(rb_eFiberError, ...) ``` during `Init_Cont()`, before `rb_eFiberError` has been initialized. ## Reproduction environment I observed this with `ruby.wasm` built against a Ruby revision older than: - https://github.com/ruby/ruby/commit/eac35edfd1101e8f7c34dbdd7b595fdac8f0ad4c For example, the parent revision reproduced the issue: - `4f7dfbe58ee2915b0724251c6464c9b4e0c34245` The issue was observed during `ruby.wasm` VM initialization, before running user Ruby code. ## Reproduction steps ### Add temporary instrumentation around `ruby_setup()` in `ruby.wasm`'s initialization code. In `packages/gems/js/ext/js/witapi-core.c`, replace `ruby_init()` with `ruby_setup()` and print `rb_errinfo()` when it fails: ```c int state = ruby_setup(); if (state) { fprintf(stderr, "ruby_setup state=%d\n", state); VALUE errinfo = rb_errinfo(); if (!NIL_P(errinfo)) { VALUE klass_name = rb_class_name(CLASS_OF(errinfo)); VALUE message = rb_funcall(errinfo, rb_intern("message"), 0); fprintf(stderr, "errinfo class=%s\n", StringValueCStr(klass_name)); fprintf(stderr, "errinfo message=%s\n", StringValueCStr(message)); } exit(EXIT_FAILURE); } ``` The actual failure happens before user Ruby code runs, so I used temporary instrumentation around `ruby_setup()` to inspect `rb_errinfo()`. ### Build `ruby.wasm` with a Ruby revision older than `eac35edfd1101e8f7c34dbdd7b595fdac8f0ad4c`. For example, create `build_manifest.json` at the `ruby.wasm` repository root: ```json {"ruby_revisions":{"head":"4f7dfbe58ee2915b0724251c6464c9b4e0c34245"}} ``` Then rebuild from a clean state: ```sh rm -rf build rubies rake npm:ruby-head-wasm-wasi ``` ### Run a minimal JS driver that only initializes the VM: ```javascript import fs from "node:fs/promises"; import { WASI } from "node:wasi"; import { RubyVM, consolePrinter } from "@ruby/wasm-wasi"; const bytes = await fs.readFile("./packages/npm-packages/ruby-head-wasm-wasi/dist/ruby.wasm"); const module = await WebAssembly.compile(bytes); const wasi = new WASI({ version: "preview1", returnOnExit: false, args: ["ruby.wasm"], env: { RUBY_FIBER_MACHINE_STACK_SIZE: String(1024 * 1024), RUBY_FIBER_VM_STACK_SIZE: String(1024 * 1024), }, }); const vm = new RubyVM(); const imports = { wasi_snapshot_preview1: wasi.wasiImport }; vm.addToImports(imports); const printer = consolePrinter({ stdout: (s) => process.stdout.write(s), stderr: (s) => process.stderr.write(s), }); printer.addToImports(imports); const instance = await WebAssembly.instantiate(module, imports); printer.setMemory(instance.exports.memory); await vm.setInstance(instance); wasi.initialize(instance); vm.initialize(); ``` ## Observed result ```text ruby_setup state=6 errinfo class=TypeError errinfo message=wrong argument type false (expected Class) ``` The failure happens during `vm.initialize()` on the JavaScript side. ## Expected result If Fiber initialization fails, the raised exception should be `FiberError`, not `TypeError`. ## Suspected cause I’ll explain using two revisions: one where I was able to reproduce the error, and another that is closer to the current master branch. ### In the reproduced revision In that revision, `fiber_pool_expand()` could directly raise with `rb_eFiberError`: https://github.com/ruby/ruby/blob/4f7dfbe58ee2915b0724251c6464c9b4e0c34245/c... ```c rb_raise(rb_eFiberError, "can't set a guard page: %s", ERRNOMSG); ``` But this could happen while `Init_Cont()` was still running, before `rb_eFiberError` was initialized: https://github.com/ruby/ruby/blob/4f7dfbe58ee2915b0724251c6464c9b4e0c34245/c... ```c fiber_pool_initialize(&shared_fiber_pool, stack_size, FIBER_POOL_INITIAL_SIZE, vm_stack_size); rb_cFiber = rb_define_class("Fiber", rb_cObject); rb_define_alloc_func(rb_cFiber, fiber_alloc); rb_eFiberError = rb_define_class("FiberError", rb_eStandardError); ``` It appears that the secondary error was caused by `rb_raise()` receiving an uninitialized `rb_eFiberError`: ```text TypeError: wrong argument type false (expected Class) ``` ### In more recent revisions In more recent revisions, `fiber_pool_expand()` no longer directly raises on the guard page setup failure. It now returns `NULL` with `errno` set: https://github.com/ruby/ruby/blob/7209523ffd909ed1914f4ec2544d327a950b19d2/c... However, the initialization order in `Init_Cont()` still appears to be the same: https://github.com/ruby/ruby/blob/7209523ffd909ed1914f4ec2544d327a950b19d2/c... `fiber_pool_initialize()` is still called before `rb_eFiberError` is initialized. ## Relation to eac35edf - https://github.com/ruby/ruby/commit/eac35edfd1101e8f7c34dbdd7b595fdac8f0ad4c This commit fixes a WASI-specific failure path. However, it may also be worth fixing the issue with the initialization order. -- https://bugs.ruby-lang.org/
participants (1)
-
ledsun (shigeru nakajima)