mirror of
https://github.com/python/cpython.git
synced 2025-07-13 14:25:18 +00:00
gh-132775: Make _PyXI_session Opaque (gh-134452)
This is mostly a refactor to clean things up a bit, most notably the "XI namespace" code. Making the session opaque requires adding the following internal-only functions: * _PyXI_NewSession() * _PyXI_FreeSession() * _PyXI_GetMainNamespace()
This commit is contained in:
parent
ec736e7dae
commit
4a4ac3ab4d
3 changed files with 490 additions and 400 deletions
|
@ -335,24 +335,9 @@ typedef struct _sharedexception {
|
||||||
PyAPI_FUNC(PyObject *) _PyXI_ApplyError(_PyXI_error *err);
|
PyAPI_FUNC(PyObject *) _PyXI_ApplyError(_PyXI_error *err);
|
||||||
|
|
||||||
|
|
||||||
typedef struct xi_session _PyXI_session;
|
|
||||||
typedef struct _sharedns _PyXI_namespace;
|
|
||||||
|
|
||||||
PyAPI_FUNC(void) _PyXI_FreeNamespace(_PyXI_namespace *ns);
|
|
||||||
PyAPI_FUNC(_PyXI_namespace *) _PyXI_NamespaceFromNames(PyObject *names);
|
|
||||||
PyAPI_FUNC(int) _PyXI_FillNamespaceFromDict(
|
|
||||||
_PyXI_namespace *ns,
|
|
||||||
PyObject *nsobj,
|
|
||||||
_PyXI_session *session);
|
|
||||||
PyAPI_FUNC(int) _PyXI_ApplyNamespace(
|
|
||||||
_PyXI_namespace *ns,
|
|
||||||
PyObject *nsobj,
|
|
||||||
PyObject *dflt);
|
|
||||||
|
|
||||||
|
|
||||||
// A cross-interpreter session involves entering an interpreter
|
// A cross-interpreter session involves entering an interpreter
|
||||||
// (_PyXI_Enter()), doing some work with it, and finally exiting
|
// with _PyXI_Enter(), doing some work with it, and finally exiting
|
||||||
// that interpreter (_PyXI_Exit()).
|
// that interpreter with _PyXI_Exit().
|
||||||
//
|
//
|
||||||
// At the boundaries of the session, both entering and exiting,
|
// At the boundaries of the session, both entering and exiting,
|
||||||
// data may be exchanged between the previous interpreter and the
|
// data may be exchanged between the previous interpreter and the
|
||||||
|
@ -360,39 +345,10 @@ PyAPI_FUNC(int) _PyXI_ApplyNamespace(
|
||||||
// isolation between interpreters. This includes setting objects
|
// isolation between interpreters. This includes setting objects
|
||||||
// in the target's __main__ module on the way in, and capturing
|
// in the target's __main__ module on the way in, and capturing
|
||||||
// uncaught exceptions on the way out.
|
// uncaught exceptions on the way out.
|
||||||
struct xi_session {
|
typedef struct xi_session _PyXI_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
|
PyAPI_FUNC(_PyXI_session *) _PyXI_NewSession(void);
|
||||||
// "ownership" of the interpreter's __main__ module. This means
|
PyAPI_FUNC(void) _PyXI_FreeSession(_PyXI_session *);
|
||||||
// it is the only thread that is allowed to run code there.
|
|
||||||
// (Caveat: for now, users may still run exec() against the
|
|
||||||
// __main__ module's dict, though that isn't advisable.)
|
|
||||||
int running;
|
|
||||||
// This is a cached reference to the __dict__ of the entered
|
|
||||||
// interpreter's __main__ module. It is looked up when at the
|
|
||||||
// beginning of the session as a convenience.
|
|
||||||
PyObject *main_ns;
|
|
||||||
|
|
||||||
// This is set if the interpreter is entered and raised an exception
|
|
||||||
// that needs to be handled in some special way during exit.
|
|
||||||
_PyXI_errcode *error_override;
|
|
||||||
// This is set if exit captured an exception to propagate.
|
|
||||||
_PyXI_error *error;
|
|
||||||
|
|
||||||
// -- pre-allocated memory --
|
|
||||||
_PyXI_error _error;
|
|
||||||
_PyXI_errcode _error_override;
|
|
||||||
};
|
|
||||||
|
|
||||||
PyAPI_FUNC(int) _PyXI_Enter(
|
PyAPI_FUNC(int) _PyXI_Enter(
|
||||||
_PyXI_session *session,
|
_PyXI_session *session,
|
||||||
|
@ -400,6 +356,8 @@ PyAPI_FUNC(int) _PyXI_Enter(
|
||||||
PyObject *nsupdates);
|
PyObject *nsupdates);
|
||||||
PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
|
PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
|
||||||
|
|
||||||
|
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(_PyXI_session *);
|
||||||
|
|
||||||
PyAPI_FUNC(PyObject *) _PyXI_ApplyCapturedException(_PyXI_session *session);
|
PyAPI_FUNC(PyObject *) _PyXI_ApplyCapturedException(_PyXI_session *session);
|
||||||
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
|
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
|
||||||
|
|
||||||
|
|
|
@ -444,42 +444,54 @@ _exec_in_interpreter(PyThreadState *tstate, PyInterpreterState *interp,
|
||||||
PyObject **p_excinfo)
|
PyObject **p_excinfo)
|
||||||
{
|
{
|
||||||
assert(!_PyErr_Occurred(tstate));
|
assert(!_PyErr_Occurred(tstate));
|
||||||
_PyXI_session session = {0};
|
_PyXI_session *session = _PyXI_NewSession();
|
||||||
|
if (session == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Prep and switch interpreters.
|
// Prep and switch interpreters.
|
||||||
if (_PyXI_Enter(&session, interp, shareables) < 0) {
|
if (_PyXI_Enter(session, interp, shareables) < 0) {
|
||||||
if (_PyErr_Occurred(tstate)) {
|
if (_PyErr_Occurred(tstate)) {
|
||||||
// If an error occured at this step, it means that interp
|
// If an error occured at this step, it means that interp
|
||||||
// was not prepared and switched.
|
// was not prepared and switched.
|
||||||
|
_PyXI_FreeSession(session);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// Now, apply the error from another interpreter:
|
// Now, apply the error from another interpreter:
|
||||||
PyObject *excinfo = _PyXI_ApplyError(session.error);
|
PyObject *excinfo = _PyXI_ApplyCapturedException(session);
|
||||||
if (excinfo != NULL) {
|
if (excinfo != NULL) {
|
||||||
*p_excinfo = excinfo;
|
*p_excinfo = excinfo;
|
||||||
}
|
}
|
||||||
assert(PyErr_Occurred());
|
assert(PyErr_Occurred());
|
||||||
|
_PyXI_FreeSession(session);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the script.
|
// Run the script.
|
||||||
int res = _run_script(script, session.main_ns);
|
int res = -1;
|
||||||
|
PyObject *mainns = _PyXI_GetMainNamespace(session);
|
||||||
|
if (mainns == NULL) {
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
res = _run_script(script, mainns);
|
||||||
|
|
||||||
|
finally:
|
||||||
// Clean up and switch back.
|
// Clean up and switch back.
|
||||||
_PyXI_Exit(&session);
|
_PyXI_Exit(session);
|
||||||
|
|
||||||
// Propagate any exception out to the caller.
|
// Propagate any exception out to the caller.
|
||||||
assert(!PyErr_Occurred());
|
assert(!PyErr_Occurred());
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
PyObject *excinfo = _PyXI_ApplyCapturedException(&session);
|
PyObject *excinfo = _PyXI_ApplyCapturedException(session);
|
||||||
if (excinfo != NULL) {
|
if (excinfo != NULL) {
|
||||||
*p_excinfo = excinfo;
|
*p_excinfo = excinfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert(!_PyXI_HasCapturedException(&session));
|
assert(!_PyXI_HasCapturedException(session));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_PyXI_FreeSession(session);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -824,22 +836,27 @@ interp_set___main___attrs(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_PyXI_session session = {0};
|
_PyXI_session *session = _PyXI_NewSession();
|
||||||
|
if (session == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// Prep and switch interpreters, including apply the updates.
|
// Prep and switch interpreters, including apply the updates.
|
||||||
if (_PyXI_Enter(&session, interp, updates) < 0) {
|
if (_PyXI_Enter(session, interp, updates) < 0) {
|
||||||
if (!PyErr_Occurred()) {
|
if (!PyErr_Occurred()) {
|
||||||
_PyXI_ApplyCapturedException(&session);
|
_PyXI_ApplyCapturedException(session);
|
||||||
assert(PyErr_Occurred());
|
assert(PyErr_Occurred());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
assert(!_PyXI_HasCapturedException(&session));
|
assert(!_PyXI_HasCapturedException(session));
|
||||||
}
|
}
|
||||||
|
_PyXI_FreeSession(session);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up and switch back.
|
// Clean up and switch back.
|
||||||
_PyXI_Exit(&session);
|
_PyXI_Exit(session);
|
||||||
|
_PyXI_FreeSession(session);
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1914,156 +1914,212 @@ _sharednsitem_apply(_PyXI_namespace_item *item, PyObject *ns, PyObject *dflt)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct _sharedns {
|
|
||||||
Py_ssize_t len;
|
typedef struct {
|
||||||
_PyXI_namespace_item *items;
|
Py_ssize_t maxitems;
|
||||||
};
|
Py_ssize_t numnames;
|
||||||
|
Py_ssize_t numvalues;
|
||||||
|
_PyXI_namespace_item items[1];
|
||||||
|
} _PyXI_namespace;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
static int
|
||||||
|
_sharedns_check_counts(_PyXI_namespace *ns)
|
||||||
|
{
|
||||||
|
if (ns->maxitems <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ns->numnames < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ns->numnames > ns->maxitems) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ns->numvalues < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (ns->numvalues > ns->numnames) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_sharedns_check_consistency(_PyXI_namespace *ns)
|
||||||
|
{
|
||||||
|
if (!_sharedns_check_counts(ns)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t i = 0;
|
||||||
|
_PyXI_namespace_item *item;
|
||||||
|
if (ns->numvalues > 0) {
|
||||||
|
item = &ns->items[0];
|
||||||
|
if (!_sharednsitem_is_initialized(item)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int64_t interpid0 = -1;
|
||||||
|
if (!_sharednsitem_has_value(item, &interpid0)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
for (; i < ns->numvalues; i++) {
|
||||||
|
item = &ns->items[i];
|
||||||
|
if (!_sharednsitem_is_initialized(item)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int64_t interpid = -1;
|
||||||
|
if (!_sharednsitem_has_value(item, &interpid)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (interpid != interpid0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < ns->numnames; i++) {
|
||||||
|
item = &ns->items[i];
|
||||||
|
if (!_sharednsitem_is_initialized(item)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (_sharednsitem_has_value(item, NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < ns->maxitems; i++) {
|
||||||
|
item = &ns->items[i];
|
||||||
|
if (_sharednsitem_is_initialized(item)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (_sharednsitem_has_value(item, NULL)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static _PyXI_namespace *
|
static _PyXI_namespace *
|
||||||
_sharedns_new(void)
|
_sharedns_alloc(Py_ssize_t maxitems)
|
||||||
{
|
{
|
||||||
_PyXI_namespace *ns = PyMem_RawCalloc(sizeof(_PyXI_namespace), 1);
|
if (maxitems < 0) {
|
||||||
|
if (!PyErr_Occurred()) {
|
||||||
|
PyErr_BadInternalCall();
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
else if (maxitems == 0) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for overflow.
|
||||||
|
size_t fixedsize = sizeof(_PyXI_namespace) - sizeof(_PyXI_namespace_item);
|
||||||
|
if ((size_t)maxitems >
|
||||||
|
((size_t)PY_SSIZE_T_MAX - fixedsize) / sizeof(_PyXI_namespace_item))
|
||||||
|
{
|
||||||
|
PyErr_NoMemory();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate the value, including items.
|
||||||
|
size_t size = fixedsize + sizeof(_PyXI_namespace_item) * maxitems;
|
||||||
|
|
||||||
|
_PyXI_namespace *ns = PyMem_RawCalloc(size, 1);
|
||||||
if (ns == NULL) {
|
if (ns == NULL) {
|
||||||
PyErr_NoMemory();
|
PyErr_NoMemory();
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
*ns = (_PyXI_namespace){ 0 };
|
ns->maxitems = maxitems;
|
||||||
|
assert(_sharedns_check_consistency(ns));
|
||||||
return ns;
|
return ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
|
||||||
_sharedns_is_initialized(_PyXI_namespace *ns)
|
|
||||||
{
|
|
||||||
if (ns->len == 0) {
|
|
||||||
assert(ns->items == NULL);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(ns->len > 0);
|
|
||||||
assert(ns->items != NULL);
|
|
||||||
assert(_sharednsitem_is_initialized(&ns->items[0]));
|
|
||||||
assert(ns->len == 1
|
|
||||||
|| _sharednsitem_is_initialized(&ns->items[ns->len - 1]));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define HAS_COMPLETE_DATA 1
|
|
||||||
#define HAS_PARTIAL_DATA 2
|
|
||||||
|
|
||||||
static int
|
|
||||||
_sharedns_has_xidata(_PyXI_namespace *ns, int64_t *p_interpid)
|
|
||||||
{
|
|
||||||
// We expect _PyXI_namespace to always be initialized.
|
|
||||||
assert(_sharedns_is_initialized(ns));
|
|
||||||
int res = 0;
|
|
||||||
_PyXI_namespace_item *item0 = &ns->items[0];
|
|
||||||
if (!_sharednsitem_is_initialized(item0)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
int64_t interpid0 = -1;
|
|
||||||
if (!_sharednsitem_has_value(item0, &interpid0)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (ns->len > 1) {
|
|
||||||
// At this point we know it is has at least partial data.
|
|
||||||
_PyXI_namespace_item *itemN = &ns->items[ns->len-1];
|
|
||||||
if (!_sharednsitem_is_initialized(itemN)) {
|
|
||||||
res = HAS_PARTIAL_DATA;
|
|
||||||
goto finally;
|
|
||||||
}
|
|
||||||
int64_t interpidN = -1;
|
|
||||||
if (!_sharednsitem_has_value(itemN, &interpidN)) {
|
|
||||||
res = HAS_PARTIAL_DATA;
|
|
||||||
goto finally;
|
|
||||||
}
|
|
||||||
assert(interpidN == interpid0);
|
|
||||||
}
|
|
||||||
res = HAS_COMPLETE_DATA;
|
|
||||||
*p_interpid = interpid0;
|
|
||||||
|
|
||||||
finally:
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
_sharedns_clear(_PyXI_namespace *ns)
|
|
||||||
{
|
|
||||||
if (!_sharedns_is_initialized(ns)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the cross-interpreter data were allocated as part of
|
|
||||||
// _PyXI_namespace_item (instead of dynamically), this is where
|
|
||||||
// we would need verify that we are clearing the items in the
|
|
||||||
// correct interpreter, to avoid a race with releasing the XI data
|
|
||||||
// via a pending call. See _sharedns_has_xidata().
|
|
||||||
for (Py_ssize_t i=0; i < ns->len; i++) {
|
|
||||||
_sharednsitem_clear(&ns->items[i]);
|
|
||||||
}
|
|
||||||
PyMem_RawFree(ns->items);
|
|
||||||
ns->items = NULL;
|
|
||||||
ns->len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_sharedns_free(_PyXI_namespace *ns)
|
_sharedns_free(_PyXI_namespace *ns)
|
||||||
{
|
{
|
||||||
_sharedns_clear(ns);
|
// If we weren't always dynamically allocating the cross-interpreter
|
||||||
|
// data in each item then we would need to use a pending call
|
||||||
|
// to call _sharedns_free(), to avoid the race between freeing
|
||||||
|
// the shared namespace and releasing the XI data.
|
||||||
|
assert(_sharedns_check_counts(ns));
|
||||||
|
Py_ssize_t i = 0;
|
||||||
|
_PyXI_namespace_item *item;
|
||||||
|
if (ns->numvalues > 0) {
|
||||||
|
// One or more items may have interpreter-specific data.
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int64_t interpid = PyInterpreterState_GetID(PyInterpreterState_Get());
|
||||||
|
int64_t interpid_i;
|
||||||
|
#endif
|
||||||
|
for (; i < ns->numvalues; i++) {
|
||||||
|
item = &ns->items[i];
|
||||||
|
assert(_sharednsitem_is_initialized(item));
|
||||||
|
// While we do want to ensure consistency across items,
|
||||||
|
// technically they don't need to match the current
|
||||||
|
// interpreter. However, we keep the constraint for
|
||||||
|
// simplicity, by giving _PyXI_FreeNamespace() the exclusive
|
||||||
|
// responsibility of dealing with the owning interpreter.
|
||||||
|
assert(_sharednsitem_has_value(item, &interpid_i));
|
||||||
|
assert(interpid_i == interpid);
|
||||||
|
_sharednsitem_clear(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < ns->numnames; i++) {
|
||||||
|
item = &ns->items[i];
|
||||||
|
assert(_sharednsitem_is_initialized(item));
|
||||||
|
assert(!_sharednsitem_has_value(item, NULL));
|
||||||
|
_sharednsitem_clear(item);
|
||||||
|
}
|
||||||
|
#ifndef NDEBUG
|
||||||
|
for (; i < ns->maxitems; i++) {
|
||||||
|
item = &ns->items[i];
|
||||||
|
assert(!_sharednsitem_is_initialized(item));
|
||||||
|
assert(!_sharednsitem_has_value(item, NULL));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
PyMem_RawFree(ns);
|
PyMem_RawFree(ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static _PyXI_namespace *
|
||||||
_sharedns_init(_PyXI_namespace *ns, PyObject *names)
|
_create_sharedns(PyObject *names)
|
||||||
{
|
{
|
||||||
assert(!_sharedns_is_initialized(ns));
|
|
||||||
assert(names != NULL);
|
assert(names != NULL);
|
||||||
Py_ssize_t len = PyDict_CheckExact(names)
|
Py_ssize_t numnames = PyDict_CheckExact(names)
|
||||||
? PyDict_Size(names)
|
? PyDict_Size(names)
|
||||||
: PySequence_Size(names);
|
: PySequence_Size(names);
|
||||||
if (len < 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (len == 0) {
|
|
||||||
PyErr_SetString(PyExc_ValueError, "empty namespaces not allowed");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
assert(len > 0);
|
|
||||||
|
|
||||||
// Allocate the items.
|
_PyXI_namespace *ns = _sharedns_alloc(numnames);
|
||||||
_PyXI_namespace_item *items =
|
if (ns == NULL) {
|
||||||
PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
|
return NULL;
|
||||||
if (items == NULL) {
|
|
||||||
PyErr_NoMemory();
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
_PyXI_namespace_item *items = ns->items;
|
||||||
|
|
||||||
// Fill in the names.
|
// Fill in the names.
|
||||||
Py_ssize_t i = -1;
|
|
||||||
if (PyDict_CheckExact(names)) {
|
if (PyDict_CheckExact(names)) {
|
||||||
|
Py_ssize_t i = 0;
|
||||||
Py_ssize_t pos = 0;
|
Py_ssize_t pos = 0;
|
||||||
for (i=0; i < len; i++) {
|
PyObject *name;
|
||||||
PyObject *key;
|
while(PyDict_Next(names, &pos, &name, NULL)) {
|
||||||
if (!PyDict_Next(names, &pos, &key, NULL)) {
|
if (_sharednsitem_init(&items[i], name) < 0) {
|
||||||
// This should not be possible.
|
|
||||||
assert(0);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
if (_sharednsitem_init(&items[i], key) < 0) {
|
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
ns->numnames += 1;
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (PySequence_Check(names)) {
|
else if (PySequence_Check(names)) {
|
||||||
for (i=0; i < len; i++) {
|
for (Py_ssize_t i = 0; i < numnames; i++) {
|
||||||
PyObject *key = PySequence_GetItem(names, i);
|
PyObject *name = PySequence_GetItem(names, i);
|
||||||
if (key == NULL) {
|
if (name == NULL) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
int res = _sharednsitem_init(&items[i], key);
|
int res = _sharednsitem_init(&items[i], name);
|
||||||
Py_DECREF(key);
|
Py_DECREF(name);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
ns->numnames += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -2071,140 +2127,79 @@ _sharedns_init(_PyXI_namespace *ns, PyObject *names)
|
||||||
"non-sequence namespace not supported");
|
"non-sequence namespace not supported");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
assert(ns->numnames == ns->maxitems);
|
||||||
ns->items = items;
|
return ns;
|
||||||
ns->len = len;
|
|
||||||
assert(_sharedns_is_initialized(ns));
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error:
|
error:
|
||||||
for (Py_ssize_t j=0; j < i; j++) {
|
|
||||||
_sharednsitem_clear(&items[j]);
|
|
||||||
}
|
|
||||||
PyMem_RawFree(items);
|
|
||||||
assert(!_sharedns_is_initialized(ns));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
_PyXI_FreeNamespace(_PyXI_namespace *ns)
|
|
||||||
{
|
|
||||||
if (!_sharedns_is_initialized(ns)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t interpid = -1;
|
|
||||||
if (!_sharedns_has_xidata(ns, &interpid)) {
|
|
||||||
_sharedns_free(ns);
|
_sharedns_free(ns);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) {
|
|
||||||
_sharedns_free(ns);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If we weren't always dynamically allocating the cross-interpreter
|
|
||||||
// data in each item then we would need to using a pending call
|
|
||||||
// to call _sharedns_free(), to avoid the race between freeing
|
|
||||||
// the shared namespace and releasing the XI data.
|
|
||||||
_sharedns_free(ns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_PyXI_namespace *
|
|
||||||
_PyXI_NamespaceFromNames(PyObject *names)
|
|
||||||
{
|
|
||||||
if (names == NULL || names == Py_None) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
_PyXI_namespace *ns = _sharedns_new();
|
|
||||||
if (ns == NULL) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_sharedns_init(ns, names) < 0) {
|
|
||||||
PyMem_RawFree(ns);
|
|
||||||
if (PySequence_Size(names) == 0) {
|
|
||||||
PyErr_Clear();
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ns;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
static int _session_is_active(_PyXI_session *);
|
|
||||||
#endif
|
|
||||||
static void _propagate_not_shareable_error(_PyXI_session *);
|
static void _propagate_not_shareable_error(_PyXI_session *);
|
||||||
|
|
||||||
int
|
static int
|
||||||
_PyXI_FillNamespaceFromDict(_PyXI_namespace *ns, PyObject *nsobj,
|
_fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj, _PyXI_session *session)
|
||||||
_PyXI_session *session)
|
|
||||||
{
|
{
|
||||||
// session must be entered already, if provided.
|
// All items are expected to be shareable.
|
||||||
assert(session == NULL || _session_is_active(session));
|
assert(_sharedns_check_counts(ns));
|
||||||
assert(_sharedns_is_initialized(ns));
|
assert(ns->numnames == ns->maxitems);
|
||||||
for (Py_ssize_t i=0; i < ns->len; i++) {
|
assert(ns->numvalues == 0);
|
||||||
_PyXI_namespace_item *item = &ns->items[i];
|
for (Py_ssize_t i=0; i < ns->maxitems; i++) {
|
||||||
if (_sharednsitem_copy_from_ns(item, nsobj) < 0) {
|
if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj) < 0) {
|
||||||
_propagate_not_shareable_error(session);
|
_propagate_not_shareable_error(session);
|
||||||
// Clear out the ones we set so far.
|
// Clear out the ones we set so far.
|
||||||
for (Py_ssize_t j=0; j < i; j++) {
|
for (Py_ssize_t j=0; j < i; j++) {
|
||||||
_sharednsitem_clear_value(&ns->items[j]);
|
_sharednsitem_clear_value(&ns->items[j]);
|
||||||
|
ns->numvalues -= 1;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
ns->numvalues += 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All items are expected to be shareable.
|
static int
|
||||||
static _PyXI_namespace *
|
_sharedns_free_pending(void *data)
|
||||||
_PyXI_NamespaceFromDict(PyObject *nsobj, _PyXI_session *session)
|
|
||||||
{
|
{
|
||||||
// session must be entered already, if provided.
|
_sharedns_free((_PyXI_namespace *)data);
|
||||||
assert(session == NULL || _session_is_active(session));
|
return 0;
|
||||||
if (nsobj == NULL || nsobj == Py_None) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (!PyDict_CheckExact(nsobj)) {
|
|
||||||
PyErr_SetString(PyExc_TypeError, "expected a dict");
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_PyXI_namespace *ns = _sharedns_new();
|
static void
|
||||||
if (ns == NULL) {
|
_destroy_sharedns(_PyXI_namespace *ns)
|
||||||
return NULL;
|
{
|
||||||
}
|
assert(_sharedns_check_counts(ns));
|
||||||
|
assert(ns->numnames == ns->maxitems);
|
||||||
if (_sharedns_init(ns, nsobj) < 0) {
|
if (ns->numvalues == 0) {
|
||||||
if (PyDict_Size(nsobj) == 0) {
|
|
||||||
PyMem_RawFree(ns);
|
|
||||||
PyErr_Clear();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_PyXI_FillNamespaceFromDict(ns, nsobj, session) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ns;
|
|
||||||
|
|
||||||
error:
|
|
||||||
assert(PyErr_Occurred()
|
|
||||||
|| (session != NULL && session->error_override != NULL));
|
|
||||||
_sharedns_free(ns);
|
_sharedns_free(ns);
|
||||||
return NULL;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int64_t interpid0;
|
||||||
_PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
|
if (!_sharednsitem_has_value(&ns->items[0], &interpid0)) {
|
||||||
|
// This shouldn't have been possible.
|
||||||
|
// We can deal with it in _sharedns_free().
|
||||||
|
_sharedns_free(ns);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_LookUpID(interpid0);
|
||||||
|
if (interp == PyInterpreterState_Get()) {
|
||||||
|
_sharedns_free(ns);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One or more items may have interpreter-specific data.
|
||||||
|
// Currently the xidata for each value is dynamically allocated,
|
||||||
|
// so technically we don't need to worry about that.
|
||||||
|
// However, explicitly adding a pending call here is simpler.
|
||||||
|
(void)_Py_CallInInterpreter(interp, _sharedns_free_pending, ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_apply_sharedns(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
|
||||||
{
|
{
|
||||||
for (Py_ssize_t i=0; i < ns->len; i++) {
|
for (Py_ssize_t i=0; i < ns->maxitems; i++) {
|
||||||
if (_sharednsitem_apply(&ns->items[i], nsobj, dflt) != 0) {
|
if (_sharednsitem_apply(&ns->items[i], nsobj, dflt) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -2213,9 +2208,79 @@ _PyXI_ApplyNamespace(_PyXI_namespace *ns, PyObject *nsobj, PyObject *dflt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**********************/
|
/*********************************/
|
||||||
/* high-level helpers */
|
/* switched-interpreter sessions */
|
||||||
/**********************/
|
/*********************************/
|
||||||
|
|
||||||
|
struct xi_session {
|
||||||
|
#define SESSION_UNUSED 0
|
||||||
|
#define SESSION_ACTIVE 1
|
||||||
|
int status;
|
||||||
|
int switched;
|
||||||
|
|
||||||
|
// Once a session has been entered, this is the tstate that was
|
||||||
|
// current before the session. If it is different from cur_tstate
|
||||||
|
// then we must have switched interpreters. Either way, this will
|
||||||
|
// be the current tstate once we exit the session.
|
||||||
|
PyThreadState *prev_tstate;
|
||||||
|
// Once a session has been entered, this is the current tstate.
|
||||||
|
// It must be current when the session exits.
|
||||||
|
PyThreadState *init_tstate;
|
||||||
|
// This is true if init_tstate needs cleanup during exit.
|
||||||
|
int own_init_tstate;
|
||||||
|
|
||||||
|
// This is true if, while entering the session, init_thread took
|
||||||
|
// "ownership" of the interpreter's __main__ module. This means
|
||||||
|
// it is the only thread that is allowed to run code there.
|
||||||
|
// (Caveat: for now, users may still run exec() against the
|
||||||
|
// __main__ module's dict, though that isn't advisable.)
|
||||||
|
int running;
|
||||||
|
// This is a cached reference to the __dict__ of the entered
|
||||||
|
// interpreter's __main__ module. It is looked up when at the
|
||||||
|
// beginning of the session as a convenience.
|
||||||
|
PyObject *main_ns;
|
||||||
|
|
||||||
|
// This is set if the interpreter is entered and raised an exception
|
||||||
|
// that needs to be handled in some special way during exit.
|
||||||
|
_PyXI_errcode *error_override;
|
||||||
|
// This is set if exit captured an exception to propagate.
|
||||||
|
_PyXI_error *error;
|
||||||
|
|
||||||
|
// -- pre-allocated memory --
|
||||||
|
_PyXI_error _error;
|
||||||
|
_PyXI_errcode _error_override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
_PyXI_session *
|
||||||
|
_PyXI_NewSession(void)
|
||||||
|
{
|
||||||
|
_PyXI_session *session = PyMem_RawCalloc(1, sizeof(_PyXI_session));
|
||||||
|
if (session == NULL) {
|
||||||
|
PyErr_NoMemory();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyXI_FreeSession(_PyXI_session *session)
|
||||||
|
{
|
||||||
|
assert(session->status == SESSION_UNUSED);
|
||||||
|
PyMem_RawFree(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
_session_is_active(_PyXI_session *session)
|
||||||
|
{
|
||||||
|
return session->status == SESSION_ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _ensure_main_ns(_PyXI_session *);
|
||||||
|
static inline void _session_set_error(_PyXI_session *, _PyXI_errcode);
|
||||||
|
static void _capture_current_exception(_PyXI_session *);
|
||||||
|
|
||||||
|
|
||||||
/* enter/exit a cross-interpreter session */
|
/* enter/exit a cross-interpreter session */
|
||||||
|
|
||||||
|
@ -2223,6 +2288,7 @@ static void
|
||||||
_enter_session(_PyXI_session *session, PyInterpreterState *interp)
|
_enter_session(_PyXI_session *session, PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
// Set here and cleared in _exit_session().
|
// Set here and cleared in _exit_session().
|
||||||
|
assert(session->status == SESSION_UNUSED);
|
||||||
assert(!session->own_init_tstate);
|
assert(!session->own_init_tstate);
|
||||||
assert(session->init_tstate == NULL);
|
assert(session->init_tstate == NULL);
|
||||||
assert(session->prev_tstate == NULL);
|
assert(session->prev_tstate == NULL);
|
||||||
|
@ -2237,15 +2303,22 @@ _enter_session(_PyXI_session *session, PyInterpreterState *interp)
|
||||||
// Switch to interpreter.
|
// Switch to interpreter.
|
||||||
PyThreadState *tstate = PyThreadState_Get();
|
PyThreadState *tstate = PyThreadState_Get();
|
||||||
PyThreadState *prev = tstate;
|
PyThreadState *prev = tstate;
|
||||||
if (interp != tstate->interp) {
|
int same_interp = (interp == tstate->interp);
|
||||||
|
if (!same_interp) {
|
||||||
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_EXEC);
|
tstate = _PyThreadState_NewBound(interp, _PyThreadState_WHENCE_EXEC);
|
||||||
// XXX Possible GILState issues?
|
// XXX Possible GILState issues?
|
||||||
session->prev_tstate = PyThreadState_Swap(tstate);
|
PyThreadState *swapped = PyThreadState_Swap(tstate);
|
||||||
assert(session->prev_tstate == prev);
|
assert(swapped == prev);
|
||||||
session->own_init_tstate = 1;
|
(void)swapped;
|
||||||
}
|
}
|
||||||
session->init_tstate = tstate;
|
|
||||||
session->prev_tstate = prev;
|
*session = (_PyXI_session){
|
||||||
|
.status = SESSION_ACTIVE,
|
||||||
|
.switched = !same_interp,
|
||||||
|
.init_tstate = tstate,
|
||||||
|
.prev_tstate = prev,
|
||||||
|
.own_init_tstate = !same_interp,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -2256,9 +2329,7 @@ _exit_session(_PyXI_session *session)
|
||||||
assert(PyThreadState_Get() == tstate);
|
assert(PyThreadState_Get() == tstate);
|
||||||
|
|
||||||
// Release any of the entered interpreters resources.
|
// Release any of the entered interpreters resources.
|
||||||
if (session->main_ns != NULL) {
|
|
||||||
Py_CLEAR(session->main_ns);
|
Py_CLEAR(session->main_ns);
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure this thread no longer owns __main__.
|
// Ensure this thread no longer owns __main__.
|
||||||
if (session->running) {
|
if (session->running) {
|
||||||
|
@ -2279,17 +2350,15 @@ _exit_session(_PyXI_session *session)
|
||||||
else {
|
else {
|
||||||
assert(!session->own_init_tstate);
|
assert(!session->own_init_tstate);
|
||||||
}
|
}
|
||||||
session->prev_tstate = NULL;
|
|
||||||
session->init_tstate = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
// For now the error data persists past the exit.
|
||||||
static int
|
*session = (_PyXI_session){
|
||||||
_session_is_active(_PyXI_session *session)
|
.error_override = session->error_override,
|
||||||
{
|
.error = session->error,
|
||||||
return (session->init_tstate != NULL);
|
._error = session->_error,
|
||||||
|
._error_override = session->_error_override,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_propagate_not_shareable_error(_PyXI_session *session)
|
_propagate_not_shareable_error(_PyXI_session *session)
|
||||||
|
@ -2306,11 +2375,102 @@ _propagate_not_shareable_error(_PyXI_session *session)
|
||||||
}
|
}
|
||||||
if (PyErr_ExceptionMatches(exctype)) {
|
if (PyErr_ExceptionMatches(exctype)) {
|
||||||
// We want to propagate the exception directly.
|
// We want to propagate the exception directly.
|
||||||
session->_error_override = _PyXI_ERR_NOT_SHAREABLE;
|
_session_set_error(session, _PyXI_ERR_NOT_SHAREABLE);
|
||||||
session->error_override = &session->_error_override;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
_PyXI_ApplyCapturedException(_PyXI_session *session)
|
||||||
|
{
|
||||||
|
assert(!PyErr_Occurred());
|
||||||
|
assert(session->error != NULL);
|
||||||
|
PyObject *res = _PyXI_ApplyError(session->error);
|
||||||
|
assert((res == NULL) != (PyErr_Occurred() == NULL));
|
||||||
|
session->error = NULL;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyXI_HasCapturedException(_PyXI_session *session)
|
||||||
|
{
|
||||||
|
return session->error != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyXI_Enter(_PyXI_session *session,
|
||||||
|
PyInterpreterState *interp, PyObject *nsupdates)
|
||||||
|
{
|
||||||
|
// Convert the attrs for cross-interpreter use.
|
||||||
|
_PyXI_namespace *sharedns = NULL;
|
||||||
|
if (nsupdates != NULL) {
|
||||||
|
Py_ssize_t len = PyDict_Size(nsupdates);
|
||||||
|
if (len < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (len > 0) {
|
||||||
|
sharedns = _create_sharedns(nsupdates);
|
||||||
|
if (sharedns == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (_fill_sharedns(sharedns, nsupdates, NULL) < 0) {
|
||||||
|
assert(session->error == NULL);
|
||||||
|
_destroy_sharedns(sharedns);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to the requested interpreter (if necessary).
|
||||||
|
_enter_session(session, interp);
|
||||||
|
_PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
|
||||||
|
|
||||||
|
// Ensure this thread owns __main__.
|
||||||
|
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
|
||||||
|
// In the case where we didn't switch interpreters, it would
|
||||||
|
// be more efficient to leave the exception in place and return
|
||||||
|
// immediately. However, life is simpler if we don't.
|
||||||
|
errcode = _PyXI_ERR_ALREADY_RUNNING;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
session->running = 1;
|
||||||
|
|
||||||
|
// Apply the cross-interpreter data.
|
||||||
|
if (sharedns != NULL) {
|
||||||
|
if (_ensure_main_ns(session) < 0) {
|
||||||
|
errcode = _PyXI_ERR_MAIN_NS_FAILURE;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (_apply_sharedns(sharedns, session->main_ns, NULL) < 0) {
|
||||||
|
errcode = _PyXI_ERR_APPLY_NS_FAILURE;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
_destroy_sharedns(sharedns);
|
||||||
|
}
|
||||||
|
|
||||||
|
errcode = _PyXI_ERR_NO_ERROR;
|
||||||
|
assert(!PyErr_Occurred());
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error:
|
||||||
|
// We want to propagate all exceptions here directly (best effort).
|
||||||
|
_session_set_error(session, errcode);
|
||||||
|
_exit_session(session);
|
||||||
|
if (sharedns != NULL) {
|
||||||
|
_destroy_sharedns(sharedns);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyXI_Exit(_PyXI_session *session)
|
||||||
|
{
|
||||||
|
_capture_current_exception(session);
|
||||||
|
_exit_session(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* in an active cross-interpreter session */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_capture_current_exception(_PyXI_session *session)
|
_capture_current_exception(_PyXI_session *session)
|
||||||
{
|
{
|
||||||
|
@ -2375,97 +2535,52 @@ _capture_current_exception(_PyXI_session *session)
|
||||||
session->error = err;
|
session->error = err;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *
|
static inline void
|
||||||
_PyXI_ApplyCapturedException(_PyXI_session *session)
|
_session_set_error(_PyXI_session *session, _PyXI_errcode errcode)
|
||||||
{
|
{
|
||||||
assert(!PyErr_Occurred());
|
assert(_session_is_active(session));
|
||||||
assert(session->error != NULL);
|
assert(PyErr_Occurred());
|
||||||
PyObject *res = _PyXI_ApplyError(session->error);
|
if (errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION) {
|
||||||
assert((res == NULL) != (PyErr_Occurred() == NULL));
|
session->_error_override = errcode;
|
||||||
session->error = NULL;
|
session->error_override = &session->_error_override;
|
||||||
return res;
|
}
|
||||||
|
_capture_current_exception(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static int
|
||||||
_PyXI_HasCapturedException(_PyXI_session *session)
|
_ensure_main_ns(_PyXI_session *session)
|
||||||
{
|
{
|
||||||
return session->error != NULL;
|
assert(_session_is_active(session));
|
||||||
|
if (session->main_ns != NULL) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
|
||||||
_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, NULL);
|
|
||||||
if (sharedns == NULL && PyErr_Occurred()) {
|
|
||||||
assert(session->error == NULL);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch to the requested interpreter (if necessary).
|
|
||||||
_enter_session(session, interp);
|
|
||||||
PyThreadState *session_tstate = session->init_tstate;
|
|
||||||
_PyXI_errcode errcode = _PyXI_ERR_UNCAUGHT_EXCEPTION;
|
|
||||||
|
|
||||||
// Ensure this thread owns __main__.
|
|
||||||
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
|
|
||||||
// In the case where we didn't switch interpreters, it would
|
|
||||||
// be more efficient to leave the exception in place and return
|
|
||||||
// immediately. However, life is simpler if we don't.
|
|
||||||
errcode = _PyXI_ERR_ALREADY_RUNNING;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
session->running = 1;
|
|
||||||
|
|
||||||
// Cache __main__.__dict__.
|
// Cache __main__.__dict__.
|
||||||
PyObject *main_mod = _Py_GetMainModule(session_tstate);
|
PyObject *main_mod = _Py_GetMainModule(session->init_tstate);
|
||||||
if (_Py_CheckMainModule(main_mod) < 0) {
|
if (_Py_CheckMainModule(main_mod) < 0) {
|
||||||
errcode = _PyXI_ERR_MAIN_NS_FAILURE;
|
return -1;
|
||||||
goto error;
|
|
||||||
}
|
}
|
||||||
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
|
PyObject *ns = PyModule_GetDict(main_mod); // borrowed
|
||||||
Py_DECREF(main_mod);
|
Py_DECREF(main_mod);
|
||||||
if (ns == NULL) {
|
if (ns == NULL) {
|
||||||
errcode = _PyXI_ERR_MAIN_NS_FAILURE;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
session->main_ns = Py_NewRef(ns);
|
|
||||||
|
|
||||||
// Apply the cross-interpreter data.
|
|
||||||
if (sharedns != NULL) {
|
|
||||||
if (_PyXI_ApplyNamespace(sharedns, ns, NULL) < 0) {
|
|
||||||
errcode = _PyXI_ERR_APPLY_NS_FAILURE;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
_PyXI_FreeNamespace(sharedns);
|
|
||||||
}
|
|
||||||
|
|
||||||
errcode = _PyXI_ERR_NO_ERROR;
|
|
||||||
assert(!PyErr_Occurred());
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
error:
|
|
||||||
assert(PyErr_Occurred());
|
|
||||||
// We want to propagate all exceptions here directly (best effort).
|
|
||||||
assert(errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION);
|
|
||||||
session->error_override = &errcode;
|
|
||||||
_capture_current_exception(session);
|
|
||||||
_exit_session(session);
|
|
||||||
if (sharedns != NULL) {
|
|
||||||
_PyXI_FreeNamespace(sharedns);
|
|
||||||
}
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
session->main_ns = Py_NewRef(ns);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
PyObject *
|
||||||
_PyXI_Exit(_PyXI_session *session)
|
_PyXI_GetMainNamespace(_PyXI_session *session)
|
||||||
{
|
{
|
||||||
|
if (!_session_is_active(session)) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "session not active");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (_ensure_main_ns(session) < 0) {
|
||||||
|
_session_set_error(session, _PyXI_ERR_MAIN_NS_FAILURE);
|
||||||
_capture_current_exception(session);
|
_capture_current_exception(session);
|
||||||
_exit_session(session);
|
return NULL;
|
||||||
|
}
|
||||||
|
return session->main_ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue