Repository navigation
A gc shared library registers the threads its host calls it on - #516
Merged
Merged
Conversation
The collector scans, and stops during a collection, only the threads registered with it. A tslang program registers its own (GC_init the one it starts on, the async runtime its pool's), but a host that is not a tslang program - an Android app's JNI threads, a C program's - calls a -mm=gc shared library on threads nobody registered, and the first collection one of them started aborted: "Collecting from unknown thread" (reproduced on Windows and on an Android emulator). - __tslang_gc_enter (AsyncGCThreadsCommon.inc): checks the calling thread once (thread_local), registers it if the collector does not know it, and unregisters it at thread exit. It also runs GC_enable_threads once: GC_register_my_thread refuses every thread until GC_allow_register_threads ran, and the collector can be initialized without it (it initializes itself on first use - a library with no top-level code has no GC_init injected at load). - GCPass injects the call first thing in every exported function of a --emit=dll gc build (passthrough "export"/"dllexport"; not the global constructors, where GC_init registers the loading thread). Internal functions are left alone. test-compile-foreign-thread-gc: foreign-thread-gc-host (C++, not tslang) loads a gc library and calls it 2000 times on the loading thread and on 8 threads of its own, with and without top-level code. It crashed before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux a shared object's code has to be PIC; without -relocation-model=pic the link failed with R_X86_64_32S (as defaultlib-collector.cmake already does). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
added a commit
that referenced
this pull request
Oct 5, 2026
…der the loader lock (#521) Every JIT run against the default library hung at 0% CPU since #516, and the release workflow's "Test Compiler with Default Library" step with it. #516 calls __tslang_gc_enter first in every exported function of a -mm=gc shared library, and its first call ran GC_enable_threads. An exported function can run while the library is being loaded - the default library's exported Number.static_constructor is a global constructor, and top-level code can call an export - and on Windows that is inside DllMain, under the loader lock. GC_allow_register_threads starts the collector's parallel marker threads and waits for them to come up; a new thread cannot start until the loader lock is released, so LoadLibrary never returned. --emit=exe links the static default library, which runs no DllMain, so only the DLL hung. __tslang_gc_enter now enables threads only for a thread it has to register. The thread a library loads on is the one GC_init registered, or one its host registered, so the load no longer starts the markers. test-compile-foreign-thread-gc: two more libraries, one whose exported class has a static constructor and one whose top-level code calls an export, both allocating into a global so the optimizer keeps the call. Both hung before this change. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Boehm scans, and stops during a collection, only the threads registered with it. A tslang program registers its own threads:
GC_initregisters the one it starts on, and the async runtime registers its pool's. But a host that isn't a tslang program calls a-mm=gcshared library on threads nobody registered. That covers an Android app's JNI threads and a plain C/C++ program's threads. The first collection one of those threads started aborted withCollecting from unknown thread. Reproduced on Windows and on an Android API 29 emulator. Only the loading thread worked.Fix
__tslang_gc_enter(AsyncGCThreadsCommon.inc, in the async runtime's gc-only object):thread_localflag as the fast path;GC_enable_threadsonce.GC_register_my_threadrefuses every thread untilGC_allow_register_threadshas run, and Boehm initializes itself on first use without it: a library with no top-level code had noGC_initinjected at load. Without this, every foreign thread was silently refused (Threads explicit registering is not previously enabled), which was found on the emulator.GCPassinjects the call first thing in every exported function (passthroughexport/dllexport) of a--emit=dllgc build. The global constructors are excluded, sinceGC_initthere registers the loading thread, and internal functions are left alone (checked in the disassembly)..so/.dllloaded by a non-tslang host had the same bug.Tests
test-compile-foreign-thread-gc.foreign-thread-gc-host(C++, not tslang) loads a gc library and calls it 2,000 times on the loading thread, then 2,000 times on each of 8 threads of its own, checking every result. It does this with and without top-level code in the library. It crashed before this change (0x80000003, Boehm's abort) and passes 20/20 after.foreign-thread-gc*because the repo's.gitignoreignoresgc-*.dlopenhost).Known issue: not fixed here, and not in tslang
On the Android emulator, multi-threaded allocation of growing pointer-free blocks can hang Boehm 8.2.12. That's the pattern the default library's
join(result += v) produces: the collector is stuck inGC_start_world→resend_lost_signalswhile one registered thread never leavesGC_suspend_handler'ssigsuspend. It reproduces in pure C with no tslang code: Boehm linked into an executable hangs 3 runs in 8, inside a.so5 in 8. It isn't parallel marking:GC_MARKERS=1hangs too. Being investigated against newer bdwgc and reported upstream. Until then,-mm=rcis the safe model for Android app libraries.🤖 Generated with Claude Code