dotnet/runtime · error · Error
GC lock only supported in main thread
Error message
GC lock only supported in main thread
What it means
Thrown by mono_wasm_gc_lock() in gc-lock.ts when threads are enabled (WasmEnableThreads) and the current context is a pthread (ENVIRONMENT_IS_PTHREAD). The native GC lock (mono_wasm_gc_lock cwrap) is only valid on the main thread; acquiring it from a worker pthread is unsupported and would corrupt the GC coordination, so it is rejected before the native call.
Source
Thrown at src/mono/browser/runtime/gc-lock.ts:19
// 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");
}
cwraps.mono_wasm_gc_unlock();
}
gc_locked = false;
}View on GitHub (pinned to 290d5ab72c)
Solutions
- Call mono_wasm_gc_lock()/unlock() only from the main thread.
- Marshal the GC-sensitive work back to the main thread (post a message / use a main-thread scheduler).
- Guard with !ENVIRONMENT_IS_PTHREAD before locking when shared code may run on either thread.
Example fix
// before (runs on a pthread)
mono_wasm_gc_lock();
// ...
mono_wasm_gc_unlock();
// after
import { ENVIRONMENT_IS_PTHREAD } from "./globals";
if (!ENVIRONMENT_IS_PTHREAD) {
mono_wasm_gc_lock();
// ...
mono_wasm_gc_unlock();
} Defensive patterns
Strategy: validation
Validate before calling
import { ENVIRONMENT_IS_PTHREAD } from "./globals";
function lockGcMainThreadOnly() {
if (ENVIRONMENT_IS_PTHREAD) throw new Error('gc_lock called on a pthread; move to main thread');
mono_wasm_gc_lock();
} Type guard
null
Try / catch
null
Prevention
- Call gc_lock only on the main thread.
- Marshal GC-sensitive work off worker threads.
When it happens
Trigger: Calling mono_wasm_gc_lock() from code running on a pthread worker (e.g. inside a pthread entry point or a Task offloaded to a worker). Importing gc-lock in worker-thread.ts/worker-interop.ts paths.
Common situations: Enabling WASM threads and then invoking GC-sensitive logic that runs on a worker. Library code that assumed main-thread execution but got scheduled on a pthread.
Related errors
AI-assisted analysis of dotnet/runtime@290d5ab72c (2026-08-06).
Data as JSON: /api/errors/601c881bb8a2098e.
Report an issue: GitHub.