{"record":{"id":"8d8308b47f9fe252","repo":"dotnet/runtime","slug":"external-roots-are-not-supported-in-multithreaded","errorCode":null,"errorMessage":"External roots are not supported in multithreaded mode","messagePattern":"External roots are not supported in multithreaded mode","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mono/browser/runtime/roots.ts","lineNumber":48,"sourceCode":"\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().\n */\nexport function mono_wasm_new_external_root<T extends MonoObject> (address: VoidPtr | MonoObjectRef): WasmRoot<T> {\n    if (WasmEnableThreads && runtimeHelpers.disableManagedTransition) throw new Error(\"External roots are not supported in multithreaded mode\");\n    let result: WasmExternalRoot<T>;\n\n    if (!address)\n        throw new Error(\"address must be a location in the native heap\");\n\n    if (_external_root_free_instances.length > 0) {\n        result = _external_root_free_instances.pop()!;\n        result._set_address(address);\n    } else {\n        result = new WasmExternalRoot<T>(address);\n    }\n\n    return result;\n}\n\n/**\n * Allocates temporary storage for a pointer into the managed heap.\n * Pointers stored here will be visible to the GC, ensuring that the object they point to aren't moved or collected.","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/dotnet/runtime/blob/60108ba66eb7d1d12f595480091b4ad80a24b172/src/mono/browser/runtime/roots.ts#L30-L66","documentation":"Thrown by mono_wasm_new_external_root when Wasm threads are enabled and disableManagedTransition is on. External roots wrap an externally-controlled native address (typically on the managed stack); that pattern is incompatible with the threaded runtime's GC coordination, so the API refuses to create one. The message variant 'in multithreaded mode' distinguishes it from the root-buffer guard.","triggerScenarios":"Calling mono_wasm_new_external_root(address) in a threaded dotnet Wasm build with disableManagedTransition active. The threaded-mode guard at line 48 fires before the address is even inspected.","commonSituations":"Code that wraps stack/native addresses as roots, ported to the threaded runtime; an interop layer that previously used external roots now running with pthreads enabled; tests targeting the threaded configuration hitting legacy binding paths.","solutions":["Avoid mono_wasm_new_external_root in threaded builds; use managed-transition-safe handle APIs.","Run with the single-threaded build if external roots are mandatory for the interop scenario.","Refactor the caller to obtain GC roots through mono_wasm_new_root / scratch roots that are compatible with the runtime mode."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import WasmEnableThreads from 'consts:wasmEnableThreads';\nimport { runtimeHelpers } from './globals';\nfunction canUseExternalRoot(): boolean {\n  return !(WasmEnableThreads && runtimeHelpers.disableManagedTransition);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Branch interop code by runtime mode before creating external roots.","Document which binding paths are single-threaded-only.","Run threaded-mode tests that exercise interop to catch regressions early."],"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"}