mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	[3.14] gh-132775: Make _PyXI_session Opaque (gh-134522)
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()
(cherry picked from commit 4a4ac3ab4d, gh-134452)
Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
			
			
This commit is contained in:
		
							parent
							
								
									068d570be8
								
							
						
					
					
						commit
						04e2dd6513
					
				
					 4 changed files with 929 additions and 861 deletions
				
			
		
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -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
	
	 Miss Islington (bot)
						Miss Islington (bot)