{"record":{"id":"5ce1ce8225e7c812","repo":"dotnet/runtime","slug":"out-of-scratch-root-space","errorCode":null,"errorMessage":"Out of scratch root space","messagePattern":"Out of scratch root space","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mono/browser/runtime/roots.ts","lineNumber":156,"sourceCode":"        return;\n\n    _scratch_root_buffer!.set(index, <any>0);\n    _scratch_root_free_indices![_scratch_root_free_indices_count] = index;\n    _scratch_root_free_indices_count++;\n}\n\nfunction _mono_wasm_claim_scratch_index () {\n    if (is_nullish(_scratch_root_buffer) || !_scratch_root_free_indices) {\n        _scratch_root_buffer = mono_wasm_new_root_buffer(maxScratchRoots, \"js roots\");\n\n        _scratch_root_free_indices = new Int32Array(maxScratchRoots);\n        _scratch_root_free_indices_count = maxScratchRoots;\n        for (let i = 0; i < maxScratchRoots; i++)\n            _scratch_root_free_indices[i] = maxScratchRoots - i - 1;\n    }\n\n    if (_scratch_root_free_indices_count < 1)\n        throw new Error(\"Out of scratch root space\");\n\n    const result = _scratch_root_free_indices[_scratch_root_free_indices_count - 1];\n    _scratch_root_free_indices_count--;\n    return result;\n}\n\nexport class WasmRootBufferImpl implements WasmRootBuffer {\n    private __count: number;\n    private length: number;\n    private __offset: VoidPtr;\n    private __offset32: number;\n    private __handle: number;\n    private __ownsAllocation: boolean;\n\n    constructor (offset: VoidPtr, capacity: number, ownsAllocation: boolean, name?: string) {\n        const capacityBytes = capacity * 4;\n\n        this.__offset = offset as any >>> 0 as any;","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/dotnet/runtime/blob/60108ba66eb7d1d12f595480091b4ad80a24b172/src/mono/browser/runtime/roots.ts#L138-L174","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nfor (const item of items) {\n  const r = mono_wasm_new_root(item.ptr);\n  // ... never released\n}\n// after\nfor (const item of items) {\n  const r = mono_wasm_new_root(item.ptr);\n  try { /* use r */ } finally { r.release(); }\n}","handlingStrategy":"try-catch","validationCode":"// Track live roots and bail before exceeding the 8192 scratch pool.\nlet liveRoots = 0;\nconst MAX_SCRATCH = 8192;\nfunction tryNewRoot<T extends MonoObject>(value?: number) {\n  if (liveRoots >= MAX_SCRATCH) return null;\n  const r = mono_wasm_new_root(value);\n  liveRoots++;\n  const origRelease = r.release.bind(r);\n  r.release = () => { origRelease(); liveRoots--; };\n  return r;\n}","typeGuard":null,"tryCatchPattern":"try {\n  const r = mono_wasm_new_root(value);\n  // ... work\n} catch (e) {\n  if ((e as Error).message === 'Out of scratch root space') {\n    // force-release pooled roots, or batch into a root buffer instead\n  }\n  throw e;\n} finally {\n  mono_wasm_release_roots(r);\n}","preventionTips":["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."],"tags":["gc-roots","resource-exhaustion","leak","scratch-roots"],"backgroundTag":null,"analyzedSha":"60108ba66eb7d1d12f595480091b4ad80a24b172","analyzedAt":"2026-08-10T18:54:11.478Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}