mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
gh-132775: Support Fallbacks in _PyObject_GetXIData() (gh-133482)
It now supports a "full" fallback to _PyFunction_GetXIData() and then `_PyPickle_GetXIData()`. There's also room for other fallback modes if that later makes sense.
This commit is contained in:
parent
0c5a8b0b55
commit
88f8102a8f
11 changed files with 581 additions and 139 deletions
|
@ -210,16 +210,16 @@ _Py_CallInInterpreterAndRawFree(PyInterpreterState *interp,
|
|||
/* cross-interpreter data */
|
||||
/**************************/
|
||||
|
||||
/* registry of {type -> xidatafunc} */
|
||||
/* registry of {type -> _PyXIData_getdata_t} */
|
||||
|
||||
/* For now we use a global registry of shareable classes. An
|
||||
alternative would be to add a tp_* slot for a class's
|
||||
xidatafunc. It would be simpler and more efficient. */
|
||||
/* For now we use a global registry of shareable classes.
|
||||
An alternative would be to add a tp_* slot for a class's
|
||||
_PyXIData_getdata_t. It would be simpler and more efficient. */
|
||||
|
||||
static void xid_lookup_init(_PyXIData_lookup_t *);
|
||||
static void xid_lookup_fini(_PyXIData_lookup_t *);
|
||||
struct _dlcontext;
|
||||
static xidatafunc lookup_getdata(struct _dlcontext *, PyObject *);
|
||||
static _PyXIData_getdata_t lookup_getdata(struct _dlcontext *, PyObject *);
|
||||
#include "crossinterp_data_lookup.h"
|
||||
|
||||
|
||||
|
@ -343,7 +343,7 @@ _set_xid_lookup_failure(PyThreadState *tstate, PyObject *obj, const char *msg,
|
|||
set_notshareableerror(tstate, cause, 0, msg);
|
||||
}
|
||||
else {
|
||||
msg = "%S does not support cross-interpreter data";
|
||||
msg = "%R does not support cross-interpreter data";
|
||||
format_notshareableerror(tstate, cause, 0, msg, obj);
|
||||
}
|
||||
}
|
||||
|
@ -356,8 +356,8 @@ _PyObject_CheckXIData(PyThreadState *tstate, PyObject *obj)
|
|||
if (get_lookup_context(tstate, &ctx) < 0) {
|
||||
return -1;
|
||||
}
|
||||
xidatafunc getdata = lookup_getdata(&ctx, obj);
|
||||
if (getdata == NULL) {
|
||||
_PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
|
||||
if (getdata.basic == NULL && getdata.fallback == NULL) {
|
||||
if (!_PyErr_Occurred(tstate)) {
|
||||
_set_xid_lookup_failure(tstate, obj, NULL, NULL);
|
||||
}
|
||||
|
@ -388,9 +388,9 @@ _check_xidata(PyThreadState *tstate, _PyXIData_t *xidata)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
_PyObject_GetXIData(PyThreadState *tstate,
|
||||
PyObject *obj, _PyXIData_t *xidata)
|
||||
static int
|
||||
_get_xidata(PyThreadState *tstate,
|
||||
PyObject *obj, xidata_fallback_t fallback, _PyXIData_t *xidata)
|
||||
{
|
||||
PyInterpreterState *interp = tstate->interp;
|
||||
|
||||
|
@ -398,6 +398,7 @@ _PyObject_GetXIData(PyThreadState *tstate,
|
|||
assert(xidata->obj == NULL);
|
||||
if (xidata->data != NULL || xidata->obj != NULL) {
|
||||
_PyErr_SetString(tstate, PyExc_ValueError, "xidata not cleared");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Call the "getdata" func for the object.
|
||||
|
@ -406,8 +407,8 @@ _PyObject_GetXIData(PyThreadState *tstate,
|
|||
return -1;
|
||||
}
|
||||
Py_INCREF(obj);
|
||||
xidatafunc getdata = lookup_getdata(&ctx, obj);
|
||||
if (getdata == NULL) {
|
||||
_PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
|
||||
if (getdata.basic == NULL && getdata.fallback == NULL) {
|
||||
if (PyErr_Occurred()) {
|
||||
Py_DECREF(obj);
|
||||
return -1;
|
||||
|
@ -419,7 +420,9 @@ _PyObject_GetXIData(PyThreadState *tstate,
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
int res = getdata(tstate, obj, xidata);
|
||||
int res = getdata.basic != NULL
|
||||
? getdata.basic(tstate, obj, xidata)
|
||||
: getdata.fallback(tstate, obj, fallback, xidata);
|
||||
Py_DECREF(obj);
|
||||
if (res != 0) {
|
||||
PyObject *cause = _PyErr_GetRaisedException(tstate);
|
||||
|
@ -439,6 +442,51 @@ _PyObject_GetXIData(PyThreadState *tstate,
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
_PyObject_GetXIDataNoFallback(PyThreadState *tstate,
|
||||
PyObject *obj, _PyXIData_t *xidata)
|
||||
{
|
||||
return _get_xidata(tstate, obj, _PyXIDATA_XIDATA_ONLY, xidata);
|
||||
}
|
||||
|
||||
int
|
||||
_PyObject_GetXIData(PyThreadState *tstate,
|
||||
PyObject *obj, xidata_fallback_t fallback,
|
||||
_PyXIData_t *xidata)
|
||||
{
|
||||
switch (fallback) {
|
||||
case _PyXIDATA_XIDATA_ONLY:
|
||||
return _get_xidata(tstate, obj, fallback, xidata);
|
||||
case _PyXIDATA_FULL_FALLBACK:
|
||||
if (_get_xidata(tstate, obj, fallback, xidata) == 0) {
|
||||
return 0;
|
||||
}
|
||||
PyObject *exc = _PyErr_GetRaisedException(tstate);
|
||||
if (PyFunction_Check(obj)) {
|
||||
if (_PyFunction_GetXIData(tstate, obj, xidata) == 0) {
|
||||
Py_DECREF(exc);
|
||||
return 0;
|
||||
}
|
||||
_PyErr_Clear(tstate);
|
||||
}
|
||||
// We could try _PyMarshal_GetXIData() but we won't for now.
|
||||
if (_PyPickle_GetXIData(tstate, obj, xidata) == 0) {
|
||||
Py_DECREF(exc);
|
||||
return 0;
|
||||
}
|
||||
// Raise the original exception.
|
||||
_PyErr_SetRaisedException(tstate, exc);
|
||||
return -1;
|
||||
default:
|
||||
#ifdef Py_DEBUG
|
||||
Py_FatalError("unsupported xidata fallback option");
|
||||
#endif
|
||||
_PyErr_SetString(tstate, PyExc_SystemError,
|
||||
"unsupported xidata fallback option");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* pickle C-API */
|
||||
|
||||
|
@ -1617,14 +1665,9 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
|
|||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
|
||||
assert(!PyErr_Occurred());
|
||||
assert(code != _PyXI_ERR_NO_ERROR);
|
||||
assert(code != _PyXI_ERR_UNCAUGHT_EXCEPTION);
|
||||
switch (code) {
|
||||
case _PyXI_ERR_NO_ERROR: _Py_FALLTHROUGH;
|
||||
case _PyXI_ERR_UNCAUGHT_EXCEPTION:
|
||||
// There is nothing to apply.
|
||||
#ifdef Py_DEBUG
|
||||
Py_UNREACHABLE();
|
||||
#endif
|
||||
return 0;
|
||||
case _PyXI_ERR_OTHER:
|
||||
// XXX msg?
|
||||
PyErr_SetNone(PyExc_InterpreterError);
|
||||
|
@ -1649,7 +1692,7 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
|
|||
break;
|
||||
default:
|
||||
#ifdef Py_DEBUG
|
||||
Py_UNREACHABLE();
|
||||
Py_FatalError("unsupported error code");
|
||||
#else
|
||||
PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
|
||||
#endif
|
||||
|
@ -1796,7 +1839,7 @@ _sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
|
|||
return -1;
|
||||
}
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
if (_PyObject_GetXIData(tstate, value, item->xidata) != 0) {
|
||||
if (_PyObject_GetXIDataNoFallback(tstate, value, item->xidata) != 0) {
|
||||
PyMem_RawFree(item->xidata);
|
||||
item->xidata = NULL;
|
||||
// The caller may want to propagate PyExc_NotShareableError
|
||||
|
|
|
@ -12,7 +12,8 @@ typedef _PyXIData_regitem_t dlregitem_t;
|
|||
// forward
|
||||
static void _xidregistry_init(dlregistry_t *);
|
||||
static void _xidregistry_fini(dlregistry_t *);
|
||||
static xidatafunc _lookup_getdata_from_registry(dlcontext_t *, PyObject *);
|
||||
static _PyXIData_getdata_t _lookup_getdata_from_registry(
|
||||
dlcontext_t *, PyObject *);
|
||||
|
||||
|
||||
/* used in crossinterp.c */
|
||||
|
@ -49,7 +50,7 @@ get_lookup_context(PyThreadState *tstate, dlcontext_t *res)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static xidatafunc
|
||||
static _PyXIData_getdata_t
|
||||
lookup_getdata(dlcontext_t *ctx, PyObject *obj)
|
||||
{
|
||||
/* Cross-interpreter objects are looked up by exact match on the class.
|
||||
|
@ -88,24 +89,24 @@ _PyXIData_FormatNotShareableError(PyThreadState *tstate,
|
|||
}
|
||||
|
||||
|
||||
xidatafunc
|
||||
_PyXIData_getdata_t
|
||||
_PyXIData_Lookup(PyThreadState *tstate, PyObject *obj)
|
||||
{
|
||||
dlcontext_t ctx;
|
||||
if (get_lookup_context(tstate, &ctx) < 0) {
|
||||
return NULL;
|
||||
return (_PyXIData_getdata_t){0};
|
||||
}
|
||||
return lookup_getdata(&ctx, obj);
|
||||
}
|
||||
|
||||
|
||||
/***********************************************/
|
||||
/* a registry of {type -> xidatafunc} */
|
||||
/* a registry of {type -> _PyXIData_getdata_t} */
|
||||
/***********************************************/
|
||||
|
||||
/* For now we use a global registry of shareable classes. An
|
||||
alternative would be to add a tp_* slot for a class's
|
||||
xidatafunc. It would be simpler and more efficient. */
|
||||
/* For now we use a global registry of shareable classes.
|
||||
An alternative would be to add a tp_* slot for a class's
|
||||
_PyXIData_getdata_t. It would be simpler and more efficient. */
|
||||
|
||||
|
||||
/* registry lifecycle */
|
||||
|
@ -200,7 +201,7 @@ _xidregistry_find_type(dlregistry_t *xidregistry, PyTypeObject *cls)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static xidatafunc
|
||||
static _PyXIData_getdata_t
|
||||
_lookup_getdata_from_registry(dlcontext_t *ctx, PyObject *obj)
|
||||
{
|
||||
PyTypeObject *cls = Py_TYPE(obj);
|
||||
|
@ -209,10 +210,12 @@ _lookup_getdata_from_registry(dlcontext_t *ctx, PyObject *obj)
|
|||
_xidregistry_lock(xidregistry);
|
||||
|
||||
dlregitem_t *matched = _xidregistry_find_type(xidregistry, cls);
|
||||
xidatafunc func = matched != NULL ? matched->getdata : NULL;
|
||||
_PyXIData_getdata_t getdata = matched != NULL
|
||||
? matched->getdata
|
||||
: (_PyXIData_getdata_t){0};
|
||||
|
||||
_xidregistry_unlock(xidregistry);
|
||||
return func;
|
||||
return getdata;
|
||||
}
|
||||
|
||||
|
||||
|
@ -220,12 +223,13 @@ _lookup_getdata_from_registry(dlcontext_t *ctx, PyObject *obj)
|
|||
|
||||
static int
|
||||
_xidregistry_add_type(dlregistry_t *xidregistry,
|
||||
PyTypeObject *cls, xidatafunc getdata)
|
||||
PyTypeObject *cls, _PyXIData_getdata_t getdata)
|
||||
{
|
||||
dlregitem_t *newhead = PyMem_RawMalloc(sizeof(dlregitem_t));
|
||||
if (newhead == NULL) {
|
||||
return -1;
|
||||
}
|
||||
assert((getdata.basic == NULL) != (getdata.fallback == NULL));
|
||||
*newhead = (dlregitem_t){
|
||||
// We do not keep a reference, to avoid keeping the class alive.
|
||||
.cls = cls,
|
||||
|
@ -283,13 +287,13 @@ _xidregistry_clear(dlregistry_t *xidregistry)
|
|||
|
||||
int
|
||||
_PyXIData_RegisterClass(PyThreadState *tstate,
|
||||
PyTypeObject *cls, xidatafunc getdata)
|
||||
PyTypeObject *cls, _PyXIData_getdata_t getdata)
|
||||
{
|
||||
if (!PyType_Check(cls)) {
|
||||
PyErr_Format(PyExc_ValueError, "only classes may be registered");
|
||||
return -1;
|
||||
}
|
||||
if (getdata == NULL) {
|
||||
if (getdata.basic == NULL && getdata.fallback == NULL) {
|
||||
PyErr_Format(PyExc_ValueError, "missing 'getdata' func");
|
||||
return -1;
|
||||
}
|
||||
|
@ -304,7 +308,8 @@ _PyXIData_RegisterClass(PyThreadState *tstate,
|
|||
|
||||
dlregitem_t *matched = _xidregistry_find_type(xidregistry, cls);
|
||||
if (matched != NULL) {
|
||||
assert(matched->getdata == getdata);
|
||||
assert(matched->getdata.basic == getdata.basic);
|
||||
assert(matched->getdata.fallback == getdata.fallback);
|
||||
matched->refcount += 1;
|
||||
goto finally;
|
||||
}
|
||||
|
@ -608,7 +613,8 @@ _tuple_shared_free(void* data)
|
|||
}
|
||||
|
||||
static int
|
||||
_tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
|
||||
_tuple_shared(PyThreadState *tstate, PyObject *obj, xidata_fallback_t fallback,
|
||||
_PyXIData_t *xidata)
|
||||
{
|
||||
Py_ssize_t len = PyTuple_GET_SIZE(obj);
|
||||
if (len < 0) {
|
||||
|
@ -636,7 +642,7 @@ _tuple_shared(PyThreadState *tstate, PyObject *obj, _PyXIData_t *xidata)
|
|||
|
||||
int res = -1;
|
||||
if (!_Py_EnterRecursiveCallTstate(tstate, " while sharing a tuple")) {
|
||||
res = _PyObject_GetXIData(tstate, item, xidata_i);
|
||||
res = _PyObject_GetXIData(tstate, item, fallback, xidata_i);
|
||||
_Py_LeaveRecursiveCallTstate(tstate);
|
||||
}
|
||||
if (res < 0) {
|
||||
|
@ -737,40 +743,48 @@ _PyFunction_GetXIData(PyThreadState *tstate, PyObject *func,
|
|||
static void
|
||||
_register_builtins_for_crossinterpreter_data(dlregistry_t *xidregistry)
|
||||
{
|
||||
#define REGISTER(TYPE, GETDATA) \
|
||||
_xidregistry_add_type(xidregistry, (PyTypeObject *)TYPE, \
|
||||
((_PyXIData_getdata_t){.basic=(GETDATA)}))
|
||||
#define REGISTER_FALLBACK(TYPE, GETDATA) \
|
||||
_xidregistry_add_type(xidregistry, (PyTypeObject *)TYPE, \
|
||||
((_PyXIData_getdata_t){.fallback=(GETDATA)}))
|
||||
// None
|
||||
if (_xidregistry_add_type(xidregistry, (PyTypeObject *)PyObject_Type(Py_None), _none_shared) != 0) {
|
||||
if (REGISTER(Py_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) {
|
||||
if (REGISTER(&PyLong_Type, _long_shared) != 0) {
|
||||
Py_FatalError("could not register int for cross-interpreter sharing");
|
||||
}
|
||||
|
||||
// bytes
|
||||
if (_xidregistry_add_type(xidregistry, &PyBytes_Type, _PyBytes_GetXIData) != 0) {
|
||||
if (REGISTER(&PyBytes_Type, _PyBytes_GetXIData) != 0) {
|
||||
Py_FatalError("could not register bytes for cross-interpreter sharing");
|
||||
}
|
||||
|
||||
// str
|
||||
if (_xidregistry_add_type(xidregistry, &PyUnicode_Type, _str_shared) != 0) {
|
||||
if (REGISTER(&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) {
|
||||
if (REGISTER(&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) {
|
||||
if (REGISTER(&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) {
|
||||
if (REGISTER_FALLBACK(&PyTuple_Type, _tuple_shared) != 0) {
|
||||
Py_FatalError("could not register tuple for cross-interpreter sharing");
|
||||
}
|
||||
|
||||
// For now, we do not register PyCode_Type or PyFunction_Type.
|
||||
#undef REGISTER
|
||||
#undef REGISTER_FALLBACK
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue