Skip to content

Commit 76abd99

Browse files
committed
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.
1 parent a4d9d64 commit 76abd99

File tree

2 files changed

+40
-7
lines changed

2 files changed

+40
-7
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Avoid unnecessary ``NotShareableError`` creation in
2+
``_PyObject_GetXIData()`` when falling back to pickle for types not in the
3+
XIData registry. This speeds up cross-interpreter transfers of mutable
4+
types such as ``list`` and ``dict``.

Python/crossinterp.c

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -536,25 +536,54 @@ _PyObject_GetXIData(PyThreadState *tstate,
536536
case _PyXIDATA_XIDATA_ONLY:
537537
return _get_xidata(tstate, obj, fallback, xidata);
538538
case _PyXIDATA_FULL_FALLBACK:
539-
if (_get_xidata(tstate, obj, fallback, xidata) == 0) {
540-
return 0;
539+
{
540+
// Check the type registry first to avoid unnecessary exception
541+
// creation when falling back to pickle for unregistered types.
542+
dlcontext_t ctx;
543+
if (get_lookup_context(tstate, &ctx) < 0) {
544+
return -1;
541545
}
542-
PyObject *exc = _PyErr_GetRaisedException(tstate);
546+
_PyXIData_getdata_t getdata = lookup_getdata(&ctx, obj);
547+
if (getdata.basic != NULL || getdata.fallback != NULL) {
548+
// Type is in the registry. Use the normal path which may
549+
// still fail (e.g. a tuple with non-shareable elements).
550+
if (_get_xidata(tstate, obj, fallback, xidata) == 0) {
551+
return 0;
552+
}
553+
// Save the exception to restore if all fallbacks fail.
554+
PyObject *exc = _PyErr_GetRaisedException(tstate);
555+
if (PyFunction_Check(obj)) {
556+
if (_PyFunction_GetXIData(tstate, obj, xidata) == 0) {
557+
Py_DECREF(exc);
558+
return 0;
559+
}
560+
_PyErr_Clear(tstate);
561+
}
562+
if (_PyPickle_GetXIData(tstate, obj, xidata) == 0) {
563+
Py_DECREF(exc);
564+
return 0;
565+
}
566+
_PyErr_SetRaisedException(tstate, exc);
567+
return -1;
568+
}
569+
// Type is NOT in the registry. Skip _get_xidata() entirely
570+
// to avoid creating and discarding a NotShareableError.
543571
if (PyFunction_Check(obj)) {
544572
if (_PyFunction_GetXIData(tstate, obj, xidata) == 0) {
545-
Py_DECREF(exc);
546573
return 0;
547574
}
548575
_PyErr_Clear(tstate);
549576
}
550577
// We could try _PyMarshal_GetXIData() but we won't for now.
551578
if (_PyPickle_GetXIData(tstate, obj, xidata) == 0) {
552-
Py_DECREF(exc);
553579
return 0;
554580
}
555-
// Raise the original exception.
556-
_PyErr_SetRaisedException(tstate, exc);
581+
// All fallbacks failed. Raise the same NotShareableError
582+
// as the non-optimized path would.
583+
_PyErr_Clear(tstate);
584+
_set_xid_lookup_failure(tstate, obj, NULL, NULL);
557585
return -1;
586+
}
558587
default:
559588
#ifdef Py_DEBUG
560589
Py_FatalError("unsupported xidata fallback option");

0 commit comments

Comments
 (0)