Skip to content

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

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

KingLizard1020 wants to merge 1 commit 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.

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

Fix

When built with AddressSanitizer or ThreadSanitizer, require 6 stack margins for _PyOS_MIN_STACK_SIZE (same constant TSan already used). Reasons differ and are documented in the header: TSan only uses half the stack in tstate_set_stack(); ASan uses the full stack but needs extra room for instrumentation. _thread.stack_size() already rejects sizes below _PyOS_MIN_STACK_SIZE + SYSTEM_PAGE_SIZE, so 127 KiB is now a ValueError on ASan builds (~200704 byte minimum on this builder). Non-ASan / release builds are unchanged (still 3 margins).

Tests

  • test_thread.ThreadRunningTests.test_stack_size: ASan-only 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.

Before: LSan reported leaks on the issue reproducer. After: threading.stack_size(127 * 1024) raises ValueError: size must be at least 200704 bytes. 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.
@github-actions
github-actions Bot force-pushed the cursor/fix-threading-stack-size-leak-a999 branch from 1f5b1e7 to 00fe666 Compare September 17, 2026 17:36
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