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

  1. Call mono_wasm_gc_lock()/unlock() only from the main thread.
  2. Marshal the GC-sensitive work back to the main thread (post a message / use a main-thread scheduler).
  3. 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

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.