Bug #21989
openFiber initialization failure on WASI/ruby.wasm raises TypeError instead of FiberError
Description
Summary¶
When building Ruby for WASM and using it via ruby.wasm, if Fiber initialization fails, the following error may occur:
TypeError: wrong argument type false (expected Class)
instead of the expected FiberError.
This seems to happen because fiber_pool_expand() can call:
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:
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:
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:
{"ruby_revisions":{"head":"4f7dfbe58ee2915b0724251c6464c9b4e0c34245"}}
Then rebuild from a clean state:
rm -rf build rubies
rake npm:ruby-head-wasm-wasi
Run a minimal JS driver that only initializes the VM:¶
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¶
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/cont.c#L557
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/cont.c#L3387-L3409
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:
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/cont.c#L600-L607
However, the initialization order in Init_Cont() still appears to be the same:
https://github.com/ruby/ruby/blob/7209523ffd909ed1914f4ec2544d327a950b19d2/cont.c#L3656-L3678
fiber_pool_initialize() is still called before rb_eFiberError is initialized.
Relation to eac35edf¶
This commit fixes a WASI-specific failure path. However, it may also be worth fixing the issue with the initialization order.
No data to display