Explainers
Troubleshooting Common Problems

Troubleshooting Common Problems

This page contains several common problems that may be encountered while using the @wasmer/sdk package.

SharedArrayBuffer and Cross-Origin Isolation

If you encounter the following warning in your console while using the @wasmer/sdk package, it indicates an issue with Cross-Origin Isolation:

WARN wasmer_js::tasks::scheduler: An error occurred while handling a message
    error=Failed to execute 'postMessage' on 'Worker': SharedArrayBuffer transfer
          requires self.crossOriginIsolated.

Root Cause

This issue arises due to security measures implemented in browsers following the Spectre and Meltdown vulnerabilities. These measures restrict the sharing of SharedArrayBuffer objects with Web Workers unless the execution context is deemed secure.

The @wasmer/sdk uses a threadpool built on Web Workers and requires sharing the same SharedArrayBuffer across multiple workers to enable WASIX threads to access the same address space. This requirement is crucial even for running single-threaded WASIX programs because the SDK internals rely on SharedArrayBuffer for communication with Web Workers.

Solution

To resolve this issue, your application must run in a secure context with Cross-Origin Isolation. This is achieved by setting specific HTTP response headers:

  1. Set 'Cross-Origin-Opener-Policy' to 'same-origin': This header ensures that your document does not share a browsing context group with cross-origin documents.
  2. Set 'Cross-Origin-Embedder-Policy' to 'require-corp': This header requires all resources to be loaded with explicit CORS policies, enabling a secure environment for SharedArrayBuffer.

After setting these headers, reload your application and verify if the issue is resolved.

If you are deploying your app to a server that doesn't allow setting the COOP and COEP headers directly (e.g. GitHub Pages) you may be able to use coi-serviceworker as a workaround. Check out Patching COOP/COEP headers for GitHub Pages Deployment for more.

Instantiation Failed Due to Memory Import Mismatch

If you encounter an error message similar to the one below when running WebAssembly modules with the @wasmer/sdk, it indicates an issue with memory imports:

ERROR wasmer_wasix::state::env: Instantiation failed
    pid=1
    error=RuntimeError: js: WebAssembly.Instance():
        Import #54 module="env" function="memory":
        memory import has 1 page which is smaller than the declared initial of 179

This error typically appears when attempting to run a package like ffmpeg in the following manner:

import { init, Wasmer } from "@wasmer/sdk";
 
await init();
 
const pkg = await Wasmer.fromRegistry("wasmer/ffmpeg@1.0.5");
const ffmpeg = pkg.commands["ffmpeg"].binary();
const module = await WebAssembly.compile(ffmpeg);
 
let instance = await run(module, {
    program: "ffmpeg",
    args: ["-i", "input.mp4", "-vf", `"transpose=2"`, "output.mp4"],
});
let result = await instance.wait();

Root Cause

The issue stems from how memory is allocated for the WebAssembly module. When using WebAssembly.compile() and run() directly, the browser doesn't provide detailed information about the memory requirements of the module, such as the number of pages needed.

Wasmer has no way of knowing how much memory the WebAssembly module is importing (the env.memory import), so it guesses (opens in a new tab) a minimum of 1 page and hopes for the best.

Most of the time this works out fine, but some WebAssembly modules (like ffmpeg) are statically defined to require significantly more (in this case, at least 179 pages).

Solution

To work around this, Wasmer has a wasm-types-polyfill feature (opens in a new tab), which parses the original *.wasm file to determine the correct amount of memory required. To utilize this workaround, you should pass the original *.wasm file's contents as a Uint8Array to the run() method.

import { init, Wasmer } from "@wasmer/sdk";
 
await init();
 
const pkg = await Wasmer.fromRegistry("wasmer/ffmpeg@1.0.5");
const ffmpeg = pkg.commands["ffmpeg"].binary();
 
let instance = await run(ffmpeg, {
    program: "ffmpeg",
    args: ["-i", "input.mp4", "-vf", `"transpose=2"`, "output.mp4"],
});
let result = await instance.wait();

A more straightforward and effective approach is to use the package's entrypoint.run() method, which automatically handles memory allocation correctly.

Here is the recommended way to run a package's command:

const pkg = await Wasmer.fromRegistry("ffmpeg/ffmpeg");
const instance = await pkg.entrypoint.run({ args: [...] });
const output = await instance.wait();

