Skip to content

gh-153062: Fix a crash iterating itertools.tee on the free-threaded build#153063

Open
tonghuaroot wants to merge 3 commits into
python:mainfrom
tonghuaroot:tee-next-ft-crash
Open

gh-153062: Fix a crash iterating itertools.tee on the free-threaded build#153063
tonghuaroot wants to merge 3 commits into
python:mainfrom
tonghuaroot:tee-next-ft-crash

Conversation

@tonghuaroot

@tonghuaroot tonghuaroot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Iterating an itertools.tee iterator concurrently crashes the free-threaded build. tee iterators are documented as not thread-safe, so concurrent use returning undefined results or raising RuntimeError is by design and is preserved here; this fixes only the memory-unsafety (corrupted reference counts and a segfault), which the free-threaded build must not permit. The other itertools iterators already lock their __next__; tee was the remaining one.

The shared teedataobject cells are locked while read, extended, or cleared. Each branch's position (dataobj and index) is snapshotted under the tee object's own critical section and revalidated before it is advanced, so the tee lock and the data-object lock are never nested: a nested critical section can be suspended under contention, which would break the atomicity the position fetch depends on (this is why Py_BEGIN_CRITICAL_SECTION2 on the two objects does not work here).

Tests in Lib/test/test_free_threading/ cover both a single branch consumed by many threads and sibling branches each consumed by their own thread; run_concurrently is barrier-synchronized so the threads actually contend. The re-entrancy guard's RuntimeError is tolerated, since it is a documented outcome. Single-threaded behavior is unchanged and test_itertools still passes.

Backport labels left to a maintainer.

…aded build

itertools.tee branches share a linked list of teedataobject cells.  On the free-threaded build, iterating one branch from multiple threads, or iterating sibling branches concurrently, raced on the shared cells and each branch's position, corrupting refcounts and crashing.

Lock each teedataobject while reading, extending, or clearing it, and snapshot each branch's position under the tee object's own lock, revalidating before advancing, so the two locks are never nested.  Concurrent iteration of one tee is undefined and may raise RuntimeError as documented, but no longer crashes.
Drop scratch files accidentally staged in the previous commit.

@corona10 corona10 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't check the PR detail, but if you write whole code with AI, please consider that the approach is proper. Adding critical section is not a free.

The free-threading snapshot and revalidation add per-element overhead on the default build, where the GIL already serializes access. Guard that path under Py_GIL_DISABLED so the default build keeps the original iteration and the free-threaded path is unchanged.
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.

2 participants