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-107137: Add _PyTupleBuilder API to the internal C API by vstinner · Pull Request #107139 · python/cpython

Conversation

@vstinner

@vstinner vstinner commented Jul 23, 2023 •
edited by bedevere-bot
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

Add _PyTupleBuilder structure and functions:

  • _PyTupleBuilder_Init()
  • _PyTupleBuilder_Alloc()
  • _PyTupleBuilder_Append()
  • _PyTupleBuilder_AppendUnsafe()
  • _PyTupleBuilder_Finish()
  • _PyTupleBuilder_Dealloc()

The builder tracks the size of the tuple and resize it in _PyTupleBuilder_Finish() if needed. Don't allocate empty tuple. Allocate an array of 16 objects on the stack to avoid allocating small tuple. _PyTupleBuilder_Append() overallocates the tuple by 25% to reduce the number of _PyTuple_Resize() calls.

Do no track the temporary internal tuple by the GC before _PyTupleBuilder_Finish() creates the final complete and consistent tuple object.

Use _PyTupleBuilder API in itertools batched_traverse(), PySequence_Tuple() and initialize_structseq_dict().

Add also helper functions:

  • _PyTuple_ResizeNoTrack()
  • _PyTuple_NewNoTrack()
  • Issue: C API: Add internal C API to build a tuple: _PyTupleBuilder #107137

Sorry, something went wrong.

Uh oh!

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

All reactions
@vstinner vstinner added the skip news label Jul 23, 2023
@vstinner
vstinner requested a review from rhettinger as a code owner July 23, 2023 17:10
@bedevere-bot bedevere-bot mentioned this pull request Jul 23, 2023
C API: Add internal C API to build a tuple: _PyTupleBuilder #107137
Closed
@bedevere-bot bedevere-bot added the awaiting core review label Jul 23, 2023
@vstinner

vstinner commented Jul 23, 2023

Copy link
Copy Markdown
Member Author

I chose to use unsigned size_t in the structure in _PyTupleBuilder_Alloc() size argument. I'm not sure if it's better or worse than classic signed Py_ssize_t type.

cc @serhiy-storchaka @methane @pablogsal @erlend-aasland @corona10

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner vstinner mentioned this pull request Jul 23, 2023
C API: Remove private C API functions (move them to the internal C API) #106320
Closed
@vstinner

vstinner commented Jul 23, 2023

Copy link
Copy Markdown
Member Author

HPy API to build a tuples and lists: https://docs.hpyproject.org/en/latest/api-reference/builder.html HPy API seems to only support creating tuple of a size known in advance: it's not possible to extend or shrink the tuple.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Jul 23, 2023

Copy link
Copy Markdown
Member Author

I'm considering to add a "Set" function later, and/or maybe a "GetUnsafe" function. But I prefer to start with a minimum API :-)

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner vstinner mentioned this pull request Jul 23, 2023
Disallow creation of incomplete/inconsistent objects capi-workgroup/problems#56
Open
@gvanrossum

gvanrossum commented Jul 23, 2023

Copy link
Copy Markdown
Member

-1. This feels unnecessarily elaborate.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Jul 23, 2023

Copy link
Copy Markdown
Member Author

This API is a fix for this old issue: #59313 "Incomplete tuple created by PyTuple_New() and accessed via the GC can trigged a crash" which was closed as "not a bug" in 2021 by @pablogsal.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Jul 23, 2023

Copy link
Copy Markdown
Member Author

@gvanrossum:

-1. This feels unnecessarily elaborate.

Do you mean that capi-workgroup/problems#56 is not an issue, or that this API is too complicated to use?

All reactions

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Jul 23, 2023

Copy link
Copy Markdown
Member

I believe we should review the problem before jumping to a solution. And if this is the solution, I'm not sure that it's worth fixing the problem. So please hold your horses here.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Jul 23, 2023

Copy link
Copy Markdown
Member Author

The root issue is that PyTuple_New() tracks directly the tuple by the GC. Moreover, _PyTuple_Resize() tracks also the tuple by the GC. My PR adds _PyTuple_NewNoTrack() and _PyTuple_ResizeNoTrack() to avoid this issue.

