dotnet/runtime · error · Error
Out of scratch root space
Error message
Out of scratch root space
What it means
Thrown by _mono_wasm_claim_scratch_index when the global scratch-root pool is exhausted. The runtime pre-allocates a fixed pool of maxScratchRoots (8192) slots; each mono_wasm_new_root that does not hit the recycle pool claims one, and once the free-indices count drops below 1, no more scratch roots can be handed out until some are released.
Source
Thrown at src/mono/browser/runtime/roots.ts:156
return;
_scratch_root_buffer!.set(index, <any>0);
_scratch_root_free_indices![_scratch_root_free_indices_count] = index;
_scratch_root_free_indices_count++;
}
function _mono_wasm_claim_scratch_index () {
if (is_nullish(_scratch_root_buffer) || !_scratch_root_free_indices) {
_scratch_root_buffer = mono_wasm_new_root_buffer(maxScratchRoots, "js roots");
_scratch_root_free_indices = new Int32Array(maxScratchRoots);
_scratch_root_free_indices_count = maxScratchRoots;
for (let i = 0; i < maxScratchRoots; i++)
_scratch_root_free_indices[i] = maxScratchRoots - i - 1;
}
if (_scratch_root_free_indices_count < 1)
throw new Error("Out of scratch root space");
const result = _scratch_root_free_indices[_scratch_root_free_indices_count - 1];
_scratch_root_free_indices_count--;
return result;
}
export class WasmRootBufferImpl implements WasmRootBuffer {
private __count: number;
private length: number;
private __offset: VoidPtr;
private __offset32: number;
private __handle: number;
private __ownsAllocation: boolean;
constructor (offset: VoidPtr, capacity: number, ownsAllocation: boolean, name?: string) {
const capacityBytes = capacity * 4;
this.__offset = offset as any >>> 0 as any;View on GitHub (pinned to 60108ba66e)
Solutions
- Always pair mono_wasm_new_root with release() - prefer try/finally blocks calling mono_wasm_release_roots.
- Batch roots into a single mono_wasm_new_root_buffer when you need many, instead of thousands of individual scratch roots.
- Find the leak: count new-root vs release-root calls in your hot path and balance them.
- Reduce peak simultaneous root count by releasing roots as soon as the interop call completes.
Example fix
// before
for (const item of items) {
const r = mono_wasm_new_root(item.ptr);
// ... never released
}
// after
for (const item of items) {
const r = mono_wasm_new_root(item.ptr);
try { /* use r */ } finally { r.release(); }
} Defensive patterns
Strategy: try-catch
Validate before calling
// Track live roots and bail before exceeding the 8192 scratch pool.
let liveRoots = 0;
const MAX_SCRATCH = 8192;
function tryNewRoot<T extends MonoObject>(value?: number) {
if (liveRoots >= MAX_SCRATCH) return null;
const r = mono_wasm_new_root(value);
liveRoots++;
const origRelease = r.release.bind(r);
r.release = () => { origRelease(); liveRoots--; };
return r;
} Try / catch
try {
const r = mono_wasm_new_root(value);
// ... work
} catch (e) {
if ((e as Error).message === 'Out of scratch root space') {
// force-release pooled roots, or batch into a root buffer instead
}
throw e;
} finally {
mono_wasm_release_roots(r);
} Prevention
- Always release roots in finally blocks.
- Prefer one root buffer for many simultaneous roots instead of thousands of scratch roots.
- Instrument release() counts against claim counts to detect leaks early.
When it happens
Trigger: Calling mono_wasm_new_root / mono_wasm_new_roots more than 8192 times concurrently without releasing roots; or a root leak where release() is never called, steadily draining the pool until it is empty.
Common situations: Interop code that allocates roots in a loop without releasing them; long-running handlers that hold many roots simultaneously; a finally block that should call mono_wasm_release_roots but is missing or skipped on an error path.
Related errors
- External roots are not supported when threads are enabled
- capacity >= 1
- External roots are not supported in multithreaded mode
- address must be a location in the native heap
- value must be an address in the managed heap
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/5ce1ce8225e7c812.
Report an issue: GitHub.