{"record":{"id":"3a0eea8cd525cf51","repo":"dotnet/runtime","slug":"gc-is-already-locked","errorCode":null,"errorMessage":"GC is already locked","messagePattern":"GC is already locked","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mono/browser/runtime/gc-lock.ts","lineNumber":15,"sourceCode":"// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n\nimport WasmEnableThreads from \"consts:wasmEnableThreads\";\nimport { ENVIRONMENT_IS_PTHREAD } from \"./globals\";\nimport cwraps from \"./cwraps\";\n\nexport let gc_locked = false;\n\n// TODO https://github.com/dotnet/runtime/issues/100411\n// after Blazor stops using mono_wasm_gc_lock, mono_wasm_gc_unlock\n\nexport function mono_wasm_gc_lock (): void {\n    if (gc_locked) {\n        throw new Error(\"GC is already locked\");\n    }\n    if (WasmEnableThreads) {\n        if (ENVIRONMENT_IS_PTHREAD) {\n            throw new Error(\"GC lock only supported in main thread\");\n        }\n        cwraps.mono_wasm_gc_lock();\n    }\n    gc_locked = true;\n}\n\nexport function mono_wasm_gc_unlock (): void {\n    if (!gc_locked) {\n        throw new Error(\"GC is not locked\");\n    }\n    if (WasmEnableThreads) {\n        if (ENVIRONMENT_IS_PTHREAD) {\n            throw new Error(\"GC lock only supported in main thread\");\n        }","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/dotnet/runtime/blob/290d5ab72cc1102813fbc0406fb186ceadabc340/src/mono/browser/runtime/gc-lock.ts#L1-L33","documentation":"Thrown by mono_wasm_gc_lock() in gc-lock.ts when the module-level gc_locked flag is already true. This lock is deliberately NOT re-entrant: it exists to batch GC-sensitive operations, so a second lock without an intervening unlock is a programming error. In the threads-enabled build it also drives the native mono_wasm_gc_lock().","triggerScenarios":"Calling mono_wasm_gc_lock() twice in a row without mono_wasm_gc_unlock() between them. A code path that locks, calls into another routine that also locks, and neither tracks nesting.","commonSituations":"Two independent features both calling gc_lock around their work and one invoking the other. Missing/unreached unlock after an early return or thrown error. Async code that locks, awaits, and another branch locks again.","solutions":["Ensure every mono_wasm_gc_lock() has a matching mono_wasm_gc_unlock() (use try/finally).","Do not nest lock callers; if an inner routine may already be inside a lock, do not lock again.","If you need nesting, track ownership yourself and skip re-locking rather than calling the non-reentrant API twice."],"exampleFix":"// before\nmono_wasm_gc_lock();\ndoWorkThatAlsoLocks(); // throws: already locked\nmono_wasm_gc_unlock();\n\n// after\nmono_wasm_gc_lock();\ntry {\n  doWorkWithoutLocking();\n} finally {\n  mono_wasm_gc_unlock();\n}","handlingStrategy":"validation","validationCode":"import { gc_locked } from \"./gc-lock\";\nfunction lockGcSafe() {\n  if (gc_locked) throw new Error('gc already locked by this caller; fix unbalanced lock/unlock');\n  mono_wasm_gc_lock();\n}","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Pair every lock with exactly one unlock (try/finally).","Never nest two lock callers; track ownership if you need nesting."],"tags":["gc","locking","concurrency","api-misuse"],"analyzedSha":"290d5ab72cc1102813fbc0406fb186ceadabc340","analyzedAt":"2026-08-06T19:57:01.276Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}