Skip to content

fix(tls): keep libflare_tls mapped after its last dlclose on macOS - #45

Merged
winding-lines merged 1 commit into
ehsanmok:mainfrom
millfolio:fix-tls-shim-unload
Nov 1, 2026
Merged

winding-lines merged 1 commit into
ehsanmok:mainfrom
millfolio:fix-tls-shim-unload

Conversation

@winding-lines

Copy link
Copy Markdown
Collaborator

Since 03aac93 ("keep the OpenSSL shim free of thread-local storage"), every fork-based test in tests/tls/test_tls.mojo segfaults on macOS. The first is test_tls_connect_valid_cert_succeeds, and the process exits with 139.

Cause:

  • dyld never unloads an image that has thread-local variables, so the old thread_local error buffer was keeping libflare_tls mapped.
  • With the buffer now a pthread key, the last dlclose (when _TlsTestServer / TlsStream drop their OwnedDLHandle) really unmaps the shim.
  • OpenSSL and the key's destructor still hold pointers into it, so the next call into those addresses crashes.
  • Linux avoids this because build.sh links with -z nodelete, which macOS's linker doesn't have.

Evidence, on 8fe83c5e, Mojo 1.2.0, osx-arm64:

  • Before: test_tls.mojo crashes in libKGENCompilerRTShared right after the first test's server handle drops.
  • Holding one extra dlopen(libflare_tls) for the whole run: 17/17 pass, which isolates the unload as the cause.
  • With this change: 17/17 pass.

Fix:

  • A constructor in openssl_wrapper.cpp takes an RTLD_NOLOAD | RTLD_NODELETE reference to its own image, found through dladdr. That's the portable spelling of -z nodelete.
  • It also covers recipe.yaml's build, which never passed -z nodelete, so the conda package was unloadable on Linux too.
  • -ldl is added to both builds for older glibc sysroots.
  • No TLS segment is reintroduced, so 03aac93's __tls_get_addr win is kept.

03aac93 moved the shim's error buffer from thread_local to a pthread key.
dyld never unloads an image with thread-local variables, and that had been
keeping libflare_tls mapped. Without them the last dlclose unmaps it while
OpenSSL and the key's destructor still point into it, and the next call
crashes. On macOS every fork-based test in tests/tls/test_tls.mojo segfaults
(exit 139) right after its server handle drops; with one extra dlopen held
for the run, all 17 pass.

Linux links with -z nodelete, which macOS's linker does not have. The shim
now takes a RTLD_NODELETE reference to itself from a constructor, the
portable spelling of the same thing, which also covers recipe.yaml's build
(it never passed -z nodelete). -ldl for older glibc sysroots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxW5zazz1MvVNCxeZzQBRL
@winding-lines
winding-lines merged commit 448b14a into ehsanmok:main Nov 1, 2026
7 checks passed
@ehsanmok

ehsanmok commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Great! Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants