gh-148223: Avoid unnecessary NotShareableError in XIData FULL_FALLBACK

Skip _get_xidata() for types not in the XIData registry when using
the FULL_FALLBACK path in _PyObject_GetXIData().  This avoids creating
and discarding a NotShareableError exception on every successful pickle
fallback transfer.

Registered types (None, bool, int, float, str, bytes, tuple) still
follow the existing path unchanged.  On total failure, the same
NotShareableError is raised as before.
This commit is contained in:
jrfk 2026-04-07 23:52:02 +09:00
parent a4d9d6483f
commit 76abd99e2c
2 changed files with 40 additions and 7 deletions

View file

@ -0,0 +1,4 @@
Avoid unnecessary ``NotShareableError`` creation in
``_PyObject_GetXIData()`` when falling back to pickle for types not in the
XIData registry. This speeds up cross-interpreter transfers of mutable
types such as ``list`` and ``dict``.

View file

@ -536,25 +536,54 @@ _PyObject_GetXIData(PyThreadState *tstate,
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;
{
// Check the type registry first to avoid unnecessary exception
// creation when falling back to pickle for unregistered types.
dlcontext_t ctx;
if (get_lookup_context(tstate, &ctx) < 0) {
return -1;
}
PyObject *exc = _PyErr_GetRaisedException(tstate);
_PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
if (getdata.basic != NULL || getdata.fallback != NULL) {
// Type is in the registry. Use the normal path which may
// still fail (e.g. a tuple with non-shareable elements).
if (_get_xidata(tstate, obj, fallback, xidata) == 0) {
return 0;
}
// Save the exception to restore if all fallbacks fail.
PyObject *exc = _PyErr_GetRaisedException(tstate);
if (PyFunction_Check(obj)) {
if (_PyFunction_GetXIData(tstate, obj, xidata) == 0) {
Py_DECREF(exc);
return 0;
}
_PyErr_Clear(tstate);
}
if (_PyPickle_GetXIData(tstate, obj, xidata) == 0) {
Py_DECREF(exc);
return 0;
}
_PyErr_SetRaisedException(tstate, exc);
return -1;
}
// Type is NOT in the registry. Skip _get_xidata() entirely
// to avoid creating and discarding a NotShareableError.
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);
// All fallbacks failed. Raise the same NotShareableError
// as the non-optimized path would.
_PyErr_Clear(tstate);
_set_xid_lookup_failure(tstate, obj, NULL, NULL);
return -1;
}
default:
#ifdef Py_DEBUG
Py_FatalError("unsupported xidata fallback option");