The _PyTupleBuilder is built on top of it to wrap the memory allocations.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner
pythongh-107137: Add _PyTupleBuilder API to the internal C API
e55f80a
Add _PyTupleBuilder structure and functions:

* _PyTupleBuilder_Init()
* _PyTupleBuilder_Alloc()
* _PyTupleBuilder_Append()
* _PyTupleBuilder_AppendUnsafe()
* _PyTupleBuilder_Finish()
* _PyTupleBuilder_Dealloc()

The builder tracks the size of the tuple and resize it in
_PyTupleBuilder_Finish() if needed. Don't allocate empty tuple.
Allocate an array of 16 objects on the stack to avoid allocating
small tuple. _PyTupleBuilder_Append() overallocates the tuple by 25%
to reduce the number of _PyTuple_Resize() calls.

Do no track the temporary internal tuple by the GC before
_PyTupleBuilder_Finish() creates the final complete and consistent
tuple object.

Use _PyTupleBuilder API in itertools batched_traverse(),
PySequence_Tuple() and initialize_structseq_dict().

Add also helper functions:

* _PyTuple_ResizeNoTrack()
* _PyTuple_NewNoTrack()
@vstinner

vstinner commented Jul 23, 2023

Copy link
Copy Markdown
Member Author

PR rebased to fix a merge conflict.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner
vstinner force-pushed the tuple_builder branch from 38a4c0c to e55f80a Compare July 23, 2023 23:21
@gvanrossum

gvanrossum commented Jul 24, 2023

Copy link
Copy Markdown
Member

All I am asking is that you hold off for now.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner
vstinner marked this pull request as draft July 24, 2023 00:58
@bedevere-bot bedevere-bot removed the awaiting core review label Jul 24, 2023
@vstinner

vstinner commented Jul 24, 2023

Copy link
Copy Markdown
Member Author

I marked this PR as a draft.

gvanrossum reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

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

@corona10

corona10 commented Jul 24, 2023 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

Guido, even if there is a better way to solve this issue, adding the internal private API doesn't look harmful.
If we decide to adopt a better solution in the future, we can replace the API with a better one at any time.

All reactions

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Jul 24, 2023

Copy link
Copy Markdown
Member

@corona10 I worry that APIs are forever, even internal ones. So I recommend that we have a discussion somewhere so we're all on the same page about the problem we're trying to solve and then we can how to solve it, rather than jumping the gun. (I don't require unanimity, just more people having thought about it and come to roughly the same conclusion than just Victor.)

All reactions

Sorry, something went wrong.

Uh oh!

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

@corona10

corona10 commented Jul 24, 2023

Copy link
Copy Markdown
Member

So I recommend that we have a discussion somewhere so we're all on the same page about the problem we're trying to solve and then we can how to solve it, rather than jumping the gun

Thank you for the answer. CPython seems to be on the brink of change in many topics.
All topics are very controversial right now, and the time looks like some core team members have to stop everything for a while and watch. But I hope that the time is not that long :)

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Jul 24, 2023

Copy link
Copy Markdown
Member Author

In the main Python branch, you can still crash PySequence_Tuple() because it creates a temporary tuple with PyTuple_New() which is immediately tracked by the GC:

import gc
TAG = object()

def monitor():
    lst = [x for x in gc.get_referrers(TAG)
           if isinstance(x, tuple)]
    t = lst[0]   # this *is* the result tuple
    print(t)     # full of nulls !
    return t     # Keep it alive for some time

def my_iter():
    yield TAG    # 'tag' gets stored in the result tuple
    t = monitor()
    for x in range(10):
        yield x  # SystemError when the tuple needs to be resized

tuple(my_iter())

code from: #59313 (comment)

Program in gdb:

vstinner@mona$ gdb -args ./python x.py 
(...)

(<object object at 0x7fffea574af0>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>, <NULL>)

Breakpoint 1, _PyTuple_Resize (pv=0x7fffffffb8b0, newsize=25)
    at Objects/tupleobject.c:910
910	        PyErr_BadInternalCall();

(gdb) up
#1  0x00000000004cd05e in PySequence_Tuple (
    v=<generator at remote 0x7fffea585a90>) at Objects/abstract.c:2135
