From ba23dd7be5229627692663013ca608e278100f1b Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 19 Dec 2023 11:22:59 -0700 Subject: [PATCH 01/20] Add accessor macros for cross-interpreter data. --- Include/internal/pycore_crossinterp.h | 24 ++++++++++++++++++++++++ Modules/_xxinterpchannelsmodule.c | 10 ++++++---- Modules/_xxinterpqueuesmodule.c | 6 +++--- Modules/_xxsubinterpretersmodule.c | 10 +++++----- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index d6e297a7e8e6db..a670bf1e258ac1 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -87,6 +87,11 @@ struct _xid { PyAPI_FUNC(_PyCrossInterpreterData *) _PyCrossInterpreterData_New(void); PyAPI_FUNC(void) _PyCrossInterpreterData_Free(_PyCrossInterpreterData *data); +#define _PyCrossInterpreterData_DATA(DATA) ((DATA)->data) +#define _PyCrossInterpreterData_OBJ(DATA) ((DATA)->obj) +#define _PyCrossInterpreterData_INTERPID(DATA) ((DATA)->interpid) +// Users should not need getters for "new_object" or "free". + /* defining cross-interpreter data */ @@ -101,6 +106,25 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_InitWithSize( PyAPI_FUNC(void) _PyCrossInterpreterData_Clear( PyInterpreterState *, _PyCrossInterpreterData *); +// Normally the Init* functions are sufficient. The only time +// additional initialization might be needed is to set the "free" func, +// though that should be infrequent. +#define _PyCrossInterpreterData_SET_FREE(DATA, FUNC) \ + do { \ + (DATA)->free = (FUNC); \ + } while (0) +// Additionally, some shareable types are essentially light wrappers +// around other shareable types. The crossinterpdatafunc of the wrapper +// can often be implemented by calling the wrapped object's +// crossinterpdatafunc and then changing the "new_object" function. +// We have _PyCrossInterpreterData_SET_NEW_OBJECT() here for that, +// but might be better to have a function like +// _PyCrossInterpreterData_AdaptToWrapper() instead. +#define _PyCrossInterpreterData_SET_NEW_OBJECT(DATA, FUNC) \ + do { \ + (DATA)->new_object = (FUNC); \ + } while (0) + /* using cross-interpreter data */ diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 4e9b8a82a3f630..c2c193fa195566 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -784,7 +784,7 @@ _channelqueue_clear_interpreter(_channelqueue *queue, int64_t interpid) while (next != NULL) { _channelitem *item = next; next = item->next; - if (item->data->interpid == interpid) { + if (_PyCrossInterpreterData_INTERPID(item->data) == interpid) { if (prev == NULL) { queue->first = item->next; } @@ -2474,7 +2474,8 @@ struct _channelid_xid { static PyObject * _channelid_from_xid(_PyCrossInterpreterData *data) { - struct _channelid_xid *xid = (struct _channelid_xid *)data->data; + struct _channelid_xid *xid = \ + (struct _channelid_xid *)_PyCrossInterpreterData_DATA(data); // It might not be imported yet, so we can't use _get_current_module(). PyObject *mod = PyImport_ImportModule(MODULE_NAME); @@ -2530,7 +2531,8 @@ _channelid_shared(PyThreadState *tstate, PyObject *obj, { return -1; } - struct _channelid_xid *xid = (struct _channelid_xid *)data->data; + struct _channelid_xid *xid = \ + (struct _channelid_xid *)_PyCrossInterpreterData_DATA(data); xid->cid = ((channelid *)obj)->cid; xid->end = ((channelid *)obj)->end; xid->resolve = ((channelid *)obj)->resolve; @@ -2680,7 +2682,7 @@ _channelend_shared(PyThreadState *tstate, PyObject *obj, if (res < 0) { return -1; } - data->new_object = _channelend_from_xid; + _PyCrossInterpreterData_SET_NEW_OBJECT(data, _channelend_from_xid); return 0; } diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 537ba9188055dd..971f9f91ed6c6b 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -602,7 +602,7 @@ _queue_clear_interpreter(_queue *queue, int64_t interpid) while (next != NULL) { _queueitem *item = next; next = item->next; - if (item->data->interpid == interpid) { + if (_PyCrossInterpreterData_INTERPID(item->data) == interpid) { if (prev == NULL) { queue->items.first = item->next; } @@ -1130,7 +1130,7 @@ _queueid_xid_free(void *data) static PyObject * _queueobj_from_xid(_PyCrossInterpreterData *data) { - int64_t qid = *(int64_t *)data->data; + int64_t qid = *(int64_t *)_PyCrossInterpreterData_DATA(data); PyObject *qidobj = PyLong_FromLongLong(qid); if (qidobj == NULL) { return NULL; @@ -1181,7 +1181,7 @@ _queueobj_shared(PyThreadState *tstate, PyObject *queueobj, _PyCrossInterpreterData_Init(data, tstate->interp, raw, NULL, _queueobj_from_xid); Py_DECREF(qidobj); - data->free = _queueid_xid_free; + _PyCrossInterpreterData_SET_FREE(data, _queueid_xid_free); return 0; } diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 4e9e13457a9eb3..af53c1d50188b6 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -152,16 +152,16 @@ typedef struct { static PyObject * xibufferview_from_xid(PyTypeObject *cls, _PyCrossInterpreterData *data) { - assert(data->data != NULL); - assert(data->obj == NULL); - assert(data->interpid >= 0); + assert(_PyCrossInterpreterData_DATA(data) != NULL); + assert(_PyCrossInterpreterData_OBJ(data) == NULL); + assert(_PyCrossInterpreterData_INTERPID(data) >= 0); XIBufferViewObject *self = PyObject_Malloc(sizeof(XIBufferViewObject)); if (self == NULL) { return NULL; } PyObject_Init((PyObject *)self, cls); - self->view = (Py_buffer *)data->data; - self->interpid = data->interpid; + self->view = (Py_buffer *)_PyCrossInterpreterData_DATA(data); + self->interpid = _PyCrossInterpreterData_INTERPID(data); return (PyObject *)self; } From db3f7de59c618d5fe14c9fff9aaf3f4f22bc564c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 19 Dec 2023 12:06:59 -0700 Subject: [PATCH 02/20] Hide newer PyCodeObject fields behind macros. --- Include/internal/pycore_code.h | 9 +++++++++ Modules/_xxsubinterpretersmodule.c | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_code.h b/Include/internal/pycore_code.h index fdd5918228455d..85536162132072 100644 --- a/Include/internal/pycore_code.h +++ b/Include/internal/pycore_code.h @@ -8,6 +8,15 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif + +// We hide some of the newer PyCodeObject fields behind macros. +// This helps with backporting certain changes to 3.12. +#define _PyCode_HAS_EXECUTORS(CODE) \ + (CODE->co_executors != NULL) +#define _PyCode_HAS_INSTRUMENTATION(CODE) \ + (CODE->_co_instrumentation_version > 0) + + #define CODE_MAX_WATCHERS 8 /* PEP 659 diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index af53c1d50188b6..4c8cf210731f72 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -372,9 +372,7 @@ check_code_object(PyCodeObject *code) } // We trust that no code objects under co_consts have unbound cell vars. - if (code->co_executors != NULL - || code->_co_instrumentation_version > 0) - { + if (_PyCode_HAS_EXECUTORS(code) || _PyCode_HAS_INSTRUMENTATION(code)) { return "only basic functions are supported"; } if (code->_co_monitoring != NULL) { From ee80f6474f964d664ae27c0a16f925a97e4bbeba Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 19 Dec 2023 12:08:23 -0700 Subject: [PATCH 03/20] Add _PyThreadState_SetWhence(). --- Include/internal/pycore_tstate.h | 7 +++++++ Modules/_xxsubinterpretersmodule.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h index 77a1dc59163d21..3e8fcf5b6ec1fa 100644 --- a/Include/internal/pycore_tstate.h +++ b/Include/internal/pycore_tstate.h @@ -13,6 +13,13 @@ extern "C" { #include "pycore_brc.h" // struct _brc_thread_state +static inline void +_PyThreadState_SetWhence(PyThreadState *tstate, int whence) +{ + tstate->_whence = whence; +} + + // Every PyThreadState is actually allocated as a _PyThreadStateImpl. The // PyThreadState fields are exposed as part of the C API, although most fields // are intended to be private. The _PyThreadStateImpl fields not exposed. diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 4c8cf210731f72..1bc84dab0c30ef 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -600,7 +600,7 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds) // Destroy the interpreter. PyThreadState *tstate = PyThreadState_New(interp); - tstate->_whence = _PyThreadState_WHENCE_INTERP; + _PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_INTERP); // XXX Possible GILState issues? PyThreadState *save_tstate = PyThreadState_Swap(tstate); Py_EndInterpreter(tstate); From ef8dea06eb92dc9249381c24bf07fa1f00785e2c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 11:09:45 -0700 Subject: [PATCH 04/20] Fix crossinterp.c. --- Python/crossinterp.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index c6ed7daeb1074a..e2f148bd7d8466 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -156,7 +156,7 @@ _xidata_init(_PyCrossInterpreterData *data) assert(data->data == NULL); assert(data->obj == NULL); *data = (_PyCrossInterpreterData){0}; - data->interpid = -1; + _PyCrossInterpreterData_INTERPID(data) = -1; } static inline void @@ -193,7 +193,9 @@ _PyCrossInterpreterData_Init(_PyCrossInterpreterData *data, // Ideally every object would know its owning interpreter. // Until then, we have to rely on the caller to identify it // (but we don't need it in all cases). - data->interpid = (interp != NULL) ? interp->id : -1; + _PyCrossInterpreterData_INTERPID(data) = (interp != NULL) + ? interp->id + : -1; data->new_object = new_object; } @@ -223,8 +225,8 @@ _PyCrossInterpreterData_Clear(PyInterpreterState *interp, assert(data != NULL); // This must be called in the owning interpreter. assert(interp == NULL - || data->interpid == -1 - || data->interpid == interp->id); + || _PyCrossInterpreterData_INTERPID(data) == -1 + || _PyCrossInterpreterData_INTERPID(data) == interp->id); _xidata_clear(data); } @@ -238,7 +240,7 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data) // data->obj may be NULL, so we don't check it. - if (data->interpid < 0) { + if (_PyCrossInterpreterData_INTERPID(data) < 0) { _PyErr_SetString(tstate, PyExc_SystemError, "missing interp"); return -1; } @@ -318,7 +320,7 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) // Reset data before re-populating. *data = (_PyCrossInterpreterData){0}; - data->interpid = -1; + _PyCrossInterpreterData_INTERPID(data) = -1; // Call the "getdata" func for the object. Py_INCREF(obj); @@ -337,7 +339,7 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) } // Fill in the blanks and validate the result. - data->interpid = interp->id; + _PyCrossInterpreterData_INTERPID(data) = interp->id; if (_check_xidata(tstate, data) != 0) { (void)_PyCrossInterpreterData_Release(data); return -1; @@ -374,7 +376,8 @@ _xidata_release(_PyCrossInterpreterData *data, int rawfree) } // Switch to the original interpreter. - PyInterpreterState *interp = _PyInterpreterState_LookUpID(data->interpid); + PyInterpreterState *interp = _PyInterpreterState_LookUpID( + _PyCrossInterpreterData_INTERPID(data)); if (interp == NULL) { // The interpreter was already destroyed. // This function shouldn't have been called. @@ -811,7 +814,7 @@ _tuple_shared_free(void* data) #endif for (Py_ssize_t i = 0; i < shared->len; i++) { if (shared->data[i] != NULL) { - assert(shared->data[i]->interpid == interpid); + assert(_PyCrossInterpreterData_INTERPID(shared->data[i]) == interpid); _PyCrossInterpreterData_Release(shared->data[i]); PyMem_RawFree(shared->data[i]); shared->data[i] = NULL; @@ -1600,7 +1603,7 @@ _sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid) return 0; } if (p_interpid != NULL) { - *p_interpid = item->data->interpid; + *p_interpid = _PyCrossInterpreterData_INTERPID(item->data); } return 1; } @@ -2014,7 +2017,7 @@ _enter_session(_PyXI_session *session, PyInterpreterState *interp) PyThreadState *prev = tstate; if (interp != tstate->interp) { tstate = PyThreadState_New(interp); - tstate->_whence = _PyThreadState_WHENCE_EXEC; + _PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_EXEC); // XXX Possible GILState issues? session->prev_tstate = PyThreadState_Swap(tstate); assert(session->prev_tstate == prev); From 71a6a9a427408b0317bc949c212e7b8c1c9e9385 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 17:59:08 -0700 Subject: [PATCH 05/20] Use PyInterpreterState_GetID(). --- Python/crossinterp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index e2f148bd7d8466..b229f4e765f9d8 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -194,7 +194,7 @@ _PyCrossInterpreterData_Init(_PyCrossInterpreterData *data, // Until then, we have to rely on the caller to identify it // (but we don't need it in all cases). _PyCrossInterpreterData_INTERPID(data) = (interp != NULL) - ? interp->id + ? PyInterpreterState_GetID(interp) : -1; data->new_object = new_object; } @@ -339,7 +339,7 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) } // Fill in the blanks and validate the result. - _PyCrossInterpreterData_INTERPID(data) = interp->id; + _PyCrossInterpreterData_INTERPID(data) = PyInterpreterState_GetID(interp); if (_check_xidata(tstate, data) != 0) { (void)_PyCrossInterpreterData_Release(data); return -1; From 00b80ff60dc65e246f794ebe6d33d7757bf3227f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 18:28:40 -0700 Subject: [PATCH 06/20] Add _PyRuntimeState_GetXIState() and _PyInterpreterState_GetXIState(). --- Include/internal/pycore_crossinterp.h | 3 +++ Python/crossinterp.c | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index a670bf1e258ac1..bdd502eeaab7aa 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -194,6 +194,9 @@ extern void _PyXI_Fini(PyInterpreterState *interp); extern PyStatus _PyXI_InitTypes(PyInterpreterState *interp); extern void _PyXI_FiniTypes(PyInterpreterState *interp); +#define _PyRuntimeState_GetXIState(runtime) (&(runtime)->xi) +#define _PyInterpreterState_GetXIState(interp) (&(interp)->xi) + /***************************/ /* short-term data sharing */ diff --git a/Python/crossinterp.c b/Python/crossinterp.c index b229f4e765f9d8..3fc68aa76efd1e 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -128,21 +128,21 @@ _init_not_shareable_error_type(PyInterpreterState *interp) return _PyStatus_ERR("could not initialize NotShareableError"); } - interp->xi.PyExc_NotShareableError = exctype; + _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError = exctype; return _PyStatus_OK(); } static void _fini_not_shareable_error_type(PyInterpreterState *interp) { - Py_CLEAR(interp->xi.PyExc_NotShareableError); + Py_CLEAR(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError); } static PyObject * _get_not_shareable_error_type(PyInterpreterState *interp) { - assert(interp->xi.PyExc_NotShareableError != NULL); - return interp->xi.PyExc_NotShareableError; + assert(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError != NULL); + return _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError; } @@ -420,13 +420,13 @@ _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data) static inline struct _xidregistry * _get_global_xidregistry(_PyRuntimeState *runtime) { - return &runtime->xi.registry; + return &_PyRuntimeState_GetXIState(runtime)->registry; } static inline struct _xidregistry * _get_xidregistry(PyInterpreterState *interp) { - return &interp->xi.registry; + return &_PyInterpreterState_GetXIState(interp)->registry; } static inline struct _xidregistry * From e5fcc75afbbebc2a26cb604deff7a8921a7fa7e8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 18:30:39 -0700 Subject: [PATCH 07/20] Factor out _interpreters_common.h. --- Modules/_interpreters_common.h | 7 +++++++ Modules/_xxinterpchannelsmodule.c | 4 +++- Modules/_xxinterpqueuesmodule.c | 4 +++- Modules/_xxsubinterpretersmodule.c | 5 +++-- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 Modules/_interpreters_common.h diff --git a/Modules/_interpreters_common.h b/Modules/_interpreters_common.h new file mode 100644 index 00000000000000..2b240b9df4a40a --- /dev/null +++ b/Modules/_interpreters_common.h @@ -0,0 +1,7 @@ + +static int +ensure_xid_class(PyTypeObject *cls, crossinterpdatafunc getdata) +{ + //assert(cls->tp_flags & Py_TPFLAGS_HEAPTYPE); + return _PyCrossInterpreterData_RegisterClass(cls, getdata); +} diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index c2c193fa195566..4fd24f78a77c05 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -17,6 +17,8 @@ #include // sched_yield() #endif +#include "_interpreters_common.h" + /* This module has the following process-global state: @@ -101,7 +103,7 @@ static int register_xid_class(PyTypeObject *cls, crossinterpdatafunc shared, struct xid_class_registry *classes) { - int res = _PyCrossInterpreterData_RegisterClass(cls, shared); + int res = ensure_xid_class(cls, shared); if (res == 0) { assert(classes->count < MAX_XID_CLASSES); // The class has refs elsewhere, so we need to incref here. diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index 971f9f91ed6c6b..d1aacb393cbb28 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -8,6 +8,8 @@ #include "Python.h" #include "pycore_crossinterp.h" // struct _xid +#include "_interpreters_common.h" + #define MODULE_NAME "_xxinterpqueues" @@ -1062,7 +1064,7 @@ set_external_queue_type(PyObject *module, PyTypeObject *queue_type) } state->queue_type = (PyTypeObject *)Py_NewRef(queue_type); - if (_PyCrossInterpreterData_RegisterClass(queue_type, _queueobj_shared) < 0) { + if (ensure_xid_class(queue_type, _queueobj_shared) < 0) { return -1; } diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 1bc84dab0c30ef..f9a002a76b2e9e 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -19,6 +19,8 @@ #include "interpreteridobject.h" #include "marshal.h" // PyMarshal_ReadObjectFromString() +#include "_interpreters_common.h" + #define MODULE_NAME "_xxsubinterpreters" @@ -267,8 +269,7 @@ register_memoryview_xid(PyObject *mod, PyTypeObject **p_state) *p_state = cls; // Register XID for the builtin memoryview type. - if (_PyCrossInterpreterData_RegisterClass( - &PyMemoryView_Type, _memoryview_shared) < 0) { + if (ensure_xid_class(&PyMemoryView_Type, _memoryview_shared) < 0) { return -1; } // We don't ever bother un-registering memoryview. From f50017e2ad561813cd5dc5e5fcb52cbb0929865c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 18:36:15 -0700 Subject: [PATCH 08/20] Factor out crossinterp_data_lookup.h. --- Include/internal/pycore_crossinterp.h | 1 - Python/crossinterp.c | 583 +------------------------ Python/crossinterp_data_lookup.h | 594 ++++++++++++++++++++++++++ 3 files changed, 614 insertions(+), 564 deletions(-) create mode 100644 Python/crossinterp_data_lookup.h diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index bdd502eeaab7aa..63abef864ff87f 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -194,7 +194,6 @@ extern void _PyXI_Fini(PyInterpreterState *interp); extern PyStatus _PyXI_InitTypes(PyInterpreterState *interp); extern void _PyXI_FiniTypes(PyInterpreterState *interp); -#define _PyRuntimeState_GetXIState(runtime) (&(runtime)->xi) #define _PyInterpreterState_GetXIState(interp) (&(interp)->xi) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 3fc68aa76efd1e..725af2c9252278 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -94,6 +94,20 @@ _Py_CallInInterpreterAndRawFree(PyInterpreterState *interp, /* cross-interpreter data */ /**************************/ +/* registry of {type -> crossinterpdatafunc} */ + +/* For now we use a global registry of shareable classes. An + alternative would be to add a tp_* slot for a class's + crossinterpdatafunc. It would be simpler and more efficient. */ + +static void xid_lookup_init(PyInterpreterState *); +static void xid_lookup_fini(PyInterpreterState *); +static crossinterpdatafunc lookup_getdata(PyInterpreterState *, PyObject *); +#include "crossinterp_data_lookup.h" + + +/* lifecycle */ + _PyCrossInterpreterData * _PyCrossInterpreterData_New(void) { @@ -255,25 +269,6 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data) return 0; } -static crossinterpdatafunc _lookup_getdata_from_registry( - PyInterpreterState *, PyObject *); - -static crossinterpdatafunc -_lookup_getdata(PyInterpreterState *interp, PyObject *obj) -{ - /* Cross-interpreter objects are looked up by exact match on the class. - We can reassess this policy when we move from a global registry to a - tp_* slot. */ - return _lookup_getdata_from_registry(interp, obj); -} - -crossinterpdatafunc -_PyCrossInterpreterData_Lookup(PyObject *obj) -{ - PyInterpreterState *interp = _PyInterpreterState_GET(); - return _lookup_getdata(interp, obj); -} - static inline void _set_xid_lookup_failure(PyInterpreterState *interp, PyObject *obj, const char *msg) @@ -298,7 +293,7 @@ int _PyObject_CheckCrossInterpreterData(PyObject *obj) { PyInterpreterState *interp = _PyInterpreterState_GET(); - crossinterpdatafunc getdata = _lookup_getdata(interp, obj); + crossinterpdatafunc getdata = lookup_getdata(interp, obj); if (getdata == NULL) { if (!PyErr_Occurred()) { _set_xid_lookup_failure(interp, obj, NULL); @@ -324,7 +319,7 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) // Call the "getdata" func for the object. Py_INCREF(obj); - crossinterpdatafunc getdata = _lookup_getdata(interp, obj); + crossinterpdatafunc getdata = lookup_getdata(interp, obj); if (getdata == NULL) { Py_DECREF(obj); if (!PyErr_Occurred()) { @@ -411,538 +406,6 @@ _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data) } -/* registry of {type -> crossinterpdatafunc} */ - -/* For now we use a global registry of shareable classes. An - alternative would be to add a tp_* slot for a class's - crossinterpdatafunc. It would be simpler and more efficient. */ - -static inline struct _xidregistry * -_get_global_xidregistry(_PyRuntimeState *runtime) -{ - return &_PyRuntimeState_GetXIState(runtime)->registry; -} - -static inline struct _xidregistry * -_get_xidregistry(PyInterpreterState *interp) -{ - return &_PyInterpreterState_GetXIState(interp)->registry; -} - -static inline struct _xidregistry * -_get_xidregistry_for_type(PyInterpreterState *interp, PyTypeObject *cls) -{ - struct _xidregistry *registry = _get_global_xidregistry(interp->runtime); - if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { - registry = _get_xidregistry(interp); - } - return registry; -} - -static int -_xidregistry_add_type(struct _xidregistry *xidregistry, - PyTypeObject *cls, crossinterpdatafunc getdata) -{ - struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem)); - if (newhead == NULL) { - return -1; - } - *newhead = (struct _xidregitem){ - // We do not keep a reference, to avoid keeping the class alive. - .cls = cls, - .refcount = 1, - .getdata = getdata, - }; - if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { - // XXX Assign a callback to clear the entry from the registry? - newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL); - if (newhead->weakref == NULL) { - PyMem_RawFree(newhead); - return -1; - } - } - newhead->next = xidregistry->head; - if (newhead->next != NULL) { - newhead->next->prev = newhead; - } - xidregistry->head = newhead; - return 0; -} - -static struct _xidregitem * -_xidregistry_remove_entry(struct _xidregistry *xidregistry, - struct _xidregitem *entry) -{ - struct _xidregitem *next = entry->next; - if (entry->prev != NULL) { - assert(entry->prev->next == entry); - entry->prev->next = next; - } - else { - assert(xidregistry->head == entry); - xidregistry->head = next; - } - if (next != NULL) { - next->prev = entry->prev; - } - Py_XDECREF(entry->weakref); - PyMem_RawFree(entry); - return next; -} - -static void -_xidregistry_clear(struct _xidregistry *xidregistry) -{ - struct _xidregitem *cur = xidregistry->head; - xidregistry->head = NULL; - while (cur != NULL) { - struct _xidregitem *next = cur->next; - Py_XDECREF(cur->weakref); - PyMem_RawFree(cur); - cur = next; - } -} - -static void -_xidregistry_lock(struct _xidregistry *registry) -{ - if (registry->global) { - PyMutex_Lock(®istry->mutex); - } - // else: Within an interpreter we rely on the GIL instead of a separate lock. -} - -static void -_xidregistry_unlock(struct _xidregistry *registry) -{ - if (registry->global) { - PyMutex_Unlock(®istry->mutex); - } -} - -static struct _xidregitem * -_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls) -{ - struct _xidregitem *cur = xidregistry->head; - while (cur != NULL) { - if (cur->weakref != NULL) { - // cur is/was a heap type. - PyObject *registered = _PyWeakref_GET_REF(cur->weakref); - if (registered == NULL) { - // The weakly ref'ed object was freed. - cur = _xidregistry_remove_entry(xidregistry, cur); - continue; - } - assert(PyType_Check(registered)); - assert(cur->cls == (PyTypeObject *)registered); - assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE); - Py_DECREF(registered); - } - if (cur->cls == cls) { - return cur; - } - cur = cur->next; - } - return NULL; -} - -int -_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls, - crossinterpdatafunc getdata) -{ - if (!PyType_Check(cls)) { - PyErr_Format(PyExc_ValueError, "only classes may be registered"); - return -1; - } - if (getdata == NULL) { - PyErr_Format(PyExc_ValueError, "missing 'getdata' func"); - return -1; - } - - int res = 0; - PyInterpreterState *interp = _PyInterpreterState_GET(); - struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); - _xidregistry_lock(xidregistry); - - struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); - if (matched != NULL) { - assert(matched->getdata == getdata); - matched->refcount += 1; - goto finally; - } - - res = _xidregistry_add_type(xidregistry, cls, getdata); - -finally: - _xidregistry_unlock(xidregistry); - return res; -} - -int -_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls) -{ - int res = 0; - PyInterpreterState *interp = _PyInterpreterState_GET(); - struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); - _xidregistry_lock(xidregistry); - - struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); - if (matched != NULL) { - assert(matched->refcount > 0); - matched->refcount -= 1; - if (matched->refcount == 0) { - (void)_xidregistry_remove_entry(xidregistry, matched); - } - res = 1; - } - - _xidregistry_unlock(xidregistry); - return res; -} - -static crossinterpdatafunc -_lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj) -{ - PyTypeObject *cls = Py_TYPE(obj); - - struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); - _xidregistry_lock(xidregistry); - - struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); - crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL; - - _xidregistry_unlock(xidregistry); - return func; -} - -/* cross-interpreter data for builtin types */ - -// bytes - -struct _shared_bytes_data { - char *bytes; - Py_ssize_t len; -}; - -static PyObject * -_new_bytes_object(_PyCrossInterpreterData *data) -{ - struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data); - return PyBytes_FromStringAndSize(shared->bytes, shared->len); -} - -static int -_bytes_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - if (_PyCrossInterpreterData_InitWithSize( - data, tstate->interp, sizeof(struct _shared_bytes_data), obj, - _new_bytes_object - ) < 0) - { - return -1; - } - struct _shared_bytes_data *shared = (struct _shared_bytes_data *)data->data; - if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) { - _PyCrossInterpreterData_Clear(tstate->interp, data); - return -1; - } - return 0; -} - -// str - -struct _shared_str_data { - int kind; - const void *buffer; - Py_ssize_t len; -}; - -static PyObject * -_new_str_object(_PyCrossInterpreterData *data) -{ - struct _shared_str_data *shared = (struct _shared_str_data *)(data->data); - return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len); -} - -static int -_str_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - if (_PyCrossInterpreterData_InitWithSize( - data, tstate->interp, sizeof(struct _shared_str_data), obj, - _new_str_object - ) < 0) - { - return -1; - } - struct _shared_str_data *shared = (struct _shared_str_data *)data->data; - shared->kind = PyUnicode_KIND(obj); - shared->buffer = PyUnicode_DATA(obj); - shared->len = PyUnicode_GET_LENGTH(obj); - return 0; -} - -// int - -static PyObject * -_new_long_object(_PyCrossInterpreterData *data) -{ - return PyLong_FromSsize_t((Py_ssize_t)(data->data)); -} - -static int -_long_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - /* Note that this means the size of shareable ints is bounded by - * sys.maxsize. Hence on 32-bit architectures that is half the - * size of maximum shareable ints on 64-bit. - */ - Py_ssize_t value = PyLong_AsSsize_t(obj); - if (value == -1 && PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_OverflowError)) { - PyErr_SetString(PyExc_OverflowError, "try sending as bytes"); - } - return -1; - } - _PyCrossInterpreterData_Init(data, tstate->interp, (void *)value, NULL, - _new_long_object); - // data->obj and data->free remain NULL - return 0; -} - -// float - -static PyObject * -_new_float_object(_PyCrossInterpreterData *data) -{ - double * value_ptr = data->data; - return PyFloat_FromDouble(*value_ptr); -} - -static int -_float_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - if (_PyCrossInterpreterData_InitWithSize( - data, tstate->interp, sizeof(double), NULL, - _new_float_object - ) < 0) - { - return -1; - } - double *shared = (double *)data->data; - *shared = PyFloat_AsDouble(obj); - return 0; -} - -// None - -static PyObject * -_new_none_object(_PyCrossInterpreterData *data) -{ - // XXX Singleton refcounts are problematic across interpreters... - return Py_NewRef(Py_None); -} - -static int -_none_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - _PyCrossInterpreterData_Init(data, tstate->interp, NULL, NULL, - _new_none_object); - // data->data, data->obj and data->free remain NULL - return 0; -} - -// bool - -static PyObject * -_new_bool_object(_PyCrossInterpreterData *data) -{ - if (data->data){ - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; -} - -static int -_bool_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - _PyCrossInterpreterData_Init(data, tstate->interp, - (void *) (Py_IsTrue(obj) ? (uintptr_t) 1 : (uintptr_t) 0), NULL, - _new_bool_object); - // data->obj and data->free remain NULL - return 0; -} - -// tuple - -struct _shared_tuple_data { - Py_ssize_t len; - _PyCrossInterpreterData **data; -}; - -static PyObject * -_new_tuple_object(_PyCrossInterpreterData *data) -{ - struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data->data); - PyObject *tuple = PyTuple_New(shared->len); - if (tuple == NULL) { - return NULL; - } - - for (Py_ssize_t i = 0; i < shared->len; i++) { - PyObject *item = _PyCrossInterpreterData_NewObject(shared->data[i]); - if (item == NULL){ - Py_DECREF(tuple); - return NULL; - } - PyTuple_SET_ITEM(tuple, i, item); - } - return tuple; -} - -static void -_tuple_shared_free(void* data) -{ - struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data); -#ifndef NDEBUG - int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET()); -#endif - for (Py_ssize_t i = 0; i < shared->len; i++) { - if (shared->data[i] != NULL) { - assert(_PyCrossInterpreterData_INTERPID(shared->data[i]) == interpid); - _PyCrossInterpreterData_Release(shared->data[i]); - PyMem_RawFree(shared->data[i]); - shared->data[i] = NULL; - } - } - PyMem_Free(shared->data); - PyMem_RawFree(shared); -} - -static int -_tuple_shared(PyThreadState *tstate, PyObject *obj, - _PyCrossInterpreterData *data) -{ - Py_ssize_t len = PyTuple_GET_SIZE(obj); - if (len < 0) { - return -1; - } - struct _shared_tuple_data *shared = PyMem_RawMalloc(sizeof(struct _shared_tuple_data)); - if (shared == NULL){ - PyErr_NoMemory(); - return -1; - } - - shared->len = len; - shared->data = (_PyCrossInterpreterData **) PyMem_Calloc(shared->len, sizeof(_PyCrossInterpreterData *)); - if (shared->data == NULL) { - PyErr_NoMemory(); - return -1; - } - - for (Py_ssize_t i = 0; i < shared->len; i++) { - _PyCrossInterpreterData *data = _PyCrossInterpreterData_New(); - if (data == NULL) { - goto error; // PyErr_NoMemory already set - } - PyObject *item = PyTuple_GET_ITEM(obj, i); - - int res = -1; - if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) { - res = _PyObject_GetCrossInterpreterData(item, data); - _Py_LeaveRecursiveCallTstate(tstate); - } - if (res < 0) { - PyMem_RawFree(data); - goto error; - } - shared->data[i] = data; - } - _PyCrossInterpreterData_Init( - data, tstate->interp, shared, obj, _new_tuple_object); - data->free = _tuple_shared_free; - return 0; - -error: - _tuple_shared_free(shared); - return -1; -} - -// registration - -static void -_register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) -{ - // None - if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) { - Py_FatalError("could not register None for cross-interpreter sharing"); - } - - // int - if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) { - Py_FatalError("could not register int for cross-interpreter sharing"); - } - - // bytes - if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) { - Py_FatalError("could not register bytes for cross-interpreter sharing"); - } - - // str - if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) { - Py_FatalError("could not register str for cross-interpreter sharing"); - } - - // bool - if (_xidregistry_add_type(xidregistry, &PyBool_Type, _bool_shared) != 0) { - Py_FatalError("could not register bool for cross-interpreter sharing"); - } - - // float - if (_xidregistry_add_type(xidregistry, &PyFloat_Type, _float_shared) != 0) { - Py_FatalError("could not register float for cross-interpreter sharing"); - } - - // tuple - if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) { - Py_FatalError("could not register tuple for cross-interpreter sharing"); - } -} - -/* registry lifecycle */ - -static void -_xidregistry_init(struct _xidregistry *registry) -{ - if (registry->initialized) { - return; - } - registry->initialized = 1; - - if (registry->global) { - // Registering the builtins is cheap so we don't bother doing it lazily. - assert(registry->head == NULL); - _register_builtins_for_crossinterpreter_data(registry); - } -} - -static void -_xidregistry_fini(struct _xidregistry *registry) -{ - if (!registry->initialized) { - return; - } - registry->initialized = 0; - - _xidregistry_clear(registry); -} - - /*************************/ /* convenience utilities */ /*************************/ @@ -2250,11 +1713,8 @@ _PyXI_Init(PyInterpreterState *interp) { PyStatus status; - // Initialize the XID registry. - if (_Py_IsMainInterpreter(interp)) { - _xidregistry_init(_get_global_xidregistry(interp->runtime)); - } - _xidregistry_init(_get_xidregistry(interp)); + // Initialize the XID lookup state (e.g. registry). + xid_lookup_init(interp); // Initialize exceptions (heap types). status = _init_not_shareable_error_type(interp); @@ -2274,11 +1734,8 @@ _PyXI_Fini(PyInterpreterState *interp) // Finalize exceptions (heap types). _fini_not_shareable_error_type(interp); - // Finalize the XID registry. - _xidregistry_fini(_get_xidregistry(interp)); - if (_Py_IsMainInterpreter(interp)) { - _xidregistry_fini(_get_global_xidregistry(interp->runtime)); - } + // Finalize the XID lookup state (e.g. registry). + xid_lookup_fini(interp); } PyStatus diff --git a/Python/crossinterp_data_lookup.h b/Python/crossinterp_data_lookup.h new file mode 100644 index 00000000000000..863919ad42fb97 --- /dev/null +++ b/Python/crossinterp_data_lookup.h @@ -0,0 +1,594 @@ + +static crossinterpdatafunc _lookup_getdata_from_registry( + PyInterpreterState *, PyObject *); + +static crossinterpdatafunc +lookup_getdata(PyInterpreterState *interp, PyObject *obj) +{ + /* Cross-interpreter objects are looked up by exact match on the class. + We can reassess this policy when we move from a global registry to a + tp_* slot. */ + return _lookup_getdata_from_registry(interp, obj); +} + +crossinterpdatafunc +_PyCrossInterpreterData_Lookup(PyObject *obj) +{ + PyInterpreterState *interp = PyInterpreterState_Get(); + return lookup_getdata(interp, obj); +} + + +/***********************************************/ +/* a registry of {type -> crossinterpdatafunc} */ +/***********************************************/ + +/* For now we use a global registry of shareable classes. An + alternative would be to add a tp_* slot for a class's + crossinterpdatafunc. It would be simpler and more efficient. */ + + +/* registry lifecycle */ + +static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *); + +static void +_xidregistry_init(struct _xidregistry *registry) +{ + if (registry->initialized) { + return; + } + registry->initialized = 1; + + if (registry->global) { + // Registering the builtins is cheap so we don't bother doing it lazily. + assert(registry->head == NULL); + _register_builtins_for_crossinterpreter_data(registry); + } +} + +static void _xidregistry_clear(struct _xidregistry *); + +static void +_xidregistry_fini(struct _xidregistry *registry) +{ + if (!registry->initialized) { + return; + } + registry->initialized = 0; + + _xidregistry_clear(registry); +} + +static inline struct _xidregistry * _get_global_xidregistry(_PyRuntimeState *); +static inline struct _xidregistry * _get_xidregistry(PyInterpreterState *); + +static void +xid_lookup_init(PyInterpreterState *interp) +{ + if (_Py_IsMainInterpreter(interp)) { + _xidregistry_init(_get_global_xidregistry(interp->runtime)); + } + _xidregistry_init(_get_xidregistry(interp)); +} + +static void +xid_lookup_fini(PyInterpreterState *interp) +{ + _xidregistry_fini(_get_xidregistry(interp)); + if (_Py_IsMainInterpreter(interp)) { + _xidregistry_fini(_get_global_xidregistry(interp->runtime)); + } +} + + +/* registry thread safety */ + +static void +_xidregistry_lock(struct _xidregistry *registry) +{ + if (registry->global) { + PyMutex_Lock(®istry->mutex); + } + // else: Within an interpreter we rely on the GIL instead of a separate lock. +} + +static void +_xidregistry_unlock(struct _xidregistry *registry) +{ + if (registry->global) { + PyMutex_Unlock(®istry->mutex); + } +} + + +/* accessing the registry */ + +static inline struct _xidregistry * +_get_global_xidregistry(_PyRuntimeState *runtime) +{ + return &runtime->xi.registry; +} + +static inline struct _xidregistry * +_get_xidregistry(PyInterpreterState *interp) +{ + return &interp->xi.registry; +} + +static inline struct _xidregistry * +_get_xidregistry_for_type(PyInterpreterState *interp, PyTypeObject *cls) +{ + struct _xidregistry *registry = _get_global_xidregistry(interp->runtime); + if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { + registry = _get_xidregistry(interp); + } + return registry; +} + +static struct _xidregitem * _xidregistry_remove_entry( + struct _xidregistry *, struct _xidregitem *); + +static struct _xidregitem * +_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls) +{ + struct _xidregitem *cur = xidregistry->head; + while (cur != NULL) { + if (cur->weakref != NULL) { + // cur is/was a heap type. + PyObject *registered = _PyWeakref_GET_REF(cur->weakref); + if (registered == NULL) { + // The weakly ref'ed object was freed. + cur = _xidregistry_remove_entry(xidregistry, cur); + continue; + } + assert(PyType_Check(registered)); + assert(cur->cls == (PyTypeObject *)registered); + assert(cur->cls->tp_flags & Py_TPFLAGS_HEAPTYPE); + Py_DECREF(registered); + } + if (cur->cls == cls) { + return cur; + } + cur = cur->next; + } + return NULL; +} + +static crossinterpdatafunc +_lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj) +{ + PyTypeObject *cls = Py_TYPE(obj); + + struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); + _xidregistry_lock(xidregistry); + + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); + crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL; + + _xidregistry_unlock(xidregistry); + return func; +} + + +/* updating the registry */ + +static int +_xidregistry_add_type(struct _xidregistry *xidregistry, + PyTypeObject *cls, crossinterpdatafunc getdata) +{ + struct _xidregitem *newhead = PyMem_RawMalloc(sizeof(struct _xidregitem)); + if (newhead == NULL) { + return -1; + } + *newhead = (struct _xidregitem){ + // We do not keep a reference, to avoid keeping the class alive. + .cls = cls, + .refcount = 1, + .getdata = getdata, + }; + if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) { + // XXX Assign a callback to clear the entry from the registry? + newhead->weakref = PyWeakref_NewRef((PyObject *)cls, NULL); + if (newhead->weakref == NULL) { + PyMem_RawFree(newhead); + return -1; + } + } + newhead->next = xidregistry->head; + if (newhead->next != NULL) { + newhead->next->prev = newhead; + } + xidregistry->head = newhead; + return 0; +} + +static struct _xidregitem * +_xidregistry_remove_entry(struct _xidregistry *xidregistry, + struct _xidregitem *entry) +{ + struct _xidregitem *next = entry->next; + if (entry->prev != NULL) { + assert(entry->prev->next == entry); + entry->prev->next = next; + } + else { + assert(xidregistry->head == entry); + xidregistry->head = next; + } + if (next != NULL) { + next->prev = entry->prev; + } + Py_XDECREF(entry->weakref); + PyMem_RawFree(entry); + return next; +} + +static void +_xidregistry_clear(struct _xidregistry *xidregistry) +{ + struct _xidregitem *cur = xidregistry->head; + xidregistry->head = NULL; + while (cur != NULL) { + struct _xidregitem *next = cur->next; + Py_XDECREF(cur->weakref); + PyMem_RawFree(cur); + cur = next; + } +} + +int +_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls, + crossinterpdatafunc getdata) +{ + if (!PyType_Check(cls)) { + PyErr_Format(PyExc_ValueError, "only classes may be registered"); + return -1; + } + if (getdata == NULL) { + PyErr_Format(PyExc_ValueError, "missing 'getdata' func"); + return -1; + } + + int res = 0; + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); + _xidregistry_lock(xidregistry); + + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); + if (matched != NULL) { + assert(matched->getdata == getdata); + matched->refcount += 1; + goto finally; + } + + res = _xidregistry_add_type(xidregistry, cls, getdata); + +finally: + _xidregistry_unlock(xidregistry); + return res; +} + +int +_PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls) +{ + int res = 0; + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls); + _xidregistry_lock(xidregistry); + + struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls); + if (matched != NULL) { + assert(matched->refcount > 0); + matched->refcount -= 1; + if (matched->refcount == 0) { + (void)_xidregistry_remove_entry(xidregistry, matched); + } + res = 1; + } + + _xidregistry_unlock(xidregistry); + return res; +} + + +/********************************************/ +/* cross-interpreter data for builtin types */ +/********************************************/ + +// bytes + +struct _shared_bytes_data { + char *bytes; + Py_ssize_t len; +}; + +static PyObject * +_new_bytes_object(_PyCrossInterpreterData *data) +{ + struct _shared_bytes_data *shared = (struct _shared_bytes_data *)(data->data); + return PyBytes_FromStringAndSize(shared->bytes, shared->len); +} + +static int +_bytes_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + if (_PyCrossInterpreterData_InitWithSize( + data, tstate->interp, sizeof(struct _shared_bytes_data), obj, + _new_bytes_object + ) < 0) + { + return -1; + } + struct _shared_bytes_data *shared = (struct _shared_bytes_data *)data->data; + if (PyBytes_AsStringAndSize(obj, &shared->bytes, &shared->len) < 0) { + _PyCrossInterpreterData_Clear(tstate->interp, data); + return -1; + } + return 0; +} + +// str + +struct _shared_str_data { + int kind; + const void *buffer; + Py_ssize_t len; +}; + +static PyObject * +_new_str_object(_PyCrossInterpreterData *data) +{ + struct _shared_str_data *shared = (struct _shared_str_data *)(data->data); + return PyUnicode_FromKindAndData(shared->kind, shared->buffer, shared->len); +} + +static int +_str_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + if (_PyCrossInterpreterData_InitWithSize( + data, tstate->interp, sizeof(struct _shared_str_data), obj, + _new_str_object + ) < 0) + { + return -1; + } + struct _shared_str_data *shared = (struct _shared_str_data *)data->data; + shared->kind = PyUnicode_KIND(obj); + shared->buffer = PyUnicode_DATA(obj); + shared->len = PyUnicode_GET_LENGTH(obj); + return 0; +} + +// int + +static PyObject * +_new_long_object(_PyCrossInterpreterData *data) +{ + return PyLong_FromSsize_t((Py_ssize_t)(data->data)); +} + +static int +_long_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + /* Note that this means the size of shareable ints is bounded by + * sys.maxsize. Hence on 32-bit architectures that is half the + * size of maximum shareable ints on 64-bit. + */ + Py_ssize_t value = PyLong_AsSsize_t(obj); + if (value == -1 && PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + PyErr_SetString(PyExc_OverflowError, "try sending as bytes"); + } + return -1; + } + _PyCrossInterpreterData_Init(data, tstate->interp, (void *)value, NULL, + _new_long_object); + // data->obj and data->free remain NULL + return 0; +} + +// float + +static PyObject * +_new_float_object(_PyCrossInterpreterData *data) +{ + double * value_ptr = data->data; + return PyFloat_FromDouble(*value_ptr); +} + +static int +_float_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + if (_PyCrossInterpreterData_InitWithSize( + data, tstate->interp, sizeof(double), NULL, + _new_float_object + ) < 0) + { + return -1; + } + double *shared = (double *)data->data; + *shared = PyFloat_AsDouble(obj); + return 0; +} + +// None + +static PyObject * +_new_none_object(_PyCrossInterpreterData *data) +{ + // XXX Singleton refcounts are problematic across interpreters... + return Py_NewRef(Py_None); +} + +static int +_none_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + _PyCrossInterpreterData_Init(data, tstate->interp, NULL, NULL, + _new_none_object); + // data->data, data->obj and data->free remain NULL + return 0; +} + +// bool + +static PyObject * +_new_bool_object(_PyCrossInterpreterData *data) +{ + if (data->data){ + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static int +_bool_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + _PyCrossInterpreterData_Init(data, tstate->interp, + (void *) (Py_IsTrue(obj) ? (uintptr_t) 1 : (uintptr_t) 0), NULL, + _new_bool_object); + // data->obj and data->free remain NULL + return 0; +} + +// tuple + +struct _shared_tuple_data { + Py_ssize_t len; + _PyCrossInterpreterData **data; +}; + +static PyObject * +_new_tuple_object(_PyCrossInterpreterData *data) +{ + struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data->data); + PyObject *tuple = PyTuple_New(shared->len); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < shared->len; i++) { + PyObject *item = _PyCrossInterpreterData_NewObject(shared->data[i]); + if (item == NULL){ + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; +} + +static void +_tuple_shared_free(void* data) +{ + struct _shared_tuple_data *shared = (struct _shared_tuple_data *)(data); +#ifndef NDEBUG + int64_t interpid = PyInterpreterState_GetID(_PyInterpreterState_GET()); +#endif + for (Py_ssize_t i = 0; i < shared->len; i++) { + if (shared->data[i] != NULL) { + assert(_PyCrossInterpreterData_INTERPID(shared->data[i]) == interpid); + _PyCrossInterpreterData_Release(shared->data[i]); + PyMem_RawFree(shared->data[i]); + shared->data[i] = NULL; + } + } + PyMem_Free(shared->data); + PyMem_RawFree(shared); +} + +static int +_tuple_shared(PyThreadState *tstate, PyObject *obj, + _PyCrossInterpreterData *data) +{ + Py_ssize_t len = PyTuple_GET_SIZE(obj); + if (len < 0) { + return -1; + } + struct _shared_tuple_data *shared = PyMem_RawMalloc(sizeof(struct _shared_tuple_data)); + if (shared == NULL){ + PyErr_NoMemory(); + return -1; + } + + shared->len = len; + shared->data = (_PyCrossInterpreterData **) PyMem_Calloc(shared->len, sizeof(_PyCrossInterpreterData *)); + if (shared->data == NULL) { + PyErr_NoMemory(); + return -1; + } + + for (Py_ssize_t i = 0; i < shared->len; i++) { + _PyCrossInterpreterData *data = _PyCrossInterpreterData_New(); + if (data == NULL) { + goto error; // PyErr_NoMemory already set + } + PyObject *item = PyTuple_GET_ITEM(obj, i); + + int res = -1; + if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) { + res = _PyObject_GetCrossInterpreterData(item, data); + _Py_LeaveRecursiveCallTstate(tstate); + } + if (res < 0) { + PyMem_RawFree(data); + goto error; + } + shared->data[i] = data; + } + _PyCrossInterpreterData_Init( + data, tstate->interp, shared, obj, _new_tuple_object); + data->free = _tuple_shared_free; + return 0; + +error: + _tuple_shared_free(shared); + return -1; +} + +// registration + +static void +_register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) +{ + // None + if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) { + Py_FatalError("could not register None for cross-interpreter sharing"); + } + + // int + if (_xidregistry_add_type(xidregistry, &PyLong_Type, _long_shared) != 0) { + Py_FatalError("could not register int for cross-interpreter sharing"); + } + + // bytes + if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _bytes_shared) != 0) { + Py_FatalError("could not register bytes for cross-interpreter sharing"); + } + + // str + if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) { + Py_FatalError("could not register str for cross-interpreter sharing"); + } + + // bool + if (_xidregistry_add_type(xidregistry, &PyBool_Type, _bool_shared) != 0) { + Py_FatalError("could not register bool for cross-interpreter sharing"); + } + + // float + if (_xidregistry_add_type(xidregistry, &PyFloat_Type, _float_shared) != 0) { + Py_FatalError("could not register float for cross-interpreter sharing"); + } + + // tuple + if (_xidregistry_add_type(xidregistry, &PyTuple_Type, _tuple_shared) != 0) { + Py_FatalError("could not register tuple for cross-interpreter sharing"); + } +} From 7bd8f7f8b5d0d877c7a2b4ebc208a951467c1750 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 18:37:15 -0700 Subject: [PATCH 09/20] Use PyInterpreterState_Get(). --- Python/crossinterp.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 725af2c9252278..fd340509760bd4 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -7,7 +7,6 @@ #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_namespace.h" //_PyNamespace_New() #include "pycore_pyerrors.h" // _PyErr_Clear() -#include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_typeobject.h" // _PyType_GetModuleName() #include "pycore_weakref.h" // _PyWeakref_GET_REF() @@ -67,7 +66,7 @@ int _Py_CallInInterpreter(PyInterpreterState *interp, _Py_simple_func func, void *arg) { - if (interp == _PyThreadState_GetCurrent()->interp) { + if (interp == PyInterpreterState_Get()) { return func(arg); } // XXX Emit a warning if this fails? @@ -79,7 +78,7 @@ int _Py_CallInInterpreterAndRawFree(PyInterpreterState *interp, _Py_simple_func func, void *arg) { - if (interp == _PyThreadState_GetCurrent()->interp) { + if (interp == PyInterpreterState_Get()) { int res = func(arg); PyMem_RawFree(arg); return res; @@ -292,7 +291,7 @@ _set_xid_lookup_failure(PyInterpreterState *interp, int _PyObject_CheckCrossInterpreterData(PyObject *obj) { - PyInterpreterState *interp = _PyInterpreterState_GET(); + PyInterpreterState *interp = PyInterpreterState_Get(); crossinterpdatafunc getdata = lookup_getdata(interp, obj); if (getdata == NULL) { if (!PyErr_Occurred()) { @@ -306,11 +305,7 @@ _PyObject_CheckCrossInterpreterData(PyObject *obj) int _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data) { - PyThreadState *tstate = _PyThreadState_GetCurrent(); -#ifdef Py_DEBUG - // The caller must hold the GIL - _Py_EnsureTstateNotNULL(tstate); -#endif + PyThreadState *tstate = PyThreadState_Get(); PyInterpreterState *interp = tstate->interp; // Reset data before re-populating. @@ -1340,7 +1335,7 @@ _PyXI_FreeNamespace(_PyXI_namespace *ns) return; } - if (interpid == PyInterpreterState_GetID(_PyInterpreterState_GET())) { + if (interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) { _sharedns_free(ns); } else { @@ -1539,7 +1534,7 @@ _propagate_not_shareable_error(_PyXI_session *session) if (session == NULL) { return; } - PyInterpreterState *interp = _PyInterpreterState_GET(); + PyInterpreterState *interp = PyInterpreterState_Get(); if (PyErr_ExceptionMatches(_get_not_shareable_error_type(interp))) { // We want to propagate the exception directly. session->_error_override = _PyXI_ERR_NOT_SHAREABLE; From f68737bd35db73f5317ee2a4b31eb5411f362f29 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 18:38:04 -0700 Subject: [PATCH 10/20] Add _Py_EMPTY_STR. --- Python/crossinterp.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index fd340509760bd4..239910011eded4 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -484,6 +484,10 @@ _convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc) return -1; } +// We accommodate backports here. +#ifndef _Py_EMPTY_STR +# define _Py_EMPTY_STR &_Py_STR(empty) +#endif static const char * _format_TracebackException(PyObject *tbexc) @@ -492,7 +496,8 @@ _format_TracebackException(PyObject *tbexc) if (lines == NULL) { return NULL; } - PyObject *formatted_obj = PyUnicode_Join(&_Py_STR(empty), lines); + assert(_Py_EMPTY_STR != NULL); + PyObject *formatted_obj = PyUnicode_Join(_Py_EMPTY_STR, lines); Py_DECREF(lines); if (formatted_obj == NULL) { return NULL; From c99fd01180ebb4dbdd156851d51ae0511bcbc176 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 4 Jan 2024 19:17:33 -0700 Subject: [PATCH 11/20] Use PyErr_SetString(). --- Python/crossinterp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 239910011eded4..7a8a76593ce11b 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -254,12 +254,12 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data) // data->obj may be NULL, so we don't check it. if (_PyCrossInterpreterData_INTERPID(data) < 0) { - _PyErr_SetString(tstate, PyExc_SystemError, "missing interp"); + PyErr_SetString(PyExc_SystemError, "missing interp"); return -1; } if (data->new_object == NULL) { - _PyErr_SetString(tstate, PyExc_SystemError, "missing new_object func"); + PyErr_SetString(PyExc_SystemError, "missing new_object func"); return -1; } From e2f9cf043d67b3cb4e7d7c01c16c54837be38a92 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 5 Jan 2024 20:40:28 -0700 Subject: [PATCH 12/20] Use MODULE_NAME for the module init func name. --- Modules/_interpreters_common.h | 6 ++++++ Modules/_xxinterpchannelsmodule.c | 20 ++++++++++-------- Modules/_xxinterpqueuesmodule.c | 12 ++++++----- Modules/_xxsubinterpretersmodule.c | 34 ++++++++++++++++-------------- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/Modules/_interpreters_common.h b/Modules/_interpreters_common.h index 2b240b9df4a40a..5661a26d8790d1 100644 --- a/Modules/_interpreters_common.h +++ b/Modules/_interpreters_common.h @@ -1,4 +1,10 @@ +#define _RESOLVE_MODINIT_FUNC_NAME(NAME) \ + PyInit_ ## NAME +#define RESOLVE_MODINIT_FUNC_NAME(NAME) \ + _RESOLVE_MODINIT_FUNC_NAME(NAME) + + static int ensure_xid_class(PyTypeObject *cls, crossinterpdatafunc getdata) { diff --git a/Modules/_xxinterpchannelsmodule.c b/Modules/_xxinterpchannelsmodule.c index 4fd24f78a77c05..a2974aced12ca0 100644 --- a/Modules/_xxinterpchannelsmodule.c +++ b/Modules/_xxinterpchannelsmodule.c @@ -82,7 +82,9 @@ channel's queue, which are safely managed via the _PyCrossInterpreterData_*() API.. The module does not create any objects that are shared globally. */ -#define MODULE_NAME "_xxinterpchannels" +#define MODULE_NAME _xxinterpchannels +#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME) +#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME) #define GLOBAL_MALLOC(TYPE) \ @@ -169,7 +171,7 @@ _get_current_interp(void) static PyObject * _get_current_module(void) { - PyObject *name = PyUnicode_FromString(MODULE_NAME); + PyObject *name = PyUnicode_FromString(MODULE_NAME_STR); if (name == NULL) { return NULL; } @@ -219,7 +221,7 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base) } #define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \ - add_new_exception(MOD, MODULE_NAME "." Py_STRINGIFY(NAME), BASE) + add_new_exception(MOD, MODULE_NAME_STR "." Py_STRINGIFY(NAME), BASE) static PyTypeObject * add_new_type(PyObject *mod, PyType_Spec *spec, crossinterpdatafunc shared, @@ -301,7 +303,7 @@ _get_current_module_state(void) if (mod == NULL) { // XXX import it? PyErr_SetString(PyExc_RuntimeError, - MODULE_NAME " module not imported yet"); + MODULE_NAME_STR " module not imported yet"); return NULL; } module_state *state = get_module_state(mod); @@ -2128,7 +2130,7 @@ static PyStructSequence_Field channel_info_fields[] = { }; static PyStructSequence_Desc channel_info_desc = { - .name = MODULE_NAME ".ChannelInfo", + .name = MODULE_NAME_STR ".ChannelInfo", .doc = channel_info_doc, .fields = channel_info_fields, .n_in_sequence = 8, @@ -2480,7 +2482,7 @@ _channelid_from_xid(_PyCrossInterpreterData *data) (struct _channelid_xid *)_PyCrossInterpreterData_DATA(data); // It might not be imported yet, so we can't use _get_current_module(). - PyObject *mod = PyImport_ImportModule(MODULE_NAME); + PyObject *mod = PyImport_ImportModule(MODULE_NAME_STR); if (mod == NULL) { return NULL; } @@ -2605,7 +2607,7 @@ static PyType_Slot channelid_typeslots[] = { }; static PyType_Spec channelid_typespec = { - .name = MODULE_NAME ".ChannelID", + .name = MODULE_NAME_STR ".ChannelID", .basicsize = sizeof(channelid), .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), @@ -3383,7 +3385,7 @@ module_free(void *mod) static struct PyModuleDef moduledef = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = MODULE_NAME, + .m_name = MODULE_NAME_STR, .m_doc = module_doc, .m_size = sizeof(module_state), .m_methods = module_functions, @@ -3394,7 +3396,7 @@ static struct PyModuleDef moduledef = { }; PyMODINIT_FUNC -PyInit__xxinterpchannels(void) +MODINIT_FUNC_NAME(void) { return PyModuleDef_Init(&moduledef); } diff --git a/Modules/_xxinterpqueuesmodule.c b/Modules/_xxinterpqueuesmodule.c index d1aacb393cbb28..7d8c67f49fefb8 100644 --- a/Modules/_xxinterpqueuesmodule.c +++ b/Modules/_xxinterpqueuesmodule.c @@ -11,7 +11,9 @@ #include "_interpreters_common.h" -#define MODULE_NAME "_xxinterpqueues" +#define MODULE_NAME _xxinterpqueues +#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME) +#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME) #define GLOBAL_MALLOC(TYPE) \ @@ -66,7 +68,7 @@ _get_current_interp(void) static PyObject * _get_current_module(void) { - PyObject *name = PyUnicode_FromString(MODULE_NAME); + PyObject *name = PyUnicode_FromString(MODULE_NAME_STR); if (name == NULL) { return NULL; } @@ -1142,7 +1144,7 @@ _queueobj_from_xid(_PyCrossInterpreterData *data) if (mod == NULL) { // XXX import it? PyErr_SetString(PyExc_RuntimeError, - MODULE_NAME " module not imported yet"); + MODULE_NAME_STR " module not imported yet"); return NULL; } @@ -1672,7 +1674,7 @@ module_free(void *mod) static struct PyModuleDef moduledef = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = MODULE_NAME, + .m_name = MODULE_NAME_STR, .m_doc = module_doc, .m_size = sizeof(module_state), .m_methods = module_functions, @@ -1683,7 +1685,7 @@ static struct PyModuleDef moduledef = { }; PyMODINIT_FUNC -PyInit__xxinterpqueues(void) +MODINIT_FUNC_NAME(void) { return PyModuleDef_Init(&moduledef); } diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index f9a002a76b2e9e..b4004d165078f7 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -22,7 +22,9 @@ #include "_interpreters_common.h" -#define MODULE_NAME "_xxsubinterpreters" +#define MODULE_NAME _xxsubinterpreters +#define MODULE_NAME_STR Py_STRINGIFY(MODULE_NAME) +#define MODINIT_FUNC_NAME RESOLVE_MODINIT_FUNC_NAME(MODULE_NAME) static PyInterpreterState * @@ -127,7 +129,7 @@ get_interpid_obj(PyInterpreterState *interp) static PyObject * _get_current_module(void) { - PyObject *name = PyUnicode_FromString(MODULE_NAME); + PyObject *name = PyUnicode_FromString(MODULE_NAME_STR); if (name == NULL) { return NULL; } @@ -211,7 +213,7 @@ static PyType_Slot XIBufferViewType_slots[] = { }; static PyType_Spec XIBufferViewType_spec = { - .name = MODULE_NAME ".CrossInterpreterBufferView", + .name = MODULE_NAME_STR ".CrossInterpreterBufferView", .basicsize = sizeof(XIBufferViewObject), .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), @@ -304,7 +306,7 @@ _get_current_module_state(void) if (mod == NULL) { // XXX import it? PyErr_SetString(PyExc_RuntimeError, - MODULE_NAME " module not imported yet"); + MODULE_NAME_STR " module not imported yet"); return NULL; } module_state *state = get_module_state(mod); @@ -690,7 +692,7 @@ static PyObject * interp_set___main___attrs(PyObject *self, PyObject *args) { PyObject *id, *updates; - if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME ".set___main___attrs", + if (!PyArg_ParseTuple(args, "OO:" MODULE_NAME_STR ".set___main___attrs", &id, &updates)) { return NULL; @@ -855,18 +857,18 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds) PyObject *id, *code; PyObject *shared = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "OO|O:" MODULE_NAME ".exec", kwlist, + "OO|O:" MODULE_NAME_STR ".exec", kwlist, &id, &code, &shared)) { return NULL; } const char *expected = "a string, a function, or a code object"; if (PyUnicode_Check(code)) { - code = (PyObject *)convert_script_arg(code, MODULE_NAME ".exec", + code = (PyObject *)convert_script_arg(code, MODULE_NAME_STR ".exec", "argument 2", expected); } else { - code = (PyObject *)convert_code_arg(code, MODULE_NAME ".exec", + code = (PyObject *)convert_code_arg(code, MODULE_NAME_STR ".exec", "argument 2", expected); } if (code == NULL) { @@ -907,12 +909,12 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) PyObject *id, *script; PyObject *shared = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "OU|O:" MODULE_NAME ".run_string", kwlist, + "OU|O:" MODULE_NAME_STR ".run_string", kwlist, &id, &script, &shared)) { return NULL; } - script = (PyObject *)convert_script_arg(script, MODULE_NAME ".exec", + script = (PyObject *)convert_script_arg(script, MODULE_NAME_STR ".exec", "argument 2", "a string"); if (script == NULL) { return NULL; @@ -933,7 +935,7 @@ PyDoc_STRVAR(run_string_doc, \n\ Execute the provided string in the identified interpreter.\n\ \n\ -(See " MODULE_NAME ".exec()."); +(See " MODULE_NAME_STR ".exec()."); static PyObject * interp_run_func(PyObject *self, PyObject *args, PyObject *kwds) @@ -942,12 +944,12 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds) PyObject *id, *func; PyObject *shared = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, - "OO|O:" MODULE_NAME ".run_func", kwlist, + "OO|O:" MODULE_NAME_STR ".run_func", kwlist, &id, &func, &shared)) { return NULL; } - PyCodeObject *code = convert_code_arg(func, MODULE_NAME ".exec", + PyCodeObject *code = convert_code_arg(func, MODULE_NAME_STR ".exec", "argument 2", "a function or a code object"); if (code == NULL) { @@ -971,7 +973,7 @@ Execute the body of the provided function in the identified interpreter.\n\ Code objects are also supported. In both cases, closures and args\n\ are not supported. Methods and other callables are not supported either.\n\ \n\ -(See " MODULE_NAME ".exec()."); +(See " MODULE_NAME_STR ".exec()."); static PyObject * @@ -1165,7 +1167,7 @@ module_free(void *mod) static struct PyModuleDef moduledef = { .m_base = PyModuleDef_HEAD_INIT, - .m_name = MODULE_NAME, + .m_name = MODULE_NAME_STR, .m_doc = module_doc, .m_size = sizeof(module_state), .m_methods = module_functions, @@ -1176,7 +1178,7 @@ static struct PyModuleDef moduledef = { }; PyMODINIT_FUNC -PyInit__xxsubinterpreters(void) +MODINIT_FUNC_NAME(void) { return PyModuleDef_Init(&moduledef); } From dedc0e8710e14b58cf38e06c189eba5cb3489493 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 8 Jan 2024 12:46:37 -0700 Subject: [PATCH 13/20] Factor out crossinterp_exceptionsw.h. --- Python/crossinterp.c | 44 +++------------------------------ Python/crossinterp_exceptions.h | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 41 deletions(-) create mode 100644 Python/crossinterp_exceptions.h diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 7a8a76593ce11b..04fc49887398c2 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -15,47 +15,9 @@ /* exceptions */ /**************/ -/* InterpreterError extends Exception */ - -static PyTypeObject _PyExc_InterpreterError = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "InterpreterError", - .tp_doc = PyDoc_STR("An interpreter was not found."), - //.tp_base = (PyTypeObject *)PyExc_BaseException, -}; -PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; - -/* InterpreterNotFoundError extends InterpreterError */ - -static PyTypeObject _PyExc_InterpreterNotFoundError = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "InterpreterNotFoundError", - .tp_doc = PyDoc_STR("An interpreter was not found."), - .tp_base = &_PyExc_InterpreterError, -}; -PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError; - -/* lifecycle */ - -static int -init_exceptions(PyInterpreterState *interp) -{ - _PyExc_InterpreterError.tp_base = (PyTypeObject *)PyExc_BaseException; - if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterError) < 0) { - return -1; - } - if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterNotFoundError) < 0) { - return -1; - } - return 0; -} - -static void -fini_exceptions(PyInterpreterState *interp) -{ - _PyStaticType_Dealloc(interp, &_PyExc_InterpreterNotFoundError); - _PyStaticType_Dealloc(interp, &_PyExc_InterpreterError); -} +static int init_exceptions(PyInterpreterState *); +static void fini_exceptions(PyInterpreterState *); +#include "crossinterp_exceptions.h" /***************************/ diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h new file mode 100644 index 00000000000000..ebf8c6aba8c7fc --- /dev/null +++ b/Python/crossinterp_exceptions.h @@ -0,0 +1,43 @@ + +/* InterpreterError extends Exception */ + +static PyTypeObject _PyExc_InterpreterError = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "InterpreterError", + .tp_doc = PyDoc_STR("An interpreter was not found."), + //.tp_base = (PyTypeObject *)PyExc_BaseException, +}; +PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; + +/* InterpreterNotFoundError extends InterpreterError */ + +static PyTypeObject _PyExc_InterpreterNotFoundError = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "InterpreterNotFoundError", + .tp_doc = PyDoc_STR("An interpreter was not found."), + .tp_base = &_PyExc_InterpreterError, +}; +PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError; + + +/* lifecycle */ + +static int +init_exceptions(PyInterpreterState *interp) +{ + _PyExc_InterpreterError.tp_base = (PyTypeObject *)PyExc_BaseException; + if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterError) < 0) { + return -1; + } + if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterNotFoundError) < 0) { + return -1; + } + return 0; +} + +static void +fini_exceptions(PyInterpreterState *interp) +{ + _PyStaticType_Dealloc(interp, &_PyExc_InterpreterNotFoundError); + _PyStaticType_Dealloc(interp, &_PyExc_InterpreterError); +} From 64605ed9563490d9ad6085710d878045ad8e0934 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 8 Jan 2024 13:37:51 -0700 Subject: [PATCH 14/20] Fix exception docstrings. --- Python/crossinterp_exceptions.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h index ebf8c6aba8c7fc..4209b7ac4f1a36 100644 --- a/Python/crossinterp_exceptions.h +++ b/Python/crossinterp_exceptions.h @@ -4,7 +4,7 @@ static PyTypeObject _PyExc_InterpreterError = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "InterpreterError", - .tp_doc = PyDoc_STR("An interpreter was not found."), + .tp_doc = PyDoc_STR("A cross-interpreter operation failed"), //.tp_base = (PyTypeObject *)PyExc_BaseException, }; PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; @@ -14,7 +14,7 @@ PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; static PyTypeObject _PyExc_InterpreterNotFoundError = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "InterpreterNotFoundError", - .tp_doc = PyDoc_STR("An interpreter was not found."), + .tp_doc = PyDoc_STR("An interpreter was not found"), .tp_base = &_PyExc_InterpreterError, }; PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError; From 5c008decb04dcee3661e1f6e4d5e16b4b2c16b6d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 8 Jan 2024 14:32:20 -0700 Subject: [PATCH 15/20] Move NotShareableError to crossinterp_exceptions.h. --- Python/crossinterp.c | 44 +-------------------------------- Python/crossinterp_exceptions.h | 38 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 04fc49887398c2..b9b14fa854964e 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -17,6 +17,7 @@ static int init_exceptions(PyInterpreterState *); static void fini_exceptions(PyInterpreterState *); +static PyObject * _get_not_shareable_error_type(PyInterpreterState *); #include "crossinterp_exceptions.h" @@ -89,38 +90,6 @@ _PyCrossInterpreterData_Free(_PyCrossInterpreterData *xid) } -/* exceptions */ - -static PyStatus -_init_not_shareable_error_type(PyInterpreterState *interp) -{ - const char *name = "_interpreters.NotShareableError"; - PyObject *base = PyExc_ValueError; - PyObject *ns = NULL; - PyObject *exctype = PyErr_NewException(name, base, ns); - if (exctype == NULL) { - PyErr_Clear(); - return _PyStatus_ERR("could not initialize NotShareableError"); - } - - _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError = exctype; - return _PyStatus_OK(); -} - -static void -_fini_not_shareable_error_type(PyInterpreterState *interp) -{ - Py_CLEAR(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError); -} - -static PyObject * -_get_not_shareable_error_type(PyInterpreterState *interp) -{ - assert(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError != NULL); - return _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError; -} - - /* defining cross-interpreter data */ static inline void @@ -1673,17 +1642,9 @@ _PyXI_Exit(_PyXI_session *session) PyStatus _PyXI_Init(PyInterpreterState *interp) { - PyStatus status; - // Initialize the XID lookup state (e.g. registry). xid_lookup_init(interp); - // Initialize exceptions (heap types). - status = _init_not_shareable_error_type(interp); - if (_PyStatus_EXCEPTION(status)) { - return status; - } - return _PyStatus_OK(); } @@ -1693,9 +1654,6 @@ _PyXI_Init(PyInterpreterState *interp) void _PyXI_Fini(PyInterpreterState *interp) { - // Finalize exceptions (heap types). - _fini_not_shareable_error_type(interp); - // Finalize the XID lookup state (e.g. registry). xid_lookup_fini(interp); } diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h index 4209b7ac4f1a36..560a3b127f78f5 100644 --- a/Python/crossinterp_exceptions.h +++ b/Python/crossinterp_exceptions.h @@ -19,12 +19,43 @@ static PyTypeObject _PyExc_InterpreterNotFoundError = { }; PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError; +/* NotShareableError extends ValueError */ + +static int +_init_not_shareable_error_type(PyInterpreterState *interp) +{ + const char *name = "_interpreters.NotShareableError"; + PyObject *base = PyExc_ValueError; + PyObject *ns = NULL; + PyObject *exctype = PyErr_NewException(name, base, ns); + if (exctype == NULL) { + return -1; + } + + _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError = exctype; + return 0; +} + +static void +_fini_not_shareable_error_type(PyInterpreterState *interp) +{ + Py_CLEAR(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError); +} + +static PyObject * +_get_not_shareable_error_type(PyInterpreterState *interp) +{ + assert(_PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError != NULL); + return _PyInterpreterState_GetXIState(interp)->PyExc_NotShareableError; +} + /* lifecycle */ static int init_exceptions(PyInterpreterState *interp) { + // builtin static types _PyExc_InterpreterError.tp_base = (PyTypeObject *)PyExc_BaseException; if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterError) < 0) { return -1; @@ -32,12 +63,19 @@ init_exceptions(PyInterpreterState *interp) if (_PyStaticType_InitBuiltin(interp, &_PyExc_InterpreterNotFoundError) < 0) { return -1; } + + // heap types + if (_init_not_shareable_error_type(interp) < 0) { + return -1; + } + return 0; } static void fini_exceptions(PyInterpreterState *interp) { + _fini_not_shareable_error_type(interp); _PyStaticType_Dealloc(interp, &_PyExc_InterpreterNotFoundError); _PyStaticType_Dealloc(interp, &_PyExc_InterpreterError); } From f17b29baeebf9d34ee4c56a16d55b67e161921cd Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 8 Jan 2024 17:23:00 -0700 Subject: [PATCH 16/20] Work around a ref leak. --- Python/crossinterp.c | 10 ++++++++++ Python/crossinterp_exceptions.h | 7 +++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index b9b14fa854964e..58afd519865709 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -17,6 +17,8 @@ static int init_exceptions(PyInterpreterState *); static void fini_exceptions(PyInterpreterState *); +static int _init_not_shareable_error_type(PyInterpreterState *); +static void _fini_not_shareable_error_type(PyInterpreterState *); static PyObject * _get_not_shareable_error_type(PyInterpreterState *); #include "crossinterp_exceptions.h" @@ -1645,6 +1647,11 @@ _PyXI_Init(PyInterpreterState *interp) // Initialize the XID lookup state (e.g. registry). xid_lookup_init(interp); + // Initialize exceptions (heap types). + if (_init_not_shareable_error_type(interp) < 0) { + return _PyStatus_ERR("failed to initialize NotShareableError"); + } + return _PyStatus_OK(); } @@ -1654,6 +1661,9 @@ _PyXI_Init(PyInterpreterState *interp) void _PyXI_Fini(PyInterpreterState *interp) { + // Finalize exceptions (heap types). + _fini_not_shareable_error_type(interp); + // Finalize the XID lookup state (e.g. registry). xid_lookup_fini(interp); } diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h index 560a3b127f78f5..1c6d314b716571 100644 --- a/Python/crossinterp_exceptions.h +++ b/Python/crossinterp_exceptions.h @@ -65,9 +65,8 @@ init_exceptions(PyInterpreterState *interp) } // heap types - if (_init_not_shareable_error_type(interp) < 0) { - return -1; - } + // We would call _init_not_shareable_error_type() here too, + // but that leads to ref leaks return 0; } @@ -75,7 +74,7 @@ init_exceptions(PyInterpreterState *interp) static void fini_exceptions(PyInterpreterState *interp) { - _fini_not_shareable_error_type(interp); + // Likewise with _fini_not_shareable_error_type(). _PyStaticType_Dealloc(interp, &_PyExc_InterpreterNotFoundError); _PyStaticType_Dealloc(interp, &_PyExc_InterpreterError); } From 18ede77ed97a83f376c1e4c445f5952ebcb3f14a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 10 Jan 2024 10:25:15 -0700 Subject: [PATCH 17/20] Fix build dependencies. --- Makefile.pre.in | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Makefile.pre.in b/Makefile.pre.in index d3b18acad61ce5..4b9d9c171b9efb 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1671,6 +1671,14 @@ Modules/pwdmodule.o: $(srcdir)/Modules/pwdmodule.c $(srcdir)/Modules/posixmodule Modules/signalmodule.o: $(srcdir)/Modules/signalmodule.c $(srcdir)/Modules/posixmodule.h +Modules/_xxsubinterpretersmodule.o: $(srcdir)/Modules/_xxsubinterpretersmodule.c $(srcdir)/Modules/_interpreters_common.h + +Modules/_xxinterpqueuesmodule.o: $(srcdir)/Modules/_xxinterpqueuesmodule.c $(srcdir)/Modules/_interpreters_common.h + +Modules/_xxinterpchannelsmodule.o: $(srcdir)/Modules/_xxinterpchannelsmodule.c $(srcdir)/Modules/_interpreters_common.h + +Python/crossinterp.o: $(srcdir)/Python/crossinterp.c $(srcdir)/Python/crossinterp_data_lookup.h $(srcdir)/Python/crossinterp_exceptions.h + Python/dynload_shlib.o: $(srcdir)/Python/dynload_shlib.c Makefile $(CC) -c $(PY_CORE_CFLAGS) \ -DSOABI='"$(SOABI)"' \ From 842a990f69fbfc7e25d78b364a0fe0e95df1bbb5 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 22 Jan 2024 12:25:41 -0700 Subject: [PATCH 18/20] Fix the Exception names. --- Python/crossinterp_exceptions.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/crossinterp_exceptions.h b/Python/crossinterp_exceptions.h index 1c6d314b716571..e418cf91d4a7af 100644 --- a/Python/crossinterp_exceptions.h +++ b/Python/crossinterp_exceptions.h @@ -3,7 +3,7 @@ static PyTypeObject _PyExc_InterpreterError = { PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "InterpreterError", + .tp_name = "interpreters.InterpreterError", .tp_doc = PyDoc_STR("A cross-interpreter operation failed"), //.tp_base = (PyTypeObject *)PyExc_BaseException, }; @@ -13,7 +13,7 @@ PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError; static PyTypeObject _PyExc_InterpreterNotFoundError = { PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "InterpreterNotFoundError", + .tp_name = "interpreters.InterpreterNotFoundError", .tp_doc = PyDoc_STR("An interpreter was not found"), .tp_base = &_PyExc_InterpreterError, }; @@ -24,7 +24,7 @@ PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFou static int _init_not_shareable_error_type(PyInterpreterState *interp) { - const char *name = "_interpreters.NotShareableError"; + const char *name = "interpreters.NotShareableError"; PyObject *base = PyExc_ValueError; PyObject *ns = NULL; PyObject *exctype = PyErr_NewException(name, base, ns); From 8448f9de22b4ae3bea7d73be5d7495c2e07ff8a9 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 22 Jan 2024 15:09:30 -0700 Subject: [PATCH 19/20] Use PyInterpreterState_GetID(). --- Python/crossinterp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 58afd519865709..143b261f9a5396 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -172,7 +172,7 @@ _PyCrossInterpreterData_Clear(PyInterpreterState *interp, // This must be called in the owning interpreter. assert(interp == NULL || _PyCrossInterpreterData_INTERPID(data) == -1 - || _PyCrossInterpreterData_INTERPID(data) == interp->id); + || _PyCrossInterpreterData_INTERPID(data) == PyInterpreterState_GetID(interp)); _xidata_clear(data); } From 455460c421d336ee61e243a4083d8c53ff67f3e7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 13 Feb 2024 11:28:56 -0700 Subject: [PATCH 20/20] Update the C analyzer. --- Tools/c-analyzer/cpython/globals-to-fix.tsv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 1d9576d083d8dc..5c5016f7137164 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -292,10 +292,10 @@ Objects/exceptions.c - PyExc_UnicodeWarning - Objects/exceptions.c - PyExc_BytesWarning - Objects/exceptions.c - PyExc_ResourceWarning - Objects/exceptions.c - PyExc_EncodingWarning - -Python/crossinterp.c - _PyExc_InterpreterError - -Python/crossinterp.c - _PyExc_InterpreterNotFoundError - -Python/crossinterp.c - PyExc_InterpreterError - -Python/crossinterp.c - PyExc_InterpreterNotFoundError - +Python/crossinterp_exceptions.h - _PyExc_InterpreterError - +Python/crossinterp_exceptions.h - _PyExc_InterpreterNotFoundError - +Python/crossinterp_exceptions.h - PyExc_InterpreterError - +Python/crossinterp_exceptions.h - PyExc_InterpreterNotFoundError - ##----------------------- ## singletons