gh-123471: Make itertools.chain thread-safe by eendebakpt · Pull Request #135689 · python/cpython · GitHub
Skip to content

gh-123471: Make itertools.chain thread-safe - #135689

Merged
kumaraditya303 merged 6 commits into
python:mainfrom
eendebakpt:chain_next
Jun 30, 2025
Merged

gh-123471: Make itertools.chain thread-safe#135689
kumaraditya303 merged 6 commits into
python:mainfrom
eendebakpt:chain_next

Conversation

@eendebakpt

@eendebakpt eendebakpt commented Jun 18, 2025

Copy link
Copy Markdown
Contributor

The itertools.chain has two attributes that are mutated duration iteration, making it non-thread safe.

Options to make it thread safe

i) Use a lock (simple, but adds a performance penalty for the single-threaded case)
ii) Make the iterator thread safe using atomics.

For the second option we can stop clearing the lz->source on exhaustion (this is a standard mitigation to avoid setting lz->source to zero (and doing a decref) while another thread is still using lz->source). To signal the iterator that lz->source is exhausted we can either add a new attribute, or replace the while (lz->source != NULL) with while (true). This creates a slower path the the iterator is exhausted, but typically once the iterator is exhausted performance is not relevant any longer. The lz->active is more problematic. Once lz->active is exhausted, we need to update this with a new one (e.g. PyIter_Next(lz->source). But lz->active only has a single reference count. So updating in one thread means having to decref the old lz->active. Possible mitigations: a) atomically load-and-incref the lz->active in chain_next b) add a new attribute that contains all the exhausted values of lz->active and clear this once the chainobject itself is deallocated.

The second option is complex and has some performance issues (e.g. more incref/decrefs, keeping references to exhausted iterators longer in memory), so in this PR we pick the first option.

@rhettinger
rhettinger removed their request for review June 18, 2025 20:58
@rhettinger

rhettinger commented Jun 18, 2025

Copy link
Copy Markdown
Contributor

Comment thread Misc/NEWS.d/next/Library/2025-06-18-19-25-32.gh-issue-123471.lx1Xbt.rst Outdated
barrier.wait()
while True:
try:
_ = next(it)

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.

Why the extra assignment to _?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Some linters complain if the value is not assigned. I can remove it though, it is not needed here.

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.

Linters tend to complain about a lot of things in the source. I'd just remove it.

Comment thread Modules/itertoolsmodule.c Outdated
@ZeroIntensity

Copy link
Copy Markdown
Member

Can you use an #ifdef to have different code paths for the gil/nogil builds? Ideally, the nogil case should be kept in its current form, clean, fast, and highly optimized.

The lock is a critical section. It's completely compiled away on the normal build, so there shouldn't be any overhead. If you're worried about the extra nested function call, I'm pretty sure that the compiler will inline it.

Also there seem to be differing notions of what "thread-safe" means, from just not-segfaulting to strict guarantees that underlying iterators are non called concurrently.

Yeah, there's a bit of a double-standard here. Basically:

  • Do people use the function/object concurrently?
    • Yes: It's actually usable in a multithreaded environment.
    • No: It's only memory-safe.

It's not totally clear to me where itertools falls. I'm aware of itertools.cycle being used in multithreaded cases, but maybe not chain?

eendebakpt and others added 2 commits June 19, 2025 20:57
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
@eendebakpt

Copy link
Copy Markdown
Contributor Author

@kumaraditya303 kumaraditya303 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

The critical sections macros do nothing on the default gil enabled build so there is no overhead. It only adds locking on free-threading builds.

@kumaraditya303
kumaraditya303 merged commit 0533c1f into python:main Jun 30, 2025
AndPuQing pushed a commit to AndPuQing/cpython that referenced this pull request Jul 11, 2025
Pranjal095 pushed a commit to Pranjal095/cpython that referenced this pull request Jul 12, 2025
picnixz pushed a commit to picnixz/cpython that referenced this pull request Jul 13, 2025
taegyunkim pushed a commit to taegyunkim/cpython that referenced this pull request Aug 4, 2025
Agent-Hellboy pushed a commit to Agent-Hellboy/cpython that referenced this pull request Aug 19, 2025
nascheme added a commit that referenced this pull request Jul 17, 2026
…132814) (GH-135689) (GH-144402) (GH-146033) (GH-142957) (GH-153791)

Combined backport of PRs from main branch, fixing free-threading data-races in itertools:

* gh-123471: make concurrent iteration over `itertools.cycle` safe under free-threading (gh-131212)
* gh-123471: Make itertools.product and itertools.combinations thread-safe (GH-132814)
* gh-123471: Make itertools.chain thread-safe (gh-135689)
* gh-123471: Make concurrent iteration over `itertools.permutations` and `itertools.combinations_with_replacement` thread-safe (gh-144402)
* gh-123471: make concurrent iteration over itertools.accumulate thread-safe (gh-144486)
* gh-123471: Make `itertools.zip_longest` safe in the FT build (gh-146033)

(cherry picked from commit 26a1cd4)
(cherry picked from commit 847d1c2)
(cherry picked from commit 0533c1f)
(cherry picked from commit 009c8c0)
(cherry picked from commit 3a24856)
(cherry picked from commit 9214e3f)

Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
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.

4 participants