Skip to content

Commit f53d9f2

Browse files
bpo-32604: Swap threads only if the interpreter is different. (gh-5778)
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.
1 parent 80d20b9 commit f53d9f2

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy