Skip to content

Commit eed3c7a

Browse files
bpo-32604: Swap threads only if the interpreter is different. (gh-5783)
The CPython runtime assumes that there is a one-to-one relationship (for a given interpreter) between PyThreadState and OS threads. Sending and receiving on a channel in the same interpreter was causing crashes because of this (specifically due to a check in PyThreadState_Swap()). The solution is to not switch threads if the interpreter is the same. (cherry picked from commit f53d9f2) Co-authored-by: Eric Snow <[email protected]>
1 parent 1d927d4 commit eed3c7a

File tree

3 files changed

+77
-14
lines changed

3 files changed

+77
-14
lines changed

Lib/test/test__xxsubinterpreters.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pickle
44
from textwrap import dedent, indent
55
import threading
6+
import time
67
import unittest
78

89
from test import support
@@ -1147,6 +1148,54 @@ def test_send_recv_different_interpreters(self):
11471148

11481149
self.assertEqual(obj, b'spam')
11491150

1151+
def test_send_recv_different_threads(self):
1152+
cid = interpreters.channel_create()
1153+
1154+
def f():
1155+
while True:
1156+
try:
1157+
obj = interpreters.channel_recv(cid)
1158+
break
1159+
except interpreters.ChannelEmptyError:
1160+
time.sleep(0.1)
1161+
interpreters.channel_send(cid, obj)
1162+
t = threading.Thread(target=f)
1163+
t.start()
1164+
1165+
interpreters.channel_send(cid, b'spam')
1166+
t.join()
1167+
obj = interpreters.channel_recv(cid)
1168+
1169+
self.assertEqual(obj, b'spam')
1170+
1171+
def test_send_recv_different_interpreters_and_threads(self):
1172+
cid = interpreters.channel_create()
1173+
id1 = interpreters.create()
1174+
out = None
1175+
1176+
def f():
1177+
nonlocal out
1178+
out = _run_output(id1, dedent(f"""
1179+
import time
1180+
import _xxsubinterpreters as _interpreters
1181+
while True:
1182+
try:
1183+
obj = _interpreters.channel_recv({int(cid)})
1184+
break
1185+
except _interpreters.ChannelEmptyError:
1186+
time.sleep(0.1)
1187+
assert(obj == b'spam')
1188+
_interpreters.channel_send({int(cid)}, b'eggs')
1189+
"""))
1190+
t = threading.Thread(target=f)
1191+
t.start()
1192+
1193+
interpreters.channel_send(cid, b'spam')
1194+
t.join()
1195+
obj = interpreters.channel_recv(cid)
1196+
1197+
self.assertEqual(obj, b'eggs')
1198+
11501199
def test_send_not_found(self):
11511200
with self.assertRaises(interpreters.ChannelNotFoundError):
11521201
interpreters.channel_send(10, b'spam')

Modules/_xxsubinterpretersmodule.c

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,8 +1759,13 @@ _run_script_in_interpreter(PyInterpreterState *interp, const char *codestr,
17591759
}
17601760

17611761
// Switch to interpreter.
1762-
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
1763-
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
1762+
PyThreadState *save_tstate = NULL;
1763+
if (interp != PyThreadState_Get()->interp) {
1764+
// XXX Using the "head" thread isn't strictly correct.
1765+
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
1766+
// XXX Possible GILState issues?
1767+
save_tstate = PyThreadState_Swap(tstate);
1768+
}
17641769

17651770
// Run the script.
17661771
_sharedexception *exc = NULL;
@@ -2079,9 +2084,9 @@ interp_create(PyObject *self, PyObject *args)
20792084
}
20802085

20812086
// Create and initialize the new interpreter.
2082-
PyThreadState *tstate, *save_tstate;
2083-
save_tstate = PyThreadState_Swap(NULL);
2084-
tstate = Py_NewInterpreter();
2087+
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
2088+
// XXX Possible GILState issues?
2089+
PyThreadState *tstate = Py_NewInterpreter();
20852090
PyThreadState_Swap(save_tstate);
20862091
if (tstate == NULL) {
20872092
/* Since no new thread state was created, there is no exception to
@@ -2096,6 +2101,7 @@ interp_create(PyObject *self, PyObject *args)
20962101
return _get_id(tstate->interp);
20972102

20982103
error:
2104+
// XXX Possible GILState issues?
20992105
save_tstate = PyThreadState_Swap(tstate);
21002106
Py_EndInterpreter(tstate);
21012107
PyThreadState_Swap(save_tstate);
@@ -2146,9 +2152,9 @@ interp_destroy(PyObject *self, PyObject *args)
21462152

21472153
// Destroy the interpreter.
21482154
//PyInterpreterState_Delete(interp);
2149-
PyThreadState *tstate, *save_tstate;
2150-
tstate = PyInterpreterState_ThreadHead(interp);
2151-
save_tstate = PyThreadState_Swap(tstate);
2155+
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
2156+
// XXX Possible GILState issues?
2157+
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
21522158
Py_EndInterpreter(tstate);
21532159
PyThreadState_Swap(save_tstate);
21542160

Python/pystate.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,10 @@ _PyInterpreterState_IDDecref(PyInterpreterState *interp)
331331
PyThread_release_lock(interp->id_mutex);
332332

333333
if (refcount == 0) {
334-
PyThreadState *tstate, *save_tstate;
335-
tstate = PyInterpreterState_ThreadHead(interp);
336-
save_tstate = PyThreadState_Swap(tstate);
334+
// XXX Using the "head" thread isn't strictly correct.
335+
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
336+
// XXX Possible GILState issues?
337+
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
337338
Py_EndInterpreter(tstate);
338339
PyThreadState_Swap(save_tstate);
339340
}
@@ -1213,8 +1214,14 @@ _PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
12131214
}
12141215
return;
12151216
}
1216-
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
1217-
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
1217+
1218+
PyThreadState *save_tstate = NULL;
1219+
if (interp != PyThreadState_Get()->interp) {
1220+
// XXX Using the "head" thread isn't strictly correct.
1221+
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
1222+
// XXX Possible GILState issues?
1223+
save_tstate = PyThreadState_Swap(tstate);
1224+
}
12181225

12191226
// "Release" the data and/or the object.
12201227
if (data->free != NULL) {
@@ -1223,8 +1230,9 @@ _PyCrossInterpreterData_Release(_PyCrossInterpreterData *data)
12231230
Py_XDECREF(data->obj);
12241231

12251232
// Switch back.
1226-
if (save_tstate != NULL)
1233+
if (save_tstate != NULL) {
12271234
PyThreadState_Swap(save_tstate);
1235+
}
12281236
}
12291237

12301238
PyObject *

0 commit comments

Comments
 (0)