Using this approach ensures that the necessary memory is allocated for the WebAssembly module, thereby avoiding the instantiation error.

Undefined __wbindgen_* Reference

When working with the @wasmer/sdk package, you might encounter the following JavaScript error:

Uncaught TypeError: Cannot read properties of undefined (reading '__wbindgen_add_to_stack_pointer')

This error typically occurs when trying to access @wasmer/sdk functionalities without properly initializing the SDK.

Root Cause

The root cause of this issue is the premature usage of the @wasmer/sdk package's functions or methods before calling the init() function.

The init() function is crucial as it loads the WebAssembly code that constitutes the core logic of the @wasmer/sdk and initializes internal states necessary for the package, including those set up by wasm-bindgen (opens in a new tab).

Solution

To resolve this issue, ensure that init() is called and awaited at the beginning of your script or module where you intend to use the @wasmer/sdk.

This setup should be done before any other interaction with the SDK's functionalities.

import { init, Wasmer } from "@wasmer/sdk";
 
async function main() {
  // Ensure to await the initialization before using any SDK functionality
  await init();
 
  // After initialization, you can safely use the SDK's features
  const pkg = await Wasmer.fromRegistry("some-package");
  // ... rest of your code
}
 
main();

Unable to Determine Hardware Concurrency

When attempting to use the @wasmer/sdk package, you might encounter the following error:

Error: Unable to determine the hardware concurrency
    at A.wbg.__wbg_new_ab87fd305ed9004b (/path/to/node_modules/@wasmer/sdk/dist/Library.cjs:11:44038)
    ... [additional stack trace] ...
  detailedMessage: 'Unable to determine the hardware concurrency',
  causes: []

This error typically arises when executing @wasmer/sdk code within a Node.js environment.

Root Cause

The @wasmer/sdk package is currently designed for browser environments and relies on specific browser features.

The error is caused by the absence of the navigator object in Node.js, which @wasmer/sdk attempts to access to determine the hardware concurrency level (specifically, navigator.hardwareConcurrency). Since Node.js doesn't provide this browser-specific object, the SDK cannot function as expected.

⚠️

This is the first of several places where @wasmer/sdk is not yet compatible with Node.js.

A future release of Node.js with nodejs/node#47769 (opens in a new tab) may resolve the navigator.hardwareConcurrency issue only for @wasmer/sdk to fail at another place where Node.js diverges from web APIs.

Solution

As of now, @wasmer/sdk does not support Node.js.

To resolve this issue, you need to run your code in a supported browser environment rather than in Node.js.

ReferenceError: document is not defined

When making a production build of your app, you might encounter an issue where worker threads fail to start up due to a ReferenceError exception.

Uncaught (in promise) ReferenceError: document is not defined
    at index-bnmRepzq.js:1:21
    at index-bnmRepzq.js:1:708

Root Cause

This issue arises because Web Workers do not have access to the DOM, and any attempt by the worker threads to access DOM elements will result in a crash.

Something to be aware of is that each Web Worker in the threadpool will import @wasmer/sdk on startup to bring in the rest of its business logic.

In production mode, a bundle often includes all dependencies, causing the @wasmer/sdk import to also execute top-level code from your dependencies. Some dependencies, like xterm.js, will interact with the DOM (opens in a new tab) and inadvertently crash the worker.

Solution

To resolve this issue, @wasmer/sdk should be dynamically imported, ensuring that the bundler puts it in a separate chunk from xterm.js. This approach prevents the worker from executing the DOM-related code in xterm.js and allows it to start up successfully.

Here's how you can implement the fix:

Modify your import statement

Change the static import of @wasmer/sdk to a dynamic one. This tells the bundler to separate it into its own chunk, avoiding the unintentional execution of DOM-related code.

// Before
import { Wasmer, init, initializeLogger, Instance } from "@wasmer/sdk/dist/WasmerSDKBundled.js";
 
// After
const { Wasmer, init, initializeLogger } = await import("@wasmer/sdk/dist/WasmerSDKBundled");

Update your vite.config.js

Ensure your Vite configuration doesn't add its own code top-level which might trigger a similar error.

vite.config.js
build: {
    modulePreload: {
        polyfill: false,
    },
},

Rebuild and redeploy

After making these changes, rebuild your app in production mode and redeploy it.

For a real-world example of this, check out the diff for wasmerio/wasmer-js#374 (opens in a new tab) on GitHub.