Skip to content

Commit fd7e08a

Browse files
gh-76785: Use Pending Calls When Releasing Cross-Interpreter Data (gh-109556)
This fixes some crashes in the _xxinterpchannels module, due to a race between interpreters.
1 parent 754519a commit fd7e08a

File tree

7 files changed

+98
-67
lines changed

7 files changed

+98
-67
lines changed

Include/cpython/pystate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ PyAPI_FUNC(void) _PyCrossInterpreterData_Clear(
309309
PyAPI_FUNC(int) _PyObject_GetCrossInterpreterData(PyObject *, _PyCrossInterpreterData *);
310310
PyAPI_FUNC(PyObject *) _PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *);
311311
PyAPI_FUNC(int) _PyCrossInterpreterData_Release(_PyCrossInterpreterData *);
312+
PyAPI_FUNC(int) _PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *);
312313

313314
PyAPI_FUNC(int) _PyObject_CheckCrossInterpreterData(PyObject *);
314315

Include/internal/pycore_ceval.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extern void _PyEval_SignalReceived(PyInterpreterState *interp);
4444
// Export for '_testinternalcapi' shared extension
4545
PyAPI_FUNC(int) _PyEval_AddPendingCall(
4646
PyInterpreterState *interp,
47-
int (*func)(void *),
47+
_Py_pending_call_func func,
4848
void *arg,
4949
int mainthreadonly);
5050

