{"record":{"id":"bc563650e7b237e5","repo":"dotnet/runtime","slug":"external-roots-are-not-supported-when-threads-are","errorCode":null,"errorMessage":"External roots are not supported when threads are enabled","messagePattern":"External roots are not supported when threads are enabled","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mono/browser/runtime/roots.ts","lineNumber":27,"sourceCode":"import { MonoObjectRef, MonoObjectRefNull, MonoObject, is_nullish, WasmRoot, WasmRootBuffer } from \"./types/internal\";\nimport { _zero_region, free, localHeapViewU32, malloc } from \"./memory\";\nimport { gc_locked } from \"./gc-lock\";\n\nconst maxScratchRoots = 8192;\nlet _scratch_root_buffer: WasmRootBuffer | null = null;\nlet _scratch_root_free_indices: Int32Array | null = null;\nlet _scratch_root_free_indices_count = 0;\nconst _scratch_root_free_instances: WasmRoot<any>[] = [];\nconst _external_root_free_instances: WasmExternalRoot<any>[] = [];\n\n/**\n * Allocates a block of memory that can safely contain pointers into the managed heap.\n * The result object has get(index) and set(index, value) methods that can be used to retrieve and store managed pointers.\n * Once you are done using the root buffer, you must call its release() method.\n * For small numbers of roots, it is preferable to use the mono_wasm_new_root and mono_wasm_new_roots APIs instead.\n */\nexport function mono_wasm_new_root_buffer (capacity: number, name?: string): WasmRootBuffer {\n    if (WasmEnableThreads && runtimeHelpers.disableManagedTransition) throw new Error(\"External roots are not supported when threads are enabled\");\n    if (capacity <= 0)\n        throw new Error(\"capacity >= 1\");\n\n    capacity = capacity | 0;\n\n    const capacityBytes = capacity * 4;\n    const offset = malloc(capacityBytes);\n    if ((<any>offset % 4) !== 0)\n        throw new Error(\"Malloc returned an unaligned offset\");\n\n    _zero_region(offset, capacityBytes);\n\n    return new WasmRootBufferImpl(offset, capacity, true, name);\n}\n\n/**\n * Allocates a WasmRoot pointing to a root provided and controlled by external code. Typicaly on managed stack.\n * Releasing this root will not de-allocate the root space. You still need to call .release().","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/dotnet/runtime/blob/60108ba66eb7d1d12f595480091b4ad80a24b172/src/mono/browser/runtime/roots.ts#L9-L45","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (throws in threaded build)\nconst buf = mono_wasm_new_root_buffer(16);\n// after - use handle-based interop in threaded mode, or run single-threaded build","handlingStrategy":"validation","validationCode":"import WasmEnableThreads from 'consts:wasmEnableThreads';\nimport { runtimeHelpers } from './globals';\nfunction canUseRootBuffer(): boolean {\n  return !(WasmEnableThreads && runtimeHelpers.disableManagedTransition);\n}\nif (canUseRootBuffer()) { const buf = mono_wasm_new_root_buffer(capacity); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["gc-roots","threading","multithreaded","unsupported-api"],"backgroundTag":null,"analyzedSha":"60108ba66eb7d1d12f595480091b4ad80a24b172","analyzedAt":"2026-08-10T18:54:11.478Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}