Repository navigation
Seed static TLS in the threads that already run - #11
Open
buyemune-sys wants to merge 2 commits into
Open
buyemune-sys wants to merge 2 commits into
buyemune-sys wants to merge 2 commits into
Conversation
Security updates replaced libapparmor1 4.0.1really4.0.1-0ubuntu0.24.04.7 with .9 and libexpat1 2.6.1-2ubuntu0.4 with ubuntu0.6, and the pool kept only the new files, so the cold-cache Fetch x86_64 job answers 404 and every x86-64 job behind it is skipped. Both pins move to the versions noble-updates ships now; their SHA256s match the archive's Packages index. The other twelve pins are still served.
A module placed in the static TLS window got its template copied only into the loading thread and, through musl's __copy_tls, into threads created afterwards. Threads already running read zeroes instead of the module's initial values — for initial-exec variables and, since every module that fits is placed in the window, for general-dynamic ones too. glibc seeds those threads on dlopen (_dl_init_static_tls), so a glibc library loaded after its users' threads exist, a common case for an application server loading plugins on demand, misbehaved only here. soloTlsRegister now seeds the block in every other thread on musl's thread list. It runs with thread creation inhibited, as musl's own dlopen does: pthread_create copies a new thread's TLS before the thread joins the list, and a thread racing the registration would otherwise miss both the template and the seeding. The smoke test's parked thread now runs the full initial-exec selfcheck instead of expecting zeroes, and the README drops the restriction and names the arena's actual size, 1 MiB rather than 16 KiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
buyemune-sys
force-pushed
the
fix/static-tls-running-threads
branch
from
October 20, 2026 08:11
d933b19 to
a722c8a
Compare
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.
The problem
A module whose TLS is placed in the static window (the pad) gets an initialized copy of its template only in the thread that called
dlopenand, through musl's__copy_tls, in threads created after the load. Threads that were already running see zeroes instead of the initial values.The README describes this as an initial-exec-only restriction. In fact every module that fits is placed in the window (
Loader::allocateStaticTls,lib/elf_loader.cpp:1540), so ordinary general-dynamic modules (the-fPICdefault) are affected too:glibc initializes the block in every live thread in this situation (
_dl_init_static_tls). For an application server that loads plugins on demand, after its worker threads already exist, this is the normal case.Also: the README says the arena is 16 KiB, while
SOLO_TLS_PADin the code is 1 MiB (lib/musl_tls.c:49).The fix
After registering the module,
soloTlsRegister(lib/musl_tls.c:183) walks musl's thread ring under__tl_lockand seeds the block at the same thread-pointer offset in each thread. All of it runs under__inhibit_ptc(), as musl's owndlopendoes. The reason:pthread_createallocates and copies a new thread's TLS before adding the thread to the list, so a thread created concurrently with the registration would otherwise miss both the template and the seeding. The real__inhibit_ptc/__acquire_ptcalready get linked in fromsrc/thread/lock_ptc.c.README: the restriction is dropped and the arena size is corrected to 1 MiB.
Test
The existing test in
tst/smoke.cpplocked in the old behavior (pre-existing thread saw %d instead of zeroed TLS). Now the "parked" thread, created before the IE module loads, runs the fullglibc_ie_selfcheck: the.tdatatemplate, zeroed.tbss, the buffer, alignment, and a write.main(new test only):pre-existing thread initial-exec selfcheck failed: 1;./build testpasses, and standalone IE/GD examples read 42 in every thread.