Include/internal/pycore_ceval_state.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ extern "C" {
1111
#include "pycore_gil.h" // struct _gil_runtime_state
1212

1313

14+
typedef int (*_Py_pending_call_func)(void *);
15+
1416
struct _pending_calls {
1517
int busy;
1618
PyThread_type_lock lock;
@@ -22,7 +24,7 @@ struct _pending_calls {
2224
int async_exc;
2325
#define NPENDINGCALLS 32
2426
struct _pending_call {
25-
int (*func)(void *);
27+
_Py_pending_call_func func;
2628
void *arg;
2729
} calls[NPENDINGCALLS];
2830
int first;

Modules/_xxinterpchannelsmodule.c

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -160,21 +160,34 @@ add_new_type(PyObject *mod, PyType_Spec *spec, crossinterpdatafunc shared)
160160
return cls;
161161
}
162162

163+
#define XID_IGNORE_EXC 1
164+
#define XID_FREE 2
165+
163166
static int
164-
_release_xid_data(_PyCrossInterpreterData *data, int ignoreexc)
167+
_release_xid_data(_PyCrossInterpreterData *data, int flags)
165168
{
169+
int ignoreexc = flags & XID_IGNORE_EXC;
166170
PyObject *exc;
167171
if (ignoreexc) {
168172
exc = PyErr_GetRaisedException();
169173
}
170-
int res = _PyCrossInterpreterData_Release(data);
174+
int res;
175+
if (flags & XID_FREE) {
176+
res = _PyCrossInterpreterData_ReleaseAndRawFree(data);
177+
}
178+
else {
179+
res = _PyCrossInterpreterData_Release(data);
180+
}
171181
if (res < 0) {
172182
/* The owning interpreter is already destroyed. */
173183
if (ignoreexc) {
174184
// XXX Emit a warning?
175185
PyErr_Clear();
176186
}
177187
}
188+
if (flags & XID_FREE) {
189+
/* Either way, we free the data. */
190+
}
178191
if (ignoreexc) {
179192
PyErr_SetRaisedException(exc);
180193
}
@@ -366,9 +379,8 @@ static void
366379
_channelitem_clear(_channelitem *item)
367380
{
368381
if (item->data != NULL) {
369-
(void)_release_xid_data(item->data, 1);
370382
// It was allocated in _channel_send().
371-
GLOBAL_FREE(item->data);
383+
(void)_release_xid_data(item->data, XID_IGNORE_EXC & XID_FREE);
372384
item->data = NULL;
373385
}
374386
item->next = NULL;
@@ -1439,14 +1451,12 @@ _channel_recv(_channels *channels, int64_t id, PyObject **res)
14391451
PyObject *obj = _PyCrossInterpreterData_NewObject(data);
14401452
if (obj == NULL) {
14411453
assert(PyErr_Occurred());
1442-
(void)_release_xid_data(data, 1);
1443-
// It was allocated in _channel_send().
1444-
GLOBAL_FREE(data);
1454+
// It was allocated in _channel_send(), so we free it.
1455+
(void)_release_xid_data(data, XID_IGNORE_EXC | XID_FREE);
14451456
return -1;
14461457
}
1447-
int release_res = _release_xid_data(data, 0);
1448-
// It was allocated in _channel_send().
1449-
GLOBAL_FREE(data);
1458+
// It was allocated in _channel_send(), so we free it.
1459+
int release_res = _release_xid_data(data, XID_FREE);
14501460
if (release_res < 0) {
14511461
// The source interpreter has been destroyed already.
14521462
assert(PyErr_Occurred());

Modules/_xxsubinterpretersmodule.c

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,17 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base)
5858
add_new_exception(MOD, MODULE_NAME "." Py_STRINGIFY(NAME), BASE)
5959

6060
static int
61-
_release_xid_data(_PyCrossInterpreterData *data, int ignoreexc)
61+
_release_xid_data(_PyCrossInterpreterData *data)
6262
{
63-
PyObject *exc;
64-
if (ignoreexc) {
65-
exc = PyErr_GetRaisedException();
66-
}
63+
PyObject *exc = PyErr_GetRaisedException();
6764
int res = _PyCrossInterpreterData_Release(data);
6865
if (res < 0) {
6966
/* The owning interpreter is already destroyed. */
7067
_PyCrossInterpreterData_Clear(NULL, data);
71-
if (ignoreexc) {
72-
// XXX Emit a warning?
73-
PyErr_Clear();
74-
}
75-
}
76-
if (ignoreexc) {
77-
PyErr_SetRaisedException(exc);
68+
// XXX Emit a warning?
69+
PyErr_Clear();
7870
}
71+
PyErr_SetRaisedException(exc);
7972
return res;
8073
}
8174

@@ -145,7 +138,7 @@ _sharednsitem_clear(struct _sharednsitem *item)
145138
PyMem_RawFree((void *)item->name);
146139
item->name = NULL;
147140
}
148-
(void)_release_xid_data(&item->data, 1);
141+
(void)_release_xid_data(&item->data);
149142
}
150143

151144
static int
@@ -174,16 +167,16 @@ typedef struct _sharedns {
174167
static _sharedns *
175168
_sharedns_new(Py_ssize_t len)
176169
{
177-
_sharedns *shared = PyMem_NEW(_sharedns, 1);
170+
_sharedns *shared = PyMem_RawCalloc(sizeof(_sharedns), 1);
178171
if (shared == NULL) {
179172
PyErr_NoMemory();
180173
return NULL;
181174
}
182175
shared->len = len;
183-
shared->items = PyMem_NEW(struct _sharednsitem, len);
176+
shared->items = PyMem_RawCalloc(sizeof(struct _sharednsitem), len);
184177
if (shared->items == NULL) {
185178
PyErr_NoMemory();
186-
PyMem_Free(shared);
179+
PyMem_RawFree(shared);
187180
return NULL;
188181
}
189182
return shared;
@@ -195,8 +188,8 @@ _sharedns_free(_sharedns *shared)
195188
for (Py_ssize_t i=0; i < shared->len; i++) {
196189
_sharednsitem_clear(&shared->items[i]);
197190
}
198-
PyMem_Free(shared->items);
199-
PyMem_Free(shared);
191+
PyMem_RawFree(shared->items);
192+
PyMem_RawFree(shared);
200193
}
201194

202195
static _sharedns *

Python/ceval_gil.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ _PyEval_SignalReceived(PyInterpreterState *interp)
763763
/* Push one item onto the queue while holding the lock. */
764764
static int
765765
_push_pending_call(struct _pending_calls *pending,
766-
int (*func)(void *), void *arg)
766+
_Py_pending_call_func func, void *arg)
767767
{
768768
int i = pending->last;
769769
int j = (i + 1) % NPENDINGCALLS;
@@ -810,7 +810,7 @@ _pop_pending_call(struct _pending_calls *pending,
810810

811811
int
812812
_PyEval_AddPendingCall(PyInterpreterState *interp,
813-
int (*func)(void *), void *arg,
813+
_Py_pending_call_func func, void *arg,
814814
int mainthreadonly)
815815
{
816816
assert(!mainthreadonly || _Py_IsMainInterpreter(interp));
@@ -834,7 +834,7 @@ _PyEval_AddPendingCall(PyInterpreterState *interp,
834834
}
835835

836836
int
837-
Py_AddPendingCall(int (*func)(void *), void *arg)
837+
Py_AddPendingCall(_Py_pending_call_func func, void *arg)
838838
{
839839
/* Legacy users of this API will continue to target the main thread
840840
(of the main interpreter). */
@@ -878,7 +878,7 @@ _make_pending_calls(struct _pending_calls *pending)
878878
{
879879
/* perform a bounded number of calls, in case of recursion */
880880
for (int i=0; i<NPENDINGCALLS; i++) {
881-
int (*func)(void *) = NULL;
881+
_Py_pending_call_func func = NULL;
882882
void *arg = NULL;
883883

884884
/* pop one item off the queue while holding the lock */

Python/pystate.c

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,10 +2309,16 @@ _xidata_init(_PyCrossInterpreterData *data)
23092309
static inline void
23102310
_xidata_clear(_PyCrossInterpreterData *data)
23112311
{
2312-
if (data->free != NULL) {
2313-
data->free(data->data);
2312+
// _PyCrossInterpreterData only has two members that need to be
2313+
// cleaned up, if set: "data" must be freed and "obj" must be decref'ed.
2314+
// In both cases the original (owning) interpreter must be used,
2315+
// which is the caller's responsibility to ensure.
2316+
if (data->data != NULL) {
2317+
if (data->free != NULL) {
2318+
data->free(data->data);
2319+
}
2320+
data->data = NULL;
23142321
}
2315-
data->data = NULL;
23162322
Py_CLEAR(data->obj);
23172323
}
23182324

@@ -2457,40 +2463,32 @@ _PyCrossInterpreterData_NewObject(_PyCrossInterpreterData *data)
24572463
return data->new_object(data);
24582464
}
24592465

2460-
typedef void (*releasefunc)(PyInterpreterState *, void *);
2461-
2462-
static void
2463-
_call_in_interpreter(PyInterpreterState *interp, releasefunc func, void *arg)
2466+
static int
2467+
_release_xidata_pending(void *data)
24642468
{
2465-
/* We would use Py_AddPendingCall() if it weren't specific to the
2466-
* main interpreter (see bpo-33608). In the meantime we take a
2467-
* naive approach.
2468-
*/
2469-
_PyRuntimeState *runtime = interp->runtime;
2470-
PyThreadState *save_tstate = NULL;
2471-
if (interp != current_fast_get(runtime)->interp) {
2472-
// XXX Using the "head" thread isn't strictly correct.
2473-
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
2474-
// XXX Possible GILState issues?
2475-
save_tstate = _PyThreadState_Swap(runtime, tstate);
2476-
}
2477-
2478-
// XXX Once the GIL is per-interpreter, this should be called with the
2479-
// calling interpreter's GIL released and the target interpreter's held.
2480-
func(interp, arg);
2469+
_xidata_clear((_PyCrossInterpreterData *)data);
2470+
return 0;
2471+
}
24812472

2482-
// Switch back.
2483-
if (save_tstate != NULL) {
2484-
_PyThreadState_Swap(runtime, save_tstate);
2485-
}
2473+
static int
2474+
_xidata_release_and_rawfree_pending(void *data)
2475+
{
2476+
_xidata_clear((_PyCrossInterpreterData *)data);
2477+
PyMem_RawFree(data);
2478+
return 0;
24862479
}
24872480

2488-
int
2489-
_PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
2481+
static int
2482+
_xidata_release(_PyCrossInterpreterData *data, int rawfree)
24902483
{
2491-
if (data->free == NULL && data->obj == NULL) {
2484+
if ((data->data == NULL || data->free == NULL) && data->obj == NULL) {
24922485
// Nothing to release!
2493-
data->data = NULL;
2486+
if (rawfree) {
2487+
PyMem_RawFree(data);
2488+
}
2489+
else {
2490+
data->data = NULL;
2491+
}
24942492
return 0;
24952493
}
24962494

@@ -2501,15 +2499,42 @@ _PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
25012499
// This function shouldn't have been called.
25022500
// XXX Someone leaked some memory...
25032501
assert(PyErr_Occurred());
2502+
if (rawfree) {
2503+
PyMem_RawFree(data);
2504+
}
25042505
return -1;
25052506
}
25062507

25072508
// "Release" the data and/or the object.
2508-
_call_in_interpreter(interp,
2509-
(releasefunc)_PyCrossInterpreterData_Clear, data);
2509+
if (interp == current_fast_get(interp->runtime)->interp) {
2510+
_xidata_clear(data);
2511+
if (rawfree) {
2512+
PyMem_RawFree(data);
2513+
}
2514+
}
2515+
else {
2516+
_Py_pending_call_func func = _release_xidata_pending;
2517+
if (rawfree) {
2518+
func = _xidata_release_and_rawfree_pending;
2519+
}
2520+
// XXX Emit a warning if this fails?
2521+
_PyEval_AddPendingCall(interp, func, data, 0);
2522+
}
25102523
return 0;
25112524
}
25122525

2526+
int
2527+
_PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
2528+
{
2529+
return _xidata_release(data, 0);
2530+
}
2531+
2532+
int
2533+
_PyCrossInterpreterData_ReleaseAndRawFree(_PyCrossInterpreterData *data)
2534+
{
2535+
return _xidata_release(data, 1);
2536+
}
2537+
25132538
/* registry of {type -> crossinterpdatafunc} */
25142539

25152540
/* For now we use a global registry of shareable classes. An

0 commit comments

Comments
 (0)