AvaloniaUI/Avalonia · error · Error
Unable to access emscripten PThread api
Error message
Unable to access emscripten PThread api
What it means
Thrown by WebRenderTargetRegistry.create when rendering on a worker thread (pthreadId != 0) and the Emscripten Module.PThread API cannot be found. PThread is required because the canvas must be transferred to the owning pthread worker as an OffscreenCanvas via postMessage.
Solutions
- Build the WASM runtime with pthread support enabled if worker-side rendering is intended.
- Route rendering to the main thread (pthreadId === 0 path) when PThread is unavailable.
- Wait for runtime ready before registering canvas targets on worker threads.
- Verify Module.PThread is present in the browser console at the time of the call.
Example fix
// before WebRenderTargetRegistry.create(pthreadId, canvas, [BrowserRenderingMode.WebGL2]); // after const hasPThreads = globalThis.Module?.PThread ?? globalThis.getDotnetRuntime(0)?.Module?.PThread; const effectivePthread = hasPThreads ? pthreadId : 0; WebRenderTargetRegistry.create(effectivePthread, canvas, [BrowserRenderingMode.WebGL2]);
Defensive patterns
Strategy: validation
Validate before calling
const self = globalThis as any; const module = self.Module ?? self.getDotnetRuntime?.(0)?.Module; const hasPThreads = !!module?.PThread; const effectivePthread = hasPThreads ? pthreadId : 0;
Type guard
function hasPThreadApi(): boolean {
const self = globalThis as any;
const m = self.Module ?? self.getDotnetRuntime?.(0)?.Module;
return !!m?.PThread;
} Try / catch
try { return WebRenderTargetRegistry.create(pthreadId, canvas, modes); }
catch (e) {
if (e instanceof Error && e.message.includes('PThread')) { return WebRenderTargetRegistry.create(0, canvas, modes); }
throw e;
} Prevention
- Build WASM with pthread support if worker rendering is intended.
- Detect PThread absence and route to the main-thread path.
- Wait for runtime ready before registering worker targets.
When it happens
Trigger: WebRenderTargetRegistry.create(pthreadId, canvas, modes) with a non-zero pthreadId, while globalThis.Module is undefined and getDotnetRuntime(0)?.Module is also missing PThread. Happens when the WASM build was compiled without pthreads support (-pthread) or the runtime is not yet initialized.
Common situations: Avalonia WASM build compiled without ENABLE_PTHREADS; runtime not fully booted when a worker-thread render target is requested; running outside the real Avalonia host where Module is absent; mismatched build expecting worker rendering but the artifact lacks pthread support.
Related errors
- Unable get pthread with id
- Unable get Worker for pthread
- Module.GL object wasn't initialized, WebGL can't be used.
- Unable to access .NET memory
- HTMLCanvasElement.getContext returned null.
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/e8c08c70db0775d9.
Report an issue: GitHub.
Appendix: source
Thrown at src/Browser/Avalonia.Browser/webapp/modules/avalonia/rendering/webRenderTargetRegistry.ts:26
private static registry: { [id: number]: ({
canvas: HTMLCanvasElement;
worker?: Worker;
}); } = {};
private static nextId = 1;
static create(pthreadId: number, canvas: HTMLCanvasElement, preferredModes: BrowserRenderingMode[]): number {
const id = WebRenderTargetRegistry.nextId++;
if (pthreadId === 0) {
WebRenderTargetRegistry.registry[id] = {
canvas
};
WebRenderTargetRegistry.targets[id] = WebRenderTargetRegistry.createRenderTarget(canvas, preferredModes);
} else {
const self = globalThis as any;
const module = self.Module ?? self.getDotnetRuntime(0)?.Module;
const pthreads = module?.PThread;
if (pthreads == null) { throw new Error("Unable to access emscripten PThread api"); }
const pthread = pthreads.pthreads[pthreadId];
if (pthread == null) { throw new Error(`Unable get pthread with id ${pthreadId}`); }
let worker: Worker | undefined;
if (pthread.postMessage != null) { worker = pthread as Worker; } else { worker = pthread.worker; }
if (worker == null) { throw new Error(`Unable get Worker for pthread ${pthreadId}`); }
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({
avaloniaCmd: "registerCanvas",
canvas: offscreen,
modes: preferredModes,
id
}, [offscreen]);
WebRenderTargetRegistry.registry[id] = {
canvas,
worker
};
}View on GitHub (pinned to 11c5427268)