Skip to content

Conversation

@encukou
Copy link
Member

@encukou encukou commented Dec 16, 2025

  • Copy the existing xxlimited to xxlimited_3_13: this module serves as a rudimentary check that we don't break older Limited API. This is similar to xxlimited_35. (Nowadays we know that it's good to have a separator between the major/minor versions, but renaming to xxlimited_3_5 isn't worth the churn.)
  • Reorganize test_xxlimited to make similar copies easier in the future
  • Switch xxlimited to Limited API 3.15, PyModExport (PEP-793), standalone instance struct with extended basicsize (PEP-697)
  • treat PyObject as opaque, as proposed in PEP-803 and PEP-809, using a private knob (_Py_OPAQUE_PYOBJECT) for now to ensure we don't rely on PyObject layout details accidentally. (I'll replace/remove the private API in 3.15 beta at latest.)

@erlend-aasland erlend-aasland removed their request for review December 16, 2025 14:45
XxoObject *self = XxoObject_CAST(op);
Py_VISIT(self->x_attr);
XxoObject_Data *data = Xxo_get_data(self);
if (data == NULL) {
Copy link
Member

Choose a reason for hiding this comment

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

Xxo_get_data will set an exception on failure, which will probably break the GC. Same for Xxo_clear below.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah; the newly documented requirements for tp_traverse are breaking the design; I'll need to add some new borrowing+infallible APIs :/

Copy link
Member

Choose a reason for hiding this comment

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

Maybe call PyErr_WriteUnraisable()?

Same for Xxo_finalize(), no? Py_tp_finalize is not supposed to exit with an exception set. _Py_Dealloc() (tp_del) checks if an exception is set if Python is built in debug mode, but PyObject_CallFinalizer() (tp_finalize) doesn't.

Copy link
Member Author

@encukou encukou Dec 17, 2025

Choose a reason for hiding this comment

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

PyErr_WriteUnraisable won't work, because tp_finalize can't create an exception object at all. It must not touch any refcounts; it's not enough to restore them to the previous state.

This is a problem. See: https://discuss.python.org/t/adding-c-api-for-use-in-tp-traverse/105331

Comment on lines 597 to 599
/* TODO: This is not quite true yet: there is a race in Xxo_setattro
* for example.
*/
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be better to mark this as Py_MOD_GIL_USED in the meantime, or are you trying to avoid the warning?

Copy link
Member Author

Choose a reason for hiding this comment

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

AFAIK, the situation is unchanged since Py_MOD_GIL_NOT_USED was added everywhere in #116882.

I think the intention is/was to make everything thread-safe before some stage of free-threading implementation?

Copy link
Member

Choose a reason for hiding this comment

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

I think we added that everywhere under the assumption that everything was already thread-safe, and anything that should be reported to us as a bug. I'm not sure that applies with our own test suite.

Copy link
Member Author

@encukou encukou Dec 16, 2025

Choose a reason for hiding this comment

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

This is not really a test suite, it supposed to be an example/template showing how to use the Limited API.
The limited API was never compatible with free-threading (yet), so I don't think there's a bug?

There's an earlier PR, #110764, which did this (Py_NOGIL was later renamed to Py_GIL_DISABLED):

+#ifndef Py_NOGIL
 #define Py_LIMITED_API 0x030d0000
+#endif

... but that seems to defeat the point of this module :/

Maybe we should have not built these modules at all in free-threading builds, and skip the relevant tests?
Doing that now seems like wasted effort though, when mutexes/critical sections are a genuine TODO item for the limited API.

Copy link
Member

Choose a reason for hiding this comment

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

If it's a template, I'm a little concerned that people will see this and think "oh, this module is thread-safe", when it's really not. The comment helps, but I don't know what percentage of people will ignore it.

@encukou
Copy link
Member Author

encukou commented Dec 16, 2025

I'll mark this draft -- this PR makes some shortcomings quite visible, and I guess it's better to address them before suggesting the new API :)

@encukou encukou marked this pull request as draft December 16, 2025 17:58
newXxoObject(PyObject *module)
// Xxo initialization
// This is the implementation of Xxo.__new__; it takes arbitrary positional
// and keyword arguments.
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand "it takes arbitrary positional and keyword arguments" since the error message below is "Xxo.__new__() takes no arguments".

Copy link
Member Author

Choose a reason for hiding this comment

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

The tp_new calling convention takes args&kwargs, so we need to check.
I agree the comment was misleading. Also the situation should be clear without the comment, so I removed it.

XxoObject *self = XxoObject_CAST(op);
Py_VISIT(self->x_attr);
XxoObject_Data *data = Xxo_get_data(self);
if (data == NULL) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe call PyErr_WriteUnraisable()?

Same for Xxo_finalize(), no? Py_tp_finalize is not supposed to exit with an exception set. _Py_Dealloc() (tp_del) checks if an exception is set if Python is built in debug mode, but PyObject_CallFinalizer() (tp_finalize) doesn't.

if (self->x_attr == NULL) {
// filter a specific attribute name
int is_reserved = PyUnicode_EqualToUTF8(name, "reserved");
if (is_reserved < 0) {
Copy link
Member

Choose a reason for hiding this comment

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

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.

3 participants