{"record":{"id":"8da488f3e9cdf0b9","repo":"dotnet/runtime","slug":"gc-is-not-locked","errorCode":null,"errorMessage":"GC is not locked","messagePattern":"GC is not locked","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mono/browser/runtime/gc-lock.ts","lineNumber":28,"sourceCode":"// 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        }\n        cwraps.mono_wasm_gc_unlock();\n    }\n    gc_locked = false;\n}\n","sourceCodeStart":10,"sourceCodeEnd":38,"githubUrl":"https://github.com/dotnet/runtime/blob/290d5ab72cc1102813fbc0406fb186ceadabc340/src/mono/browser/runtime/gc-lock.ts#L10-L38","documentation":"Thrown by mono_wasm_gc_unlock() in gc-lock.ts when gc_locked is false, i.e. an unlock with no prior lock. The lock is stateful (gc_locked flag) and not reference-counted, so unbalanced unlocks are caught immediately to prevent the native GC lock from being released while not held.","triggerScenarios":"Calling mono_wasm_gc_unlock() without a matching mono_wasm_gc_lock(). A lock that threw before setting gc_locked (e.g. the pthread or already-locked guards) followed by an unconditional unlock in a finally block.","commonSituations":"try/finally that always unlocks even when the lock call itself threw. Early returns between lock and unlock. Two routines where one unlocks the other's lock.","solutions":["Only unlock what you locked; pair every lock with exactly one unlock.","In try/finally, only unlock if the lock actually succeeded (track a local `locked` boolean).","Audit early-return and throw paths to ensure the unlock is not skipped or doubled."],"exampleFix":"// before: unlock runs even if lock threw\nmono_wasm_gc_lock();\ntry { work(); }\nfinally { mono_wasm_gc_unlock(); } // throws if lock failed\n\n// after\nlet locked = false;\ntry {\n  mono_wasm_gc_lock();\n  locked = true;\n  work();\n} finally {\n  if (locked) mono_wasm_gc_unlock();\n}","handlingStrategy":"validation","validationCode":"import { gc_locked } from \"./gc-lock\";\nlet didLock = false;\nfunction unlockGcSafe() {\n  if (!gc_locked) throw new Error('unlock without lock; balance your calls');\n  mono_wasm_gc_unlock();\n}","typeGuard":"null","tryCatchPattern":"let locked = false;\ntry { mono_wasm_gc_lock(); locked = true; work(); }\nfinally { if (locked) mono_wasm_gc_unlock(); }","preventionTips":["Only unlock what you locked.","Track a local `locked` flag in try/finally so a failed lock does not trigger a bogus unlock."],"tags":["gc","locking","concurrency","api-misuse"],"analyzedSha":"290d5ab72cc1102813fbc0406fb186ceadabc340","analyzedAt":"2026-08-06T19:57:01.276Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}