Skip to main content

Include/internal/pycore_crossinterp.h

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_crossinterp.h

The central internal header for everything that crosses an interpreter boundary inside a single CPython process. The file defines _PyXIData_t (the typed data capsule that carries a shareable representation of a Python object), the session API (_PyXI_session) used to enter and exit a foreign interpreter safely, the error-propagation machinery (_PyXI_excinfo, _PyXI_failure), and the per-interpreter state structs (_PyXI_state_t, _PyXI_global_state_t). It also includes pycore_crossinterp_data_registry.h to pull in the type-registration tables.

The PEP 734 sub-interpreter model requires that objects sent between interpreters are either immortal, type objects, or wrapped as cross-interp data via _PyObject_GetXIData. All other objects violate interpreter isolation and must not be shared directly. This header is the implementation contract for that requirement.

gopy does not yet implement sub-interpreters or isolated PyInterpreterState instances, so none of this file has been ported. It is documented here to track what will be needed when multi-interpreter support lands.

Map

LinesSymbolRolegopy
18-19PyExc_InterpreterError, PyExc_InterpreterNotFoundErrorPublic exception singletonsnot yet ported
26-34_Py_simple_func, _Py_CallInInterpreter, _Py_CallInInterpreterAndRawFreeSchedule a C callback in another interpreternot yet ported
40-85_PyXIData_t (struct _xidata)Typed cross-interpreter data capsulenot yet ported
87-88_PyXIData_New, _PyXIData_FreeHeap-allocate / free a capsulenot yet ported
90-93_PyXIData_DATA, _PyXIData_OBJ, _PyXIData_INTERPIDField accessor macrosnot yet ported
98-129_PyXIData_Init, _PyXIData_InitWithSize, _PyXIData_Clear, setter macrosInitialise a capsule from an objectnot yet ported
134-150xidata_fallback_t, xidatafunc, xidatafbfunc, _PyXIData_getdata_tFunction-pointer types for per-class convertersnot yet ported
152-174_PyXIData_Lookup, _PyObject_CheckXIData, _PyObject_GetXIData[NoFallback]Look up and invoke a type's converternot yet ported
176-230_PyBytes_*, _PyPickle_*, _PyMarshal_*, _PyCode_*, _PyFunction_* XIData helpersBuilt-in per-type serialisation pathsnot yet ported
234-236_PyXIData_NewObject, _PyXIData_Release, _PyXIData_ReleaseAndRawFreeDeserialise or discard a capsulenot yet ported
241-243#include "pycore_crossinterp_data_registry.h"Type-registry structs and functionsnot yet ported
252-268_PyXI_global_state_t, _PyXI_state_tPer-runtime and per-interpreter XI statenot yet ported
270-271_PyXI_GET_GLOBAL_STATE, _PyXI_GET_STATEAccessor macros for the state structsnot yet ported
274-283_PyXI_Init, _PyXI_Fini, _PyXI_InitTypes, _PyXI_FiniTypes, global variantsLifecycle functionsnot yet ported
295-309_PyXI_excinfoCross-interpreter exception snapshotnot yet ported
306-309_PyXI_NewExcInfo, _PyXI_FreeExcInfo, _PyXI_FormatExcInfo, _PyXI_ExcInfoAsObjectWork with an exception snapshotnot yet ported
312-323_PyXI_errcode enumError classification for a sessionnot yet ported
325-338_PyXI_failure, _PyXI_NewFailure, _PyXI_FreeFailure, _PyXI_InitFailure*, _PyXI_GetFailureCodeCarry an error out of a sessionnot yet ported
336-338_PyXI_UnwrapNotShareableErrorConvert NotShareableError back to a Python exceptionnot yet ported
351-384_PyXI_session, _PyXI_session_result, _PyXI_Enter, _PyXI_Exit, _PyXI_GetMainNamespace, _PyXI_Preserve, _PyXI_GetPreservedFull session lifecyclenot yet ported
392-400_PyXI_NewInterpreter, _PyXI_EndInterpreterCreate / destroy an interpreter for testsnot yet ported

Reading

Cross-interpreter data capsule: _PyXIData_t (lines 40 to 93)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_crossinterp.h#L40-93

