dotnet/runtime · error · Error
External roots are not supported when threads are enabled
Error message
External roots are not supported when threads are enabled
What it means
Thrown by mono_wasm_new_root_buffer when both WasmEnableThreads is true and runtimeHelpers.disableManagedTransition is set. External/root buffer allocations are unsafe in the threaded runtime because GC coordination across threads differs, so the API is blocked in that configuration. The message variant here ('when threads are enabled') is specific to the root-buffer factory.
Source
Thrown at src/mono/browser/runtime/roots.ts:27
import { MonoObjectRef, MonoObjectRefNull, MonoObject, is_nullish, WasmRoot, WasmRootBuffer } from "./types/internal";
import { _zero_region, free, localHeapViewU32, malloc } from "./memory";
import { gc_locked } from "./gc-lock";
const maxScratchRoots = 8192;
let _scratch_root_buffer: WasmRootBuffer | null = null;
let _scratch_root_free_indices: Int32Array | null = null;
let _scratch_root_free_indices_count = 0;
const _scratch_root_free_instances: WasmRoot<any>[] = [];
const _external_root_free_instances: WasmExternalRoot<any>[] = [];
/**
* Allocates a block of memory that can safely contain pointers into the managed heap.
* The result object has get(index) and set(index, value) methods that can be used to retrieve and store managed pointers.
* Once you are done using the root buffer, you must call its release() method.
* For small numbers of roots, it is preferable to use the mono_wasm_new_root and mono_wasm_new_roots APIs instead.
*/
export function mono_wasm_new_root_buffer (capacity: number, name?: string): WasmRootBuffer {
if (WasmEnableThreads && runtimeHelpers.disableManagedTransition) throw new Error("External roots are not supported when threads are enabled");
if (capacity <= 0)
throw new Error("capacity >= 1");
capacity = capacity | 0;
const capacityBytes = capacity * 4;
const offset = malloc(capacityBytes);
if ((<any>offset % 4) !== 0)
throw new Error("Malloc returned an unaligned offset");
_zero_region(offset, capacityBytes);
return new WasmRootBufferImpl(offset, capacity, true, name);
}
/**
* Allocates a WasmRoot pointing to a root provided and controlled by external code. Typicaly on managed stack.
* Releasing this root will not de-allocate the root space. You still need to call .release().View on GitHub (pinned to 60108ba66e)
Solutions
- Switch to APIs that are valid in threaded mode - avoid external/root-buffer allocation; use the managed interop handle APIs instead.
- Run with the single-threaded Wasm build (WasmEnableThreads=false) if external root buffers are required.
- Ensure you are not constructing root buffers from raw JS interop code; let the generated bindings/manage handles do it.
Example fix
// before (throws in threaded build) const buf = mono_wasm_new_root_buffer(16); // after - use handle-based interop in threaded mode, or run single-threaded build
Defensive patterns
Strategy: validation
Validate before calling
import WasmEnableThreads from 'consts:wasmEnableThreads';
import { runtimeHelpers } from './globals';
function canUseRootBuffer(): boolean {
return !(WasmEnableThreads && runtimeHelpers.disableManagedTransition);
}
if (canUseRootBuffer()) { const buf = mono_wasm_new_root_buffer(capacity); } Prevention
- Detect threaded mode at module init and branch away from external/root-buffer APIs.
- Prefer handle-based interop in threaded builds.
- Keep root-buffer allocation code isolated so it can be swapped per runtime mode.
When it happens
Trigger: Calling mono_wasm_new_root_buffer (the API that allocates a GC-visible root block) while running a multithreaded dotnet Wasm build with disableManagedTransition active. The combined flag check fails on line 27.
Common situations: Migrating code from the single-threaded runtime to the threaded (pthreads) build; running tests under the threaded configuration that previously called the root-buffer API; a dependency that allocates root buffers being used with threads enabled.
Related errors
- External roots are not supported in multithreaded mode
- Cannot call synchronous C# methods.
- Cannot call synchronous C# method from inside a synchronous
- Invalid jsThreadBlockingMode
- capacity >= 1
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/bc563650e7b237e5.
Report an issue: GitHub.