Skip to content

gh-141044: Fix ASan leak with small threading.stack_size() - #157691

Open
KingLizard1020 wants to merge 20 commits into
python:mainfrom
KingLizard1020:cursor/fix-threading-stack-size-leak-a999
Open

KingLizard1020 wants to merge 20 commits into
python:mainfrom
KingLizard1020:cursor/fix-threading-stack-size-leak-a999

Conversation

@KingLizard1020

@KingLizard1020 KingLizard1020 commented Sep 17, 2026

Copy link
Copy Markdown

Fix a LeakSanitizer / reference leak when creating a threading.Thread after threading.stack_size() is set to a small value (the reported repro used 127 KiB and did not join).

Issue: #141044

Diagnosis

This is a real reference leak, not an LSan false positive. With a debug + AddressSanitizer build:

  • threading.stack_size(127 * 1024) followed by Thread.start() (with or without join()) leaks Thread / _ThreadHandle / Context objects.
  • Default stack size / 256 KiB do not leak.
  • _thread.start_new_thread() at 127 KiB does not leak; threading.Thread does, because bootstrap runs _context.run(self.run) then finally: self._delete().

C stack overflow protection (gh-130396) reserves soft/hard margins at the bottom of each thread stack. Under ASan, instrumentation consumes extra C frames, so the old 3-margin minimum left almost no working space above the soft limit. At 127 KiB, _testinternalcapi.get_c_recursion_remaining() was already ~50 and bootstrap failed to finish cleanup.

The new test_stack_size_no_leak also failed on Py_DEBUG (non-ASan) CI at the old 3-margin floor (~102 KiB): eight threads still leaked ~355 refs. Clean from ~139 KiB upward; 6 margins (~200 KiB) is clean.

3.13 did not use pthread_getattr_np stack limits, which is why it showed no leak.

Fix

When built with Py_DEBUG, AddressSanitizer, ThreadSanitizer, or UndefinedBehaviorSanitizer (same set as _PyOS_LOG2_STACK_MARGIN), require 6 stack margins for _PyOS_MIN_STACK_SIZE. Release builds stay at 3. Reasons differ and are documented in the header: TSan only uses half the stack in tstate_set_stack(); ASan/debug need extra room for instrumentation / checked builds. _thread.stack_size() already rejects sizes below _PyOS_MIN_STACK_SIZE + SYSTEM_PAGE_SIZE, so 127 KiB is now a ValueError on those builds (~200704 byte minimum on this builder).

Tests

  • test_thread.ThreadRunningTests.test_stack_size: ASan/debug ValueError for 127 * 1024, and a failed set must leave size at 0.
  • test_threading.ThreadTests.test_stack_size_no_leak: original unjoined 127 KiB repro (exits cleanly when rejected), unjoined + joined threads at the new minimum with gettotalrefcount() and ASAN_OPTIONS=detect_leaks=1.

Verification

Built with ./configure --with-address-sanitizer --with-pydebug && make -j, and separately with pydebug alone.

Before: LSan / refcount leaks on the issue reproducer and on the new minimum under Py_DEBUG. After: threading.stack_size(127 * 1024) raises ValueError where the floor applies; threads at the new minimum are clean under refcount + LSan (joined and unjoined).

Local: ./python -m test test_thread test_threading -v -m '*stack_size*' → SUCCESS.

AddressSanitizer instrumentation consumes extra C stack, so the old
minimum left no working space above the soft recursion limit.
threading.Thread bootstrap then leaked thread objects. Require 6 stack
margins under ASan, matching TSan. Cover the unjoined original repro
and confirm the new minimum does not leak. Move the NEWS blurb to
Library, matching pythongh-143191.
Py_DEBUG (and UBSan) share the larger stack margin with ASan/TSan, but
still used a 3-margin minimum. threading.Thread bootstrap then leaked
references at that floor; the new refcount test failed on Ubuntu/macOS
CI. Align the minimum with ASan/TSan. Release builds stay at 3 margins.
TSan only uses half the stack in tstate_set_stack(), so the 6-margin
minimum that is enough for ASan/debug still leaked Thread objects at
the floor under ThreadSanitizer (refcount delta ~355). Require 12
margins for TSan. Update tests that assumed 256 KiB is always accepted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant