dotnet/runtime · error · Error
GC is already locked
Error message
GC is already locked
What it means
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().
Source
Thrown at src/mono/browser/runtime/gc-lock.ts:15
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
import WasmEnableThreads from "consts:wasmEnableThreads";
import { ENVIRONMENT_IS_PTHREAD } from "./globals";
import cwraps from "./cwraps";
export let gc_locked = false;
// 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");
}View on GitHub (pinned to 290d5ab72c)
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.
Example fix
// before
mono_wasm_gc_lock();
doWorkThatAlsoLocks(); // throws: already locked
mono_wasm_gc_unlock();
// after
mono_wasm_gc_lock();
try {
doWorkWithoutLocking();
} finally {
mono_wasm_gc_unlock();
} Defensive patterns
Strategy: validation
Validate before calling
import { gc_locked } from "./gc-lock";
function lockGcSafe() {
if (gc_locked) throw new Error('gc already locked by this caller; fix unbalanced lock/unlock');
mono_wasm_gc_lock();
} Type guard
null
Try / catch
null
Prevention
- Pair every lock with exactly one unlock (try/finally).
- Never nest two lock callers; track ownership if you need nesting.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of dotnet/runtime@290d5ab72c (2026-08-06).
Data as JSON: /api/errors/3a0eea8cd525cf51.
Report an issue: GitHub.