2135	            if (_PyTuple_Resize(&result, n) != 0) {

The crash occurs in _PyTuple_Resize(): that's why I added no only _PyTuple_NewNoTrack(), but also _PyTuple_ResizeNoTrack().

The problem is that there is a second strong reference to the tuple (created by monitor()): (Py_SIZE(v) != 0 && Py_REFCNT(v) != 1) test failed in _PyTuple_Resize().

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Jul 24, 2023

Copy link
Copy Markdown
Member Author

In the main Python branch, you can still crash PySequence_Tuple() because it creates a temporary tuple with PyTuple_New() which is immediately tracked by the GC

This PR fix this bug.

With this PR, monitor() cannot fail the tuple anymore, since it's no longer tracked by the GC while the tuple is being filled, and so it's no longer possible to crash Python in PySequence_Tuple().

monitor() fails at:

Traceback (most recent call last):
  File "/home/vstinner/python/main/x.py", line 17, in <module>
    tuple(my_iter())
  File "/home/vstinner/python/main/x.py", line 13, in my_iter
    t = monitor()
        ^^^^^^^^^
  File "/home/vstinner/python/main/x.py", line 7, in monitor
    t = lst[0]   # this *is* the result tuple
        ~~~^^^
IndexError: list index out of range
All reactions

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Jul 24, 2023

Copy link
Copy Markdown
Member

@vstinner Please stop pushing. We've lived with this forever. It doesn't have to be fixed today.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Jul 24, 2023

Copy link
Copy Markdown
Member Author

I extracted the non-controversial part of this PR, only _PyTuple_NewNoTrack() and _PyTuple_ResizeNoTrack(), in a new PR fixing the PySequence_Tuple() crash: PR #107183.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner vstinner mentioned this pull request Jul 24, 2023
gh-107137: Add _PyTuple_NewNoTrack() internal C API #107183
Closed
@rhettinger
rhettinger removed their request for review July 25, 2023 20:53
rhettinger
rhettinger reviewed Jul 25, 2023
View reviewed changes
Comment thread Modules/itertoolsmodule.c Resolved

Uh oh!

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

@rhettinger

rhettinger commented Jul 28, 2023

Copy link
Copy Markdown
Contributor

Can tuples be made robust enough to survive being called while partially filled? The tuple_dealloc code already uses Py_XDECREF to support NULL elements. Perhaps the other tuple methods could be similarly fortified.

If not, tools like itertools.batched still have another reasonable defense against the likes of gc.get_referrers(). When PyTuple_New(n) is called, it can be immediately filled with Py_None objects. Then as new data arrives, it can be swapped in. That way the tuple is always in a consistent state even if accessed by GC before completion.

All reactions

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Jul 28, 2023

Copy link
Copy Markdown
Member

Raymond's idea of filling with None is more viable than it used to be since None is now immortal (as of 3.12) so we won't need to worry about its refcount. Still, I worry that there might be C code that creates a new tuple and somehow relies on the items being NULL.

erlend-aasland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

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

@vstinner

vstinner commented Aug 26, 2023

Copy link
Copy Markdown
Member Author

It seems like issues solved by the proposed _PyTupleBuilder API have different solutions discussed at PR #107183. This API doesn't seem to be the preferred option, so I prefer to close my PR and investigate other options first.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner vstinner closed this Aug 26, 2023
@vstinner
vstinner deleted the tuple_builder branch August 26, 2023 02:58
This was referenced Oct 30, 2023
gh-106168: Check allocated instead of size index bounds in PyList_SET_ITEM() #111480
Merged
[C API] Make _PyTuple_FromArray() function public #111489
Closed
@vstinner

vstinner commented Oct 30, 2023

Copy link
Copy Markdown
Member Author

Follow-up: I created issue #111489 to make _PyTuple_FromArraySteal() and _PyList_FromArraySteal() functions public.

All reactions

Sorry, something went wrong.

Uh oh!

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

@vstinner vstinner mentioned this pull request Oct 10, 2025
[C API] Add PyTupleWriter API #139888
Closed
@vstinner vstinner mentioned this pull request Nov 9, 2026
[C API] Add PyTupleWriter API #161694
Open
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@rhettinger rhettinger rhettinger left review comments

Uh oh!

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

Assignees

No one assigned

Labels

skip news

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

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

5 participants

@vstinner @gvanrossum @corona10 @rhettinger @bedevere-bot
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.