From 76abd99e2c6cfbb18f96e3caf9c4eeeeb850d239 Mon Sep 17 00:00:00 2001 From: jrfk Date: Tue, 7 Apr 2026 23:52:02 +0900 Subject: [PATCH] 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. --- ...-04-07-00-00-00.gh-issue-148223.Rk4xPe.rst | 4 ++ Python/crossinterp.c | 43 ++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-00-00-00.gh-issue-148223.Rk4xPe.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-00-00-00.gh-issue-148223.Rk4xPe.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-00-00-00.gh-issue-148223.Rk4xPe.rst new file mode 100644 index 00000000000000..c9308cf9219d13 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-07-00-00-00.gh-issue-148223.Rk4xPe.rst @@ -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``. diff --git a/Python/crossinterp.c b/Python/crossinterp.c index 4cd4b32ef906bb..b2aabaa5df7508 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -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");