diff --git a/Doc/data/python3.14.abi b/Doc/data/python3.14.abi
index decb2ce3c6f39e..64f4d473b72def 100644
--- a/Doc/data/python3.14.abi
+++ b/Doc/data/python3.14.abi
@@ -1195,11 +1195,13 @@
+
+
@@ -1510,19 +1512,18 @@
-
-
-
+
+
-
+
@@ -4246,17 +4247,17 @@
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -7099,7 +7100,7 @@
-
+
@@ -7349,7 +7350,7 @@
-
+
@@ -26915,73 +26916,59 @@
-
-
-
+
+
-
+
+
+
+
-
+
-
-
-
-
+
-
+
+
+
+
-
+
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -27194,75 +27181,66 @@
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -27298,11 +27276,11 @@
-
+
-
+
@@ -27321,7 +27299,7 @@
-
+
@@ -27478,15 +27456,15 @@
-
+
-
+
-
+
@@ -27494,11 +27472,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -27542,7 +27520,7 @@
-
+
@@ -27613,7 +27591,7 @@
-
+
@@ -27687,9 +27665,9 @@
-
+
-
+
@@ -27729,17 +27707,17 @@
-
+
-
+
-
-
-
-
+
+
+
+
@@ -27760,7 +27738,7 @@
-
+
@@ -27774,7 +27752,7 @@
-
+
@@ -27782,15 +27760,15 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -27850,7 +27828,7 @@
-
+
@@ -27885,15 +27863,15 @@
-
+
-
+
-
+
@@ -27983,8 +27961,8 @@
-
-
+
+
@@ -27996,8 +27974,8 @@
-
-
+
+
@@ -28018,7 +27996,7 @@
-
+
@@ -28031,7 +28009,7 @@
-
+
@@ -28040,22 +28018,22 @@
-
+
-
-
+
+
-
-
+
+
@@ -28072,7 +28050,7 @@
-
+
@@ -28081,7 +28059,7 @@
-
+
@@ -28089,24 +28067,24 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -28116,9 +28094,9 @@
-
-
-
+
+
+
@@ -28126,48 +28104,48 @@
-
+
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
@@ -28198,7 +28176,7 @@
-
+
@@ -28335,7 +28313,7 @@
-
+
@@ -28347,11 +28325,11 @@
-
+
-
-
+
+
@@ -28371,8 +28349,8 @@
-
-
+
+
@@ -28392,27 +28370,27 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
+
@@ -28480,13 +28458,13 @@
-
+
-
+
@@ -28511,21 +28489,21 @@
-
+
-
+
-
+
-
+
@@ -28534,7 +28512,7 @@
-
+
@@ -28548,7 +28526,7 @@
-
+
@@ -28557,7 +28535,7 @@
-
+
@@ -28625,12 +28603,12 @@
-
+
-
+
@@ -28647,19 +28625,19 @@
-
+
-
+
-
+
@@ -28694,12 +28672,12 @@
-
+
-
+
@@ -28712,44 +28690,44 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -28758,32 +28736,32 @@
-
+
-
+
-
+
-
+
-
+
@@ -28808,7 +28786,7 @@
-
+
@@ -28816,12 +28794,12 @@
-
-
+
+
-
+
@@ -28831,33 +28809,33 @@
-
+
-
+
-
+
-
+
-
+
@@ -28865,85 +28843,85 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -28952,62 +28930,62 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
+
+
+
+
+
@@ -29019,8 +28997,8 @@
-
-
+
+
@@ -29066,12 +29044,12 @@
-
+
-
+
@@ -29079,8 +29057,8 @@
-
-
+
+
@@ -29089,53 +29067,53 @@
-
+
-
+
-
+
-
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -29151,7 +29129,7 @@
-
+
@@ -29160,21 +29138,21 @@
-
+
-
+
-
+
-
+
@@ -29190,65 +29168,65 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -29256,18 +29234,18 @@
-
+
-
+
-
+
-
+
@@ -29288,10 +29266,10 @@
-
+
-
+
@@ -29303,16 +29281,16 @@
-
+
-
+
-
-
-
-
+
+
+
+
@@ -29414,21 +29392,21 @@
-
+
-
-
-
-
+
+
+
+
-
+
-
+
@@ -29437,27 +29415,27 @@
-
+
-
+
-
+
-
+
-
+
@@ -29523,16 +29501,16 @@
-
-
+
+
-
+
-
+
@@ -29546,7 +29524,7 @@
-
+
@@ -29558,23 +29536,23 @@
-
-
+
+
-
+
-
+
-
+
@@ -29592,11 +29570,11 @@
-
+
-
+
@@ -29606,36 +29584,36 @@
-
+
-
+
-
+
-
+
-
-
-
-
+
+
+
+
-
+
-
+
-
+
-
+
@@ -29647,25 +29625,25 @@
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -29673,7 +29651,7 @@
-
+
@@ -29681,38 +29659,38 @@
-
+
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
@@ -29723,17 +29701,17 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -29903,13 +29881,13 @@
-
+
-
-
+
+
@@ -29941,7 +29919,7 @@
-
+
@@ -30289,7 +30267,7 @@
-
+
@@ -30298,8 +30276,8 @@
-
-
+
+
@@ -30364,19 +30342,19 @@
-
+
-
-
+
+
-
-
-
-
+
+
+
+
@@ -30396,7 +30374,7 @@
-
+
@@ -30458,7 +30436,7 @@
-
+
@@ -30469,7 +30447,7 @@
-
+
@@ -30482,24 +30460,24 @@
-
+
-
+
-
+
-
+
-
+
@@ -30807,7 +30785,7 @@
-
+
@@ -30821,7 +30799,7 @@
-
+
@@ -30858,22 +30836,22 @@
-
-
-
-
+
+
+
+
-
+
-
-
+
+
-
-
+
+
@@ -31004,7 +30982,7 @@
-
+
@@ -31013,8 +30991,8 @@
-
-
+
+
@@ -31035,7 +31013,7 @@
-
+
@@ -31130,10 +31108,10 @@
-
-
-
-
+
+
+
+
@@ -31163,7 +31141,7 @@
-
+
@@ -31184,8 +31162,8 @@
-
-
+
+
@@ -31207,26 +31185,26 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
+
-
+
@@ -31259,7 +31237,7 @@
-
+
@@ -31280,7 +31258,7 @@
-
+
@@ -31310,7 +31288,7 @@
-
+
@@ -31319,17 +31297,17 @@
-
+
-
+
-
-
+
+
@@ -31338,11 +31316,11 @@
-
+
-
+
@@ -31377,13 +31355,13 @@
-
+
-
+
@@ -31397,8 +31375,8 @@
-
-
+
+
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 45fa47d62c78a3..1272927413868b 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -335,24 +335,9 @@ typedef struct _sharedexception {
PyAPI_FUNC(PyObject *) _PyXI_ApplyError(_PyXI_error *err);
-typedef struct xi_session _PyXI_session;
-typedef struct _sharedns _PyXI_namespace;
-
-PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
-PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
-PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
- _PyXI_namespace *ns,
- PyObject *nsobj,
- _PyXI_session *session);
-PyAPI_FUNC(int) _PyXI_ApplyNamespace(
- _PyXI_namespace *ns,
- PyObject *nsobj,
- PyObject *dflt);
-
-
// A cross-interpreter session involves entering an interpreter
-// (_PyXI_Enter()), doing some work with it, and finally exiting
-// that interpreter (_PyXI_Exit()).
+// with _PyXI_Enter(), doing some work with it, and finally exiting
+// that interpreter with _PyXI_Exit().
//
// At the boundaries of the session, both entering and exiting,
// data may be exchanged between the previous interpreter and the
@@ -360,39 +345,10 @@ PyAPI_FUNC(int) _PyXI_ApplyNamespace(
// isolation between interpreters. This includes setting objects
// in the target's __main__ module on the way in, and capturing
// uncaught exceptions on the way out.
-struct xi_session {
- // Once a session has been entered, this is the tstate that was
- // current before the session. If it is different from cur_tstate
- // then we must have switched interpreters. Either way, this will
- // be the current tstate once we exit the session.
- PyThreadState *prev_tstate;
- // Once a session has been entered, this is the current tstate.
- // It must be current when the session exits.
- PyThreadState *init_tstate;
- // This is true if init_tstate needs cleanup during exit.
- int own_init_tstate;
-
- // This is true if, while entering the session, init_thread took
- // "ownership" of the interpreter's __main__ module. This means
- // it is the only thread that is allowed to run code there.
- // (Caveat: for now, users may still run exec() against the
- // __main__ module's dict, though that isn't advisable.)
- int running;
- // This is a cached reference to the __dict__ of the entered
- // interpreter's __main__ module. It is looked up when at the
- // beginning of the session as a convenience.
- PyObject *main_ns;
-
- // This is set if the interpreter is entered and raised an exception
- // that needs to be handled in some special way during exit.
- _PyXI_errcode *error_override;
- // This is set if exit captured an exception to propagate.
- _PyXI_error *error;
-
- // -- pre-allocated memory --
- _PyXI_error _error;
- _PyXI_errcode _error_override;
-};
+typedef struct xi_session _PyXI_session;
+
+PyAPI_FUNC(_PyXI_session *) _PyXI_NewSession(void);
+PyAPI_FUNC(void) _PyXI_FreeSession(_PyXI_session *);
PyAPI_FUNC(int) _PyXI_Enter(
_PyXI_session *session,
@@ -400,6 +356,8 @@ PyAPI_FUNC(int) _PyXI_Enter(
PyObject *nsupdates);
PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
+PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(_PyXI_session *);
+
PyAPI_FUNC(PyObject *) _PyXI_ApplyCapturedException(_PyXI_session *session);
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
diff --git a/Modules/_interpretersmodule.c b/Modules/_interpretersmodule.c
index 7f84b38a70e127..376517ab92360f 100644
--- a/Modules/_interpretersmodule.c
+++ b/Modules/_interpretersmodule.c
@@ -444,42 +444,54 @@ _exec_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp,
PyObject **p_excinfo)
{
assert(!_PyErr_Occurred(tstate));
- _PyXI_session session = {0};
+ _PyXI_session *session = _PyXI_NewSession();
+ if (session == NULL) {
+ return -1;
+ }
// Prep and switch interpreters.
- if (_PyXI_Enter(&session, interp, shareables) < 0) {
+ if (_PyXI_Enter(session, interp, shareables) < 0) {
if (_PyErr_Occurred(tstate)) {
// If an error occured at this step, it means that interp
// was not prepared and switched.
+ _PyXI_FreeSession(session);
return -1;
}
// Now, apply the error from another interpreter:
- PyObject *excinfo = _PyXI_ApplyError(session.error);
+ PyObject *excinfo = _PyXI_ApplyCapturedException(session);
if (excinfo != NULL) {
*p_excinfo = excinfo;
}
assert(PyErr_Occurred());
+ _PyXI_FreeSession(session);
return -1;
}
// Run the script.
- int res = _run_script(script, session.main_ns);
+ int res = -1;
+ PyObject *mainns = _PyXI_GetMainNamespace(session);
+ if (mainns == NULL) {
+ goto finally;
+ }
+ res = _run_script(script, mainns);
+finally:
// Clean up and switch back.
- _PyXI_Exit(&session);
+ _PyXI_Exit(session);
// Propagate any exception out to the caller.
assert(!PyErr_Occurred());
if (res < 0) {
- PyObject *excinfo = _PyXI_ApplyCapturedException(&session);
+ PyObject *excinfo = _PyXI_ApplyCapturedException(session);
if (excinfo != NULL) {
*p_excinfo = excinfo;
}
}
else {
- assert(!_PyXI_HasCapturedException(&session));
+ assert(!_PyXI_HasCapturedException(session));
}
+ _PyXI_FreeSession(session);
return res;
}
@@ -824,22 +836,27 @@ interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
}
}
- _PyXI_session session = {0};
+ _PyXI_session *session = _PyXI_NewSession();
+ if (session == NULL) {
+ return NULL;
+ }
// Prep and switch interpreters, including apply the updates.
- if (_PyXI_Enter(&session, interp, updates) < 0) {
+ if (_PyXI_Enter(session, interp, updates) < 0) {
if (!PyErr_Occurred()) {
- _PyXI_ApplyCapturedException(&session);
+ _PyXI_ApplyCapturedException(session);
assert(PyErr_Occurred());
}
else {
- assert(!_PyXI_HasCapturedException(&session));
+ assert(!_PyXI_HasCapturedException(session));
}
+ _PyXI_FreeSession(session);
return NULL;
}
// Clean up and switch back.
- _PyXI_Exit(&session);
+ _PyXI_Exit(session);
+ _PyXI_FreeSession(session);
Py_RETURN_NONE;
}
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 6681b969183925..65ccab32daf730 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -1914,156 +1914,212 @@ _sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
return res;
}
-struct _sharedns {
- Py_ssize_t len;
- _PyXI_namespace_item *items;
-};
-static _PyXI_namespace *
-_sharedns_new(void)
-{
- _PyXI_namespace *ns = PyMem_RawCalloc(sizeof(_PyXI_namespace), 1);
- if (ns == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- *ns = (_PyXI_namespace){ 0 };
- return ns;
-}
+typedef struct {
+ Py_ssize_t maxitems;
+ Py_ssize_t numnames;
+ Py_ssize_t numvalues;
+ _PyXI_namespace_item items[1];
+} _PyXI_namespace;
+#ifndef NDEBUG
static int
-_sharedns_is_initialized(_PyXI_namespace *ns)
+_sharedns_check_counts(_PyXI_namespace *ns)
{
- if (ns->len == 0) {
- assert(ns->items == NULL);
+ if (ns->maxitems <= 0) {
+ return 0;
+ }
+ if (ns->numnames < 0) {
+ return 0;
+ }
+ if (ns->numnames > ns->maxitems) {
+ return 0;
+ }
+ if (ns->numvalues < 0) {
+ return 0;
+ }
+ if (ns->numvalues > ns->numnames) {
return 0;
}
-
- assert(ns->len > 0);
- assert(ns->items != NULL);
- assert(_sharednsitem_is_initialized(&ns->items[0]));
- assert(ns->len == 1
- || _sharednsitem_is_initialized(&ns->items[ns->len - 1]));
return 1;
}
-#define HAS_COMPLETE_DATA 1
-#define HAS_PARTIAL_DATA 2
-
static int
-_sharedns_has_xidata(_PyXI_namespace *ns, int64_t *p_interpid)
+_sharedns_check_consistency(_PyXI_namespace *ns)
{
- // We expect _PyXI_namespace to always be initialized.
- assert(_sharedns_is_initialized(ns));
- int res = 0;
- _PyXI_namespace_item *item0 = &ns->items[0];
- if (!_sharednsitem_is_initialized(item0)) {
+ if (!_sharedns_check_counts(ns)) {
return 0;
}
- int64_t interpid0 = -1;
- if (!_sharednsitem_has_value(item0, &interpid0)) {
- return 0;
+
+ Py_ssize_t i = 0;
+ _PyXI_namespace_item *item;
+ if (ns->numvalues > 0) {
+ item = &ns->items[0];
+ if (!_sharednsitem_is_initialized(item)) {
+ return 0;
+ }
+ int64_t interpid0 = -1;
+ if (!_sharednsitem_has_value(item, &interpid0)) {
+ return 0;
+ }
+ i += 1;
+ for (; i < ns->numvalues; i++) {
+ item = &ns->items[i];
+ if (!_sharednsitem_is_initialized(item)) {
+ return 0;
+ }
+ int64_t interpid = -1;
+ if (!_sharednsitem_has_value(item, &interpid)) {
+ return 0;
+ }
+ if (interpid != interpid0) {
+ return 0;
+ }
+ }
}
- if (ns->len > 1) {
- // At this point we know it is has at least partial data.
- _PyXI_namespace_item *itemN = &ns->items[ns->len-1];
- if (!_sharednsitem_is_initialized(itemN)) {
- res = HAS_PARTIAL_DATA;
- goto finally;
+ for (; i < ns->numnames; i++) {
+ item = &ns->items[i];
+ if (!_sharednsitem_is_initialized(item)) {
+ return 0;
}
- int64_t interpidN = -1;
- if (!_sharednsitem_has_value(itemN, &interpidN)) {
- res = HAS_PARTIAL_DATA;
- goto finally;
+ if (_sharednsitem_has_value(item, NULL)) {
+ return 0;
}
- assert(interpidN == interpid0);
}
- res = HAS_COMPLETE_DATA;
- *p_interpid = interpid0;
-
-finally:
- return res;
+ for (; i < ns->maxitems; i++) {
+ item = &ns->items[i];
+ if (_sharednsitem_is_initialized(item)) {
+ return 0;
+ }
+ if (_sharednsitem_has_value(item, NULL)) {
+ return 0;
+ }
+ }
+ return 1;
}
+#endif
-static void
-_sharedns_clear(_PyXI_namespace *ns)
+static _PyXI_namespace *
+_sharedns_alloc(Py_ssize_t maxitems)
{
- if (!_sharedns_is_initialized(ns)) {
- return;
+ if (maxitems < 0) {
+ if (!PyErr_Occurred()) {
+ PyErr_BadInternalCall();
+ }
+ return NULL;
+ }
+ else if (maxitems == 0) {
+ PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
+ return NULL;
}
- // If the cross-interpreter data were allocated as part of
- // _PyXI_namespace_item (instead of dynamically), this is where
- // we would need verify that we are clearing the items in the
- // correct interpreter, to avoid a race with releasing the XI data
- // via a pending call. See _sharedns_has_xidata().
- for (Py_ssize_t i=0; i < ns->len; i++) {
- _sharednsitem_clear(&ns->items[i]);
+ // Check for overflow.
+ size_t fixedsize = sizeof(_PyXI_namespace) - sizeof(_PyXI_namespace_item);
+ if ((size_t)maxitems >
+ ((size_t)PY_SSIZE_T_MAX - fixedsize) / sizeof(_PyXI_namespace_item))
+ {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ // Allocate the value, including items.
+ size_t size = fixedsize + sizeof(_PyXI_namespace_item) * maxitems;
+
+ _PyXI_namespace *ns = PyMem_RawCalloc(size, 1);
+ if (ns == NULL) {
+ PyErr_NoMemory();
+ return NULL;
}
- PyMem_RawFree(ns->items);
- ns->items = NULL;
- ns->len = 0;
+ ns->maxitems = maxitems;
+ assert(_sharedns_check_consistency(ns));
+ return ns;
}
static void
_sharedns_free(_PyXI_namespace *ns)
{
- _sharedns_clear(ns);
+ // If we weren't always dynamically allocating the cross-interpreter
+ // data in each item then we would need to use a pending call
+ // to call _sharedns_free(), to avoid the race between freeing
+ // the shared namespace and releasing the XI data.
+ assert(_sharedns_check_counts(ns));
+ Py_ssize_t i = 0;
+ _PyXI_namespace_item *item;
+ if (ns->numvalues > 0) {
+ // One or more items may have interpreter-specific data.
+#ifndef NDEBUG
+ int64_t interpid = PyInterpreterState_GetID(PyInterpreterState_Get());
+ int64_t interpid_i;
+#endif
+ for (; i < ns->numvalues; i++) {
+ item = &ns->items[i];
+ assert(_sharednsitem_is_initialized(item));
+ // While we do want to ensure consistency across items,
+ // technically they don't need to match the current
+ // interpreter. However, we keep the constraint for
+ // simplicity, by giving _PyXI_FreeNamespace() the exclusive
+ // responsibility of dealing with the owning interpreter.
+ assert(_sharednsitem_has_value(item, &interpid_i));
+ assert(interpid_i == interpid);
+ _sharednsitem_clear(item);
+ }
+ }
+ for (; i < ns->numnames; i++) {
+ item = &ns->items[i];
+ assert(_sharednsitem_is_initialized(item));
+ assert(!_sharednsitem_has_value(item, NULL));
+ _sharednsitem_clear(item);
+ }
+#ifndef NDEBUG
+ for (; i < ns->maxitems; i++) {
+ item = &ns->items[i];
+ assert(!_sharednsitem_is_initialized(item));
+ assert(!_sharednsitem_has_value(item, NULL));
+ }
+#endif
+
PyMem_RawFree(ns);
}
-static int
-_sharedns_init(_PyXI_namespace *ns, PyObject *names)
+static _PyXI_namespace *
+_create_sharedns(PyObject *names)
{
- assert(!_sharedns_is_initialized(ns));
assert(names != NULL);
- Py_ssize_t len = PyDict_CheckExact(names)
+ Py_ssize_t numnames = PyDict_CheckExact(names)
? PyDict_Size(names)
: PySequence_Size(names);
- if (len < 0) {
- return -1;
- }
- if (len == 0) {
- PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
- return -1;
- }
- assert(len > 0);
- // Allocate the items.
- _PyXI_namespace_item *items =
- PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
- if (items == NULL) {
- PyErr_NoMemory();
- return -1;
+ _PyXI_namespace *ns = _sharedns_alloc(numnames);
+ if (ns == NULL) {
+ return NULL;
}
+ _PyXI_namespace_item *items = ns->items;
// Fill in the names.
- Py_ssize_t i = -1;
if (PyDict_CheckExact(names)) {
+ Py_ssize_t i = 0;
Py_ssize_t pos = 0;
- for (i=0; i < len; i++) {
- PyObject *key;
- if (!PyDict_Next(names, &pos, &key, NULL)) {
- // This should not be possible.
- assert(0);
- goto error;
- }
- if (_sharednsitem_init(&items[i], key) < 0) {
+ PyObject *name;
+ while(PyDict_Next(names, &pos, &name, NULL)) {
+ if (_sharednsitem_init(&items[i], name) < 0) {
goto error;
}
+ ns->numnames += 1;
+ i += 1;
}
}
else if (PySequence_Check(names)) {
- for (i=0; i < len; i++) {
- PyObject *key = PySequence_GetItem(names, i);
- if (key == NULL) {
+ for (Py_ssize_t i = 0; i < numnames; i++) {
+ PyObject *name = PySequence_GetItem(names, i);
+ if (name == NULL) {
goto error;
}
- int res = _sharednsitem_init(&items[i], key);
- Py_DECREF(key);
+ int res = _sharednsitem_init(&items[i], name);
+ Py_DECREF(name);
if (res < 0) {
goto error;
}
+ ns->numnames += 1;
}
}
else {
@@ -2071,140 +2127,79 @@ _sharedns_init(_PyXI_namespace *ns, PyObject *names)
"non-sequence namespace not supported");
goto error;
}
-
- ns->items = items;
- ns->len = len;
- assert(_sharedns_is_initialized(ns));
- return 0;
+ assert(ns->numnames == ns->maxitems);
+ return ns;
error:
- for (Py_ssize_t j=0; j < i; j++) {
- _sharednsitem_clear(&items[j]);
- }
- PyMem_RawFree(items);
- assert(!_sharedns_is_initialized(ns));
- return -1;
-}
-
-void
-_PyXI_FreeNamespace(_PyXI_namespace *ns)
-{
- if (!_sharedns_is_initialized(ns)) {
- return;
- }
-
- int64_t interpid = -1;
- if (!_sharedns_has_xidata(ns, &interpid)) {
- _sharedns_free(ns);
- return;
- }
-
- if (interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) {
- _sharedns_free(ns);
- }
- else {
- // If we weren't always dynamically allocating the cross-interpreter
- // data in each item then we would need to using a pending call
- // to call _sharedns_free(), to avoid the race between freeing
- // the shared namespace and releasing the XI data.
- _sharedns_free(ns);
- }
-}
-
-_PyXI_namespace *
-_PyXI_NamespaceFromNames(PyObject *names)
-{
- if (names == NULL || names == Py_None) {
- return NULL;
- }
-
- _PyXI_namespace *ns = _sharedns_new();
- if (ns == NULL) {
- return NULL;
- }
-
- if (_sharedns_init(ns, names) < 0) {
- PyMem_RawFree(ns);
- if (PySequence_Size(names) == 0) {
- PyErr_Clear();
- }
- return NULL;
- }
-
- return ns;
+ _sharedns_free(ns);
+ return NULL;
}
-#ifndef NDEBUG
-static int _session_is_active(_PyXI_session *);
-#endif
static void _propagate_not_shareable_error(_PyXI_session *);
-int
-_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj,
- _PyXI_session *session)
-{
- // session must be entered already, if provided.
- assert(session == NULL || _session_is_active(session));
- assert(_sharedns_is_initialized(ns));
- for (Py_ssize_t i=0; i < ns->len; i++) {
- _PyXI_namespace_item *item = &ns->items[i];
- if (_sharednsitem_copy_from_ns(item, nsobj) < 0) {
+static int
+_fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj, _PyXI_session *session)
+{
+ // All items are expected to be shareable.
+ assert(_sharedns_check_counts(ns));
+ assert(ns->numnames == ns->maxitems);
+ assert(ns->numvalues == 0);
+ for (Py_ssize_t i=0; i < ns->maxitems; i++) {
+ if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj) < 0) {
_propagate_not_shareable_error(session);
// Clear out the ones we set so far.
for (Py_ssize_t j=0; j < i; j++) {
_sharednsitem_clear_value(&ns->items[j]);
+ ns->numvalues -= 1;
}
return -1;
}
+ ns->numvalues += 1;
}
return 0;
}
-// All items are expected to be shareable.
-static _PyXI_namespace *
-_PyXI_NamespaceFromDict(PyObject *nsobj, _PyXI_session *session)
+static int
+_sharedns_free_pending(void *data)
{
- // session must be entered already, if provided.
- assert(session == NULL || _session_is_active(session));
- if (nsobj == NULL || nsobj == Py_None) {
- return NULL;
- }
- if (!PyDict_CheckExact(nsobj)) {
- PyErr_SetString(PyExc_TypeError, "expected a dict");
- return NULL;
- }
+ _sharedns_free((_PyXI_namespace *)data);
+ return 0;
+}
- _PyXI_namespace *ns = _sharedns_new();
- if (ns == NULL) {
- return NULL;
+static void
+_destroy_sharedns(_PyXI_namespace *ns)
+{
+ assert(_sharedns_check_counts(ns));
+ assert(ns->numnames == ns->maxitems);
+ if (ns->numvalues == 0) {
+ _sharedns_free(ns);
+ return;
}
- if (_sharedns_init(ns, nsobj) < 0) {
- if (PyDict_Size(nsobj) == 0) {
- PyMem_RawFree(ns);
- PyErr_Clear();
- return NULL;
- }
- goto error;
+ int64_t interpid0;
+ if (!_sharednsitem_has_value(&ns->items[0], &interpid0)) {
+ // This shouldn't have been possible.
+ // We can deal with it in _sharedns_free().
+ _sharedns_free(ns);
+ return;
}
-
- if (_PyXI_FillNamespaceFromDict(ns, nsobj, session) < 0) {
- goto error;
+ PyInterpreterState *interp = _PyInterpreterState_LookUpID(interpid0);
+ if (interp == PyInterpreterState_Get()) {
+ _sharedns_free(ns);
+ return;
}
- return ns;
-
-error:
- assert(PyErr_Occurred()
- || (session != NULL && session->error_override != NULL));
- _sharedns_free(ns);
- return NULL;
+ // One or more items may have interpreter-specific data.
+ // Currently the xidata for each value is dynamically allocated,
+ // so technically we don't need to worry about that.
+ // However, explicitly adding a pending call here is simpler.
+ (void)_Py_CallInInterpreter(interp, _sharedns_free_pending, ns);
}
-int
-_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
+static int
+_apply_sharedns(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
{
- for (Py_ssize_t i=0; i < ns->len; i++) {
+ for (Py_ssize_t i=0; i < ns->maxitems; i++) {
if (_sharednsitem_apply(&ns->items[i], nsobj, dflt) != 0) {
return -1;
}
@@ -2213,9 +2208,79 @@ _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
}
-/**********************/
-/* high-level helpers */
-/**********************/
+/*********************************/
+/* switched-interpreter sessions */
+/*********************************/
+
+struct xi_session {
+#define SESSION_UNUSED 0
+#define SESSION_ACTIVE 1
+ int status;
+ int switched;
+
+ // Once a session has been entered, this is the tstate that was
+ // current before the session. If it is different from cur_tstate
+ // then we must have switched interpreters. Either way, this will
+ // be the current tstate once we exit the session.
+ PyThreadState *prev_tstate;
+ // Once a session has been entered, this is the current tstate.
+ // It must be current when the session exits.
+ PyThreadState *init_tstate;
+ // This is true if init_tstate needs cleanup during exit.
+ int own_init_tstate;
+
+ // This is true if, while entering the session, init_thread took
+ // "ownership" of the interpreter's __main__ module. This means
+ // it is the only thread that is allowed to run code there.
+ // (Caveat: for now, users may still run exec() against the
+ // __main__ module's dict, though that isn't advisable.)
+ int running;
+ // This is a cached reference to the __dict__ of the entered
+ // interpreter's __main__ module. It is looked up when at the
+ // beginning of the session as a convenience.
+ PyObject *main_ns;
+
+ // This is set if the interpreter is entered and raised an exception
+ // that needs to be handled in some special way during exit.
+ _PyXI_errcode *error_override;
+ // This is set if exit captured an exception to propagate.
+ _PyXI_error *error;
+
+ // -- pre-allocated memory --
+ _PyXI_error _error;
+ _PyXI_errcode _error_override;
+};
+
+
+_PyXI_session *
+_PyXI_NewSession(void)
+{
+ _PyXI_session *session = PyMem_RawCalloc(1, sizeof(_PyXI_session));
+ if (session == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ return session;
+}
+
+void
+_PyXI_FreeSession(_PyXI_session *session)
+{
+ assert(session->status == SESSION_UNUSED);
+ PyMem_RawFree(session);
+}
+
+
+static inline int
+_session_is_active(_PyXI_session *session)
+{
+ return session->status == SESSION_ACTIVE;
+}
+
+static int _ensure_main_ns(_PyXI_session *);
+static inline void _session_set_error(_PyXI_session *, _PyXI_errcode);
+static void _capture_current_exception(_PyXI_session *);
+
/* enter/exit a cross-interpreter session */
@@ -2223,6 +2288,7 @@ static void
_enter_session(_PyXI_session *session, PyInterpreterState *interp)
{
// Set here and cleared in _exit_session().
+ assert(session->status == SESSION_UNUSED);
assert(!session->own_init_tstate);
assert(session->init_tstate == NULL);
assert(session->prev_tstate == NULL);
@@ -2237,15 +2303,22 @@ _enter_session(_PyXI_session *session, PyInterpreterState *interp)
// Switch to interpreter.
PyThreadState *tstate = PyThreadState_Get();
PyThreadState *prev = tstate;
- if (interp != tstate->interp) {
+ int same_interp = (interp == tstate->interp);
+ if (!same_interp) {
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_EXEC);
// XXX Possible GILState issues?
- session->prev_tstate = PyThreadState_Swap(tstate);
- assert(session->prev_tstate == prev);
- session->own_init_tstate = 1;
+ PyThreadState *swapped = PyThreadState_Swap(tstate);
+ assert(swapped == prev);
+ (void)swapped;
}
- session->init_tstate = tstate;
- session->prev_tstate = prev;
+
+ *session = (_PyXI_session){
+ .status = SESSION_ACTIVE,
+ .switched = !same_interp,
+ .init_tstate = tstate,
+ .prev_tstate = prev,
+ .own_init_tstate = !same_interp,
+ };
}
static void
@@ -2256,9 +2329,7 @@ _exit_session(_PyXI_session *session)
assert(PyThreadState_Get() == tstate);
// Release any of the entered interpreters resources.
- if (session->main_ns != NULL) {
- Py_CLEAR(session->main_ns);
- }
+ Py_CLEAR(session->main_ns);
// Ensure this thread no longer owns __main__.
if (session->running) {
@@ -2279,17 +2350,15 @@ _exit_session(_PyXI_session *session)
else {
assert(!session->own_init_tstate);
}
- session->prev_tstate = NULL;
- session->init_tstate = NULL;
-}
-#ifndef NDEBUG
-static int
-_session_is_active(_PyXI_session *session)
-{
- return (session->init_tstate != NULL);
+ // For now the error data persists past the exit.
+ *session = (_PyXI_session){
+ .error_override = session->error_override,
+ .error = session->error,
+ ._error = session->_error,
+ ._error_override = session->_error_override,
+ };
}
-#endif
static void
_propagate_not_shareable_error(_PyXI_session *session)
@@ -2306,11 +2375,102 @@ _propagate_not_shareable_error(_PyXI_session *session)
}
if (PyErr_ExceptionMatches(exctype)) {
// We want to propagate the exception directly.
- session->_error_override = _PyXI_ERR_NOT_SHAREABLE;
- session->error_override = &session->_error_override;
+ _session_set_error(session, _PyXI_ERR_NOT_SHAREABLE);
}
}
+PyObject *
+_PyXI_ApplyCapturedException(_PyXI_session *session)
+{
+ assert(!PyErr_Occurred());
+ assert(session->error != NULL);
+ PyObject *res = _PyXI_ApplyError(session->error);
+ assert((res == NULL) != (PyErr_Occurred() == NULL));
+ session->error = NULL;
+ return res;
+}
+
+int
+_PyXI_HasCapturedException(_PyXI_session *session)
+{
+ return session->error != NULL;
+}
+
+int
+_PyXI_Enter(_PyXI_session *session,
+ PyInterpreterState *interp, PyObject *nsupdates)
+{
+ // Convert the attrs for cross-interpreter use.
+ _PyXI_namespace *sharedns = NULL;
+ if (nsupdates != NULL) {
+ Py_ssize_t len = PyDict_Size(nsupdates);
+ if (len < 0) {
+ return -1;
+ }
+ if (len > 0) {
+ sharedns = _create_sharedns(nsupdates);
+ if (sharedns == NULL) {
+ return -1;
+ }
+ if (_fill_sharedns(sharedns, nsupdates, NULL) < 0) {
+ assert(session->error == NULL);
+ _destroy_sharedns(sharedns);
+ return -1;
+ }
+ }
+ }
+
+ // Switch to the requested interpreter (if necessary).
+ _enter_session(session, interp);
+ _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+
+ // Ensure this thread owns __main__.
+ if (_PyInterpreterState_SetRunningMain(interp) < 0) {
+ // In the case where we didn't switch interpreters, it would
+ // be more efficient to leave the exception in place and return
+ // immediately. However, life is simpler if we don't.
+ errcode = _PyXI_ERR_ALREADY_RUNNING;
+ goto error;
+ }
+ session->running = 1;
+
+ // Apply the cross-interpreter data.
+ if (sharedns != NULL) {
+ if (_ensure_main_ns(session) < 0) {
+ errcode = _PyXI_ERR_MAIN_NS_FAILURE;
+ goto error;
+ }
+ if (_apply_sharedns(sharedns, session->main_ns, NULL) < 0) {
+ errcode = _PyXI_ERR_APPLY_NS_FAILURE;
+ goto error;
+ }
+ _destroy_sharedns(sharedns);
+ }
+
+ errcode = _PyXI_ERR_NO_ERROR;
+ assert(!PyErr_Occurred());
+ return 0;
+
+error:
+ // We want to propagate all exceptions here directly (best effort).
+ _session_set_error(session, errcode);
+ _exit_session(session);
+ if (sharedns != NULL) {
+ _destroy_sharedns(sharedns);
+ }
+ return -1;
+}
+
+void
+_PyXI_Exit(_PyXI_session *session)
+{
+ _capture_current_exception(session);
+ _exit_session(session);
+}
+
+
+/* in an active cross-interpreter session */
+
static void
_capture_current_exception(_PyXI_session *session)
{
@@ -2372,100 +2532,55 @@ _capture_current_exception(_PyXI_session *session)
// Finished!
assert(!PyErr_Occurred());
- session->error = err;
-}
-
-PyObject *
-_PyXI_ApplyCapturedException(_PyXI_session *session)
-{
- assert(!PyErr_Occurred());
- assert(session->error != NULL);
- PyObject *res = _PyXI_ApplyError(session->error);
- assert((res == NULL) != (PyErr_Occurred() == NULL));
- session->error = NULL;
- return res;
+ session->error = err;
}
-int
-_PyXI_HasCapturedException(_PyXI_session *session)
+static inline void
+_session_set_error(_PyXI_session *session, _PyXI_errcode errcode)
{
- return session->error != NULL;
+ assert(_session_is_active(session));
+ assert(PyErr_Occurred());
+ if (errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ session->_error_override = errcode;
+ session->error_override = &session->_error_override;
+ }
+ _capture_current_exception(session);
}
-int
-_PyXI_Enter(_PyXI_session *session,
- PyInterpreterState *interp, PyObject *nsupdates)
+static int
+_ensure_main_ns(_PyXI_session *session)
{
- // Convert the attrs for cross-interpreter use.
- _PyXI_namespace *sharedns = NULL;
- if (nsupdates != NULL) {
- sharedns = _PyXI_NamespaceFromDict(nsupdates, NULL);
- if (sharedns == NULL && PyErr_Occurred()) {
- assert(session->error == NULL);
- return -1;
- }
- }
-
- // Switch to the requested interpreter (if necessary).
- _enter_session(session, interp);
- PyThreadState *session_tstate = session->init_tstate;
- _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
-
- // Ensure this thread owns __main__.
- if (_PyInterpreterState_SetRunningMain(interp) < 0) {
- // In the case where we didn't switch interpreters, it would
- // be more efficient to leave the exception in place and return
- // immediately. However, life is simpler if we don't.
- errcode = _PyXI_ERR_ALREADY_RUNNING;
- goto error;
+ assert(_session_is_active(session));
+ if (session->main_ns != NULL) {
+ return 0;
}
- session->running = 1;
-
// Cache __main__.__dict__.
- PyObject *main_mod = _Py_GetMainModule(session_tstate);
+ PyObject *main_mod = _Py_GetMainModule(session->init_tstate);
if (_Py_CheckMainModule(main_mod) < 0) {
- errcode = _PyXI_ERR_MAIN_NS_FAILURE;
- goto error;
+ return -1;
}
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
Py_DECREF(main_mod);
if (ns == NULL) {
- errcode = _PyXI_ERR_MAIN_NS_FAILURE;
- goto error;
+ return -1;
}
session->main_ns = Py_NewRef(ns);
-
- // Apply the cross-interpreter data.
- if (sharedns != NULL) {
- if (_PyXI_ApplyNamespace(sharedns, ns, NULL) < 0) {
- errcode = _PyXI_ERR_APPLY_NS_FAILURE;
- goto error;
- }
- _PyXI_FreeNamespace(sharedns);
- }
-
- errcode = _PyXI_ERR_NO_ERROR;
- assert(!PyErr_Occurred());
return 0;
-
-error:
- assert(PyErr_Occurred());
- // We want to propagate all exceptions here directly (best effort).
- assert(errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION);
- session->error_override = &errcode;
- _capture_current_exception(session);
- _exit_session(session);
- if (sharedns != NULL) {
- _PyXI_FreeNamespace(sharedns);
- }
- return -1;
}
-void
-_PyXI_Exit(_PyXI_session *session)
+PyObject *
+_PyXI_GetMainNamespace(_PyXI_session *session)
{
- _capture_current_exception(session);
- _exit_session(session);
+ if (!_session_is_active(session)) {
+ PyErr_SetString(PyExc_RuntimeError, "session not active");
+ return NULL;
+ }
+ if (_ensure_main_ns(session) < 0) {
+ _session_set_error(session, _PyXI_ERR_MAIN_NS_FAILURE);
+ _capture_current_exception(session);
+ return NULL;
+ }
+ return session->main_ns;
}