dotnet/runtime · error · Error
GC is not locked
Error message
GC is not locked
What it means
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.
Source
Thrown at src/mono/browser/runtime/gc-lock.ts:28
// TODO https://github.com/dotnet/runtime/issues/100411
// after Blazor stops using mono_wasm_gc_lock, mono_wasm_gc_unlock
export function mono_wasm_gc_lock (): void {
if (gc_locked) {
throw new Error("GC is already locked");
}
if (WasmEnableThreads) {
if (ENVIRONMENT_IS_PTHREAD) {
throw new Error("GC lock only supported in main thread");
}
cwraps.mono_wasm_gc_lock();
}
gc_locked = true;
}
export function mono_wasm_gc_unlock (): void {
if (!gc_locked) {
throw new Error("GC is not locked");
}
if (WasmEnableThreads) {
if (ENVIRONMENT_IS_PTHREAD) {
throw new Error("GC lock only supported in main thread");
}
cwraps.mono_wasm_gc_unlock();
}
gc_locked = false;
}
View on GitHub (pinned to 290d5ab72c)
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.
Example fix
// before: unlock runs even if lock threw
mono_wasm_gc_lock();
try { work(); }
finally { mono_wasm_gc_unlock(); } // throws if lock failed
// after
let locked = false;
try {
mono_wasm_gc_lock();
locked = true;
work();
} finally {
if (locked) mono_wasm_gc_unlock();
} Defensive patterns
Strategy: validation
Validate before calling
import { gc_locked } from "./gc-lock";
let didLock = false;
function unlockGcSafe() {
if (!gc_locked) throw new Error('unlock without lock; balance your calls');
mono_wasm_gc_unlock();
} Type guard
null
Try / catch
let locked = false;
try { mono_wasm_gc_lock(); locked = true; work(); }
finally { if (locked) mono_wasm_gc_unlock(); } Prevention
- Only unlock what you locked.
- Track a local `locked` flag in try/finally so a failed lock does not trigger a bogus unlock.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- GC is already locked
- GC lock only supported in main thread
- multiple clients in parallel are not allowed
AI-assisted analysis of dotnet/runtime@290d5ab72c (2026-08-06).
Data as JSON: /api/errors/8da488f3e9cdf0b9.
Report an issue: GitHub.