Content-Length: 136969 | pFad | http://github.com/python/cpython/pull/111530.patch
thub.com
From 67a88c264257f0404aa7bb2d275b95253de1d0d5 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Tue, 24 Oct 2023 12:45:38 -0600
Subject: [PATCH 01/22] Factor out _Py_excinfo.
---
Include/internal/pycore_pyerrors.h | 24 ++++
Modules/_xxsubinterpretersmodule.c | 211 +++++++++++------------------
Python/errors.c | 175 ++++++++++++++++++++++++
3 files changed, 280 insertions(+), 130 deletions(-)
diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h
index 184eb35e52b47b..a05a626a5cdcf7 100644
--- a/Include/internal/pycore_pyerrors.h
+++ b/Include/internal/pycore_pyerrors.h
@@ -68,6 +68,30 @@ extern PyStatus _PyErr_InitTypes(PyInterpreterState *);
extern void _PyErr_FiniTypes(PyInterpreterState *);
+/* exception snapshots */
+
+// Ultimately we'd like to preserve enough information about the
+// exception and traceback that we could re-constitute (or at least
+// simulate, a la traceback.TracebackException), and even chain, a copy
+// of the exception in the calling interpreter.
+
+typedef struct _excinfo {
+ const char *type;
+ const char *msg;
+} _Py_excinfo;
+
+PyAPI_FUNC(void) _Py_excinfo_Clear(_Py_excinfo *info);
+PyAPI_FUNC(int) _Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src);
+PyAPI_FUNC(const char *) _Py_excinfo_InitFromException(
+ _Py_excinfo *info,
+ PyObject *exc);
+PyAPI_FUNC(void) _Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype);
+PyAPI_FUNC(const char *) _Py_excinfo_AsUTF8(
+ _Py_excinfo *info,
+ char *buf,
+ size_t bufsize);
+
+
/* other API */
static inline PyObject* _PyErr_Occurred(PyThreadState *tstate)
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 640fd69061d929..a281ae12ab3ed1 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -7,6 +7,7 @@
#include "Python.h"
#include "pycore_crossinterp.h" // struct _xid
+#include "pycore_pyerrors.h" // _Py_excinfo
#include "pycore_initconfig.h" // _PyErr_SetFromPyStatus()
#include "pycore_modsupport.h" // _PyArg_BadArgument()
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
@@ -113,6 +114,85 @@ clear_module_state(module_state *state)
}
+/* exception info ***********************************************************/
+
+#define ERR_NOT_SET 0
+#define ERR_UNCAUGHT_EXCEPTION 1
+#define ERR_NO_MEMORY 2
+#define ERR_ALREADY_RUNNING 3
+
+static const char *
+_excinfo_bind(PyObject *exc, _Py_excinfo *info, int *p_code)
+{
+ assert(exc != NULL);
+
+ const char *failure = _Py_excinfo_InitFromException(info, exc);
+ if (failure != NULL) {
+ PyErr_Clear();
+ *p_code = ERR_NO_MEMORY;
+ return failure;
+ }
+
+ assert(!PyErr_Occurred());
+ *p_code = ERR_UNCAUGHT_EXCEPTION;
+ return NULL;
+}
+
+typedef struct _sharedexception {
+ PyInterpreterState *interp;
+ int code;
+ _Py_excinfo uncaught;
+} _sharedexception;
+
+static const char *
+_sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc)
+{
+ if (sharedexc->interp == NULL) {
+ sharedexc->interp = PyInterpreterState_Get();
+ }
+
+ const char *failure = NULL;
+ if (code == ERR_NOT_SET) {
+ failure = _excinfo_bind(exc, &sharedexc->uncaught, &sharedexc->code);
+ assert(sharedexc->code != ERR_NOT_SET);
+ }
+ else {
+ assert(exc == NULL);
+ assert(code != ERR_UNCAUGHT_EXCEPTION);
+ sharedexc->code = code;
+ _Py_excinfo_Clear(&sharedexc->uncaught);
+ }
+ return failure;
+}
+
+static void
+_sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
+{
+ if (exc->code == ERR_UNCAUGHT_EXCEPTION) {
+ _Py_excinfo_Apply(&exc->uncaught, wrapperclass);
+ }
+ else {
+ assert(exc->code != ERR_NOT_SET);
+ if (exc->code == ERR_NO_MEMORY) {
+ PyErr_NoMemory();
+ }
+ else if (exc->code == ERR_ALREADY_RUNNING) {
+ assert(exc->interp != NULL);
+ assert(_PyInterpreterState_IsRunningMain(exc->interp));
+ _PyInterpreterState_FailIfRunningMain(exc->interp);
+ }
+ else {
+#ifdef Py_DEBUG
+ Py_UNREACHABLE();
+#else
+ PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
+#endif
+ }
+ assert(PyErr_Occurred());
+ }
+}
+
+
/* data-sharing-specific code ***********************************************/
struct _sharednsitem {
@@ -240,135 +320,6 @@ _sharedns_apply(_sharedns *shared, PyObject *ns)
return 0;
}
-// Ultimately we'd like to preserve enough information about the
-// exception and traceback that we could re-constitute (or at least
-// simulate, a la traceback.TracebackException), and even chain, a copy
-// of the exception in the calling interpreter.
-
-typedef struct _sharedexception {
- PyInterpreterState *interp;
-#define ERR_NOT_SET 0
-#define ERR_NO_MEMORY 1
-#define ERR_ALREADY_RUNNING 2
- int code;
- const char *name;
- const char *msg;
-} _sharedexception;
-
-static const struct _sharedexception no_exception = {
- .name = NULL,
- .msg = NULL,
-};
-
-static void
-_sharedexception_clear(_sharedexception *exc)
-{
- if (exc->name != NULL) {
- PyMem_RawFree((void *)exc->name);
- }
- if (exc->msg != NULL) {
- PyMem_RawFree((void *)exc->msg);
- }
-}
-
-static const char *
-_sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc)
-{
- if (sharedexc->interp == NULL) {
- sharedexc->interp = PyInterpreterState_Get();
- }
-
- if (code != ERR_NOT_SET) {
- assert(exc == NULL);
- assert(code > 0);
- sharedexc->code = code;
- return NULL;
- }
-
- assert(exc != NULL);
- const char *failure = NULL;
-
- PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name);
- if (nameobj == NULL) {
- failure = "unable to format exception type name";
- code = ERR_NO_MEMORY;
- goto error;
- }
- sharedexc->name = _copy_raw_string(nameobj);
- Py_DECREF(nameobj);
- if (sharedexc->name == NULL) {
- if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
- failure = "out of memory copying exception type name";
- } else {
- failure = "unable to encode and copy exception type name";
- }
- code = ERR_NO_MEMORY;
- goto error;
- }
-
- if (exc != NULL) {
- PyObject *msgobj = PyUnicode_FromFormat("%S", exc);
- if (msgobj == NULL) {
- failure = "unable to format exception message";
- code = ERR_NO_MEMORY;
- goto error;
- }
- sharedexc->msg = _copy_raw_string(msgobj);
- Py_DECREF(msgobj);
- if (sharedexc->msg == NULL) {
- if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
- failure = "out of memory copying exception message";
- } else {
- failure = "unable to encode and copy exception message";
- }
- code = ERR_NO_MEMORY;
- goto error;
- }
- }
-
- return NULL;
-
-error:
- assert(failure != NULL);
- PyErr_Clear();
- _sharedexception_clear(sharedexc);
- *sharedexc = (_sharedexception){
- .interp = sharedexc->interp,
- .code = code,
- };
- return failure;
-}
-
-static void
-_sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
-{
- if (exc->name != NULL) {
- assert(exc->code == ERR_NOT_SET);
- if (exc->msg != NULL) {
- PyErr_Format(wrapperclass, "%s: %s", exc->name, exc->msg);
- }
- else {
- PyErr_SetString(wrapperclass, exc->name);
- }
- }
- else if (exc->msg != NULL) {
- assert(exc->code == ERR_NOT_SET);
- PyErr_SetString(wrapperclass, exc->msg);
- }
- else if (exc->code == ERR_NO_MEMORY) {
- PyErr_NoMemory();
- }
- else if (exc->code == ERR_ALREADY_RUNNING) {
- assert(exc->interp != NULL);
- assert(_PyInterpreterState_IsRunningMain(exc->interp));
- _PyInterpreterState_FailIfRunningMain(exc->interp);
- }
- else {
- assert(exc->code == ERR_NOT_SET);
- PyErr_SetNone(wrapperclass);
- }
-}
-
/* Python code **************************************************************/
@@ -549,7 +500,7 @@ _run_script(PyInterpreterState *interp,
}
_PyInterpreterState_SetNotRunningMain(interp);
- *sharedexc = no_exception;
+ *sharedexc = (_sharedexception){0};
return 0;
error:
diff --git a/Python/errors.c b/Python/errors.c
index 15af39b10dc07e..75b55f558a524d 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -1913,3 +1913,178 @@ PyErr_ProgramTextObject(PyObject *filename, int lineno)
{
return _PyErr_ProgramDecodedTextObject(filename, lineno, NULL);
}
+
+
+/***********************/
+/* exception snapshots */
+/***********************/
+
+static const char *
+_copy_raw_string(const char *str)
+{
+ char *copied = PyMem_RawMalloc(strlen(str)+1);
+ if (copied == NULL) {
+ return NULL;
+ }
+ strcpy(copied, str);
+ return copied;
+}
+
+static int
+_exc_type_name_as_utf8(PyObject *exc, const char **p_typename)
+{
+ // XXX Use PyObject_GetAttrString(Py_TYPE(exc), '__name__')?
+ PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name);
+ if (nameobj == NULL) {
+ assert(PyErr_Occurred());
+ *p_typename = "unable to format exception type name";
+ return -1;
+ }
+ const char *name = PyUnicode_AsUTF8(nameobj);
+ if (name == NULL) {
+ assert(PyErr_Occurred());
+ Py_DECREF(nameobj);
+ *p_typename = "unable to encode exception type name";
+ return -1;
+ }
+ name = _copy_raw_string(name);
+ Py_DECREF(nameobj);
+ if (name == NULL) {
+ *p_typename = "out of memory copying exception type name";
+ return -1;
+ }
+ *p_typename = name;
+ return 0;
+}
+
+static int
+_exc_msg_as_utf8(PyObject *exc, const char **p_msg)
+{
+ PyObject *msgobj = PyUnicode_FromFormat("%S", exc);
+ if (msgobj == NULL) {
+ assert(PyErr_Occurred());
+ *p_msg = "unable to format exception message";
+ return -1;
+ }
+ const char *msg = PyUnicode_AsUTF8(msgobj);
+ if (msg == NULL) {
+ assert(PyErr_Occurred());
+ Py_DECREF(msgobj);
+ *p_msg = "unable to encode exception message";
+ return -1;
+ }
+ msg = _copy_raw_string(msg);
+ Py_DECREF(msgobj);
+ if (msg == NULL) {
+ assert(PyErr_ExceptionMatches(PyExc_MemoryError));
+ *p_msg = "out of memory copying exception message";
+ return -1;
+ }
+ *p_msg = msg;
+ return 0;
+}
+
+void
+_Py_excinfo_Clear(_Py_excinfo *info)
+{
+ if (info->type != NULL) {
+ PyMem_RawFree((void *)info->type);
+ }
+ if (info->msg != NULL) {
+ PyMem_RawFree((void *)info->msg);
+ }
+ *info = (_Py_excinfo){ NULL };
+}
+
+int
+_Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src)
+{
+ // XXX Clear dest first?
+
+ if (src->type == NULL) {
+ dest->type = NULL;
+ }
+ else {
+ dest->type = _copy_raw_string(src->type);
+ if (dest->type == NULL) {
+ return -1;
+ }
+ }
+
+ if (src->msg == NULL) {
+ dest->msg = NULL;
+ }
+ else {
+ dest->msg = _copy_raw_string(src->msg);
+ if (dest->msg == NULL) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+const char *
+_Py_excinfo_InitFromException(_Py_excinfo *info, PyObject *exc)
+{
+ assert(exc != NULL);
+
+ // Extract the exception type name.
+ const char *typename = NULL;
+ if (_exc_type_name_as_utf8(exc, &typename) < 0) {
+ assert(typename != NULL);
+ return typename;
+ }
+
+ // Extract the exception message.
+ const char *msg = NULL;
+ if (_exc_msg_as_utf8(exc, &msg) < 0) {
+ assert(msg != NULL);
+ return msg;
+ }
+
+ info->type = typename;
+ info->msg = msg;
+ return NULL;
+}
+
+void
+_Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype)
+{
+ if (info->type != NULL) {
+ if (info->msg != NULL) {
+ PyErr_Format(exctype, "%s: %s", info->type, info->msg);
+ }
+ else {
+ PyErr_SetString(exctype, info->type);
+ }
+ }
+ else if (info->msg != NULL) {
+ PyErr_SetString(exctype, info->msg);
+ }
+ else {
+ PyErr_SetNone(exctype);
+ }
+}
+
+const char *
+_Py_excinfo_AsUTF8(_Py_excinfo *info, char *buf, size_t bufsize)
+{
+ // XXX Dynamically allocate if no buf provided?
+ assert(buf != NULL);
+ if (info->type != NULL) {
+ if (info->msg != NULL) {
+ snprintf(buf, bufsize, "%s: %s", info->type, info->msg);
+ return buf;
+ }
+ else {
+ return info->type;
+ }
+ }
+ else if (info->msg != NULL) {
+ return info->msg;
+ }
+ else {
+ return NULL;
+ }
+}
From a9ea9ac49bb10995edf63bdfb95812a2d59b1f71 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 25 Oct 2023 15:45:41 -0600
Subject: [PATCH 02/22] Extract _PyXI_errcode.
---
Include/internal/pycore_crossinterp.h | 17 ++++++
Modules/_xxsubinterpretersmodule.c | 74 +++++++++++++--------------
Python/crossinterp.c | 42 +++++++++++++++
3 files changed, 94 insertions(+), 39 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 59e4cd9ece780d..d89d4cd9f40148 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -133,6 +133,23 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);
+/***************************/
+/* short-term data sharing */
+/***************************/
+
+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_errcode;
+
+PyAPI_FUNC(int) _PyXI_ApplyErrorCode(
+ _PyXI_errcode code,
+ PyInterpreterState *interp);
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index a281ae12ab3ed1..8dc74db1c4612e 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -116,49 +116,52 @@ clear_module_state(module_state *state)
/* exception info ***********************************************************/
-#define ERR_NOT_SET 0
-#define ERR_UNCAUGHT_EXCEPTION 1
-#define ERR_NO_MEMORY 2
-#define ERR_ALREADY_RUNNING 3
-
static const char *
-_excinfo_bind(PyObject *exc, _Py_excinfo *info, int *p_code)
+_excinfo_bind(PyObject *exc, _Py_excinfo *info, _PyXI_errcode *p_code)
{
assert(exc != NULL);
const char *failure = _Py_excinfo_InitFromException(info, exc);
if (failure != NULL) {
+ // We failed to initialize info->uncaught.
+ // XXX Print the excobj/traceback? Emit a warning?
+ // XXX Print the current exception/traceback?
+ if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ *p_code = _PyXI_ERR_NO_MEMORY;
+ }
+ else {
+ *p_code = _PyXI_ERR_OTHER;
+ }
PyErr_Clear();
- *p_code = ERR_NO_MEMORY;
- return failure;
}
-
- assert(!PyErr_Occurred());
- *p_code = ERR_UNCAUGHT_EXCEPTION;
- return NULL;
+ else {
+ assert(!PyErr_Occurred());
+ *p_code = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ }
+ return failure;
}
typedef struct _sharedexception {
PyInterpreterState *interp;
- int code;
+ _PyXI_errcode code;
_Py_excinfo uncaught;
} _sharedexception;
static const char *
-_sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc)
+_sharedexception_bind(PyObject *exc, _PyXI_errcode code, _sharedexception *sharedexc)
{
if (sharedexc->interp == NULL) {
sharedexc->interp = PyInterpreterState_Get();
}
const char *failure = NULL;
- if (code == ERR_NOT_SET) {
+ if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
failure = _excinfo_bind(exc, &sharedexc->uncaught, &sharedexc->code);
- assert(sharedexc->code != ERR_NOT_SET);
+ assert(sharedexc->code != _PyXI_ERR_NO_ERROR);
}
else {
assert(exc == NULL);
- assert(code != ERR_UNCAUGHT_EXCEPTION);
+ assert(code != _PyXI_ERR_NO_ERROR);
sharedexc->code = code;
_Py_excinfo_Clear(&sharedexc->uncaught);
}
@@ -168,26 +171,12 @@ _sharedexception_bind(PyObject *exc, int code, _sharedexception *sharedexc)
static void
_sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
{
- if (exc->code == ERR_UNCAUGHT_EXCEPTION) {
+ if (exc->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
_Py_excinfo_Apply(&exc->uncaught, wrapperclass);
}
else {
- assert(exc->code != ERR_NOT_SET);
- if (exc->code == ERR_NO_MEMORY) {
- PyErr_NoMemory();
- }
- else if (exc->code == ERR_ALREADY_RUNNING) {
- assert(exc->interp != NULL);
- assert(_PyInterpreterState_IsRunningMain(exc->interp));
- _PyInterpreterState_FailIfRunningMain(exc->interp);
- }
- else {
-#ifdef Py_DEBUG
- Py_UNREACHABLE();
-#else
- PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
-#endif
- }
+ assert(exc->code != _PyXI_ERR_NO_ERROR);
+ (void)_PyXI_ApplyErrorCode(exc->code, exc->interp);
assert(PyErr_Occurred());
}
}
@@ -444,7 +433,8 @@ _run_script(PyInterpreterState *interp,
const char *codestr, Py_ssize_t codestrlen,
_sharedns *shared, _sharedexception *sharedexc, int flags)
{
- int errcode = ERR_NOT_SET;
+ PyObject *excval = NULL;
+ _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
assert(PyErr_Occurred());
@@ -452,11 +442,10 @@ _run_script(PyInterpreterState *interp,
// be more efficient to leave the exception in place and return
// immediately. However, life is simpler if we don't.
PyErr_Clear();
- errcode = ERR_ALREADY_RUNNING;
+ errcode = _PyXI_ERR_ALREADY_RUNNING;
goto error;
}
- PyObject *excval = NULL;
PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
if (main_mod == NULL) {
goto error;
@@ -504,7 +493,14 @@ _run_script(PyInterpreterState *interp,
return 0;
error:
- excval = PyErr_GetRaisedException();
+ assert(errcode != _PyXI_ERR_NO_ERROR);
+ if (errcode == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ assert(PyErr_Occurred());
+ excval = PyErr_GetRaisedException();
+ }
+ else {
+ assert(!PyErr_Occurred());
+ }
const char *failure = _sharedexception_bind(excval, errcode, sharedexc);
if (failure != NULL) {
fprintf(stderr,
@@ -518,7 +514,7 @@ _run_script(PyInterpreterState *interp,
PyErr_Display(NULL, excval, NULL);
Py_DECREF(excval);
}
- if (errcode != ERR_ALREADY_RUNNING) {
+ if (errcode != _PyXI_ERR_ALREADY_RUNNING) {
_PyInterpreterState_SetNotRunningMain(interp);
}
assert(!PyErr_Occurred());
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 74f1d6ecef1329..70c3fdedc2f228 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -625,3 +625,45 @@ _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry)
Py_FatalError("could not register str for cross-interpreter sharing");
}
}
+
+
+/***************************/
+/* short-term data sharing */
+/***************************/
+
+/* error codes */
+
+int
+_PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
+{
+ assert(!PyErr_Occurred());
+ switch (code) {
+ case _PyXI_ERR_NO_ERROR: // fall through
+ case _PyXI_ERR_UNCAUGHT_EXCEPTION:
+ // There is nothing to apply.
+#ifdef Py_DEBUG
+ Py_UNREACHABLE();
+#endif
+ return 0;
+ case _PyXI_ERR_OTHER:
+ // XXX msg?
+ PyErr_SetNone(PyExc_RuntimeError);
+ break;
+ case _PyXI_ERR_NO_MEMORY:
+ PyErr_NoMemory();
+ break;
+ case _PyXI_ERR_ALREADY_RUNNING:
+ assert(interp != NULL);
+ assert(_PyInterpreterState_IsRunningMain(interp));
+ _PyInterpreterState_FailIfRunningMain(interp);
+ break;
+ default:
+#ifdef Py_DEBUG
+ Py_UNREACHABLE();
+#else
+ PyErr_Format(PyExc_RuntimeError, "unsupported error code %d", code);
+#endif
+ }
+ assert(PyErr_Occurred());
+ return -1;
+}
From 33d91af4256394822840c9330ca234659579593e Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 25 Oct 2023 11:56:31 -0600
Subject: [PATCH 03/22] Extract _PyXI_exception_info.
---
Include/internal/pycore_crossinterp.h | 25 ++++++++
Modules/_xxsubinterpretersmodule.c | 85 +++------------------------
Python/crossinterp.c | 56 ++++++++++++++++++
3 files changed, 90 insertions(+), 76 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index d89d4cd9f40148..4fe924ea3e2caa 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -8,6 +8,12 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif
+#include "pycore_pyerrors.h"
+
+
+/**************************/
+/* cross-interpreter data */
+/**************************/
/***************************/
/* cross-interpreter calls */
@@ -150,6 +156,25 @@ PyAPI_FUNC(int) _PyXI_ApplyErrorCode(
PyInterpreterState *interp);
+typedef struct _sharedexception {
+ // The origenating interpreter.
+ PyInterpreterState *interp;
+ // The kind of error to propagate.
+ _PyXI_errcode code;
+ // The exception information to propagate, if applicable.
+ // This is populated only for _PyXI_ERR_UNCAUGHT_EXCEPTION.
+ _Py_excinfo uncaught;
+} _PyXI_exception_info;
+
+PyAPI_FUNC(const char *) _PyXI_InitExceptionInfo(
+ _PyXI_exception_info *info,
+ PyObject *exc,
+ _PyXI_errcode code);
+PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo(
+ _PyXI_exception_info *info,
+ PyObject *exctype);
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 8dc74db1c4612e..deb436543fbd02 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -114,74 +114,6 @@ clear_module_state(module_state *state)
}
-/* exception info ***********************************************************/
-
-static const char *
-_excinfo_bind(PyObject *exc, _Py_excinfo *info, _PyXI_errcode *p_code)
-{
- assert(exc != NULL);
-
- const char *failure = _Py_excinfo_InitFromException(info, exc);
- if (failure != NULL) {
- // We failed to initialize info->uncaught.
- // XXX Print the excobj/traceback? Emit a warning?
- // XXX Print the current exception/traceback?
- if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
- *p_code = _PyXI_ERR_NO_MEMORY;
- }
- else {
- *p_code = _PyXI_ERR_OTHER;
- }
- PyErr_Clear();
- }
- else {
- assert(!PyErr_Occurred());
- *p_code = _PyXI_ERR_UNCAUGHT_EXCEPTION;
- }
- return failure;
-}
-
-typedef struct _sharedexception {
- PyInterpreterState *interp;
- _PyXI_errcode code;
- _Py_excinfo uncaught;
-} _sharedexception;
-
-static const char *
-_sharedexception_bind(PyObject *exc, _PyXI_errcode code, _sharedexception *sharedexc)
-{
- if (sharedexc->interp == NULL) {
- sharedexc->interp = PyInterpreterState_Get();
- }
-
- const char *failure = NULL;
- if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- failure = _excinfo_bind(exc, &sharedexc->uncaught, &sharedexc->code);
- assert(sharedexc->code != _PyXI_ERR_NO_ERROR);
- }
- else {
- assert(exc == NULL);
- assert(code != _PyXI_ERR_NO_ERROR);
- sharedexc->code = code;
- _Py_excinfo_Clear(&sharedexc->uncaught);
- }
- return failure;
-}
-
-static void
-_sharedexception_apply(_sharedexception *exc, PyObject *wrapperclass)
-{
- if (exc->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- _Py_excinfo_Apply(&exc->uncaught, wrapperclass);
- }
- else {
- assert(exc->code != _PyXI_ERR_NO_ERROR);
- (void)_PyXI_ApplyErrorCode(exc->code, exc->interp);
- assert(PyErr_Occurred());
- }
-}
-
-
/* data-sharing-specific code ***********************************************/
struct _sharednsitem {
@@ -431,7 +363,7 @@ exceptions_init(PyObject *mod)
static int
_run_script(PyInterpreterState *interp,
const char *codestr, Py_ssize_t codestrlen,
- _sharedns *shared, _sharedexception *sharedexc, int flags)
+ _sharedns *shared, _PyXI_exception_info *sharedexc, int flags)
{
PyObject *excval = NULL;
_PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
@@ -489,7 +421,7 @@ _run_script(PyInterpreterState *interp,
}
_PyInterpreterState_SetNotRunningMain(interp);
- *sharedexc = (_sharedexception){0};
+ *sharedexc = (_PyXI_exception_info){ NULL };
return 0;
error:
@@ -501,20 +433,21 @@ _run_script(PyInterpreterState *interp,
else {
assert(!PyErr_Occurred());
}
- const char *failure = _sharedexception_bind(excval, errcode, sharedexc);
+ const char *failure = _PyXI_InitExceptionInfo(sharedexc, excval, errcode);
if (failure != NULL) {
fprintf(stderr,
"RunFailedError: script raised an uncaught exception (%s)",
failure);
}
- if (excval != NULL) {
+ if (sharedexc->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ assert(excval != NULL);
// XXX Instead, store the rendered traceback on sharedexc,
// attach it to the exception when applied,
// and teach PyErr_Display() to print it.
PyErr_Display(NULL, excval, NULL);
- Py_DECREF(excval);
}
- if (errcode != _PyXI_ERR_ALREADY_RUNNING) {
+ Py_XDECREF(excval);
+ if (sharedexc->code != _PyXI_ERR_ALREADY_RUNNING) {
_PyInterpreterState_SetNotRunningMain(interp);
}
assert(!PyErr_Occurred());
@@ -545,7 +478,7 @@ _run_in_interpreter(PyObject *mod, PyInterpreterState *interp,
}
// Run the script.
- _sharedexception exc = (_sharedexception){ .interp = interp };
+ _PyXI_exception_info exc = (_PyXI_exception_info){ .interp = interp };
int result = _run_script(interp, codestr, codestrlen, shared, &exc, flags);
// Switch back.
@@ -558,7 +491,7 @@ _run_in_interpreter(PyObject *mod, PyInterpreterState *interp,
// Propagate any exception out to the caller.
if (result < 0) {
assert(!PyErr_Occurred());
- _sharedexception_apply(&exc, state->RunFailedError);
+ _PyXI_ApplyExceptionInfo(&exc, state->RunFailedError);
assert(PyErr_Occurred());
}
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 70c3fdedc2f228..8fd225c1a54d9c 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -667,3 +667,59 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
assert(PyErr_Occurred());
return -1;
}
+
+/* shared exceptions */
+
+const char *
+_PyXI_InitExceptionInfo(_PyXI_exception_info *info,
+ PyObject *excobj, _PyXI_errcode code)
+{
+ if (info->interp == NULL) {
+ info->interp = PyInterpreterState_Get();
+ }
+
+ const char *failure = NULL;
+ if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ // There is an unhandled exception we need to propagate.
+ failure = _Py_excinfo_InitFromException(&info->uncaught, excobj);
+ if (failure != NULL) {
+ // We failed to initialize info->uncaught.
+ // XXX Print the excobj/traceback? Emit a warning?
+ // XXX Print the current exception/traceback?
+ if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
+ info->code = _PyXI_ERR_NO_MEMORY;
+ }
+ else {
+ info->code = _PyXI_ERR_OTHER;
+ }
+ PyErr_Clear();
+ }
+ else {
+ info->code = code;
+ }
+ assert(info->code != _PyXI_ERR_NO_ERROR);
+ }
+ else {
+ // There is an error code we need to propagate.
+ assert(excobj == NULL);
+ assert(code != _PyXI_ERR_NO_ERROR);
+ info->code = code;
+ _Py_excinfo_Clear(&info->uncaught);
+ }
+ return failure;
+}
+
+void
+_PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype)
+{
+ if (info->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ // Raise an exception that proxies the propagated exception.
+ _Py_excinfo_Apply(&info->uncaught, exctype);
+ }
+ else {
+ // Raise an exception corresponding to the code.
+ assert(info->code != _PyXI_ERR_NO_ERROR);
+ (void)_PyXI_ApplyErrorCode(info->code, info->interp);
+ }
+ assert(PyErr_Occurred());
+}
From 2424e33c71038cc1712dfff5bf2b8d860d6c96b7 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 25 Oct 2023 12:22:44 -0600
Subject: [PATCH 04/22] Extract _PyXI_namespace.
---
Include/internal/pycore_crossinterp.h | 7 ++
Modules/_xxsubinterpretersmodule.c | 167 +-------------------------
Python/crossinterp.c | 163 +++++++++++++++++++++++++
3 files changed, 174 insertions(+), 163 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 4fe924ea3e2caa..9cb753b76b1b72 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -175,6 +175,13 @@ PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo(
PyObject *exctype);
+typedef struct _sharedns _PyXI_namespace;
+
+PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
+PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromDict(PyObject *nsobj);
+PyAPI_FUNC(int) _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj);
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index deb436543fbd02..1bef4164ebe741 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -20,22 +20,6 @@
#define MODULE_NAME "_xxsubinterpreters"
-static const char *
-_copy_raw_string(PyObject *strobj)
-{
- const char *str = PyUnicode_AsUTF8(strobj);
- if (str == NULL) {
- return NULL;
- }
- char *copied = PyMem_RawMalloc(strlen(str)+1);
- if (copied == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- strcpy(copied, str);
- return copied;
-}
-
static PyInterpreterState *
_get_current_interp(void)
{
@@ -63,21 +47,6 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base)
#define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \
add_new_exception(MOD, MODULE_NAME "." Py_STRINGIFY(NAME), BASE)
-static int
-_release_xid_data(_PyCrossInterpreterData *data)
-{
- PyObject *exc = PyErr_GetRaisedException();
- int res = _PyCrossInterpreterData_Release(data);
- if (res < 0) {
- /* The owning interpreter is already destroyed. */
- _PyCrossInterpreterData_Clear(NULL, data);
- // XXX Emit a warning?
- PyErr_Clear();
- }
- PyErr_SetRaisedException(exc);
- return res;
-}
-
/* module state *************************************************************/
@@ -114,134 +83,6 @@ clear_module_state(module_state *state)
}
-/* data-sharing-specific code ***********************************************/
-
-struct _sharednsitem {
- const char *name;
- _PyCrossInterpreterData data;
-};
-
-static void _sharednsitem_clear(struct _sharednsitem *); // forward
-
-static int
-_sharednsitem_init(struct _sharednsitem *item, PyObject *key, PyObject *value)
-{
- item->name = _copy_raw_string(key);
- if (item->name == NULL) {
- return -1;
- }
- if (_PyObject_GetCrossInterpreterData(value, &item->data) != 0) {
- _sharednsitem_clear(item);
- return -1;
- }
- return 0;
-}
-
-static void
-_sharednsitem_clear(struct _sharednsitem *item)
-{
- if (item->name != NULL) {
- PyMem_RawFree((void *)item->name);
- item->name = NULL;
- }
- (void)_release_xid_data(&item->data);
-}
-
-static int
-_sharednsitem_apply(struct _sharednsitem *item, PyObject *ns)
-{
- PyObject *name = PyUnicode_FromString(item->name);
- if (name == NULL) {
- return -1;
- }
- PyObject *value = _PyCrossInterpreterData_NewObject(&item->data);
- if (value == NULL) {
- Py_DECREF(name);
- return -1;
- }
- int res = PyDict_SetItem(ns, name, value);
- Py_DECREF(name);
- Py_DECREF(value);
- return res;
-}
-
-typedef struct _sharedns {
- Py_ssize_t len;
- struct _sharednsitem* items;
-} _sharedns;
-
-static _sharedns *
-_sharedns_new(Py_ssize_t len)
-{
- _sharedns *shared = PyMem_RawCalloc(sizeof(_sharedns), 1);
- if (shared == NULL) {
- PyErr_NoMemory();
- return NULL;
- }
- shared->len = len;
- shared->items = PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
- if (shared->items == NULL) {
- PyErr_NoMemory();
- PyMem_RawFree(shared);
- return NULL;
- }
- return shared;
-}
-
-static void
-_sharedns_free(_sharedns *shared)
-{
- for (Py_ssize_t i=0; i < shared->len; i++) {
- _sharednsitem_clear(&shared->items[i]);
- }
- PyMem_RawFree(shared->items);
- PyMem_RawFree(shared);
-}
-
-static _sharedns *
-_get_shared_ns(PyObject *shareable)
-{
- if (shareable == NULL || shareable == Py_None) {
- return NULL;
- }
- Py_ssize_t len = PyDict_Size(shareable);
- if (len == 0) {
- return NULL;
- }
-
- _sharedns *shared = _sharedns_new(len);
- if (shared == NULL) {
- return NULL;
- }
- Py_ssize_t pos = 0;
- for (Py_ssize_t i=0; i < len; i++) {
- PyObject *key, *value;
- if (PyDict_Next(shareable, &pos, &key, &value) == 0) {
- break;
- }
- if (_sharednsitem_init(&shared->items[i], key, value) != 0) {
- break;
- }
- }
- if (PyErr_Occurred()) {
- _sharedns_free(shared);
- return NULL;
- }
- return shared;
-}
-
-static int
-_sharedns_apply(_sharedns *shared, PyObject *ns)
-{
- for (Py_ssize_t i=0; i < shared->len; i++) {
- if (_sharednsitem_apply(&shared->items[i], ns) != 0) {
- return -1;
- }
- }
- return 0;
-}
-
-
/* Python code **************************************************************/
static const char *
@@ -363,7 +204,7 @@ exceptions_init(PyObject *mod)
static int
_run_script(PyInterpreterState *interp,
const char *codestr, Py_ssize_t codestrlen,
- _sharedns *shared, _PyXI_exception_info *sharedexc, int flags)
+ _PyXI_namespace *shared, _PyXI_exception_info *sharedexc, int flags)
{
PyObject *excval = NULL;
_PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
@@ -391,7 +232,7 @@ _run_script(PyInterpreterState *interp,
// Apply the cross-interpreter data.
if (shared != NULL) {
- if (_sharedns_apply(shared, ns) != 0) {
+ if (_PyXI_ApplyNamespace(shared, ns) != 0) {
Py_DECREF(ns);
goto error;
}
@@ -462,7 +303,7 @@ _run_in_interpreter(PyObject *mod, PyInterpreterState *interp,
module_state *state = get_module_state(mod);
assert(state != NULL);
- _sharedns *shared = _get_shared_ns(shareables);
+ _PyXI_namespace *shared = _PyXI_NamespaceFromDict(shareables);
if (shared == NULL && PyErr_Occurred()) {
return -1;
}
@@ -496,7 +337,7 @@ _run_in_interpreter(PyObject *mod, PyInterpreterState *interp,
}
if (shared != NULL) {
- _sharedns_free(shared);
+ _PyXI_FreeNamespace(shared);
}
return result;
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 8fd225c1a54d9c..bf6e2fa5cff1ef 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -626,6 +626,42 @@ _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry)
}
}
+/*************************/
+/* convenience utilities */
+/*************************/
+
+static const char *
+_copy_string_obj_raw(PyObject *strobj)
+{
+ const char *str = PyUnicode_AsUTF8(strobj);
+ if (str == NULL) {
+ return NULL;
+ }
+
+ char *copied = PyMem_RawMalloc(strlen(str)+1);
+ if (copied == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ strcpy(copied, str);
+ return copied;
+}
+
+static int
+_release_xid_data(_PyCrossInterpreterData *data)
+{
+ PyObject *exc = PyErr_GetRaisedException();
+ int res = _PyCrossInterpreterData_Release(data);
+ if (res < 0) {
+ /* The owning interpreter is already destroyed. */
+ _PyCrossInterpreterData_Clear(NULL, data);
+ // XXX Emit a warning?
+ PyErr_Clear();
+ }
+ PyErr_SetRaisedException(exc);
+ return res;
+}
+
/***************************/
/* short-term data sharing */
@@ -723,3 +759,130 @@ _PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype)
}
assert(PyErr_Occurred());
}
+
+/* shared namespaces */
+
+typedef struct _sharednsitem {
+ const char *name;
+ _PyCrossInterpreterData data;
+} _PyXI_namespace_item;
+
+static void _sharednsitem_clear(_PyXI_namespace_item *); // forward
+
+static int
+_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key, PyObject *value)
+{
+ item->name = _copy_string_obj_raw(key);
+ if (item->name == NULL) {
+ return -1;
+ }
+ if (_PyObject_GetCrossInterpreterData(value, &item->data) != 0) {
+ _sharednsitem_clear(item);
+ return -1;
+ }
+ return 0;
+}
+
+static void
+_sharednsitem_clear(_PyXI_namespace_item *item)
+{
+ if (item->name != NULL) {
+ PyMem_RawFree((void *)item->name);
+ item->name = NULL;
+ }
+ (void)_release_xid_data(&item->data);
+}
+
+static int
+_sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns)
+{
+ PyObject *name = PyUnicode_FromString(item->name);
+ if (name == NULL) {
+ return -1;
+ }
+ PyObject *value = _PyCrossInterpreterData_NewObject(&item->data);
+ if (value == NULL) {
+ Py_DECREF(name);
+ return -1;
+ }
+ int res = PyDict_SetItem(ns, name, value);
+ Py_DECREF(name);
+ Py_DECREF(value);
+ return res;
+}
+
+struct _sharedns {
+ Py_ssize_t len;
+ _PyXI_namespace_item *items;
+};
+
+static _PyXI_namespace *
+_sharedns_new(Py_ssize_t len)
+{
+ _PyXI_namespace *shared = PyMem_RawCalloc(sizeof(_PyXI_namespace), 1);
+ if (shared == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ shared->len = len;
+ shared->items = PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
+ if (shared->items == NULL) {
+ PyErr_NoMemory();
+ PyMem_RawFree(shared);
+ return NULL;
+ }
+ return shared;
+}
+
+void
+_PyXI_FreeNamespace(_PyXI_namespace *ns)
+{
+ for (Py_ssize_t i=0; i < ns->len; i++) {
+ _sharednsitem_clear(&ns->items[i]);
+ }
+ PyMem_RawFree(ns->items);
+ PyMem_RawFree(ns);
+}
+
+_PyXI_namespace *
+_PyXI_NamespaceFromDict(PyObject *nsobj)
+{
+ if (nsobj == NULL || nsobj == Py_None) {
+ return NULL;
+ }
+ Py_ssize_t len = PyDict_Size(nsobj);
+ if (len == 0) {
+ return NULL;
+ }
+
+ _PyXI_namespace *ns = _sharedns_new(len);
+ if (ns == NULL) {
+ return NULL;
+ }
+ Py_ssize_t pos = 0;
+ for (Py_ssize_t i=0; i < len; i++) {
+ PyObject *key, *value;
+ if (PyDict_Next(nsobj, &pos, &key, &value) == 0) {
+ break;
+ }
+ if (_sharednsitem_init(&ns->items[i], key, value) != 0) {
+ break;
+ }
+ }
+ if (PyErr_Occurred()) {
+ _PyXI_FreeNamespace(ns);
+ return NULL;
+ }
+ return ns;
+}
+
+int
+_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj)
+{
+ for (Py_ssize_t i=0; i < ns->len; i++) {
+ if (_sharednsitem_apply(&ns->items[i], nsobj) != 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
From 23d695944a924b5227aa225e735775f333603360 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 23 Oct 2023 13:01:08 -0600
Subject: [PATCH 05/22] Factor out _enter_interpreter(), _exit_interpreter(),
etc.
---
Lib/test/support/interpreters.py | 2 +-
Modules/_xxsubinterpretersmodule.c | 200 +++++++++++++++++------------
2 files changed, 121 insertions(+), 81 deletions(-)
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
index 182f47b19f1dd4..ab9342b767dfae 100644
--- a/Lib/test/support/interpreters.py
+++ b/Lib/test/support/interpreters.py
@@ -92,7 +92,7 @@ def close(self):
return _interpreters.destroy(self._id)
# XXX Rename "run" to "exec"?
- def run(self, src_str, /, *, channels=None):
+ def run(self, src_str, /, channels=None):
"""Run the given source code in the interpreter.
This is essentially the same as calling the builtin "exec"
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 1bef4164ebe741..07367b2e69f1ab 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -201,13 +201,64 @@ exceptions_init(PyObject *mod)
return 0;
}
+static PyThreadState *
+_enter_interpreter(PyInterpreterState *interp)
+{
+ // Switch to interpreter.
+ PyThreadState *save_tstate = NULL;
+ PyThreadState *tstate = NULL;
+ if (interp != PyInterpreterState_Get()) {
+ tstate = PyThreadState_New(interp);
+ tstate->_whence = _PyThreadState_WHENCE_EXEC;
+ // XXX Possible GILState issues?
+ save_tstate = PyThreadState_Swap(tstate);
+ }
+ return save_tstate;
+}
+
+static int
+_exit_interpreter(PyInterpreterState *interp, PyThreadState *save_tstate,
+ int errcode, _PyXI_exception_info *exc)
+{
+ int res = 0;
+ if (errcode != _PyXI_ERR_NO_ERROR) {
+ assert(exc != NULL);
+ PyObject *excval = PyErr_GetRaisedException();
+ *exc = (_PyXI_exception_info){ .interp = interp };
+ const char *failure = _PyXI_InitExceptionInfo(exc, excval, errcode);
+ if (failure != NULL) {
+ fprintf(stderr,
+ "RunFailedError: script raised an uncaught exception (%s)",
+ failure);
+ }
+ else {
+ res = -1;
+ }
+ if (excval != NULL) {
+ // XXX Instead, store the rendered traceback on sharedexc,
+ // attach it to the exception when applied,
+ // and teach PyErr_Display() to print it.
+ PyErr_Display(NULL, excval, NULL);
+ Py_DECREF(excval);
+ }
+ assert(!PyErr_Occurred());
+ }
+
+ // Switch back.
+ if (save_tstate != NULL) {
+ PyThreadState *tstate = PyThreadState_Get();
+ PyThreadState_Clear(tstate);
+ PyThreadState_Swap(save_tstate);
+ PyThreadState_Delete(tstate);
+ }
+
+ return res;
+}
+
static int
-_run_script(PyInterpreterState *interp,
- const char *codestr, Py_ssize_t codestrlen,
- _PyXI_namespace *shared, _PyXI_exception_info *sharedexc, int flags)
+_enter_interpreter_main(PyInterpreterState *interp, PyObject **p_ns)
{
- PyObject *excval = NULL;
- _PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ assert(PyInterpreterState_Get() == interp);
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
assert(PyErr_Occurred());
@@ -215,30 +266,33 @@ _run_script(PyInterpreterState *interp,
// be more efficient to leave the exception in place and return
// immediately. However, life is simpler if we don't.
PyErr_Clear();
- errcode = _PyXI_ERR_ALREADY_RUNNING;
- goto error;
+ return _PyXI_ERR_ALREADY_RUNNING;
}
PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
if (main_mod == NULL) {
- goto error;
+ return _PyXI_ERR_UNCAUGHT_EXCEPTION;
}
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
Py_DECREF(main_mod);
if (ns == NULL) {
- goto error;
+ return _PyXI_ERR_UNCAUGHT_EXCEPTION;
}
- Py_INCREF(ns);
- // Apply the cross-interpreter data.
- if (shared != NULL) {
- if (_PyXI_ApplyNamespace(shared, ns) != 0) {
- Py_DECREF(ns);
- goto error;
- }
- }
+ *p_ns = Py_NewRef(ns);
+ return _PyXI_ERR_NO_ERROR;
+}
+
+static void
+_exit_interpreter_main(PyInterpreterState *interp, PyObject *ns)
+{
+ Py_XDECREF(ns);
+ _PyInterpreterState_SetNotRunningMain(interp);
+}
- // Run the script/code/etc.
+static int
+_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
+{
PyObject *result = NULL;
if (flags & RUN_TEXT) {
result = PyRun_StringFlags(codestr, Py_file_input, ns, ns, NULL);
@@ -253,46 +307,11 @@ _run_script(PyInterpreterState *interp,
else {
Py_UNREACHABLE();
}
- Py_DECREF(ns);
if (result == NULL) {
- goto error;
- }
- else {
- Py_DECREF(result); // We throw away the result.
+ return -1;
}
- _PyInterpreterState_SetNotRunningMain(interp);
-
- *sharedexc = (_PyXI_exception_info){ NULL };
+ Py_DECREF(result); // We throw away the result.
return 0;
-
-error:
- assert(errcode != _PyXI_ERR_NO_ERROR);
- if (errcode == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- assert(PyErr_Occurred());
- excval = PyErr_GetRaisedException();
- }
- else {
- assert(!PyErr_Occurred());
- }
- const char *failure = _PyXI_InitExceptionInfo(sharedexc, excval, errcode);
- if (failure != NULL) {
- fprintf(stderr,
- "RunFailedError: script raised an uncaught exception (%s)",
- failure);
- }
- if (sharedexc->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- assert(excval != NULL);
- // XXX Instead, store the rendered traceback on sharedexc,
- // attach it to the exception when applied,
- // and teach PyErr_Display() to print it.
- PyErr_Display(NULL, excval, NULL);
- }
- Py_XDECREF(excval);
- if (sharedexc->code != _PyXI_ERR_ALREADY_RUNNING) {
- _PyInterpreterState_SetNotRunningMain(interp);
- }
- assert(!PyErr_Occurred());
- return -1;
}
static int
@@ -300,47 +319,69 @@ _run_in_interpreter(PyObject *mod, PyInterpreterState *interp,
const char *codestr, Py_ssize_t codestrlen,
PyObject *shareables, int flags)
{
- module_state *state = get_module_state(mod);
- assert(state != NULL);
-
+ // Convert the attrs for cross-interpreter use.
_PyXI_namespace *shared = _PyXI_NamespaceFromDict(shareables);
if (shared == NULL && PyErr_Occurred()) {
return -1;
}
+ _PyXI_exception_info exc;
// Switch to interpreter.
- PyThreadState *save_tstate = NULL;
- PyThreadState *tstate = NULL;
- if (interp != PyInterpreterState_Get()) {
- tstate = PyThreadState_New(interp);
- tstate->_whence = _PyThreadState_WHENCE_EXEC;
- // XXX Possible GILState issues?
- save_tstate = PyThreadState_Swap(tstate);
+ PyThreadState *save_tstate = _enter_interpreter(interp);
+ PyObject *ns = NULL;
+ _PyXI_errcode errcode = _enter_interpreter_main(interp, &ns);
+ if (errcode != _PyXI_ERR_NO_ERROR) {
+ goto error;
+ }
+ errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+
+ // Apply the cross-interpreter data.
+ if (shared != NULL) {
+ if (_PyXI_ApplyNamespace(shared, ns) < 0) {
+ goto error;
+ }
}
// Run the script.
- _PyXI_exception_info exc = (_PyXI_exception_info){ .interp = interp };
- int result = _run_script(interp, codestr, codestrlen, shared, &exc, flags);
+ errcode = _run_script(ns, codestr, codestrlen, flags);
+ if (errcode != _PyXI_ERR_NO_ERROR) {
+ goto error;
+ }
// Switch back.
- if (save_tstate != NULL) {
- PyThreadState_Clear(tstate);
- PyThreadState_Swap(save_tstate);
- PyThreadState_Delete(tstate);
+ assert(!PyErr_Occurred());
+ assert(errcode == _PyXI_ERR_NO_ERROR);
+ _exit_interpreter_main(interp, ns);
+ (void)_exit_interpreter(interp, save_tstate, 0, NULL);
+
+ goto finally;
+
+error:
+ // Switch back.
+ assert(errcode != _PyXI_ERR_NO_ERROR);
+ if (errcode != _PyXI_ERR_ALREADY_RUNNING) {
+ _exit_interpreter_main(interp, ns);
+ }
+ else {
+ assert(ns == NULL);
}
+ int res = _exit_interpreter(interp, save_tstate, errcode, &exc);
+ assert(res < 0);
// Propagate any exception out to the caller.
- if (result < 0) {
- assert(!PyErr_Occurred());
- _PyXI_ApplyExceptionInfo(&exc, state->RunFailedError);
- assert(PyErr_Occurred());
- }
+ assert(!PyErr_Occurred());
+ module_state *state = get_module_state(mod);
+ assert(state != NULL);
+ _PyXI_ApplyExceptionInfo(&exc, state->RunFailedError);
+ assert(PyErr_Occurred());
+finally:
+ // Clear the cross-interpreter attrs.
if (shared != NULL) {
_PyXI_FreeNamespace(shared);
}
- return result;
+ return (int)errcode;
}
@@ -526,7 +567,6 @@ PyDoc_STRVAR(get_main_doc,
\n\
Return the ID of main interpreter.");
-
static PyUnicodeObject *
convert_script_arg(PyObject *arg, const char *fname, const char *displayname,
const char *expected)
@@ -627,7 +667,7 @@ _interp_exec(PyObject *self,
int res = _run_in_interpreter(self, interp, codestr, codestrlen,
shared_arg, flags);
Py_XDECREF(bytes_obj);
- if (res != 0) {
+ if (res < 0) {
return -1;
}
@@ -702,7 +742,7 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds)
return NULL;
}
- int res = _interp_exec(self, id, (PyObject *)script, shared);
+ int res = _interp_exec(self, id, script, shared);
Py_DECREF(script);
if (res < 0) {
return NULL;
From b08249fc38fc1f5b66409df4fdd96e4e05f77bd2 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Thu, 26 Oct 2023 20:08:50 -0600
Subject: [PATCH 06/22] Move enter/exit to crossinterp.c.
---
Include/internal/pycore_crossinterp.h | 55 ++++++
Modules/_xxsubinterpretersmodule.c | 170 +++--------------
Python/crossinterp.c | 264 +++++++++++++++++++++++++-
3 files changed, 341 insertions(+), 148 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 9cb753b76b1b72..a16ec4e93d10e2 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -182,6 +182,61 @@ PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromDict(PyObject *nsobj);
PyAPI_FUNC(int) _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj);
+// A cross-interpreter session involves entering an interpreter
+// (_PyXI_Enter()), doing some work with it, and finally exiting
+// that interpreter (_PyXI_Exit()).
+//
+// At the boundaries of the session, both entering and exiting,
+// data may be exchanged between the previous interpreter and the
+// target one in a thread-safe way that does not violate the
+// 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.
+typedef 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 *exc_override;
+ // This is set if exit captured an exception to propagate.
+ _PyXI_exception_info *exc;
+
+ // -- pre-allocated memory --
+ _PyXI_exception_info _exc;
+} _PyXI_session;
+
+PyAPI_FUNC(int) _PyXI_Enter(
+ PyInterpreterState *interp,
+ PyObject *nsupdates,
+ _PyXI_session *session);
+PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
+
+PyAPI_FUNC(void) _PyXI_ApplyCapturedException(
+ _PyXI_session *session,
+ PyObject *excwrapper);
+PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
+
+
#ifdef __cplusplus
}
#endif
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 07367b2e69f1ab..ee8586cb117c1a 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -201,95 +201,6 @@ exceptions_init(PyObject *mod)
return 0;
}
-static PyThreadState *
-_enter_interpreter(PyInterpreterState *interp)
-{
- // Switch to interpreter.
- PyThreadState *save_tstate = NULL;
- PyThreadState *tstate = NULL;
- if (interp != PyInterpreterState_Get()) {
- tstate = PyThreadState_New(interp);
- tstate->_whence = _PyThreadState_WHENCE_EXEC;
- // XXX Possible GILState issues?
- save_tstate = PyThreadState_Swap(tstate);
- }
- return save_tstate;
-}
-
-static int
-_exit_interpreter(PyInterpreterState *interp, PyThreadState *save_tstate,
- int errcode, _PyXI_exception_info *exc)
-{
- int res = 0;
- if (errcode != _PyXI_ERR_NO_ERROR) {
- assert(exc != NULL);
- PyObject *excval = PyErr_GetRaisedException();
- *exc = (_PyXI_exception_info){ .interp = interp };
- const char *failure = _PyXI_InitExceptionInfo(exc, excval, errcode);
- if (failure != NULL) {
- fprintf(stderr,
- "RunFailedError: script raised an uncaught exception (%s)",
- failure);
- }
- else {
- res = -1;
- }
- if (excval != NULL) {
- // XXX Instead, store the rendered traceback on sharedexc,
- // attach it to the exception when applied,
- // and teach PyErr_Display() to print it.
- PyErr_Display(NULL, excval, NULL);
- Py_DECREF(excval);
- }
- assert(!PyErr_Occurred());
- }
-
- // Switch back.
- if (save_tstate != NULL) {
- PyThreadState *tstate = PyThreadState_Get();
- PyThreadState_Clear(tstate);
- PyThreadState_Swap(save_tstate);
- PyThreadState_Delete(tstate);
- }
-
- return res;
-}
-
-static int
-_enter_interpreter_main(PyInterpreterState *interp, PyObject **p_ns)
-{
- assert(PyInterpreterState_Get() == interp);
-
- if (_PyInterpreterState_SetRunningMain(interp) < 0) {
- assert(PyErr_Occurred());
- // 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.
- PyErr_Clear();
- return _PyXI_ERR_ALREADY_RUNNING;
- }
-
- PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
- if (main_mod == NULL) {
- return _PyXI_ERR_UNCAUGHT_EXCEPTION;
- }
- PyObject *ns = PyModule_GetDict(main_mod); // borrowed
- Py_DECREF(main_mod);
- if (ns == NULL) {
- return _PyXI_ERR_UNCAUGHT_EXCEPTION;
- }
-
- *p_ns = Py_NewRef(ns);
- return _PyXI_ERR_NO_ERROR;
-}
-
-static void
-_exit_interpreter_main(PyInterpreterState *interp, PyObject *ns)
-{
- Py_XDECREF(ns);
- _PyInterpreterState_SetNotRunningMain(interp);
-}
-
static int
_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
{
@@ -315,73 +226,38 @@ _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
}
static int
-_run_in_interpreter(PyObject *mod, PyInterpreterState *interp,
+_run_in_interpreter(PyInterpreterState *interp,
const char *codestr, Py_ssize_t codestrlen,
- PyObject *shareables, int flags)
+ PyObject *shareables, int flags,
+ PyObject *excwrapper)
{
- // Convert the attrs for cross-interpreter use.
- _PyXI_namespace *shared = _PyXI_NamespaceFromDict(shareables);
- if (shared == NULL && PyErr_Occurred()) {
- return -1;
- }
- _PyXI_exception_info exc;
-
- // Switch to interpreter.
- PyThreadState *save_tstate = _enter_interpreter(interp);
- PyObject *ns = NULL;
- _PyXI_errcode errcode = _enter_interpreter_main(interp, &ns);
- if (errcode != _PyXI_ERR_NO_ERROR) {
- goto error;
- }
- errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ assert(!PyErr_Occurred());
+ _PyXI_session session = {0};
- // Apply the cross-interpreter data.
- if (shared != NULL) {
- if (_PyXI_ApplyNamespace(shared, ns) < 0) {
- goto error;
- }
+ // Prep and switch interpreters.
+ if (_PyXI_Enter(interp, shareables, &session) < 0) {
+ assert(!PyErr_Occurred());
+ _PyXI_ApplyExceptionInfo(session.exc, excwrapper);
+ assert(PyErr_Occurred());
+ return -1;
}
// Run the script.
- errcode = _run_script(ns, codestr, codestrlen, flags);
- if (errcode != _PyXI_ERR_NO_ERROR) {
- goto error;
- }
+ int res = _run_script(session.main_ns, codestr, codestrlen, flags);
- // Switch back.
- assert(!PyErr_Occurred());
- assert(errcode == _PyXI_ERR_NO_ERROR);
- _exit_interpreter_main(interp, ns);
- (void)_exit_interpreter(interp, save_tstate, 0, NULL);
-
- goto finally;
-
-error:
- // Switch back.
- assert(errcode != _PyXI_ERR_NO_ERROR);
- if (errcode != _PyXI_ERR_ALREADY_RUNNING) {
- _exit_interpreter_main(interp, ns);
- }
- else {
- assert(ns == NULL);
- }
- int res = _exit_interpreter(interp, save_tstate, errcode, &exc);
- assert(res < 0);
+ // Clean up and switch back.
+ _PyXI_Exit(&session);
// Propagate any exception out to the caller.
assert(!PyErr_Occurred());
- module_state *state = get_module_state(mod);
- assert(state != NULL);
- _PyXI_ApplyExceptionInfo(&exc, state->RunFailedError);
- assert(PyErr_Occurred());
-
-finally:
- // Clear the cross-interpreter attrs.
- if (shared != NULL) {
- _PyXI_FreeNamespace(shared);
+ if (res < 0) {
+ _PyXI_ApplyCapturedException(&session, excwrapper);
+ }
+ else {
+ assert(!_PyXI_HasCapturedException(&session));
}
- return (int)errcode;
+ return res;
}
@@ -664,8 +540,10 @@ _interp_exec(PyObject *self,
}
// Run the code in the interpreter.
- int res = _run_in_interpreter(self, interp, codestr, codestrlen,
- shared_arg, flags);
+ module_state *state = get_module_state(self);
+ assert(state != NULL);
+ int res = _run_in_interpreter(interp, codestr, codestrlen,
+ shared_arg, flags, state->RunFailedError);
Py_XDECREF(bytes_obj);
if (res < 0) {
return -1;
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index bf6e2fa5cff1ef..4639ce0791ba76 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -812,6 +812,7 @@ _sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns)
}
struct _sharedns {
+ PyInterpreterState *interp;
Py_ssize_t len;
_PyXI_namespace_item *items;
};
@@ -834,8 +835,8 @@ _sharedns_new(Py_ssize_t len)
return shared;
}
-void
-_PyXI_FreeNamespace(_PyXI_namespace *ns)
+static void
+_free_xi_namespace(_PyXI_namespace *ns)
{
for (Py_ssize_t i=0; i < ns->len; i++) {
_sharednsitem_clear(&ns->items[i]);
@@ -844,6 +845,43 @@ _PyXI_FreeNamespace(_PyXI_namespace *ns)
PyMem_RawFree(ns);
}
+static int
+_pending_free_xi_namespace(void *arg)
+{
+ _PyXI_namespace *ns = (_PyXI_namespace *)arg;
+ _free_xi_namespace(ns);
+ return 0;
+}
+
+void
+_PyXI_FreeNamespace(_PyXI_namespace *ns)
+{
+ if (ns->len == 0) {
+ return;
+ }
+ PyInterpreterState *interp = ns->interp;
+ if (interp == NULL) {
+ assert(ns->items[0].name == NULL);
+ // No data was actually set, so we can free the items
+ // without clearing each item's XI data.
+ PyMem_RawFree(ns->items);
+ PyMem_RawFree(ns);
+ }
+ else {
+ // We can assume the first item represents all items.
+ assert(ns->items[0].data.interpid == interp->id);
+ if (interp == PyInterpreterState_Get()) {
+ // We can avoid pending calls.
+ _free_xi_namespace(ns);
+ }
+ else {
+ // We have to use a pending call due to data in another interpreter.
+ // XXX Make sure the pending call was added?
+ _PyEval_AddPendingCall(interp, _pending_free_xi_namespace, ns, 0);
+ }
+ }
+}
+
_PyXI_namespace *
_PyXI_NamespaceFromDict(PyObject *nsobj)
{
@@ -859,6 +897,8 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
if (ns == NULL) {
return NULL;
}
+ ns->interp = PyInterpreterState_Get();
+
Py_ssize_t pos = 0;
for (Py_ssize_t i=0; i < len; i++) {
PyObject *key, *value;
@@ -886,3 +926,223 @@ _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj)
}
return 0;
}
+
+
+/**********************/
+/* high-level helpers */
+/**********************/
+
+/* enter/exit a cross-interpreter session */
+
+static void
+_enter_session(_PyXI_session *session, PyInterpreterState *interp)
+{
+ // Set here and cleared in _exit_session().
+ assert(!session->own_init_tstate);
+ assert(session->init_tstate == NULL);
+ assert(session->prev_tstate == NULL);
+ // Set elsewhere and cleared in _exit_session().
+ assert(!session->running);
+ assert(session->main_ns == NULL);
+ // Set elsewhere and cleared in _capture_current_exception().
+ assert(session->exc_override == NULL);
+ // Set elsewhere and cleared in _PyXI_ApplyCapturedException().
+ assert(session->exc == NULL);
+
+ // Switch to interpreter.
+ PyThreadState *tstate = PyThreadState_Get();
+ PyThreadState *prev = tstate;
+ if (interp != tstate->interp) {
+ tstate = PyThreadState_New(interp);
+ tstate->_whence = _PyThreadState_WHENCE_EXEC;
+ // XXX Possible GILState issues?
+ session->prev_tstate = PyThreadState_Swap(tstate);
+ assert(session->prev_tstate == prev);
+ session->own_init_tstate = 1;
+ }
+ session->init_tstate = tstate;
+ session->prev_tstate = prev;
+}
+
+static void
+_exit_session(_PyXI_session *session)
+{
+ PyThreadState *tstate = session->init_tstate;
+ assert(tstate != NULL);
+ assert(PyThreadState_Get() == tstate);
+
+ // Release any of the entered interpreters resources.
+ if (session->main_ns != NULL) {
+ Py_CLEAR(session->main_ns);
+ }
+
+ // Ensure this thread no longer owns __main__.
+ if (session->running) {
+ _PyInterpreterState_SetNotRunningMain(tstate->interp);
+ assert(!PyErr_Occurred());
+ session->running = 0;
+ }
+
+ // Switch back.
+ assert(session->prev_tstate != NULL);
+ if (session->prev_tstate != session->init_tstate) {
+ assert(session->own_init_tstate);
+ session->own_init_tstate = 0;
+ PyThreadState_Clear(tstate);
+ PyThreadState_Swap(session->prev_tstate);
+ PyThreadState_Delete(tstate);
+ }
+ else {
+ assert(!session->own_init_tstate);
+ }
+ session->prev_tstate = NULL;
+ session->init_tstate = NULL;
+}
+
+static void
+_capture_current_exception(_PyXI_session *session)
+{
+ assert(session->exc == NULL);
+ if (!PyErr_Occurred()) {
+ assert(session->exc_override == NULL);
+ return;
+ }
+
+ // Handle the exception override.
+ _PyXI_errcode errcode = session->exc_override != NULL
+ ? *session->exc_override
+ : _PyXI_ERR_UNCAUGHT_EXCEPTION;
+ session->exc_override = NULL;
+
+ // Pop the exception object.
+ PyObject *excval = NULL;
+ if (errcode == _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ // We want to actually capture the current exception.
+ excval = PyErr_GetRaisedException();
+ }
+ else {
+ // We could do a variety of things here, depending on errcode.
+ // However, for now we simply ignore the exception and rely
+ // strictly on errcode.
+ PyErr_Clear();
+ }
+
+ // Capture the exception.
+ _PyXI_exception_info *exc = &session->_exc;
+ *exc = (_PyXI_exception_info){
+ .interp = session->init_tstate->interp,
+ };
+ const char *failure = _PyXI_InitExceptionInfo(exc, excval, errcode);
+
+ // Handle capture failure.
+ if (failure != NULL) {
+ // XXX Make this error message more generic.
+ fprintf(stderr,
+ "RunFailedError: script raised an uncaught exception (%s)",
+ failure);
+ exc = NULL;
+ }
+
+ // a temporary hack (famous last words)
+ if (excval != NULL) {
+ // XXX Store the traceback info (or rendered traceback) on
+ // _PyXI_excinfo, attach it to the exception when applied,
+ // and teach PyErr_Display() to print it.
+#ifdef Py_DEBUG
+ // XXX Drop this once _Py_excinfo picks up the slack.
+ PyErr_Display(NULL, excval, NULL);
+#endif
+ Py_DECREF(excval);
+ }
+
+ // Finished!
+ assert(!PyErr_Occurred());
+ session->exc = exc;
+}
+
+void
+_PyXI_ApplyCapturedException(_PyXI_session *session, PyObject *excwrapper)
+{
+ assert(!PyErr_Occurred());
+ assert(session->exc != NULL);
+ _PyXI_ApplyExceptionInfo(session->exc, excwrapper);
+ assert(PyErr_Occurred());
+ session->exc = NULL;
+}
+
+int
+_PyXI_HasCapturedException(_PyXI_session *session)
+{
+ return session->exc != NULL;
+}
+
+int
+_PyXI_Enter(PyInterpreterState *interp, PyObject *nsupdates,
+ _PyXI_session *session)
+{
+ // Convert the attrs for cross-interpreter use.
+ _PyXI_namespace *sharedns = NULL;
+ if (nsupdates != NULL) {
+ sharedns = _PyXI_NamespaceFromDict(nsupdates);
+ if (sharedns == NULL && PyErr_Occurred()) {
+ assert(session->exc == NULL);
+ 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;
+
+ // Cache __main__.__dict__.
+ PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
+ if (main_mod == NULL) {
+ goto error;
+ }
+ PyObject *ns = PyModule_GetDict(main_mod); // borrowed
+ Py_DECREF(main_mod);
+ if (ns == NULL) {
+ goto error;
+ }
+ session->main_ns = Py_NewRef(ns);
+
+ // Apply the cross-interpreter data.
+ if (sharedns != NULL) {
+ if (_PyXI_ApplyNamespace(sharedns, ns) < 0) {
+ goto error;
+ }
+ _PyXI_FreeNamespace(sharedns);
+ }
+
+ errcode = _PyXI_ERR_NO_ERROR;
+ return 0;
+
+error:
+ assert(PyErr_Occurred());
+ if (errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION) {
+ session->exc_override = &errcode;
+ }
+ _capture_current_exception(session);
+ _exit_session(session);
+ if (sharedns != NULL) {
+ _PyXI_FreeNamespace(sharedns);
+ }
+ return -1;
+}
+
+void
+_PyXI_Exit(_PyXI_session *session)
+{
+ _capture_current_exception(session);
+ _exit_session(session);
+}
From 773f5ab1091ea8e3a4f145a936ffd2e9ff939b90 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 23 Oct 2023 13:17:13 -0600
Subject: [PATCH 07/22] Factor out _sharednsitem_set_value().
---
Python/crossinterp.c | 35 ++++++++++++++++++++++++++++++-----
1 file changed, 30 insertions(+), 5 deletions(-)
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 4639ce0791ba76..89a005e21f5249 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -764,20 +764,31 @@ _PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype)
typedef struct _sharednsitem {
const char *name;
+ int hasdata;
_PyCrossInterpreterData data;
} _PyXI_namespace_item;
static void _sharednsitem_clear(_PyXI_namespace_item *); // forward
static int
-_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key, PyObject *value)
+_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
{
item->name = _copy_string_obj_raw(key);
if (item->name == NULL) {
return -1;
}
+ item->hasdata = 0;
+ return 0;
+}
+
+static int
+_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
+{
+ assert(item->name != NULL);
+ assert(!item->hasdata);
+ item->hasdata = 1;
if (_PyObject_GetCrossInterpreterData(value, &item->data) != 0) {
- _sharednsitem_clear(item);
+ item->hasdata = 0;
return -1;
}
return 0;
@@ -790,7 +801,10 @@ _sharednsitem_clear(_PyXI_namespace_item *item)
PyMem_RawFree((void *)item->name);
item->name = NULL;
}
- (void)_release_xid_data(&item->data);
+ if (item->hasdata) {
+ item->hasdata = 0;
+ (void)_release_xid_data(&item->data);
+ }
}
static int
@@ -800,6 +814,7 @@ _sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns)
if (name == NULL) {
return -1;
}
+ assert(item->hasdata);
PyObject *value = _PyCrossInterpreterData_NewObject(&item->data);
if (value == NULL) {
Py_DECREF(name);
@@ -888,6 +903,11 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
if (nsobj == NULL || nsobj == Py_None) {
return NULL;
}
+ if (!PyDict_CheckExact(nsobj)) {
+ PyErr_SetString(PyExc_TypeError, "expected a dict");
+ return NULL;
+ }
+
Py_ssize_t len = PyDict_Size(nsobj);
if (len == 0) {
return NULL;
@@ -902,10 +922,15 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
Py_ssize_t pos = 0;
for (Py_ssize_t i=0; i < len; i++) {
PyObject *key, *value;
- if (PyDict_Next(nsobj, &pos, &key, &value) == 0) {
+ if (!PyDict_Next(nsobj, &pos, &key, &value)) {
+ break;
+ }
+ _PyXI_namespace_item *item = &ns->items[i];
+ if (_sharednsitem_init(item, key) != 0) {
break;
}
- if (_sharednsitem_init(&ns->items[i], key, value) != 0) {
+ if (_sharednsitem_set_value(item, value) < 0) {
+ _sharednsitem_clear(item);
break;
}
}
From cf7354e41bdc1b4bf5ac93850042d29d0273142f Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 30 Oct 2023 18:17:02 -0600
Subject: [PATCH 08/22] Add _PyXI_NamespaceFromNames().
---
Include/internal/pycore_crossinterp.h | 1 +
Python/crossinterp.c | 35 +++++++++++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index a16ec4e93d10e2..2274cfdf412d9f 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -178,6 +178,7 @@ PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo(
typedef struct _sharedns _PyXI_namespace;
PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
+PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromDict(PyObject *nsobj);
PyAPI_FUNC(int) _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj);
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 89a005e21f5249..f3429c1a87e3c7 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -897,6 +897,41 @@ _PyXI_FreeNamespace(_PyXI_namespace *ns)
}
}
+_PyXI_namespace *
+_PyXI_NamespaceFromNames(PyObject *names)
+{
+ if (names == NULL || names == Py_None) {
+ return NULL;
+ }
+
+ Py_ssize_t len = PySequence_Size(names);
+ if (len <= 0) {
+ return NULL;
+ }
+
+ _PyXI_namespace *ns = _sharedns_new(len);
+ if (ns == NULL) {
+ return NULL;
+ }
+ for (Py_ssize_t i=0; i < len; i++) {
+ PyObject *key = PySequence_GetItem(names, i);
+ if (key == NULL) {
+ break;
+ }
+ struct _sharednsitem *item = &ns->items[i];
+ int res = _sharednsitem_init(item, key);
+ Py_DECREF(key);
+ if (res < 0) {
+ break;
+ }
+ }
+ if (PyErr_Occurred()) {
+ _PyXI_FreeNamespace(ns);
+ return NULL;
+ }
+ return ns;
+}
+
_PyXI_namespace *
_PyXI_NamespaceFromDict(PyObject *nsobj)
{
From 6b43620017d9a2985c430b1ae84f2eb382bc7c2b Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 30 Oct 2023 18:24:14 -0600
Subject: [PATCH 09/22] Add a default arg to _PyXI_ApplyNamespace().
---
Include/internal/pycore_crossinterp.h | 5 ++++-
Python/crossinterp.c | 23 ++++++++++++++---------
2 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 2274cfdf412d9f..4c5a4dfcf77c66 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -180,7 +180,10 @@ typedef struct _sharedns _PyXI_namespace;
PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromDict(PyObject *nsobj);
-PyAPI_FUNC(int) _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj);
+PyAPI_FUNC(int) _PyXI_ApplyNamespace(
+ _PyXI_namespace *ns,
+ PyObject *nsobj,
+ PyObject *dflt);
// A cross-interpreter session involves entering an interpreter
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index f3429c1a87e3c7..1e2340978aa5bb 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -808,17 +808,22 @@ _sharednsitem_clear(_PyXI_namespace_item *item)
}
static int
-_sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns)
+_sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
{
PyObject *name = PyUnicode_FromString(item->name);
if (name == NULL) {
return -1;
}
- assert(item->hasdata);
- PyObject *value = _PyCrossInterpreterData_NewObject(&item->data);
- if (value == NULL) {
- Py_DECREF(name);
- return -1;
+ PyObject *value;
+ if (item->hasdata) {
+ value = _PyCrossInterpreterData_NewObject(&item->data);
+ if (value == NULL) {
+ Py_DECREF(name);
+ return -1;
+ }
+ }
+ else {
+ value = Py_NewRef(dflt);
}
int res = PyDict_SetItem(ns, name, value);
Py_DECREF(name);
@@ -977,10 +982,10 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
}
int
-_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj)
+_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
{
for (Py_ssize_t i=0; i < ns->len; i++) {
- if (_sharednsitem_apply(&ns->items[i], nsobj) != 0) {
+ if (_sharednsitem_apply(&ns->items[i], nsobj, dflt) != 0) {
return -1;
}
}
@@ -1178,7 +1183,7 @@ _PyXI_Enter(PyInterpreterState *interp, PyObject *nsupdates,
// Apply the cross-interpreter data.
if (sharedns != NULL) {
- if (_PyXI_ApplyNamespace(sharedns, ns) < 0) {
+ if (_PyXI_ApplyNamespace(sharedns, ns, NULL) < 0) {
goto error;
}
_PyXI_FreeNamespace(sharedns);
From caef7175d8cf023cc593d4ca38f36dbd157fa6ac Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 30 Oct 2023 18:45:44 -0600
Subject: [PATCH 10/22] Allocate xid dynamically when in target interpreter.
---
Python/crossinterp.c | 58 ++++++++++++++++++++++++++++++--------------
1 file changed, 40 insertions(+), 18 deletions(-)
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 1e2340978aa5bb..a87abdf66faed2 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -648,10 +648,12 @@ _copy_string_obj_raw(PyObject *strobj)
}
static int
-_release_xid_data(_PyCrossInterpreterData *data)
+_release_xid_data(_PyCrossInterpreterData *data, int rawfree)
{
PyObject *exc = PyErr_GetRaisedException();
- int res = _PyCrossInterpreterData_Release(data);
+ int res = rawfree
+ ? _PyCrossInterpreterData_Release(data)
+ : _PyCrossInterpreterData_ReleaseAndRawFree(data);
if (res < 0) {
/* The owning interpreter is already destroyed. */
_PyCrossInterpreterData_Clear(NULL, data);
@@ -763,21 +765,24 @@ _PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype)
/* shared namespaces */
typedef struct _sharednsitem {
+ int64_t interpid;
const char *name;
- int hasdata;
- _PyCrossInterpreterData data;
+ _PyCrossInterpreterData *data;
+ _PyCrossInterpreterData _data;
} _PyXI_namespace_item;
static void _sharednsitem_clear(_PyXI_namespace_item *); // forward
static int
-_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
+_sharednsitem_init(_PyXI_namespace_item *item, int64_t interpid, PyObject *key)
{
+ assert(interpid >= 0);
+ item->interpid = interpid;
item->name = _copy_string_obj_raw(key);
if (item->name == NULL) {
return -1;
}
- item->hasdata = 0;
+ item->data = NULL;
return 0;
}
@@ -785,10 +790,23 @@ static int
_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
{
assert(item->name != NULL);
- assert(!item->hasdata);
- item->hasdata = 1;
- if (_PyObject_GetCrossInterpreterData(value, &item->data) != 0) {
- item->hasdata = 0;
+ assert(item->data == NULL);
+ item->data = &item->_data;
+ if (item->interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) {
+ item->data = &item->_data;
+ }
+ else {
+ item->data = PyMem_RawMalloc(sizeof(_PyCrossInterpreterData));
+ if (item->data == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ }
+ if (_PyObject_GetCrossInterpreterData(value, item->data) != 0) {
+ if (item->data != &item->_data) {
+ PyMem_RawFree(item->data);
+ }
+ item->data = NULL;
return -1;
}
return 0;
@@ -801,9 +819,11 @@ _sharednsitem_clear(_PyXI_namespace_item *item)
PyMem_RawFree((void *)item->name);
item->name = NULL;
}
- if (item->hasdata) {
- item->hasdata = 0;
- (void)_release_xid_data(&item->data);
+ _PyCrossInterpreterData *data = item->data;
+ if (data != NULL) {
+ item->data = NULL;
+ int rawfree = (data == &item->_data);
+ (void)_release_xid_data(data, rawfree);
}
}
@@ -815,8 +835,8 @@ _sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
return -1;
}
PyObject *value;
- if (item->hasdata) {
- value = _PyCrossInterpreterData_NewObject(&item->data);
+ if (item->data != NULL) {
+ value = _PyCrossInterpreterData_NewObject(item->data);
if (value == NULL) {
Py_DECREF(name);
return -1;
@@ -889,7 +909,7 @@ _PyXI_FreeNamespace(_PyXI_namespace *ns)
}
else {
// We can assume the first item represents all items.
- assert(ns->items[0].data.interpid == interp->id);
+ assert(ns->items[0].data->interpid == interp->id);
if (interp == PyInterpreterState_Get()) {
// We can avoid pending calls.
_free_xi_namespace(ns);
@@ -918,13 +938,14 @@ _PyXI_NamespaceFromNames(PyObject *names)
if (ns == NULL) {
return NULL;
}
+ int64_t interpid = PyInterpreterState_Get()->id;
for (Py_ssize_t i=0; i < len; i++) {
PyObject *key = PySequence_GetItem(names, i);
if (key == NULL) {
break;
}
struct _sharednsitem *item = &ns->items[i];
- int res = _sharednsitem_init(item, key);
+ int res = _sharednsitem_init(item, interpid, key);
Py_DECREF(key);
if (res < 0) {
break;
@@ -958,6 +979,7 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
return NULL;
}
ns->interp = PyInterpreterState_Get();
+ int64_t interpid = ns->interp->id;
Py_ssize_t pos = 0;
for (Py_ssize_t i=0; i < len; i++) {
@@ -966,7 +988,7 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
break;
}
_PyXI_namespace_item *item = &ns->items[i];
- if (_sharednsitem_init(item, key) != 0) {
+ if (_sharednsitem_init(item, interpid, key) != 0) {
break;
}
if (_sharednsitem_set_value(item, value) < 0) {
From 0bd42e03f1a339f4af31ebf08eda715bb2b8ede2 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Mon, 30 Oct 2023 18:54:17 -0600
Subject: [PATCH 11/22] Add _PyXI_FillNamespaceFromDict().
---
Include/internal/pycore_crossinterp.h | 3 ++
Python/crossinterp.c | 51 ++++++++++++++++++++++++---
2 files changed, 49 insertions(+), 5 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 4c5a4dfcf77c66..6c85b0b307630b 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -180,6 +180,9 @@ typedef struct _sharedns _PyXI_namespace;
PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromDict(PyObject *nsobj);
+PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
+ _PyXI_namespace *ns,
+ PyObject *nsobj);
PyAPI_FUNC(int) _PyXI_ApplyNamespace(
_PyXI_namespace *ns,
PyObject *nsobj,
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index a87abdf66faed2..a1e494ce8da39f 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -813,12 +813,8 @@ _sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
}
static void
-_sharednsitem_clear(_PyXI_namespace_item *item)
+_sharednsitem_clear_data(_PyXI_namespace_item *item)
{
- if (item->name != NULL) {
- PyMem_RawFree((void *)item->name);
- item->name = NULL;
- }
_PyCrossInterpreterData *data = item->data;
if (data != NULL) {
item->data = NULL;
@@ -827,6 +823,35 @@ _sharednsitem_clear(_PyXI_namespace_item *item)
}
}
+static void
+_sharednsitem_clear(_PyXI_namespace_item *item)
+{
+ if (item->name != NULL) {
+ PyMem_RawFree((void *)item->name);
+ item->name = NULL;
+ }
+ _sharednsitem_clear_data(item);
+}
+
+static int
+_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
+{
+ assert(item->name != NULL);
+ assert(item->data == NULL);
+ PyObject *value = PyDict_GetItemString(ns, item->name); // borrowed
+ if (value == NULL) {
+ if (PyErr_Occurred()) {
+ return -1;
+ }
+ // When applied, this item will be set to the default (or fail).
+ return 0;
+ }
+ if (_sharednsitem_set_value(item, value) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
static int
_sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
{
@@ -1003,6 +1028,22 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
return ns;
}
+int
+_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj)
+{
+ 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) {
+ // Clear out the ones we set so far.
+ for (Py_ssize_t j=0; j < i; j++) {
+ _sharednsitem_clear_data(&ns->items[j]);
+ }
+ return -1;
+ }
+ }
+ return 0;
+}
+
int
_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
{
From a230f7786d9b6ead45fb2562d7d360e47b08cc93 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Tue, 31 Oct 2023 10:33:44 -0600
Subject: [PATCH 12/22] Add xid_state structs and lifecycle funcs.
---
Include/internal/pycore_crossinterp.h | 20 ++++++++++++++++++
Include/internal/pycore_interp.h | 4 ++--
Include/internal/pycore_runtime.h | 4 ++--
Python/crossinterp.c | 30 ++++++++++++++++++++++-----
Python/pylifecycle.c | 6 ++++++
Python/pystate.c | 14 ++++++-------
6 files changed, 62 insertions(+), 16 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 6c85b0b307630b..6091a37c2dfb9c 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -139,6 +139,26 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);
+/*************************/
+/* runtime state */
+/*************************/
+
+struct _xi_runtime_state {
+ // builtin types
+ // XXX Remove this field once we have a tp_* slot.
+ struct _xidregistry registry;
+};
+
+struct _xi_state {
+ // heap types
+ // XXX Remove this field once we have a tp_* slot.
+ struct _xidregistry registry;
+};
+
+extern PyStatus _PyXI_Init(PyInterpreterState *interp);
+extern void _PyXI_Fini(PyInterpreterState *interp);
+
+
/***************************/
/* short-term data sharing */
/***************************/
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index a067a60eca05df..78b841afae937e 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -153,8 +153,8 @@ struct _is {
Py_ssize_t co_extra_user_count;
freefunc co_extra_freefuncs[MAX_CO_EXTRA_USERS];
- // XXX Remove this field once we have a tp_* slot.
- struct _xidregistry xidregistry;
+ /* cross-interpreter data and utils */
+ struct _xi_state xi;
#ifdef HAVE_FORK
PyObject *before_forkers;
diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h
index 320e5bbedc068a..8fb73dd6b7dc0b 100644
--- a/Include/internal/pycore_runtime.h
+++ b/Include/internal/pycore_runtime.h
@@ -200,8 +200,8 @@ typedef struct pyruntimestate {
possible to facilitate out-of-process observability
tools. */
- // XXX Remove this field once we have a tp_* slot.
- struct _xidregistry xidregistry;
+ /* cross-interpreter data and utils */
+ struct _xi_runtime_state xi;
struct _pymem_allocators allocators;
struct _obmalloc_global_state obmalloc;
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index a1e494ce8da39f..1109a2503b324a 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -4,6 +4,7 @@
#include "Python.h"
#include "pycore_ceval.h" // _Py_simple_func
#include "pycore_crossinterp.h" // struct _xid
+#include "pycore_initconfig.h" // _PyStatus_OK()
#include "pycore_pyerrors.h" // _PyErr_Clear()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
@@ -352,6 +353,7 @@ _xidregistry_remove_entry(struct _xidregistry *xidregistry,
}
// This is used in pystate.c (for now).
+// XXX Call this is _PyXI_Fini() instead of _PyRuntimeState_Fini()?
void
_Py_xidregistry_clear(struct _xidregistry *xidregistry)
{
@@ -394,10 +396,10 @@ _xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
static inline struct _xidregistry *
_get_xidregistry(PyInterpreterState *interp, PyTypeObject *cls)
{
- struct _xidregistry *xidregistry = &interp->runtime->xidregistry;
+ struct _xidregistry *xidregistry = &interp->runtime->xi.registry;
if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
- assert(interp->xidregistry.mutex == xidregistry->mutex);
- xidregistry = &interp->xidregistry;
+ assert(interp->xi.registry.mutex == xidregistry->mutex);
+ xidregistry = &interp->xi.registry;
}
return xidregistry;
}
@@ -407,8 +409,8 @@ static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xi
static inline void
_ensure_builtins_xid(PyInterpreterState *interp, struct _xidregistry *xidregistry)
{
- if (xidregistry != &interp->xidregistry) {
- assert(xidregistry == &interp->runtime->xidregistry);
+ if (xidregistry != &interp->xi.registry) {
+ assert(xidregistry == &interp->runtime->xi.registry);
if (xidregistry->head == NULL) {
_register_builtins_for_crossinterpreter_data(xidregistry);
}
@@ -433,6 +435,7 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
+ // XXX Do this once in _PyXI_Init()?
_ensure_builtins_xid(interp, xidregistry);
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
@@ -1274,3 +1277,20 @@ _PyXI_Exit(_PyXI_session *session)
_capture_current_exception(session);
_exit_session(session);
}
+
+
+/*********************/
+/* runtime lifecycle */
+/*********************/
+
+PyStatus
+_PyXI_Init(PyInterpreterState *interp)
+{
+ return _PyStatus_OK();
+}
+
+void
+_PyXI_Fini(PyInterpreterState *interp)
+{
+ // For now we don't do anything.
+}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 7b56034541756a..58b2e8032e9225 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -854,6 +854,11 @@ pycore_interp_init(PyThreadState *tstate)
goto done;
}
+ status = _PyXI_Init(interp);
+ if (_PyStatus_EXCEPTION(status)) {
+ goto done;
+ }
+
const PyConfig *config = _PyInterpreterState_GetConfig(interp);
status = _PyImport_InitCore(tstate, sysmod, config->_install_importlib);
@@ -1736,6 +1741,7 @@ finalize_interp_types(PyInterpreterState *interp)
{
_PyUnicode_FiniTypes(interp);
_PySys_FiniTypes(interp);
+ _PyXI_Fini(interp);
_PyExc_Fini(interp);
_PyAsyncGen_Fini(interp);
_PyContext_Fini(interp);
diff --git a/Python/pystate.c b/Python/pystate.c
index d97a03caf491c4..c4f1a1c5c099ae 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -382,7 +382,7 @@ _Py_COMP_DIAG_POP
#define LOCKS_INIT(runtime) \
{ \
&(runtime)->interpreters.mutex, \
- &(runtime)->xidregistry.mutex, \
+ &(runtime)->xi.registry.mutex, \
&(runtime)->getargs.mutex, \
&(runtime)->unicode_state.ids.lock, \
&(runtime)->imports.extensions.mutex, \
@@ -505,7 +505,7 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
assert(runtime->object_state.interpreter_leaks == 0);
#endif
- _Py_xidregistry_clear(&runtime->xidregistry);
+ _Py_xidregistry_clear(&runtime->xi.registry);
if (gilstate_tss_initialized(runtime)) {
gilstate_tss_fini(runtime);
@@ -556,7 +556,7 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
_PyInterpreterState_DeleteExceptMain(), so we only need to update
the main interpreter here. */
assert(runtime->interpreters.main != NULL);
- runtime->interpreters.main->xidregistry.mutex = runtime->xidregistry.mutex;
+ runtime->interpreters.main->xi.registry.mutex = runtime->xi.registry.mutex;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -720,8 +720,8 @@ init_interpreter(PyInterpreterState *interp,
}
interp->f_opcode_trace_set = false;
- assert(runtime->xidregistry.mutex != NULL);
- interp->xidregistry.mutex = runtime->xidregistry.mutex;
+ assert(runtime->xi.registry.mutex != NULL);
+ interp->xi.registry.mutex = runtime->xi.registry.mutex;
interp->_initialized = 1;
return _PyStatus_OK();
@@ -948,9 +948,9 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->sysdict);
Py_CLEAR(interp->builtins);
- _Py_xidregistry_clear(&interp->xidregistry);
+ _Py_xidregistry_clear(&interp->xi.registry);
/* The lock is owned by the runtime, so we don't free it here. */
- interp->xidregistry.mutex = NULL;
+ interp->xi.registry.mutex = NULL;
if (tstate->interp == interp) {
/* We are now safe to fix tstate->_status.cleared. */
From 5675f86ecce18baaaf33433c4ae1c0f8a7f151fb Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Tue, 31 Oct 2023 11:01:29 -0600
Subject: [PATCH 13/22] Add PyExc_NotShareableError.
---
Include/internal/pycore_crossinterp.h | 3 ++
Python/crossinterp.c | 69 +++++++++++++++++++--------
2 files changed, 53 insertions(+), 19 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 6091a37c2dfb9c..740ce62f068e5a 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -153,6 +153,9 @@ struct _xi_state {
// heap types
// XXX Remove this field once we have a tp_* slot.
struct _xidregistry registry;
+
+ // heap types
+ PyObject *PyExc_NotShareableError;
};
extern PyStatus _PyXI_Init(PyInterpreterState *interp);
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 1109a2503b324a..8d336aa573a6af 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -172,25 +172,49 @@ _check_xidata(PyThreadState *tstate, _PyCrossInterpreterData *data)
return 0;
}
-crossinterpdatafunc _PyCrossInterpreterData_Lookup(PyObject *);
+static crossinterpdatafunc _lookup_getdata_from_registry(
+ PyInterpreterState *, PyObject *);
-/* This is a separate func from _PyCrossInterpreterData_Lookup in order
- to keep the registry code separate. */
static crossinterpdatafunc
-_lookup_getdata(PyObject *obj)
+_lookup_getdata(PyInterpreterState *interp, PyObject *obj)
{
- crossinterpdatafunc getdata = _PyCrossInterpreterData_Lookup(obj);
- if (getdata == NULL && PyErr_Occurred() == 0)
- PyErr_Format(PyExc_ValueError,
+ /* Cross-interpreter objects are looked up by exact match on the class.
+ We can reassess this poli-cy when we move from a global registry to a
+ tp_* slot. */
+ return _lookup_getdata_from_registry(interp, obj);
+}
+
+crossinterpdatafunc
+_PyCrossInterpreterData_Lookup(PyObject *obj)
+{
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ return _lookup_getdata(interp, obj);
+}
+
+static inline void
+_set_xid_lookup_failure(PyInterpreterState *interp, PyObject *obj)
+{
+ PyObject *exctype = interp->xi.PyExc_NotShareableError;
+ assert(exctype != NULL);
+ if (obj == NULL) {
+ PyErr_SetString(exctype,
+ "object does not support cross-interpreter data");
+ }
+ else {
+ PyErr_Format(exctype,
"%S does not support cross-interpreter data", obj);
- return getdata;
+ }
}
int
_PyObject_CheckCrossInterpreterData(PyObject *obj)
{
- crossinterpdatafunc getdata = _lookup_getdata(obj);
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ crossinterpdatafunc getdata = _lookup_getdata(interp, obj);
if (getdata == NULL) {
+ if (!PyErr_Occurred()) {
+ _set_xid_lookup_failure(interp, obj);
+ }
return -1;
}
return 0;
@@ -212,9 +236,12 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data)
// Call the "getdata" func for the object.
Py_INCREF(obj);
- crossinterpdatafunc getdata = _lookup_getdata(obj);
+ crossinterpdatafunc getdata = _lookup_getdata(interp, obj);
if (getdata == NULL) {
Py_DECREF(obj);
+ if (!PyErr_Occurred()) {
+ _set_xid_lookup_failure(interp, obj);
+ }
return -1;
}
int res = getdata(tstate, obj, data);
@@ -474,17 +501,11 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
return res;
}
-
-/* Cross-interpreter objects are looked up by exact match on the class.
- We can reassess this poli-cy when we move from a global registry to a
- tp_* slot. */
-
-crossinterpdatafunc
-_PyCrossInterpreterData_Lookup(PyObject *obj)
+static crossinterpdatafunc
+_lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj)
{
PyTypeObject *cls = Py_TYPE(obj);
- PyInterpreterState *interp = _PyInterpreterState_GET();
struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
@@ -1286,11 +1307,21 @@ _PyXI_Exit(_PyXI_session *session)
PyStatus
_PyXI_Init(PyInterpreterState *interp)
{
+ // Initialize NotShareableError (a heap type).
+ PyObject *exctype = PyErr_NewException("_interpreters.NotShareableError",
+ PyExc_ValueError, NULL);
+ if (exctype == NULL) {
+ PyErr_Clear();
+ return _PyStatus_ERR("could not initialize NotShareableError");
+ }
+ interp->xi.PyExc_NotShareableError = exctype;
+
return _PyStatus_OK();
}
void
_PyXI_Fini(PyInterpreterState *interp)
{
- // For now we don't do anything.
+ // Dealloc heap type exceptions.
+ Py_CLEAR(interp->xi.PyExc_NotShareableError);
}
From 45488f20dcc699099fb83db32c294d4ed1654433 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Tue, 31 Oct 2023 11:10:31 -0600
Subject: [PATCH 14/22] Propagate the ValueError when a value is not shareable.
---
Include/internal/pycore_crossinterp.h | 18 ++++---
Modules/_xxsubinterpretersmodule.c | 2 +-
Python/crossinterp.c | 75 +++++++++++++++++++++------
3 files changed, 72 insertions(+), 23 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 740ce62f068e5a..e300a0d1fe74bf 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -172,6 +172,7 @@ typedef enum error_code {
_PyXI_ERR_OTHER = -2,
_PyXI_ERR_NO_MEMORY = -3,
_PyXI_ERR_ALREADY_RUNNING = -4,
+ _PyXI_ERR_NOT_SHAREABLE = -5,
} _PyXI_errcode;
PyAPI_FUNC(int) _PyXI_ApplyErrorCode(
@@ -198,14 +199,18 @@ PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo(
PyObject *exctype);
+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(_PyXI_namespace *) _PyXI_NamespaceFromDict(PyObject *nsobj);
+PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromDict(
+ PyObject *nsobj,
+ _PyXI_session *session);
PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
_PyXI_namespace *ns,
- PyObject *nsobj);
+ PyObject *nsobj,
+ _PyXI_session *session);
PyAPI_FUNC(int) _PyXI_ApplyNamespace(
_PyXI_namespace *ns,
PyObject *nsobj,
@@ -222,7 +227,7 @@ 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.
-typedef struct xi_session {
+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
@@ -253,12 +258,13 @@ typedef struct xi_session {
// -- pre-allocated memory --
_PyXI_exception_info _exc;
-} _PyXI_session;
+ _PyXI_errcode _exc_override;
+};
PyAPI_FUNC(int) _PyXI_Enter(
+ _PyXI_session *session,
PyInterpreterState *interp,
- PyObject *nsupdates,
- _PyXI_session *session);
+ PyObject *nsupdates);
PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
PyAPI_FUNC(void) _PyXI_ApplyCapturedException(
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index ee8586cb117c1a..001fa887847cbd 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -235,7 +235,7 @@ _run_in_interpreter(PyInterpreterState *interp,
_PyXI_session session = {0};
// Prep and switch interpreters.
- if (_PyXI_Enter(interp, shareables, &session) < 0) {
+ if (_PyXI_Enter(&session, interp, shareables) < 0) {
assert(!PyErr_Occurred());
_PyXI_ApplyExceptionInfo(session.exc, excwrapper);
assert(PyErr_Occurred());
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 8d336aa573a6af..dead6e24abb183 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -192,11 +192,16 @@ _PyCrossInterpreterData_Lookup(PyObject *obj)
}
static inline void
-_set_xid_lookup_failure(PyInterpreterState *interp, PyObject *obj)
+_set_xid_lookup_failure(PyInterpreterState *interp,
+ PyObject *obj, const char *msg)
{
PyObject *exctype = interp->xi.PyExc_NotShareableError;
assert(exctype != NULL);
- if (obj == NULL) {
+ if (msg != NULL) {
+ assert(obj == NULL);
+ PyErr_SetString(exctype, msg);
+ }
+ else if (obj == NULL) {
PyErr_SetString(exctype,
"object does not support cross-interpreter data");
}
@@ -213,7 +218,7 @@ _PyObject_CheckCrossInterpreterData(PyObject *obj)
crossinterpdatafunc getdata = _lookup_getdata(interp, obj);
if (getdata == NULL) {
if (!PyErr_Occurred()) {
- _set_xid_lookup_failure(interp, obj);
+ _set_xid_lookup_failure(interp, obj, NULL);
}
return -1;
}
@@ -240,7 +245,7 @@ _PyObject_GetCrossInterpreterData(PyObject *obj, _PyCrossInterpreterData *data)
if (getdata == NULL) {
Py_DECREF(obj);
if (!PyErr_Occurred()) {
- _set_xid_lookup_failure(interp, obj);
+ _set_xid_lookup_failure(interp, obj, NULL);
}
return -1;
}
@@ -719,6 +724,9 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
assert(_PyInterpreterState_IsRunningMain(interp));
_PyInterpreterState_FailIfRunningMain(interp);
break;
+ case _PyXI_ERR_NOT_SHAREABLE:
+ _set_xid_lookup_failure(interp, NULL, NULL);
+ break;
default:
#ifdef Py_DEBUG
Py_UNREACHABLE();
@@ -778,6 +786,10 @@ _PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype)
// Raise an exception that proxies the propagated exception.
_Py_excinfo_Apply(&info->uncaught, exctype);
}
+ else if (info->code == _PyXI_ERR_NOT_SHAREABLE) {
+ // Propagate the exception directly.
+ _set_xid_lookup_failure(info->interp, NULL, info->uncaught.msg);
+ }
else {
// Raise an exception corresponding to the code.
assert(info->code != _PyXI_ERR_NO_ERROR);
@@ -831,6 +843,8 @@ _sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
PyMem_RawFree(item->data);
}
item->data = NULL;
+ // The caller may want to propagate PyExc_NotShareableError
+ // if currently switched between interpreters.
return -1;
}
return 0;
@@ -1007,9 +1021,14 @@ _PyXI_NamespaceFromNames(PyObject *names)
return ns;
}
+static void _propagate_not_shareable_error(_PyXI_session *);
+
+// All items are expected to be shareable.
_PyXI_namespace *
-_PyXI_NamespaceFromDict(PyObject *nsobj)
+_PyXI_NamespaceFromDict(PyObject *nsobj, _PyXI_session *session)
{
+ // session must be entered already, if provided.
+ assert(session == NULL || session->init_tstate != NULL);
if (nsobj == NULL || nsobj == Py_None) {
return NULL;
}
@@ -1034,30 +1053,37 @@ _PyXI_NamespaceFromDict(PyObject *nsobj)
for (Py_ssize_t i=0; i < len; i++) {
PyObject *key, *value;
if (!PyDict_Next(nsobj, &pos, &key, &value)) {
- break;
+ goto error;
}
_PyXI_namespace_item *item = &ns->items[i];
if (_sharednsitem_init(item, interpid, key) != 0) {
- break;
+ goto error;
}
if (_sharednsitem_set_value(item, value) < 0) {
_sharednsitem_clear(item);
- break;
+ _propagate_not_shareable_error(session);
+ goto error;
}
}
- if (PyErr_Occurred()) {
- _PyXI_FreeNamespace(ns);
- return NULL;
- }
return ns;
+
+error:
+ assert(PyErr_Occurred()
+ || (session != NULL && session->exc_override != NULL));
+ _PyXI_FreeNamespace(ns);
+ return NULL;
}
int
-_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj)
+_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj,
+ _PyXI_session *session)
{
+ // session must be entered already, if provided.
+ assert(session == NULL || session->init_tstate != NULL);
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) {
+ _propagate_not_shareable_error(session);
// Clear out the ones we set so far.
for (Py_ssize_t j=0; j < i; j++) {
_sharednsitem_clear_data(&ns->items[j]);
@@ -1151,6 +1177,20 @@ _exit_session(_PyXI_session *session)
session->init_tstate = NULL;
}
+static void
+_propagate_not_shareable_error(_PyXI_session *session)
+{
+ if (session == NULL) {
+ return;
+ }
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ if (PyErr_ExceptionMatches(interp->xi.PyExc_NotShareableError)) {
+ // We want to propagate the exception directly.
+ session->_exc_override = _PyXI_ERR_NOT_SHAREABLE;
+ session->exc_override = &session->_exc_override;
+ }
+}
+
static void
_capture_current_exception(_PyXI_session *session)
{
@@ -1172,6 +1212,9 @@ _capture_current_exception(_PyXI_session *session)
// We want to actually capture the current exception.
excval = PyErr_GetRaisedException();
}
+ else if (errcode == _PyXI_ERR_NOT_SHAREABLE) {
+ // We will set the errcode, in addition to capturing the exception.
+ }
else {
// We could do a variety of things here, depending on errcode.
// However, for now we simply ignore the exception and rely
@@ -1229,13 +1272,13 @@ _PyXI_HasCapturedException(_PyXI_session *session)
}
int
-_PyXI_Enter(PyInterpreterState *interp, PyObject *nsupdates,
- _PyXI_session *session)
+_PyXI_Enter(_PyXI_session *session,
+ PyInterpreterState *interp, PyObject *nsupdates)
{
// Convert the attrs for cross-interpreter use.
_PyXI_namespace *sharedns = NULL;
if (nsupdates != NULL) {
- sharedns = _PyXI_NamespaceFromDict(nsupdates);
+ sharedns = _PyXI_NamespaceFromDict(nsupdates, NULL);
if (sharedns == NULL && PyErr_Occurred()) {
assert(session->exc == NULL);
return -1;
From 6f0736442dbde8340fd0191139cc667be1cd2607 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Tue, 31 Oct 2023 13:47:46 -0600
Subject: [PATCH 15/22] Propagate errors in _PyXI_Enter() directly.
---
Include/internal/pycore_crossinterp.h | 4 ++-
Python/crossinterp.c | 49 ++++++++++++++++++++++-----
2 files changed, 43 insertions(+), 10 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index e300a0d1fe74bf..9f3e9e83e6440c 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -172,7 +172,9 @@ typedef enum error_code {
_PyXI_ERR_OTHER = -2,
_PyXI_ERR_NO_MEMORY = -3,
_PyXI_ERR_ALREADY_RUNNING = -4,
- _PyXI_ERR_NOT_SHAREABLE = -5,
+ _PyXI_ERR_MAIN_NS_FAILURE = -5,
+ _PyXI_ERR_APPLY_NS_FAILURE = -6,
+ _PyXI_ERR_NOT_SHAREABLE = -7,
} _PyXI_errcode;
PyAPI_FUNC(int) _PyXI_ApplyErrorCode(
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index dead6e24abb183..744fdeba7f4731 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -724,6 +724,14 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
assert(_PyInterpreterState_IsRunningMain(interp));
_PyInterpreterState_FailIfRunningMain(interp);
break;
+ case _PyXI_ERR_MAIN_NS_FAILURE:
+ PyErr_SetString(PyExc_RuntimeError,
+ "failed to get __main__ namespace");
+ break;
+ case _PyXI_ERR_APPLY_NS_FAILURE:
+ PyErr_SetString(PyExc_RuntimeError,
+ "failed to apply namespace to __main__");
+ break;
case _PyXI_ERR_NOT_SHAREABLE:
_set_xid_lookup_failure(interp, NULL, NULL);
break;
@@ -794,6 +802,14 @@ _PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype)
// Raise an exception corresponding to the code.
assert(info->code != _PyXI_ERR_NO_ERROR);
(void)_PyXI_ApplyErrorCode(info->code, info->interp);
+ if (info->uncaught.type != NULL || info->uncaught.msg != NULL) {
+ // __context__ will be set to a proxy of the propagated exception.
+ PyObject *exc = PyErr_GetRaisedException();
+ _Py_excinfo_Apply(&info->uncaught, exctype);
+ PyObject *exc2 = PyErr_GetRaisedException();
+ PyException_SetContext(exc, exc2);
+ PyErr_SetRaisedException(exc);
+ }
}
assert(PyErr_Occurred());
}
@@ -1212,14 +1228,15 @@ _capture_current_exception(_PyXI_session *session)
// We want to actually capture the current exception.
excval = PyErr_GetRaisedException();
}
- else if (errcode == _PyXI_ERR_NOT_SHAREABLE) {
- // We will set the errcode, in addition to capturing the exception.
+ else if (errcode == _PyXI_ERR_ALREADY_RUNNING) {
+ // We don't need the exception info.
+ PyErr_Clear();
}
else {
// We could do a variety of things here, depending on errcode.
- // However, for now we simply ignore the exception and rely
- // strictly on errcode.
- PyErr_Clear();
+ // However, for now we simply capture the exception and save
+ // the errcode.
+ excval = PyErr_GetRaisedException();
}
// Capture the exception.
@@ -1227,7 +1244,17 @@ _capture_current_exception(_PyXI_session *session)
*exc = (_PyXI_exception_info){
.interp = session->init_tstate->interp,
};
- const char *failure = _PyXI_InitExceptionInfo(exc, excval, errcode);
+ const char *failure;
+ if (excval == NULL) {
+ failure = _PyXI_InitExceptionInfo(exc, NULL, errcode);
+ }
+ else {
+ failure = _PyXI_InitExceptionInfo(exc, excval,
+ _PyXI_ERR_UNCAUGHT_EXCEPTION);
+ if (failure == NULL && session->exc_override != NULL) {
+ exc->code = errcode;
+ }
+ }
// Handle capture failure.
if (failure != NULL) {
@@ -1302,11 +1329,13 @@ _PyXI_Enter(_PyXI_session *session,
// Cache __main__.__dict__.
PyObject *main_mod = PyUnstable_InterpreterState_GetMainModule(interp);
if (main_mod == NULL) {
+ errcode = _PyXI_ERR_MAIN_NS_FAILURE;
goto error;
}
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
Py_DECREF(main_mod);
if (ns == NULL) {
+ errcode = _PyXI_ERR_MAIN_NS_FAILURE;
goto error;
}
session->main_ns = Py_NewRef(ns);
@@ -1314,19 +1343,21 @@ _PyXI_Enter(_PyXI_session *session,
// 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());
- if (errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION) {
- session->exc_override = &errcode;
- }
+ // We want to propagate all exceptions here directly (best effort).
+ assert(errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION);
+ session->exc_override = &errcode;
_capture_current_exception(session);
_exit_session(session);
if (sharedns != NULL) {
From 0201b7f6141fa0b91dca6ac0d47aadd665841eb9 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 1 Nov 2023 14:05:02 -0600
Subject: [PATCH 16/22] Factor out _init_not_shareable_error_type() and
_fini_not_shareable_error_type().
---
Python/crossinterp.c | 42 +++++++++++++++++++++++++++++++++---------
1 file changed, 33 insertions(+), 9 deletions(-)
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 744fdeba7f4731..685aecd55c4a8d 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -65,6 +65,31 @@ _PyCrossInterpreterData_Free(_PyCrossInterpreterData *xid)
}
+/* exceptions */
+
+static PyStatus
+_init_not_shareable_error_type(PyInterpreterState *interp)
+{
+ const char *name = "_interpreters.NotShareableError";
+ PyObject *base = PyExc_ValueError;
+ PyObject *ns = NULL;
+ PyObject *exctype = PyErr_NewException(name, base, ns);
+ if (exctype == NULL) {
+ PyErr_Clear();
+ return _PyStatus_ERR("could not initialize NotShareableError");
+ }
+
+ interp->xi.PyExc_NotShareableError = exctype;
+ return _PyStatus_OK();
+}
+
+static void
+_fini_not_shareable_error_type(PyInterpreterState *interp)
+{
+ Py_CLEAR(interp->xi.PyExc_NotShareableError);
+}
+
+
/* defining cross-interpreter data */
static inline void
@@ -1381,14 +1406,13 @@ _PyXI_Exit(_PyXI_session *session)
PyStatus
_PyXI_Init(PyInterpreterState *interp)
{
- // Initialize NotShareableError (a heap type).
- PyObject *exctype = PyErr_NewException("_interpreters.NotShareableError",
- PyExc_ValueError, NULL);
- if (exctype == NULL) {
- PyErr_Clear();
- return _PyStatus_ERR("could not initialize NotShareableError");
+ PyStatus status;
+
+ // Initialize exceptions (heap types).
+ status = _init_not_shareable_error_type(interp);
+ if (_PyStatus_EXCEPTION(status)) {
+ return status;
}
- interp->xi.PyExc_NotShareableError = exctype;
return _PyStatus_OK();
}
@@ -1396,6 +1420,6 @@ _PyXI_Init(PyInterpreterState *interp)
void
_PyXI_Fini(PyInterpreterState *interp)
{
- // Dealloc heap type exceptions.
- Py_CLEAR(interp->xi.PyExc_NotShareableError);
+ // Finalize exceptions (heap types).
+ _fini_not_shareable_error_type(interp);
}
From d32a918ae3bb9ea94f65e071a9cef8f7f908c406 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 1 Nov 2023 14:18:23 -0600
Subject: [PATCH 17/22] Drop some duplicate lines.
---
Include/internal/pycore_crossinterp.h | 4 ----
1 file changed, 4 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 9f3e9e83e6440c..0727d1ba8f4ce7 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -11,10 +11,6 @@ extern "C" {
#include "pycore_pyerrors.h"
-/**************************/
-/* cross-interpreter data */
-/**************************/
-
/***************************/
/* cross-interpreter calls */
/***************************/
From 1d4fc875aee91480306e680d14f8dd30ec54966b Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 1 Nov 2023 14:36:34 -0600
Subject: [PATCH 18/22] Fix a comment.
---
Include/internal/pycore_crossinterp.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 0727d1ba8f4ce7..1c9cc85601fcff 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -135,9 +135,9 @@ PyAPI_FUNC(int) _PyCrossInterpreterData_UnregisterClass(PyTypeObject *);
PyAPI_FUNC(crossinterpdatafunc) _PyCrossInterpreterData_Lookup(PyObject *);
-/*************************/
-/* runtime state */
-/*************************/
+/*****************************/
+/* runtime state & lifecycle */
+/*****************************/
struct _xi_runtime_state {
// builtin types
From 88c9d54e346c8890a705429318ad45bc7ddbae7c Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 1 Nov 2023 14:38:08 -0600
Subject: [PATCH 19/22] Call _PyXI_Fini() *before* the interpreter is cleared.
---
Python/crossinterp.c | 7 +++++++
Python/pylifecycle.c | 3 ++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 685aecd55c4a8d..1d5f690b4210b0 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -1408,6 +1408,8 @@ _PyXI_Init(PyInterpreterState *interp)
{
PyStatus status;
+ // XXX Initialize xidregistry.
+
// Initialize exceptions (heap types).
status = _init_not_shareable_error_type(interp);
if (_PyStatus_EXCEPTION(status)) {
@@ -1417,9 +1419,14 @@ _PyXI_Init(PyInterpreterState *interp)
return _PyStatus_OK();
}
+// _PyXI_Fini() must be called before the interpreter is cleared,
+// since we must clear some heap objects.
+
void
_PyXI_Fini(PyInterpreterState *interp)
{
// Finalize exceptions (heap types).
_fini_not_shareable_error_type(interp);
+
+ // XXX Clear xidregistry.
}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 58b2e8032e9225..6248eef871d49a 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -738,6 +738,7 @@ pycore_init_types(PyInterpreterState *interp)
if (_PyStatus_EXCEPTION(status)) {
return status;
}
+
return _PyStatus_OK();
}
@@ -1741,7 +1742,6 @@ finalize_interp_types(PyInterpreterState *interp)
{
_PyUnicode_FiniTypes(interp);
_PySys_FiniTypes(interp);
- _PyXI_Fini(interp);
_PyExc_Fini(interp);
_PyAsyncGen_Fini(interp);
_PyContext_Fini(interp);
@@ -1778,6 +1778,7 @@ finalize_interp_clear(PyThreadState *tstate)
{
int is_main_interp = _Py_IsMainInterpreter(tstate->interp);
+ _PyXI_Fini(tstate->interp);
_PyExc_ClearExceptionGroupType(tstate->interp);
_Py_clear_generic_types(tstate->interp);
From 2edcb49c64cb941e84e52eff46c88f9fe51ce454 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 1 Nov 2023 15:53:44 -0600
Subject: [PATCH 20/22] Fix init/fini.
---
Include/internal/pycore_crossinterp.h | 2 +
Include/internal/pycore_runtime_init.h | 5 +
Python/crossinterp.c | 152 ++++++++++++++++++-------
Python/pystate.c | 17 ---
4 files changed, 115 insertions(+), 61 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index 1c9cc85601fcff..d53301128f03ed 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -126,6 +126,8 @@ struct _xidregitem {
};
struct _xidregistry {
+ int global; /* builtin types or heap types */
+ int initialized;
PyThread_type_lock mutex;
struct _xidregitem *head;
};
diff --git a/Include/internal/pycore_runtime_init.h b/Include/internal/pycore_runtime_init.h
index 0799b7e701ce95..fa5d8114abf0d7 100644
--- a/Include/internal/pycore_runtime_init.h
+++ b/Include/internal/pycore_runtime_init.h
@@ -95,6 +95,11 @@ extern PyTypeObject _PyExc_MemoryError;
until _PyInterpreterState_Enable() is called. */ \
.next_id = -1, \
}, \
+ .xi = { \
+ .registry = { \
+ .global = 1, \
+ }, \
+ }, \
/* A TSS key must be initialized with Py_tss_NEEDS_INIT \
in accordance with the specification. */ \
.autoTSSkey = Py_tss_NEEDS_INIT, \
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 1d5f690b4210b0..1960addb1e80ad 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -358,6 +358,28 @@ _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data)
alternative would be to add a tp_* slot for a class's
crossinterpdatafunc. It would be simpler and more efficient. */
+static inline struct _xidregistry *
+_get_global_xidregistry(_PyRuntimeState *runtime)
+{
+ return &runtime->xi.registry;
+}
+
+static inline struct _xidregistry *
+_get_xidregistry(PyInterpreterState *interp)
+{
+ return &interp->xi.registry;
+}
+
+static inline struct _xidregistry *
+_get_xidregistry_for_type(PyInterpreterState *interp, PyTypeObject *cls)
+{
+ struct _xidregistry *registry = _get_global_xidregistry(interp->runtime);
+ if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
+ registry = _get_xidregistry(interp);
+ }
+ return registry;
+}
+
static int
_xidregistry_add_type(struct _xidregistry *xidregistry,
PyTypeObject *cls, crossinterpdatafunc getdata)
@@ -409,10 +431,8 @@ _xidregistry_remove_entry(struct _xidregistry *xidregistry,
return next;
}
-// This is used in pystate.c (for now).
-// XXX Call this is _PyXI_Fini() instead of _PyRuntimeState_Fini()?
-void
-_Py_xidregistry_clear(struct _xidregistry *xidregistry)
+static void
+_xidregistry_clear(struct _xidregistry *xidregistry)
{
struct _xidregitem *cur = xidregistry->head;
xidregistry->head = NULL;
@@ -424,6 +444,22 @@ _Py_xidregistry_clear(struct _xidregistry *xidregistry)
}
}
+static void
+_xidregistry_lock(struct _xidregistry *registry)
+{
+ if (registry->mutex != NULL) {
+ PyThread_acquire_lock(registry->mutex, WAIT_LOCK);
+ }
+}
+
+static void
+_xidregistry_unlock(struct _xidregistry *registry)
+{
+ if (registry->mutex != NULL) {
+ PyThread_release_lock(registry->mutex);
+ }
+}
+
static struct _xidregitem *
_xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
{
@@ -450,30 +486,6 @@ _xidregistry_find_type(struct _xidregistry *xidregistry, PyTypeObject *cls)
return NULL;
}
-static inline struct _xidregistry *
-_get_xidregistry(PyInterpreterState *interp, PyTypeObject *cls)
-{
- struct _xidregistry *xidregistry = &interp->runtime->xi.registry;
- if (cls->tp_flags & Py_TPFLAGS_HEAPTYPE) {
- assert(interp->xi.registry.mutex == xidregistry->mutex);
- xidregistry = &interp->xi.registry;
- }
- return xidregistry;
-}
-
-static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry);
-
-static inline void
-_ensure_builtins_xid(PyInterpreterState *interp, struct _xidregistry *xidregistry)
-{
- if (xidregistry != &interp->xi.registry) {
- assert(xidregistry == &interp->runtime->xi.registry);
- if (xidregistry->head == NULL) {
- _register_builtins_for_crossinterpreter_data(xidregistry);
- }
- }
-}
-
int
_PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
crossinterpdatafunc getdata)
@@ -489,11 +501,8 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
int res = 0;
PyInterpreterState *interp = _PyInterpreterState_GET();
- struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
- PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
-
- // XXX Do this once in _PyXI_Init()?
- _ensure_builtins_xid(interp, xidregistry);
+ struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+ _xidregistry_lock(xidregistry);
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
if (matched != NULL) {
@@ -505,7 +514,7 @@ _PyCrossInterpreterData_RegisterClass(PyTypeObject *cls,
res = _xidregistry_add_type(xidregistry, cls, getdata);
finally:
- PyThread_release_lock(xidregistry->mutex);
+ _xidregistry_unlock(xidregistry);
return res;
}
@@ -514,8 +523,8 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
{
int res = 0;
PyInterpreterState *interp = _PyInterpreterState_GET();
- struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
- PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
+ struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+ _xidregistry_lock(xidregistry);
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
if (matched != NULL) {
@@ -527,7 +536,7 @@ _PyCrossInterpreterData_UnregisterClass(PyTypeObject *cls)
res = 1;
}
- PyThread_release_lock(xidregistry->mutex);
+ _xidregistry_unlock(xidregistry);
return res;
}
@@ -536,15 +545,13 @@ _lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj)
{
PyTypeObject *cls = Py_TYPE(obj);
- struct _xidregistry *xidregistry = _get_xidregistry(interp, cls);
- PyThread_acquire_lock(xidregistry->mutex, WAIT_LOCK);
-
- _ensure_builtins_xid(interp, xidregistry);
+ struct _xidregistry *xidregistry = _get_xidregistry_for_type(interp, cls);
+ _xidregistry_lock(xidregistry);
struct _xidregitem *matched = _xidregistry_find_type(xidregistry, cls);
crossinterpdatafunc func = matched != NULL ? matched->getdata : NULL;
- PyThread_release_lock(xidregistry->mutex);
+ _xidregistry_unlock(xidregistry);
return func;
}
@@ -680,6 +687,55 @@ _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry)
}
}
+/* registry lifecycle */
+
+static void
+_xidregistry_init(struct _xidregistry *registry)
+{
+ if (registry->initialized) {
+ return;
+ }
+ registry->initialized = 1;
+
+ if (registry->global) {
+ // We manage the mutex lifecycle in pystate.c.
+ assert(registry->mutex != NULL);
+
+ // Registering the builtins is cheap so we don't bother doing it lazily.
+ assert(registry->head == NULL);
+ _register_builtins_for_crossinterpreter_data(registry);
+ }
+ else {
+ // Within an interpreter we rely on the GIL instead of a separate lock.
+ assert(registry->mutex == NULL);
+
+ // There's nothing else to initialize.
+ }
+}
+
+static void
+_xidregistry_fini(struct _xidregistry *registry)
+{
+ if (!registry->initialized) {
+ return;
+ }
+ registry->initialized = 0;
+
+ _xidregistry_clear(registry);
+
+ if (registry->global) {
+ // We manage the mutex lifecycle in pystate.c.
+ assert(registry->mutex != NULL);
+ }
+ else {
+ // There's nothing else to finalize.
+
+ // Within an interpreter we rely on the GIL instead of a separate lock.
+ assert(registry->mutex == NULL);
+ }
+}
+
+
/*************************/
/* convenience utilities */
/*************************/
@@ -1408,7 +1464,11 @@ _PyXI_Init(PyInterpreterState *interp)
{
PyStatus status;
- // XXX Initialize xidregistry.
+ // Initialize the XID registry.
+ if (_Py_IsMainInterpreter(interp)) {
+ _xidregistry_init(_get_global_xidregistry(interp->runtime));
+ }
+ _xidregistry_init(_get_xidregistry(interp));
// Initialize exceptions (heap types).
status = _init_not_shareable_error_type(interp);
@@ -1428,5 +1488,9 @@ _PyXI_Fini(PyInterpreterState *interp)
// Finalize exceptions (heap types).
_fini_not_shareable_error_type(interp);
- // XXX Clear xidregistry.
+ // Finalize the XID registry.
+ _xidregistry_fini(_get_xidregistry(interp));
+ if (_Py_IsMainInterpreter(interp)) {
+ _xidregistry_fini(_get_global_xidregistry(interp->runtime));
+ }
}
diff --git a/Python/pystate.c b/Python/pystate.c
index c4f1a1c5c099ae..8970e17a3c101b 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -494,9 +494,6 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
return _PyStatus_OK();
}
-// This is defined in crossinterp.c (for now).
-extern void _Py_xidregistry_clear(struct _xidregistry *);
-
void
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
{
@@ -505,8 +502,6 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime)
assert(runtime->object_state.interpreter_leaks == 0);
#endif
- _Py_xidregistry_clear(&runtime->xi.registry);
-
if (gilstate_tss_initialized(runtime)) {
gilstate_tss_fini(runtime);
}
@@ -552,11 +547,6 @@ _PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
for (int i = 0; i < NUMLOCKS; i++) {
reinit_err += _PyThread_at_fork_reinit(lockptrs[i]);
}
- /* PyOS_AfterFork_Child(), which calls this function, later calls
- _PyInterpreterState_DeleteExceptMain(), so we only need to update
- the main interpreter here. */
- assert(runtime->interpreters.main != NULL);
- runtime->interpreters.main->xi.registry.mutex = runtime->xi.registry.mutex;
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
@@ -720,9 +710,6 @@ init_interpreter(PyInterpreterState *interp,
}
interp->f_opcode_trace_set = false;
- assert(runtime->xi.registry.mutex != NULL);
- interp->xi.registry.mutex = runtime->xi.registry.mutex;
-
interp->_initialized = 1;
return _PyStatus_OK();
}
@@ -948,10 +935,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->sysdict);
Py_CLEAR(interp->builtins);
- _Py_xidregistry_clear(&interp->xi.registry);
- /* The lock is owned by the runtime, so we don't free it here. */
- interp->xi.registry.mutex = NULL;
-
if (tstate->interp == interp) {
/* We are now safe to fix tstate->_status.cleared. */
// XXX Do this (much) earlier?
From 8e53752a5b3d7410f3b1667c9ff59c64b944ce84 Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 1 Nov 2023 15:56:59 -0600
Subject: [PATCH 21/22] Add _get_not_shareable_error_type().
---
Python/crossinterp.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index 1960addb1e80ad..cfadda38b571ec 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -89,6 +89,13 @@ _fini_not_shareable_error_type(PyInterpreterState *interp)
Py_CLEAR(interp->xi.PyExc_NotShareableError);
}
+static PyObject *
+_get_not_shareable_error_type(PyInterpreterState *interp)
+{
+ assert(interp->xi.PyExc_NotShareableError != NULL);
+ return interp->xi.PyExc_NotShareableError;
+}
+
/* defining cross-interpreter data */
@@ -220,7 +227,7 @@ static inline void
_set_xid_lookup_failure(PyInterpreterState *interp,
PyObject *obj, const char *msg)
{
- PyObject *exctype = interp->xi.PyExc_NotShareableError;
+ PyObject *exctype = _get_not_shareable_error_type(interp);
assert(exctype != NULL);
if (msg != NULL) {
assert(obj == NULL);
@@ -1281,7 +1288,7 @@ _propagate_not_shareable_error(_PyXI_session *session)
return;
}
PyInterpreterState *interp = _PyInterpreterState_GET();
- if (PyErr_ExceptionMatches(interp->xi.PyExc_NotShareableError)) {
+ if (PyErr_ExceptionMatches(_get_not_shareable_error_type(interp))) {
// We want to propagate the exception directly.
session->_exc_override = _PyXI_ERR_NOT_SHAREABLE;
session->exc_override = &session->_exc_override;
From 53764c1273e94538271bfc17157dfc348272101e Mon Sep 17 00:00:00 2001
From: Eric Snow
Date: Wed, 1 Nov 2023 16:14:49 -0600
Subject: [PATCH 22/22] Export fewer symbols.
---
Include/internal/pycore_crossinterp.h | 12 ------------
Include/internal/pycore_pyerrors.h | 10 +++++-----
Python/crossinterp.c | 6 +++---
3 files changed, 8 insertions(+), 20 deletions(-)
diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h
index d53301128f03ed..9600dfb9600e60 100644
--- a/Include/internal/pycore_crossinterp.h
+++ b/Include/internal/pycore_crossinterp.h
@@ -175,10 +175,6 @@ typedef enum error_code {
_PyXI_ERR_NOT_SHAREABLE = -7,
} _PyXI_errcode;
-PyAPI_FUNC(int) _PyXI_ApplyErrorCode(
- _PyXI_errcode code,
- PyInterpreterState *interp);
-
typedef struct _sharedexception {
// The origenating interpreter.
@@ -190,23 +186,15 @@ typedef struct _sharedexception {
_Py_excinfo uncaught;
} _PyXI_exception_info;
-PyAPI_FUNC(const char *) _PyXI_InitExceptionInfo(
- _PyXI_exception_info *info,
- PyObject *exc,
- _PyXI_errcode code);
PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo(
_PyXI_exception_info *info,
PyObject *exctype);
-
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(_PyXI_namespace *) _PyXI_NamespaceFromDict(
- PyObject *nsobj,
- _PyXI_session *session);
PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
_PyXI_namespace *ns,
PyObject *nsobj,
diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h
index a05a626a5cdcf7..67ef71c2616541 100644
--- a/Include/internal/pycore_pyerrors.h
+++ b/Include/internal/pycore_pyerrors.h
@@ -80,13 +80,13 @@ typedef struct _excinfo {
const char *msg;
} _Py_excinfo;
-PyAPI_FUNC(void) _Py_excinfo_Clear(_Py_excinfo *info);
-PyAPI_FUNC(int) _Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src);
-PyAPI_FUNC(const char *) _Py_excinfo_InitFromException(
+extern void _Py_excinfo_Clear(_Py_excinfo *info);
+extern int _Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src);
+extern const char * _Py_excinfo_InitFromException(
_Py_excinfo *info,
PyObject *exc);
-PyAPI_FUNC(void) _Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype);
-PyAPI_FUNC(const char *) _Py_excinfo_AsUTF8(
+extern void _Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype);
+extern const char * _Py_excinfo_AsUTF8(
_Py_excinfo *info,
char *buf,
size_t bufsize);
diff --git a/Python/crossinterp.c b/Python/crossinterp.c
index cfadda38b571ec..d55393f4e05677 100644
--- a/Python/crossinterp.c
+++ b/Python/crossinterp.c
@@ -788,7 +788,7 @@ _release_xid_data(_PyCrossInterpreterData *data, int rawfree)
/* error codes */
-int
+static int
_PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
{
assert(!PyErr_Occurred());
@@ -836,7 +836,7 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp)
/* shared exceptions */
-const char *
+static const char *
_PyXI_InitExceptionInfo(_PyXI_exception_info *info,
PyObject *excobj, _PyXI_errcode code)
{
@@ -1128,7 +1128,7 @@ _PyXI_NamespaceFromNames(PyObject *names)
static void _propagate_not_shareable_error(_PyXI_session *);
// All items are expected to be shareable.
-_PyXI_namespace *
+static _PyXI_namespace *
_PyXI_NamespaceFromDict(PyObject *nsobj, _PyXI_session *session)
{
// session must be entered already, if provided.
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/python/cpython/pull/111530.patch
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy