gh-76785: Improved Subinterpreters Compatibility with 3.12 (1/2) (gh-126704)

These changes makes it easier to backport the _interpreters, _interpqueues, and _interpchannels modules to Python 3.12.

This involves the following:

* rename several structs and typedefs
* add several typedefs
* stop using the PyThreadState.state field directly in parking_lot.c
This commit is contained in:
Eric Snow 2024-11-11 15:58:46 -07:00 committed by GitHub
parent 036930d844
commit a6d48e8f83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 55 additions and 43 deletions

View File

@ -39,14 +39,14 @@ extern int _Py_CallInInterpreterAndRawFree(
/* cross-interpreter data */ /* cross-interpreter data */
/**************************/ /**************************/
typedef struct _xid _PyXIData_t; typedef struct _xidata _PyXIData_t;
typedef PyObject *(*xid_newobjectfunc)(_PyXIData_t *); typedef PyObject *(*xid_newobjfunc)(_PyXIData_t *);
typedef void (*xid_freefunc)(void *); typedef void (*xid_freefunc)(void *);
// _PyXIData_t is similar to Py_buffer as an effectively // _PyXIData_t is similar to Py_buffer as an effectively
// opaque struct that holds data outside the object machinery. This // opaque struct that holds data outside the object machinery. This
// is necessary to pass safely between interpreters in the same process. // is necessary to pass safely between interpreters in the same process.
struct _xid { struct _xidata {
// data is the cross-interpreter-safe derivation of a Python object // data is the cross-interpreter-safe derivation of a Python object
// (see _PyObject_GetXIData). It will be NULL if the // (see _PyObject_GetXIData). It will be NULL if the
// new_object func (below) encodes the data. // new_object func (below) encodes the data.
@ -72,7 +72,7 @@ struct _xid {
// interpreter given the data. The resulting object (a new // interpreter given the data. The resulting object (a new
// reference) will be equivalent to the original object. This field // reference) will be equivalent to the original object. This field
// is required. // is required.
xid_newobjectfunc new_object; xid_newobjfunc new_object;
// free is called when the data is released. If it is NULL then // free is called when the data is released. If it is NULL then
// nothing will be done to free the data. For some types this is // nothing will be done to free the data. For some types this is
// okay (e.g. bytes) and for those types this field should be set // okay (e.g. bytes) and for those types this field should be set
@ -117,11 +117,11 @@ PyAPI_FUNC(int) _PyXIData_ReleaseAndRawFree(_PyXIData_t *);
PyAPI_FUNC(void) _PyXIData_Init( PyAPI_FUNC(void) _PyXIData_Init(
_PyXIData_t *data, _PyXIData_t *data,
PyInterpreterState *interp, void *shared, PyObject *obj, PyInterpreterState *interp, void *shared, PyObject *obj,
xid_newobjectfunc new_object); xid_newobjfunc new_object);
PyAPI_FUNC(int) _PyXIData_InitWithSize( PyAPI_FUNC(int) _PyXIData_InitWithSize(
_PyXIData_t *, _PyXIData_t *,
PyInterpreterState *interp, const size_t, PyObject *, PyInterpreterState *interp, const size_t, PyObject *,
xid_newobjectfunc); xid_newobjfunc);
PyAPI_FUNC(void) _PyXIData_Clear( PyInterpreterState *, _PyXIData_t *); PyAPI_FUNC(void) _PyXIData_Clear( PyInterpreterState *, _PyXIData_t *);
// Normally the Init* functions are sufficient. The only time // Normally the Init* functions are sufficient. The only time
@ -155,12 +155,12 @@ PyAPI_FUNC(void) _PyXIData_Clear( PyInterpreterState *, _PyXIData_t *);
/* runtime state & lifecycle */ /* runtime state & lifecycle */
/*****************************/ /*****************************/
struct _xi_runtime_state { typedef struct {
// builtin types // builtin types
_PyXIData_lookup_t data_lookup; _PyXIData_lookup_t data_lookup;
}; } _PyXI_global_state_t;
struct _xi_state { typedef struct {
// heap types // heap types
_PyXIData_lookup_t data_lookup; _PyXIData_lookup_t data_lookup;
@ -171,7 +171,7 @@ struct _xi_state {
// heap types // heap types
PyObject *PyExc_NotShareableError; PyObject *PyExc_NotShareableError;
} exceptions; } exceptions;
}; } _PyXI_state_t;
extern PyStatus _PyXI_Init(PyInterpreterState *interp); extern PyStatus _PyXI_Init(PyInterpreterState *interp);
extern void _PyXI_Fini(PyInterpreterState *interp); extern void _PyXI_Fini(PyInterpreterState *interp);

View File

@ -7,30 +7,30 @@
// alternative would be to add a tp_* slot for a class's // alternative would be to add a tp_* slot for a class's
// xidatafunc. It would be simpler and more efficient. // xidatafunc. It would be simpler and more efficient.
struct _xidregitem; struct _xid_regitem;
struct _xidregitem { typedef struct _xid_regitem {
struct _xidregitem *prev; struct _xid_regitem *prev;
struct _xidregitem *next; struct _xid_regitem *next;
/* This can be a dangling pointer, but only if weakref is set. */ /* This can be a dangling pointer, but only if weakref is set. */
PyTypeObject *cls; PyTypeObject *cls;
/* This is NULL for builtin types. */ /* This is NULL for builtin types. */
PyObject *weakref; PyObject *weakref;
size_t refcount; size_t refcount;
xidatafunc getdata; xidatafunc getdata;
}; } _PyXIData_regitem_t;
struct _xidregistry { typedef struct {
int global; /* builtin types or heap types */ int global; /* builtin types or heap types */
int initialized; int initialized;
PyMutex mutex; PyMutex mutex;
struct _xidregitem *head; _PyXIData_regitem_t *head;
}; } _PyXIData_registry_t;
PyAPI_FUNC(int) _PyXIData_RegisterClass(PyTypeObject *, xidatafunc); PyAPI_FUNC(int) _PyXIData_RegisterClass(PyTypeObject *, xidatafunc);
PyAPI_FUNC(int) _PyXIData_UnregisterClass(PyTypeObject *); PyAPI_FUNC(int) _PyXIData_UnregisterClass(PyTypeObject *);
struct _xid_lookup_state { struct _xid_lookup_state {
// XXX Remove this field once we have a tp_* slot. // XXX Remove this field once we have a tp_* slot.
struct _xidregistry registry; _PyXIData_registry_t registry;
}; };

View File

@ -16,7 +16,7 @@ extern "C" {
#include "pycore_code.h" // struct callable_cache #include "pycore_code.h" // struct callable_cache
#include "pycore_codecs.h" // struct codecs_state #include "pycore_codecs.h" // struct codecs_state
#include "pycore_context.h" // struct _Py_context_state #include "pycore_context.h" // struct _Py_context_state
#include "pycore_crossinterp.h" // struct _xidregistry #include "pycore_crossinterp.h" // _PyXI_state_t
#include "pycore_dict_state.h" // struct _Py_dict_state #include "pycore_dict_state.h" // struct _Py_dict_state
#include "pycore_dtoa.h" // struct _dtoa_state #include "pycore_dtoa.h" // struct _dtoa_state
#include "pycore_exceptions.h" // struct _Py_exc_state #include "pycore_exceptions.h" // struct _Py_exc_state
@ -205,7 +205,7 @@ struct _is {
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS]; freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
/* cross-interpreter data and utils */ /* cross-interpreter data and utils */
struct _xi_state xi; _PyXI_state_t xi;
#ifdef HAVE_FORK #ifdef HAVE_FORK
PyObject *before_forkers; PyObject *before_forkers;

View File

@ -141,6 +141,12 @@ _PyThreadState_GET(void)
#endif #endif
} }
static inline int
_PyThreadState_IsAttached(PyThreadState *tstate)
{
return (_Py_atomic_load_int_relaxed(&tstate->state) == _Py_THREAD_ATTACHED);
}
// Attaches the current thread to the interpreter. // Attaches the current thread to the interpreter.
// //
// This may block while acquiring the GIL (if the GIL is enabled) or while // This may block while acquiring the GIL (if the GIL is enabled) or while

View File

@ -11,7 +11,7 @@ extern "C" {
#include "pycore_atexit.h" // struct _atexit_runtime_state #include "pycore_atexit.h" // struct _atexit_runtime_state
#include "pycore_audit.h" // _Py_AuditHookEntry #include "pycore_audit.h" // _Py_AuditHookEntry
#include "pycore_ceval_state.h" // struct _ceval_runtime_state #include "pycore_ceval_state.h" // struct _ceval_runtime_state
#include "pycore_crossinterp.h" // struct _xidregistry #include "pycore_crossinterp.h" // _PyXI_global_state_t
#include "pycore_debug_offsets.h" // _Py_DebugOffsets #include "pycore_debug_offsets.h" // _Py_DebugOffsets
#include "pycore_faulthandler.h" // struct _faulthandler_runtime_state #include "pycore_faulthandler.h" // struct _faulthandler_runtime_state
#include "pycore_floatobject.h" // struct _Py_float_runtime_state #include "pycore_floatobject.h" // struct _Py_float_runtime_state
@ -106,7 +106,7 @@ typedef struct pyruntimestate {
tools. */ tools. */
/* cross-interpreter data and utils */ /* cross-interpreter data and utils */
struct _xi_runtime_state xi; _PyXI_global_state_t xi;
struct _pymem_allocators allocators; struct _pymem_allocators allocators;
struct _obmalloc_global_state obmalloc; struct _obmalloc_global_state obmalloc;

View File

@ -63,7 +63,7 @@ _globals (static struct globals):
data (void *) data (void *)
obj (PyObject *) obj (PyObject *)
interpid (int64_t) interpid (int64_t)
new_object (xid_newobjectfunc) new_object (xid_newobjfunc)
free (xid_freefunc) free (xid_freefunc)
last (struct _channelitem *): last (struct _channelitem *):
... ...

View File

@ -126,7 +126,7 @@ void
_PyXIData_Init(_PyXIData_t *data, _PyXIData_Init(_PyXIData_t *data,
PyInterpreterState *interp, PyInterpreterState *interp,
void *shared, PyObject *obj, void *shared, PyObject *obj,
xid_newobjectfunc new_object) xid_newobjfunc new_object)
{ {
assert(data != NULL); assert(data != NULL);
assert(new_object != NULL); assert(new_object != NULL);
@ -150,7 +150,7 @@ int
_PyXIData_InitWithSize(_PyXIData_t *data, _PyXIData_InitWithSize(_PyXIData_t *data,
PyInterpreterState *interp, PyInterpreterState *interp,
const size_t size, PyObject *obj, const size_t size, PyObject *obj,
xid_newobjectfunc new_object) xid_newobjfunc new_object)
{ {
assert(size > 0); assert(size > 0);
// For now we always free the shared data in the same interpreter // For now we always free the shared data in the same interpreter
@ -202,11 +202,9 @@ _check_xidata(PyThreadState *tstate, _PyXIData_t *data)
} }
static inline void static inline void
_set_xid_lookup_failure(PyInterpreterState *interp, _set_xid_lookup_failure(_PyXI_state_t *state, PyObject *obj, const char *msg)
PyObject *obj, const char *msg)
{ {
exceptions_t *state = &_PyInterpreterState_GetXIState(interp)->exceptions; PyObject *exctype = state->exceptions.PyExc_NotShareableError;
PyObject *exctype = state->PyExc_NotShareableError;
assert(exctype != NULL); assert(exctype != NULL);
if (msg != NULL) { if (msg != NULL) {
assert(obj == NULL); assert(obj == NULL);
@ -226,10 +224,11 @@ int
_PyObject_CheckXIData(PyObject *obj) _PyObject_CheckXIData(PyObject *obj)
{ {
PyInterpreterState *interp = PyInterpreterState_Get(); PyInterpreterState *interp = PyInterpreterState_Get();
_PyXI_state_t *state = _PyXI_GET_STATE(interp);
xidatafunc getdata = lookup_getdata(interp, obj); xidatafunc getdata = lookup_getdata(interp, obj);
if (getdata == NULL) { if (getdata == NULL) {
if (!PyErr_Occurred()) { if (!PyErr_Occurred()) {
_set_xid_lookup_failure(interp, obj, NULL); _set_xid_lookup_failure(state, obj, NULL);
} }
return -1; return -1;
} }
@ -241,6 +240,7 @@ _PyObject_GetXIData(PyObject *obj, _PyXIData_t *data)
{ {
PyThreadState *tstate = PyThreadState_Get(); PyThreadState *tstate = PyThreadState_Get();
PyInterpreterState *interp = tstate->interp; PyInterpreterState *interp = tstate->interp;
_PyXI_state_t *state = _PyXI_GET_STATE(interp);
// Reset data before re-populating. // Reset data before re-populating.
*data = (_PyXIData_t){0}; *data = (_PyXIData_t){0};
@ -252,7 +252,7 @@ _PyObject_GetXIData(PyObject *obj, _PyXIData_t *data)
if (getdata == NULL) { if (getdata == NULL) {
Py_DECREF(obj); Py_DECREF(obj);
if (!PyErr_Occurred()) { if (!PyErr_Occurred()) {
_set_xid_lookup_failure(interp, obj, NULL); _set_xid_lookup_failure(state, obj, NULL);
} }
return -1; return -1;
} }
@ -969,6 +969,7 @@ _PyXI_ClearExcInfo(_PyXI_excinfo *info)
static int static int
_PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp) _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
{ {
_PyXI_state_t *state;
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
switch (code) { switch (code) {
case _PyXI_ERR_NO_ERROR: _Py_FALLTHROUGH; case _PyXI_ERR_NO_ERROR: _Py_FALLTHROUGH;
@ -999,7 +1000,8 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
"failed to apply namespace to __main__"); "failed to apply namespace to __main__");
break; break;
case _PyXI_ERR_NOT_SHAREABLE: case _PyXI_ERR_NOT_SHAREABLE:
_set_xid_lookup_failure(interp, NULL, NULL); state = _PyXI_GET_STATE(interp);
_set_xid_lookup_failure(state, NULL, NULL);
break; break;
default: default:
#ifdef Py_DEBUG #ifdef Py_DEBUG
@ -1061,7 +1063,8 @@ _PyXI_ApplyError(_PyXI_error *error)
} }
else if (error->code == _PyXI_ERR_NOT_SHAREABLE) { else if (error->code == _PyXI_ERR_NOT_SHAREABLE) {
// Propagate the exception directly. // Propagate the exception directly.
_set_xid_lookup_failure(error->interp, NULL, error->uncaught.msg); _PyXI_state_t *state = _PyXI_GET_STATE(error->interp);
_set_xid_lookup_failure(state, NULL, error->uncaught.msg);
} }
else { else {
// Raise an exception corresponding to the code. // Raise an exception corresponding to the code.
@ -1606,9 +1609,9 @@ _propagate_not_shareable_error(_PyXI_session *session)
return; return;
} }
PyInterpreterState *interp = PyInterpreterState_Get(); PyInterpreterState *interp = PyInterpreterState_Get();
exceptions_t *state = &_PyInterpreterState_GetXIState(interp)->exceptions; _PyXI_state_t *state = _PyXI_GET_STATE(interp);
assert(state->PyExc_NotShareableError != NULL); assert(state->exceptions.PyExc_NotShareableError != NULL);
if (PyErr_ExceptionMatches(state->PyExc_NotShareableError)) { if (PyErr_ExceptionMatches(state->exceptions.PyExc_NotShareableError)) {
// We want to propagate the exception directly. // We want to propagate the exception directly.
session->_error_override = _PyXI_ERR_NOT_SHAREABLE; session->_error_override = _PyXI_ERR_NOT_SHAREABLE;
session->error_override = &session->_error_override; session->error_override = &session->_error_override;
@ -1779,11 +1782,13 @@ _PyXI_Exit(_PyXI_session *session)
PyStatus PyStatus
_PyXI_Init(PyInterpreterState *interp) _PyXI_Init(PyInterpreterState *interp)
{ {
_PyXI_state_t *state = _PyXI_GET_STATE(interp);
// Initialize the XID lookup state (e.g. registry). // Initialize the XID lookup state (e.g. registry).
if (_Py_IsMainInterpreter(interp)) { if (_Py_IsMainInterpreter(interp)) {
xid_lookup_init(&_PyXI_GET_GLOBAL_STATE(interp)->data_lookup); xid_lookup_init(&_PyXI_GET_GLOBAL_STATE(interp)->data_lookup);
} }
xid_lookup_init(&_PyXI_GET_STATE(interp)->data_lookup); xid_lookup_init(&state->data_lookup);
// Initialize exceptions.(heap types). // Initialize exceptions.(heap types).
// See _PyXI_InitTypes() for the static types. // See _PyXI_InitTypes() for the static types.
@ -1801,12 +1806,14 @@ _PyXI_Init(PyInterpreterState *interp)
void void
_PyXI_Fini(PyInterpreterState *interp) _PyXI_Fini(PyInterpreterState *interp)
{ {
_PyXI_state_t *state = _PyXI_GET_STATE(interp);
// Finalize exceptions (heap types). // Finalize exceptions (heap types).
// See _PyXI_FiniTypes() for the static types. // See _PyXI_FiniTypes() for the static types.
fini_heap_exctypes(&_PyXI_GET_STATE(interp)->exceptions); fini_heap_exctypes(&_PyXI_GET_STATE(interp)->exceptions);
// Finalize the XID lookup state (e.g. registry). // Finalize the XID lookup state (e.g. registry).
xid_lookup_fini(&_PyXI_GET_STATE(interp)->data_lookup); xid_lookup_fini(&state->data_lookup);
if (_Py_IsMainInterpreter(interp)) { if (_Py_IsMainInterpreter(interp)) {
xid_lookup_fini(&_PyXI_GET_GLOBAL_STATE(interp)->data_lookup); xid_lookup_fini(&_PyXI_GET_GLOBAL_STATE(interp)->data_lookup);
} }

View File

@ -1,8 +1,8 @@
#include "pycore_weakref.h" // _PyWeakref_GET_REF() #include "pycore_weakref.h" // _PyWeakref_GET_REF()
typedef struct _xidregistry dlregistry_t; typedef _PyXIData_registry_t dlregistry_t;
typedef struct _xidregitem dlregitem_t; typedef _PyXIData_regitem_t dlregitem_t;
// forward // forward

View File

@ -221,8 +221,7 @@ _PySemaphore_Wait(_PySemaphore *sema, PyTime_t timeout, int detach)
PyThreadState *tstate = NULL; PyThreadState *tstate = NULL;
if (detach) { if (detach) {
tstate = _PyThreadState_GET(); tstate = _PyThreadState_GET();
if (tstate && _Py_atomic_load_int_relaxed(&tstate->state) == if (tstate && _PyThreadState_IsAttached(tstate)) {
_Py_THREAD_ATTACHED) {
// Only detach if we are attached // Only detach if we are attached
PyEval_ReleaseThread(tstate); PyEval_ReleaseThread(tstate);
} }