GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-150791: add critical section for `groupby.next` by KowalskiThomas · Pull Request #150792 · python/cpython

Conversation

@KowalskiThomas

@KowalskiThomas KowalskiThomas commented Jun 2, 2026 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Fixes #150791.

  • Issue: groupby_next data race on free-threaded builds #150791

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot mentioned this pull request Jun 2, 2026
groupby_next data race on free-threaded builds #150791
Open
KowalskiThomas added 3 commits June 2, 2026 16:26
@KowalskiThomas
misc: add news entry
247d386
@KowalskiThomas
test: add regression test
54fc409
@KowalskiThomas
fix: add critical section for grouper_next
ba8c0cd
@KowalskiThomas
KowalskiThomas force-pushed the kowalski/fix-critical-section-for-groupby-next branch from 5e04717 to ba8c0cd Compare June 2, 2026 14:28
@KowalskiThomas
KowalskiThomas marked this pull request as ready for review June 5, 2026 12:41
@KowalskiThomas
KowalskiThomas requested a review from rhettinger as a code owner June 5, 2026 12:41
@bedevere-app bedevere-app Bot added the awaiting review label Jun 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 90 days with no activity.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Sep 5, 2026
cmaloney
cmaloney reviewed Nov 7, 2026
View reviewed changes
Comment thread Modules/itertoolsmodule.c
apply to the same objects even under re-entrancy. */
PyObject *tgtkey = Py_NewRef(igo->tgtkey);
PyObject *currkey = Py_NewRef(gbo->currkey);
rcmp = PyObject_RichCompareBool(tgtkey, currkey, Py_EQ);

@cmaloney cmaloney Nov 7, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This runs arbitrary code which may result in the deallocation of anything which the function doesn't have a strong reference to.

That introduces a number of failure cases (the code can modify the grouper object, code could call this function again...)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@KowalskiThomas KowalskiThomas Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Yep, it seems obvious to me now but I didn't realise that when I made the PR... I'll take another look.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@KowalskiThomas KowalskiThomas Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I just checked the code and it's not as bad as I assumed (which reassures me on my first version 😁), there are guardrails around that rich compare but indeed it seems like there are cases where they're not enough.

In the loop, all the paths that return NULL are safe as well since they don't read anything.

The lines left after that are the ones that run after a break...

Py_INCREF(gbo->currkey);
Py_XSETREF(gbo->tgtkey, gbo->currkey);

grouper = _grouper_create(gbo, gbo->tgtkey);
if (grouper == NULL)
    return NULL;

return _PyTuple_FromPairSteal(Py_NewRef(gbo->currkey), grouper);

gbo itself shouldn't ever be NULL since it's kept as a strong reference throughout the method call. My understanding though is that gbo->currkey could be (e.g. if some Python code ends up calling _grouper_next which will do Py_CLEAR(gbo->currkey)).

In my latest commit, I changed the code so that we (1) keep a strong reference to gbo->currkey throughout and (2) avoid re-reading the field and use "our" reference instead.

I also updated the test to detach the CS in Rich Compare which means it should now flex the fix.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@KowalskiThomas
fix: hold strong reference to key
310dcd2
@KowalskiThomas
KowalskiThomas requested a review from cmaloney November 9, 2026 18:06
@github-actions github-actions Bot removed the stale Stale PR or inactive for long period of time. label Nov 10, 2026
cmaloney
cmaloney reviewed Nov 12, 2026
View reviewed changes
Comment thread Lib/test/test_itertools.py
self.assertEqual(len(items), 1)

@threading_helper.requires_working_threading()
def test_groupby_concurrent_next_does_not_crash(self):

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This should probably go in https://github.com/python/cpython/blob/main/Lib/test/test_free_threading/test_itertools.py

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
cmaloney
cmaloney reviewed Nov 12, 2026
View reviewed changes
Comment thread Lib/test/test_itertools.py

@threading_helper.requires_working_threading()
def test_groupby_concurrent_next_does_not_crash(self):
# regression test for gh-150791

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

For gh- references typically do at the start,

# gh-150791: Concurrent next calls on a shared groupby object and its groupers
# should not crash even with arbitrary code __eq__.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_itertools.py
# Concurrent next calls on a shared groupby object and its groupers
# should not crash, even when __eq__ suspends the critical section.
class K:
__slots__ = ("v",)

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Is the __slots__ critical to the test?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_itertools.py
def __hash__(self) -> int:
return hash(self.v)

keys: list[K] = [K(i // 3) for i in range(5_000)]

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

don't do type annotations in cpython source with very a few exceptions currently. Only a few internal-only bits of cpython code use type annotations and are checked with mypy, elsewhere don't add currently.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Misc/NEWS.d/next/Library/2026-06-02-14-22-05.gh-issue-150791.aQ7rNp.rst
@@ -0,0 +1 @@
Fix a data race in :func:`itertools.groupby` on free-threaded builds where concurrent calls to :func:`next` could corrupt the iterator's internal state.

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Fix :func:itertools.groupby data races in :term:free-threading.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Modules/itertoolsmodule.c
return NULL;
else if (rcmp == 0)
} else if (rcmp == 0) {
key = currkey;

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Default to Py_XSETREF and/or Py_SETREF for cases like this, there are lots of edge cases in other options.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_itertools.py
except Exception as e:
errors.append(e)

threads = [threading.Thread(target=consume) for _ in range(8)]

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I think this will be able to turn into threading_helper.run_concurrently

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_itertools.py
# critical section held on the groupby object.
time.sleep(0)
return self.v == other.v
def __hash__(self) -> int:

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I don't think this is needed / necessary

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Modules/itertoolsmodule.c
int rcmp;

if (gbo->currgrouper != igo)
if (gbo->currgrouper != igo) {

@cmaloney cmaloney Nov 12, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

This can change while the critical section is released during __eq__ if another thread calls next() on the groupby object. I think re-checking it would be good? (although doesn't crash at least)

items = [("a", 1), ("a", 2), ("b", 3), ("b", 4)]
g = itertools.groupby(items, key=lambda it: Key(it[0]))   # Key.__eq__ can block on an Event
ka, grp_a = next(g)

# thread t1:
next(grp_a)        # ('a', 1)
next(grp_a)        # compares Key('a')#1 == Key('a')#2 -> blocks inside __eq__

# main thread, while t1 is blocked:
kb, grp_b = next(g)
# then release t1

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@cmaloney cmaloney cmaloney left review comments
@rhettinger rhettinger Awaiting requested review from rhettinger rhettinger is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

groupby_next data race on free-threaded builds

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@KowalskiThomas @cmaloney
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.