From 33b7434d85550eb39c5d7f91b2e83970355e64d4 Mon Sep 17 00:00:00 2001 From: Marius S <39998+winding-lines@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:53:46 -0700 Subject: [PATCH] fix(tls): keep libflare_tls mapped after its last dlclose on macOS 03aac93c 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 Claude-Session: https://claude.ai/code/session_01KxW5zazz1MvVNCxeZzQBRL --- flare/tls/ffi/build.sh | 2 +- flare/tls/ffi/openssl_wrapper.cpp | 15 +++++++++++++++ recipe.yaml | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/flare/tls/ffi/build.sh b/flare/tls/ffi/build.sh index 78da7855..d8e7748e 100755 --- a/flare/tls/ffi/build.sh +++ b/flare/tls/ffi/build.sh @@ -98,7 +98,7 @@ if $CXX -O2 -std=c++17 -fPIC -DNDEBUG -shared \ "$SOURCE" \ -I"$CONDA_PREFIX/include" \ -L"$CONDA_PREFIX/lib" \ - -lssl -lcrypto \ + -lssl -lcrypto -ldl \ $NODELETE \ -Wl,-rpath,"$CONDA_PREFIX/lib"; then echo "" diff --git a/flare/tls/ffi/openssl_wrapper.cpp b/flare/tls/ffi/openssl_wrapper.cpp index 0a4e4e18..858ceb61 100644 --- a/flare/tls/ffi/openssl_wrapper.cpp +++ b/flare/tls/ffi/openssl_wrapper.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +46,20 @@ static pthread_key_t last_error_key; static pthread_once_t last_error_once = PTHREAD_ONCE_INIT; +/* This library must never be unloaded: the error key's destructor and the + * callbacks it hands OpenSSL are code in this image, and both can run after + * the caller's handle is gone. Callers load it with dlopen and dlclose it + * when their handle drops. Linux builds link with -z nodelete; macOS has no + * such flag, and since the shim lost its TLS segment (which dyld refuses to + * unload) the last dlclose really unmaps it, and the next call into it + * crashes. A RTLD_NODELETE reference taken at load time is the portable + * spelling of -z nodelete. */ +__attribute__((constructor)) static void pin_self() { + Dl_info info; + if (dladdr(reinterpret_cast(&pin_self), &info) && info.dli_fname) + (void)dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_NODELETE); +} + static void last_error_key_init() { pthread_key_create(&last_error_key, [](void* p) { delete static_cast(p); diff --git a/recipe.yaml b/recipe.yaml index 95c2293d..0cd4a042 100644 --- a/recipe.yaml +++ b/recipe.yaml @@ -71,7 +71,7 @@ build: flare/tls/ffi/openssl_wrapper.cpp -I$PREFIX/include -L$PREFIX/lib - -lssl -lcrypto + -lssl -lcrypto -ldl -Wl,-rpath,$PREFIX/lib # ── flare: zlib FFI wrapper (Content-Encoding gzip / deflate) ──────── # Loaded at runtime by flare.http.encoding, the compression