Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent accidental re-initialization #3307

Closed
matheus23 opened this issue Feb 14, 2023 · 0 comments · Fixed by #3320
Closed

Prevent accidental re-initialization #3307

matheus23 opened this issue Feb 14, 2023 · 0 comments · Fixed by #3320

Comments

@matheus23
Copy link

matheus23 commented Feb 14, 2023

Motivation

Calling init, or other initialization functions twice accidentally can cause subtle bugs. It should check if it ever got initialized before and just return if it did.

Proposed Solution

In finalizeInit, perhaps a check like if (wasm != null) return wasm; as the first operation.

Alternatives

Well, personally I'd love to use wasm-bindgen generated modules similar to how they've been designed in the browser APIs: During initialization, you provide the imports and the initialization call gives you all exports. Imports and exports are tied to a specific wasm module instance.
However, I realize two things:

Additional Context

When we called init twice, the issue we got was actually really subtle and annoying to debug:
We used the weak refs feature, i.e. FinalizationRegistrys were generated for us.

So in the main wasm-bindgen-generated project.js file (we used wasm-pack build --target web) we have following code generated:

let wasm;

// ...

const PublicNodeFinalization = new FinalizationRegistry(ptr => wasm.__wbg_publicnode_free(ptr));

// ...

function finalizeInit(instance, module) {
    wasm = instance.exports;
    init.__wbindgen_wasm_module = module;
    cachedInt32Memory0 = null;
    cachedUint32Memory0 = null;
    cachedUint8Memory0 = null;


    return wasm;
}

Now, imagine you cause finalizeInit to run once, then the wasm variable will be set to some module.
In our code, we now continued running operations on our PublicNode. This causes them to be allocated and returned to JS and registered in their respective FinalizationRegistry.
Please note that the FinalizationRegistry closes over the wasm variable.
So now our code accidentally called finalizeInit again, which overwrites the last wasm value, and created a buch more PublicNodes.

When eventually enough PublicNodes were created, GC kicked in and deleted some older ones. These older ones then had their finalizers run and that caused the finalizers to be run on the "wrong" wasm memory. The ptr parameter wouldn't make sense in the new wasm instance.

Unfortunately that was really hard to debug, since finalizers only run when you've accumulated a bunch of garbage.
There's also other oddities like the error causing a "memory access out of bounds" (likely due to the ptr value being too big for the new wasm instance), but straight up causing a SIGBUS in nodejs on M1 Macs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant