{"record":{"id":"601c881bb8a2098e","repo":"dotnet/runtime","slug":"gc-lock-only-supported-in-main-thread","errorCode":null,"errorMessage":"GC lock only supported in main thread","messagePattern":"GC lock only supported in main thread","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mono/browser/runtime/gc-lock.ts","lineNumber":19,"sourceCode":"// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n\nimport WasmEnableThreads from \"consts:wasmEnableThreads\";\nimport { ENVIRONMENT_IS_PTHREAD } from \"./globals\";\nimport cwraps from \"./cwraps\";\n\nexport let gc_locked = false;\n\n// 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}","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/dotnet/runtime/blob/290d5ab72cc1102813fbc0406fb186ceadabc340/src/mono/browser/runtime/gc-lock.ts#L1-L37","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (runs on a pthread)\nmono_wasm_gc_lock();\n// ...\nmono_wasm_gc_unlock();\n\n// after\nimport { ENVIRONMENT_IS_PTHREAD } from \"./globals\";\nif (!ENVIRONMENT_IS_PTHREAD) {\n  mono_wasm_gc_lock();\n  // ...\n  mono_wasm_gc_unlock();\n}","handlingStrategy":"validation","validationCode":"import { ENVIRONMENT_IS_PTHREAD } from \"./globals\";\nfunction lockGcMainThreadOnly() {\n  if (ENVIRONMENT_IS_PTHREAD) throw new Error('gc_lock called on a pthread; move to main thread');\n  mono_wasm_gc_lock();\n}","typeGuard":"null","tryCatchPattern":"null","preventionTips":["Call gc_lock only on the main thread.","Marshal GC-sensitive work off worker threads."],"tags":["gc","locking","threads","pthread"],"analyzedSha":"290d5ab72cc1102813fbc0406fb186ceadabc340","analyzedAt":"2026-08-06T19:57:01.276Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}