alloc_tracker: the leak dump registers first so it runs last, and a frame with no dynsym symbol prints its module and offset - #3966
Merged
Conversation
…rame with no dynsym symbol prints its module and offset The atexit handler is the last one only where init_seg(lib) exists, so on GCC/Clang it ran before every other static dtor and reported each process-lifetime cache the runtime frees there: 495 allocations over 50 sites on a hello-world, 1473 over 80 with -jit. init_priority(101) on the registrar restores the MSVC ordering, guarded by __has_attribute so a toolchain with neither keeps today's behavior. print_frame's POSIX arm printed a bare "?" whenever dladdr found no symbol, which is every static and hidden-visibility function, and dropped the module and offset that make the frame recoverable. It now falls through the same three tiers as the MSVC arm - symbol+offset inside kMaxTrustedSymbolOffset, then module+offset, then "?". libhv's GlobalThreadPool is a call_once heap singleton, born on the first request a handler dispatches and deleted only by exitInstance(), which nothing called: ~Module_HV now calls hv::async::cleanup(). Including hasync.h needs the vendored libhv include dir marked SYSTEM - hthreadpool.h trips -Wsign-compare under -Werror. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aleksisch
force-pushed
the
aleksisch/fix/leak-dump-exit-ordering
branch
from
September 8, 2026 10:05
9b6ab74 to
a8a0f51
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.
Behavior change: a POSIX RelWithDebInfo build stops reporting every process-lifetime cache as a leak, and stops exiting 1 on a clean run.
Why. The leak dump runs from an
atexithandler that has to be registered FIRST to run LAST. Only MSVC did that, through#pragma init_seg(lib). On GCC and Clang the dump ran before every other static destructor and reported the caches those destructors free: 495 allocations over 50 sites on a hello-world, 1473 over 80 sites with-jit, and_Exit(1)every run. Separatelydladdrresolves no symbol for a static or hidden function, and the POSIX frame printer answered?- about 60 of those 80 stacks could not be read.What changes.
init_priority(101)on the atexit registrar gives GCC and Clang the MSVC ordering, guarded by__has_attribute.print_frame's POSIX arm gains a module-plus-offset tier for the framesdladdrcannot name, instead of answering?.~Module_HVcallshv::async::cleanup(). libhv'sGlobalThreadPoolis acall_onceheap singleton that onlyexitInstance()frees, and nothing called it.SYSTEM, becausehthreadpool.htrips-Wsign-compareunder-Werror.Observable behavior.
daslang hello.das: 495 allocs / 50 sites / exit 1 -> no report, exit 0daslang -jit hello.das: 1473 allocs / 583,205 B / 80 sites -> 16 allocs / 106,135 B / 16 sites, all LLVMManagedStaticplus one libz3 static ctor.dynsymsymbol:0x7e6e7e4b39a1 ?->0x7e6e7e4b39a1 /path/liblibDaScriptDyn_runtime.so+0xab39a1dastest --test tests/dasHV: driver exit 1 with a 7-site report -> exit 0, 143/143run_tests_interpreteron POSIX RelWithDebInfo: target FAILED -> 13245 passed, 0 failedWhere to look. The registrar and
print_frameinsrc/misc/alloc_tracker.cpp; the risky spot is the ordering change, which moves the dump after every static destructor - safe becausegetMapandgetMutexplacement-new into static storage and are never destructed.Validation, claims, ledger
Validation
preflight --fulldid not complete on this tip. It was 19 minutes into a run against the previous tip when that tip was amended away, so its verdict would have described a commit that no longer exists; it was stopped rather than finished. Run against the pushed tip instead: the fulltestssuite via dastest (13268 tests, 13245 passed, 0 failed, 0 errors, 23 skipped),tests/dasHV(143/143, driver exit 0),src/misc/REVIEW.dasOK, and the leak report itself on both arms. Earlier on the pre-amend tip:ctest -L small117/123, the 6 red cells pre-existing and unrelated - 4Not Runfor binaries the incremental target never built,watchdog_smokelikewise, andC API jit enablefailing onHOST_FAST_MATHunresolvable inmodules/dasLLVM/daslib/llvm_jit_run.das:824, which is also red on the nightlylinux Release fastmathlane.DAS_TRACK_ALLOCis$<CONFIG:RelWithDebInfo>only, and the sole RelWithDebInfo job isbuild_windows_relwithdebinfo_nightly(schedule or dispatch, upstream repo only), whereinit_seg(lib)already gave the right order. CI cannot re-prove the POSIX arm.Claims - stated, not tested
init_priorityis accepted by clang forx86_64-apple-macos11,arm64-apple-ios14andwasm32-unknown-emscripten(syntax-checked per target), but no lane builds RelWithDebInfo there, so the ordering itself is unverified on those targets. A break looks like today's Linux behavior: a full report of process-lifetime caches._MSC_VERarm andDAS_LEAK_DUMP_EARLY_INITexpands empty. Its symbolizer, includingkMaxTrustedSymbolOffset, is untouched. Not compiled here.dladdranswers with no symbol rather than a far neighbour - every static function in the reports printed?, and every symbol it did print carried a small offset - so the guard would never fire, and the pre-existing symbol branch had none either. Darwin'sdladdrdoes return a far preceding symbol, so a misattributed name there is the failure mode; the fix is one comparison, to be added with a case in hand.Not done
-jit, all third-party load-time state: LLVMManagedStaticglobals (cl::Option::addArgument,TimerGroup,DebugCounter,initSignalsOptions) and one 40-byte libz3 static ctor. They needLLVMShutdown(), and there is no hook to call it from:modules/dasLLVM/src/dasLLVM.cppis a stub that does not link LLVM,module_jit.cppreaches LLVM only through das-passed function pointers, and neither daslang nor daslib has a process-exit hook.LLVMShutdownis bound atmodules/dasLLVM/bindings/llvm_func.das:52with no caller. So-jitin RelWithDebInfo still exits 1.build_windows_relwithdebinfo_nightlyis green, so it is not surfacing the libhv leak either, although its ordering is correct and the leak is platform-independent. Its log was not retrievable, so the reason is open.hv::async::startup(), so its handler pool takes libhv's defaults (min 1, maxhardware_concurrency(), 60s idle) and ignoresDAS_JOBQUE_THREADSand the core-tier logic insrc/misc/job_que.cpp. Unchanged here.skills/memory_leak_detection.mdindexes six leak-detection mechanisms and this tracker is not one of them, though it is the only one that changes a process's exit code.