Skip to content

gh-161792: Fix crashes in the callable iterator - #161793

Open
nascheme wants to merge 1 commit into
python:mainfrom
nascheme:gh-161792-calliter-ft-fix
Open

nascheme wants to merge 1 commit into
python:mainfrom
nascheme:gh-161792-calliter-ft-fix

Conversation

@nascheme

Copy link
Copy Markdown
Member

calliter_iternext() called it_callable and compared the result against it_sentinel through borrowed pointers.

Sharing a callable iterator between threads, or calling __setstate__() on one that is in use, is not supported: the values produced are unspecified. It must not crash, though. That lets the fix avoid a lock:

  • Readers load a field (acquire) and take a strong reference right away, before any code can run. The callable is held across the call and the sentinel across the comparison.
  • Writers (exhaustion and __setstate__()) swap the field with an atomic exchange, so only one thread gets the old value, and release it with _PyObject_XDecRefDelayed(). In the free-threaded build that defers the DECREF until no thread can still be between its load and its incref; in the default build it is a plain Py_XDECREF().

This also covers gh-158031, so it overlaps with #158046, which takes a strong reference to the sentinel only.

Performance

Release builds (Clang 20, no PGO/LTO), single thread pinned to one CPU, ns per item including consumption overhead (deque(islice(it, N), maxlen=0)), best of 7, mean of two runs.

Free-threaded build:

Workload main PR
iter(int, 1) 9.2 9.9
Python function, sentinel 1 24.4 25.5
Python function, object() sentinel 29.7 30.6
Python function, no sentinel 19.1 19.8
bound __next__ of itertools.repeat, sentinel 1 13.1 14.1

Default (GIL) build:

Workload main PR
iter(int, 1) 7.8 9.5
Python function, sentinel 1 21.0 21.7
Python function, object() sentinel 22.4 25.2
Python function, no sentinel 16.6 16.6
bound __next__ of itertools.repeat, sentinel 1 12.1 13.7

The cost is about 1 ns per item in the free-threaded build and 0-3 ns in the default build, from the strong references to the callable and sentinel. It is only visible when the callable is trivially cheap.

calliter_iternext() used borrowed references to it_callable and
it_sentinel, which could be freed by __setstate__() during the call or
comparison (pythongh-158031), or by another thread in the free-threaded build.
Take strong references when reading the fields, and update them with an
atomic exchange plus a delayed DECREF.
@nascheme nascheme added the type-crash A hard crash of the interpreter, possibly with a core dump label Nov 11, 2026
@nascheme
nascheme marked this pull request as ready for review November 11, 2026 05:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting core review type-crash A hard crash of the interpreter, possibly with a core dump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant