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 originating 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 policy 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 policy 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 originating 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. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy