Skip to content

gh-154570: Fix itertools.accumulate() silently corrupting its total on reentrancy#154571

Open
PhysicistJohn wants to merge 3 commits into
python:mainfrom
PhysicistJohn:gh-154570-accumulate-reentrancy-fix
Open

gh-154570: Fix itertools.accumulate() silently corrupting its total on reentrancy#154571
PhysicistJohn wants to merge 3 commits into
python:mainfrom
PhysicistJohn:gh-154570-accumulate-reentrancy-fix

Conversation

@PhysicistJohn

Copy link
Copy Markdown

A source iterable or func= callable that calls back into next() on
the same accumulate object mid-step silently corrupted the running
total instead of erroring. Fixes gh-154570.

Adds a running guard field to accumulateobject, mirroring the
existing pattern already used by teedataobject for the identical
class of bug (see teedataobject_getitem_lock_held). accumulate
now raises RuntimeError("cannot re-enter the accumulate iterator")
on reentrant access instead of silently corrupting state, matching
tee's existing behavior for the same defect class.

Adds a regression test (test_accumulate_reenter) in the same style
as the existing test_tee_reenter.

…otal on reentrancy

A source iterable or func= callable that calls back into next() on
the same accumulate object mid-step silently corrupted the running
total instead of erroring.

Adds a running guard field to accumulateobject, mirroring the
existing pattern already used by teedataobject for the identical
class of bug (see teedataobject_getitem_lock_held). accumulate now
raises RuntimeError("cannot re-enter the accumulate iterator") on
reentrant access instead of silently corrupting state, matching
tee's existing behavior for the same defect class.

Adds a regression test (test_accumulate_reenter) in the same style
as the existing test_tee_reenter.
@python-cla-bot

python-cla-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@brijkapadia brijkapadia 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.

Comment thread Lib/test/test_itertools.py Outdated
Comment on lines +161 to +163
# A source iterable (or binop) that calls back into next() on the
# same accumulate object must not be allowed to silently corrupt
# the running total.

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.

This comment seems unnecessary given that this PR is more about preventing reentrancy in itertools.accumulate than fixing a specific bug.

Suggested change
# A source iterable (or binop) that calls back into next() on the
# same accumulate object must not be allowed to silently corrupt
# the running total.

Comment on lines +1 to +6
Fix :func:`itertools.accumulate` silently corrupting its running total
when the source iterable (or the ``func`` callable) re-enters the same
:func:`!accumulate` iterator via a nested call to :func:`!next`. It now
raises :exc:`RuntimeError` on re-entry instead of producing wrong,
silently-dropped values, matching the existing behavior of
:func:`itertools.tee`.

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.

I don't think that you need to say all of this information. Something like this is probably better:

Suggested change
Fix :func:`itertools.accumulate` silently corrupting its running total
when the source iterable (or the ``func`` callable) re-enters the same
:func:`!accumulate` iterator via a nested call to :func:`!next`. It now
raises :exc:`RuntimeError` on re-entry instead of producing wrong,
silently-dropped values, matching the existing behavior of
:func:`itertools.tee`.
Now, :func:`itertools.accumulate` raises a :exc:`RuntimeError` during reentrancy.

Comment thread Modules/itertoolsmodule.c Outdated
PyObject *binop;
PyObject *initial;
itertools_state *state;
int running;

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.

uint8_t uses less memory than int:

Suggested change
int running;
uint8_t running;

…-path reentrancy test

Per brijkapadia's review on pythonGH-154571:
- Remove the explanatory comment above test_accumulate_reenter.
- Change accumulateobject.running from int to uint8_t (no call sites
  needed changes; assigning 0/1 to a uint8_t requires no source edit).
- Shorten the NEWS entry, keeping one clause on what was actually
  broken (silent corruption) rather than only the new behavior.

Also add test_accumulate_reenter_func, covering reentrancy triggered
via the func callable rather than the source iterable -- the guard
protects both paths but only the iterable path had a test.

teedataobject.running (same file, same purpose, for itertools.tee) is
deliberately left as int rather than also narrowed, to avoid scope
creep beyond what was requested.
@PhysicistJohn

Copy link
Copy Markdown
Author

Thanks for the review! Applied all three: removed the comment, running
is now uint8_t (didn't need to touch any call sites, assigning 0 or 1
to a uint8_t just works), and trimmed the NEWS entry. Kept one clause
describing what was actually broken though, since the original wording
only said what the new behavior is, not what bug it fixes.

Also added a test for reentrancy through the func callable, not just
the source iterable. The guard covers both but only one was tested.

Didn't touch teedataobject.running, it's still int, so there's a size
mismatch between the two guard fields now. Left it alone since it's
outside this diff, happy to fix that too if you'd rather they match.

…nrelated to this change

Signed-off-by: PhysicistJohn <54456354+PhysicistJohn@users.noreply.github.com>
@brijkapadia

Copy link
Copy Markdown
Contributor

I don't think we should treat this as fixing a bug,

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.

itertools.accumulate() silently corrupts its running total on reentrancy

2 participants