Summary
iter(callable, sentinel) can deadlock if the sentinel comparison executes
Python-level __eq__ code and that __eq__ re-enters the same iterator.
This seems related to #6589. The original reproducer there uses the same object
for the callable result and the sentinel, so current main may short-circuit on
identity before calling __eq__. With distinct objects, __eq__ is called and
the deadlock still reproduces.
Reproducer
class Value:
entered = False
it = None
def __eq__(self, other):
if not self.entered:
self.entered = True
try:
next(self.it)
except StopIteration:
pass
return True
value = Value()
sentinel = object()
itr = iter(lambda: value, sentinel)
value.it = itr
try:
next(itr)
except StopIteration:
pass
else:
raise AssertionError("outer next should stop")
assert value.entered
Expected
CPython exits normally. The inner re-entrant next() exhausts the callable
iterator, and the outer next() also finishes with StopIteration.
With debug prints added, CPython produces:
Python 3.14.5
calling outer next()
__eq__ entered
calling inner next()
__eq__ entered
__eq__ returning True
inner StopIteration
__eq__ returning True
outer StopIteration
entered True
Actual
RustPython main hangs. With debug prints added, the output is:
calling outer next()
__eq__ entered
calling inner next()
RustPython timed out after 5 seconds (hang/deadlock)
It looks like PyCallableIterator::next is holding its status lock while running
sentinel equality. Since equality can run Python code, the inner next() waits
on the same lock held by the outer next().
Python Documentation
iter(callable, sentinel) docs:
https://docs.python.org/3/library/functions.html#iter
CPython callable iterator implementation:
https://github.com/python/cpython/blob/main/Objects/iterobject.c
Summary
iter(callable, sentinel)can deadlock if the sentinel comparison executesPython-level
__eq__code and that__eq__re-enters the same iterator.This seems related to #6589. The original reproducer there uses the same object
for the callable result and the sentinel, so current
mainmay short-circuit onidentity before calling
__eq__. With distinct objects,__eq__is called andthe deadlock still reproduces.
Reproducer
Expected
CPython exits normally. The inner re-entrant
next()exhausts the callableiterator, and the outer
next()also finishes withStopIteration.With debug prints added, CPython produces:
Actual
RustPython
mainhangs. With debug prints added, the output is:It looks like
PyCallableIterator::nextis holding its status lock while runningsentinel equality. Since equality can run Python code, the inner
next()waitson the same lock held by the outer
next().Python Documentation
iter(callable, sentinel)docs:https://docs.python.org/3/library/functions.html#iter
CPython callable iterator implementation:
https://github.com/python/cpython/blob/main/Objects/iterobject.c