Skip to content

Don't free KLU factors in the finalizer pass at exit - #46

Merged
ViralBShah merged 2 commits into
mainfrom
skip-free-at-exit
Oct 30, 2026
Merged

ViralBShah merged 2 commits into
mainfrom
skip-free-at-exit

Conversation

@ViralBShah

Copy link
Copy Markdown
Member

Fixes #31. Refs #38.

The bug

At exit, jl_atexit_hook runs Base._atexit and then runs all finalizers, including those of objects that are still reachable, while tasks on other threads may still be running. The KLUFactorization finalizer then frees the Numeric and Symbolic objects under an in-flight klu_solve, which segfaults.

The reproducer in #31 makes no copies. Each task calls klu(A) itself, and the script ends without waiting for its tasks. That explains the reporter's two observations: both @sync and changing the finalizer make the crash go away. A trace of the finalizer logged frees of up to 7 objects still in use after the main task finished.

The fix

An atexit hook sets _EXITING, and the finalizer returns early once it is set. Hooks run before the finalizer pass. The memory is left to the OS, and the process is exiting anyway. PowerNetworkMatrices.jl made the same change in its own wrapper in 8b8fb959.

Results

Locally on macOS arm64 with Julia 1.13:

main This PR
#31 scenario with the issue's matrix, 24 tasks × 2000 solves, -t 8 10/10 segfault 0/10
New regression script, -t 4 10/10 fail 0/10
Solves/s at 8 threads, one factorization per task 16,910 17,501

The new test runs the scenario in a subprocess, because the crash happens during process exit. The full suite passes at -t 4.

Windows and Linux were not tested locally. The crash log in #31 has the same in expression starting at none:0 signature as these runs.

Overlap with #42

#42 also changes the finalizer, so whichever merges second needs a small conflict resolution. #42 stops this crash too, but this fix keeps libklu calls running in parallel.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.15%. Comparing base (63298d8) to head (3a171d1).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #46      +/-   ##
==========================================
+ Coverage   59.02%   59.15%   +0.13%     
==========================================
  Files           2        2              
  Lines         593      595       +2     
==========================================
+ Hits          350      352       +2     
  Misses        243      243              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

At exit Julia runs all finalizers, including those of objects still in
use by tasks on other threads, so a finalizer could free the Numeric and
Symbolic objects out from under a running klu_solve. Skip the frees once
the process is exiting, and leave that memory to the OS.

Fixes #31

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The subprocess's output is the only evidence of what crashed, and this test exists to catch a
crash that may be specific to a platform.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ViralBShah

Copy link
Copy Markdown
Member Author

Small review fix in 3a171d1: the exit test keeps the subprocess stderr, so a platform-specific crash leaves evidence in CI, and the comment is shorter. Also checked on Linux, which the description lists as untested: the #31 scenario crashed 7/10 runs on main and 0/10 with this PR. Tests pass on Julia 1.13 and 1.10.

@ViralBShah
ViralBShah merged commit 2f4c914 into main Oct 30, 2026
8 checks passed
@ViralBShah
ViralBShah deleted the skip-free-at-exit branch October 30, 2026 20:34
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.

Segfault in threaded copies of KLUFactorization

1 participant