Skip to content

Commit 24e245e

Browse files
Add _PyXI_Preserve().
1 parent a91c728 commit 24e245e

File tree

2 files changed

+139
-10
lines changed

2 files changed

+139
-10
lines changed

Include/internal/pycore_crossinterp.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ PyAPI_FUNC(_PyXI_session *) _PyXI_NewSession(void);
351351
PyAPI_FUNC(void) _PyXI_FreeSession(_PyXI_session *);
352352

353353
typedef struct {
354+
PyObject *preserved;
354355
PyObject *excinfo;
355356
} _PyXI_session_result;
356357

@@ -363,6 +364,9 @@ PyAPI_FUNC(int) _PyXI_Exit(_PyXI_session *, _PyXI_session_result *);
363364

364365
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(_PyXI_session *);
365366

367+
PyAPI_FUNC(int) _PyXI_Preserve(_PyXI_session *, const char *, PyObject *);
368+
PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(_PyXI_session_result *, const char *);
369+
366370

367371
/*************/
368372
/* other API */

Python/crossinterp.c

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1835,7 +1835,8 @@ _sharednsitem_has_value(_PyXI_namespace_item *item, int64_t *p_interpid)
18351835
}
18361836

18371837
static int
1838-
_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
1838+
_sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value,
1839+
xidata_fallback_t fallback)
18391840
{
18401841
assert(_sharednsitem_is_initialized(item));
18411842
assert(item->xidata == NULL);
@@ -1844,8 +1845,9 @@ _sharednsitem_set_value(_PyXI_namespace_item *item, PyObject *value)
18441845
return -1;
18451846
}
18461847
PyThreadState *tstate = PyThreadState_Get();
1847-
// XXX Use _PyObject_GetXIDataWithFallback()?
1848-
if (_PyObject_GetXIData(tstate, value, item->xidata) != 0) {
1848+
if (_PyObject_GetXIDataWithFallback(
1849+
tstate, value, fallback, item->xidata) < 0)
1850+
{
18491851
PyMem_RawFree(item->xidata);
18501852
item->xidata = NULL;
18511853
// The caller may want to propagate PyExc_NotShareableError
@@ -1877,7 +1879,8 @@ _sharednsitem_clear(_PyXI_namespace_item *item)
18771879
}
18781880

18791881
static int
1880-
_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
1882+
_sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns,
1883+
xidata_fallback_t fallback)
18811884
{
18821885
assert(item->name != NULL);
18831886
assert(item->xidata == NULL);
@@ -1889,7 +1892,7 @@ _sharednsitem_copy_from_ns(struct _sharednsitem *item, PyObject *ns)
18891892
// When applied, this item will be set to the default (or fail).
18901893
return 0;
18911894
}
1892-
if (_sharednsitem_set_value(item, value) < 0) {
1895+
if (_sharednsitem_set_value(item, value, fallback) < 0) {
18931896
return -1;
18941897
}
18951898
return 0;
@@ -2143,14 +2146,15 @@ _create_sharedns(PyObject *names)
21432146
static void _propagate_not_shareable_error(_PyXI_session *);
21442147

21452148
static int
2146-
_fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj, _PyXI_session *session)
2149+
_fill_sharedns(_PyXI_namespace *ns, PyObject *nsobj,
2150+
xidata_fallback_t fallback, _PyXI_session *session)
21472151
{
21482152
// All items are expected to be shareable.
21492153
assert(_sharedns_check_counts(ns));
21502154
assert(ns->numnames == ns->maxitems);
21512155
assert(ns->numvalues == 0);
21522156
for (Py_ssize_t i=0; i < ns->maxitems; i++) {
2153-
if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj) < 0) {
2157+
if (_sharednsitem_copy_from_ns(&ns->items[i], nsobj, fallback) < 0) {
21542158
if (session != NULL) {
21552159
_propagate_not_shareable_error(session);
21562160
}
@@ -2259,10 +2263,14 @@ struct xi_session {
22592263
// beginning of the session as a convenience.
22602264
PyObject *main_ns;
22612265

2266+
// This is a dict of objects that will be available (via sharing)
2267+
// once the session exits. Do not access this directly; use
2268+
// _PyXI_Preserve() and _PyXI_GetPreserved() instead;
2269+
PyObject *_preserved;
2270+
22622271
struct xi_session_error error;
22632272
};
22642273

2265-
22662274
_PyXI_session *
22672275
_PyXI_NewSession(void)
22682276
{
@@ -2355,14 +2363,16 @@ _exit_session(_PyXI_session *session)
23552363
PyThreadState *tstate = session->init_tstate;
23562364
assert(tstate != NULL);
23572365
assert(PyThreadState_Get() == tstate);
2366+
assert(!_PyErr_Occurred(tstate));
23582367

23592368
// Release any of the entered interpreters resources.
23602369
Py_CLEAR(session->main_ns);
2370+
Py_CLEAR(session->_preserved);
23612371

23622372
// Ensure this thread no longer owns __main__.
23632373
if (session->running) {
23642374
_PyInterpreterState_SetNotRunningMain(tstate->interp);
2365-
assert(!PyErr_Occurred());
2375+
assert(!_PyErr_Occurred(tstate));
23662376
session->running = 0;
23672377
}
23682378

@@ -2420,7 +2430,9 @@ _PyXI_Enter(_PyXI_session *session,
24202430
if (sharedns == NULL) {
24212431
return -1;
24222432
}
2423-
if (_fill_sharedns(sharedns, nsupdates, NULL) < 0) {
2433+
// For now we limit it to shareable objects.
2434+
xidata_fallback_t fallback = _PyXIDATA_XIDATA_ONLY;
2435+
if (_fill_sharedns(sharedns, nsupdates, fallback, NULL) < 0) {
24242436
assert(session->error.info == NULL);
24252437
_destroy_sharedns(sharedns);
24262438
return -1;
@@ -2490,12 +2502,27 @@ _PyXI_Enter(_PyXI_session *session,
24902502
return -1;
24912503
}
24922504

2505+
static PyObject * _capture_preserved(_PyXI_session *);
2506+
24932507
int
24942508
_PyXI_Exit(_PyXI_session *session, _PyXI_session_result *result)
24952509
{
24962510
_capture_current_exception(session);
24972511
assert(!PyErr_Occurred());
24982512

2513+
if (result != NULL) {
2514+
result->preserved = _capture_preserved(session);
2515+
if (result->preserved == NULL && PyErr_Occurred()) {
2516+
if (session->error.info != NULL) {
2517+
PyErr_FormatUnraisable(
2518+
"Exception ignored while capturing preserved objects");
2519+
}
2520+
else {
2521+
_capture_current_exception(session);
2522+
}
2523+
}
2524+
}
2525+
24992526
struct xi_session_error err;
25002527
(void)_session_pop_error(session, &err);
25012528
_exit_session(session);
@@ -2639,6 +2666,104 @@ _PyXI_GetMainNamespace(_PyXI_session *session)
26392666
}
26402667

26412668

2669+
static PyObject *
2670+
_capture_preserved(_PyXI_session *session)
2671+
{
2672+
assert(_PyThreadState_GET() == session->init_tstate); // active session
2673+
if (session->init_tstate == session->prev_tstate) {
2674+
// didn't switch
2675+
return Py_XNewRef(session->_preserved);
2676+
}
2677+
2678+
_PyXI_namespace *preserved = NULL;
2679+
if (session->_preserved != NULL) {
2680+
Py_ssize_t len = PyDict_Size(session->_preserved);
2681+
if (len < 0) {
2682+
return NULL;
2683+
}
2684+
if (len > 0) {
2685+
preserved = _create_sharedns(session->_preserved);
2686+
if (preserved == NULL) {
2687+
return NULL;
2688+
}
2689+
if (_fill_sharedns(preserved, session->_preserved,
2690+
_PyXIDATA_FULL_FALLBACK, NULL) < 0)
2691+
{
2692+
assert(session->error.info == NULL);
2693+
_destroy_sharedns(preserved);
2694+
return NULL;
2695+
}
2696+
}
2697+
Py_CLEAR(session->_preserved);
2698+
}
2699+
if (preserved == NULL) {
2700+
return NULL;
2701+
}
2702+
2703+
// We need to switch back to the original interpreter long enough
2704+
// to restore the preservd objects.
2705+
(void)PyThreadState_Swap(session->prev_tstate);
2706+
2707+
PyObject *ns = PyDict_New();
2708+
if (ns == NULL) {
2709+
goto finally;
2710+
}
2711+
if (_apply_sharedns(preserved, ns, NULL) < 0) {
2712+
Py_CLEAR(ns);
2713+
goto finally;
2714+
}
2715+
2716+
finally:
2717+
// Swap back into the session.
2718+
(void)PyThreadState_Swap(session->init_tstate);
2719+
assert(preserved != NULL);
2720+
_destroy_sharedns(preserved);
2721+
return ns;
2722+
}
2723+
2724+
static void
2725+
set_exc_with_cause(PyObject *exctype, const char *msg)
2726+
{
2727+
PyObject *cause = PyErr_GetRaisedException();
2728+
PyErr_SetString(exctype, msg);
2729+
PyObject *exc = PyErr_GetRaisedException();
2730+
PyException_SetCause(exc, cause);
2731+
PyErr_SetRaisedException(exc);
2732+
}
2733+
2734+
int
2735+
_PyXI_Preserve(_PyXI_session *session, const char *name, PyObject *value)
2736+
{
2737+
if (session->init_tstate == NULL) {
2738+
PyErr_SetString(PyExc_RuntimeError, "session not active");
2739+
return -1;
2740+
}
2741+
if (session->_preserved == NULL) {
2742+
session->_preserved = PyDict_New();
2743+
if (session->_preserved == NULL) {
2744+
set_exc_with_cause(PyExc_RuntimeError,
2745+
"failed to initialize preserved objects");
2746+
return -1;
2747+
}
2748+
}
2749+
if (PyDict_SetItemString(session->_preserved, name, value) < 0) {
2750+
set_exc_with_cause(PyExc_RuntimeError, "failed to preserve object");
2751+
return -1;
2752+
}
2753+
return 0;
2754+
}
2755+
2756+
PyObject *
2757+
_PyXI_GetPreserved(_PyXI_session_result *result, const char *name)
2758+
{
2759+
PyObject *value = NULL;
2760+
if (result->preserved != NULL) {
2761+
(void)PyDict_GetItemStringRef(result->preserved, name, &value);
2762+
}
2763+
return value;
2764+
}
2765+
2766+
26422767
/*********************/
26432768
/* runtime lifecycle */
26442769
/*********************/

0 commit comments

Comments
 (0)