_PyXIData_t is the unit of exchange. It stores a raw void *data pointer, an optional back-reference to the originating PyObject, the originating interpreter's ID (used instead of a pointer to survive interpreter deletion), and two function pointers: new_object to reconstruct a Python object in the receiving interpreter, and free to release the raw data when done.

struct _xidata {
void *data; // serialised representation
PyObject *obj; // source object (owned ref) if data is bound to it
int64_t interpid; // ID of the originating interpreter
xid_newobjfunc new_object; // reconstruct on the far side
xid_freefunc free; // release data; defaults to PyMem_RawFree
};

Using an interpreter ID rather than a PyInterpreterState * pointer is deliberate: if the originating interpreter is deleted before the capsule is consumed, the ID remains valid as a tombstone. IDs are never reused, so there is no risk of aliasing a new interpreter.

Session API: _PyXI_Enter and _PyXI_Exit (lines 341 to 384)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_crossinterp.h#L341-384

A session is the safe bracket around any cross-interpreter call. _PyXI_Enter switches the current thread to the target interpreter and optionally populates its __main__ namespace with values from nsupdates. _PyXI_Exit restores the original interpreter, captures any uncaught exception into a _PyXI_failure, and returns the result struct to the caller.

// Enter target interpreter; inject nsupdates into __main__ if non-NULL.
PyAPI_FUNC(int) _PyXI_Enter(
_PyXI_session *session,
PyInterpreterState *interp,
PyObject *nsupdates,
_PyXI_session_result *);

// Exit and capture any failure; fill result->preserved if _PyXI_Preserve was called.
PyAPI_FUNC(int) _PyXI_Exit(
_PyXI_session *,
_PyXI_failure *,
_PyXI_session_result *);

The _PyXI_Preserve / _PyXI_GetPreserved pair lets the caller shuttle one named value back across the boundary, serialised as a _PyXIData_t capsule on exit and deserialised on return.

Error propagation: _PyXI_errcode and _PyXI_failure (lines 312 to 338)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_crossinterp.h#L312-338

Nine error codes cover every failure mode a session can encounter, from a plain uncaught exception (_PyXI_ERR_UNCAUGHT_EXCEPTION) to namespace injection failures and serialisation failures. The _PyXI_failure struct carries one code plus an optional _PyXI_excinfo snapshot so the originating interpreter can reconstruct or display the error without needing access to the target's live exception state.

typedef enum error_code {
_PyXI_ERR_NO_ERROR = 0,
_PyXI_ERR_UNCAUGHT_EXCEPTION = -1,
_PyXI_ERR_OTHER = -2,
_PyXI_ERR_NO_MEMORY = -3,
_PyXI_ERR_ALREADY_RUNNING = -4,
_PyXI_ERR_MAIN_NS_FAILURE = -5,
_PyXI_ERR_APPLY_NS_FAILURE = -6,
_PyXI_ERR_PRESERVE_FAILURE = -7,
_PyXI_ERR_EXC_PROPAGATION_FAILURE = -8,
_PyXI_ERR_NOT_SHAREABLE = -9,
} _PyXI_errcode;

gopy mirror

gopy does not yet implement PyInterpreterState isolation or the _interpreters / _channels built-in modules that exercise this API. When multi-interpreter support is added, the natural mapping will be: _PyXIData_t becomes a Go struct with an any data field and function values for NewObject/Free, and sessions map to a pair of goroutine-safe calls that switch the active interpreter context. The registry in pycore_crossinterp_data_registry.h maps to a sync.Map keyed by *Type.

CPython 3.14 changes

3.14 is a significant reorganisation of the cross-interpreter subsystem relative to 3.12. _PyCrossInterpreterData was renamed to _PyXIData_t and the entire public naming convention shifted from _PyCrossInterpreterData_* to _PyXIData_*. The session struct (_PyXI_session) gained _PyXI_Preserve / _PyXI_GetPreserved for round-tripping a single value back to the caller. The _PyXI_failure type replaced ad-hoc int error returns. PEP 734 (interpreters.create()) targets 3.14 as its first stable release, making this header the implementation backbone of the new feature.