Skip to content

Commit 6f7346b

Browse files
authored
[3.9] bpo-40826: Fix GIL usage in PyOS_Readline() (GH-20613) (GH-20616)
* bpo-40826: Fix GIL usage in PyOS_Readline() (GH-20579) Fix GIL usage in PyOS_Readline(): lock the GIL to set an exception. Pass tstate to my_fgets() and _PyOS_WindowsConsoleReadline(). Cleanup these functions. (cherry picked from commit c353764) * bpo-40826: Add _PyOS_InterruptOccurred(tstate) function (GH-20599) my_fgets() now calls _PyOS_InterruptOccurred(tstate) to check for pending signals, rather calling PyOS_InterruptOccurred(). my_fgets() is called with the GIL released, whereas PyOS_InterruptOccurred() must be called with the GIL held. test_repl: use text=True and avoid SuppressCrashReport in test_multiline_string_parsing(). Fix my_fgets() on Windows: fgets(fp) does crash if fileno(fp) is closed. (cherry picked from commit fa7ab6a)
1 parent 5b8787e commit 6f7346b

File tree

5 files changed

+128
-43
lines changed

5 files changed

+128
-43
lines changed

Include/internal/pycore_pystate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ PyAPI_FUNC(void) _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime);
317317

318318
PyAPI_FUNC(void) _PyGILState_Reinit(_PyRuntimeState *runtime);
319319

320+
321+
PyAPI_FUNC(int) _PyOS_InterruptOccurred(PyThreadState *tstate);
322+
320323
#ifdef __cplusplus
321324
}
322325
#endif

Lib/test/test_repl.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw):
2929
# test.support.script_helper.
3030
env = kw.setdefault('env', dict(os.environ))
3131
env['TERM'] = 'vt100'
32-
return subprocess.Popen(cmd_line, executable=sys.executable,
32+
return subprocess.Popen(cmd_line,
33+
executable=sys.executable,
34+
text=True,
3335
stdin=subprocess.PIPE,
3436
stdout=stdout, stderr=stderr,
3537
**kw)
@@ -49,12 +51,11 @@ def test_no_memory(self):
4951
sys.exit(0)
5052
"""
5153
user_input = dedent(user_input)
52-
user_input = user_input.encode()
5354
p = spawn_repl()
5455
with SuppressCrashReport():
5556
p.stdin.write(user_input)
5657
output = kill_python(p)
57-
self.assertIn(b'After the exception.', output)
58+
self.assertIn('After the exception.', output)
5859
# Exit code 120: Py_FinalizeEx() failed to flush stdout and stderr.
5960
self.assertIn(p.returncode, (1, 120))
6061

@@ -86,13 +87,22 @@ def test_multiline_string_parsing(self):
8687
</test>"""
8788
'''
8889
user_input = dedent(user_input)
89-
user_input = user_input.encode()
9090
p = spawn_repl()
91-
with SuppressCrashReport():
92-
p.stdin.write(user_input)
91+
p.stdin.write(user_input)
9392
output = kill_python(p)
9493
self.assertEqual(p.returncode, 0)
9594

95+
def test_close_stdin(self):
96+
user_input = dedent('''
97+
import os
98+
print("before close")
99+
os.close(0)
100+
''')
101+
process = spawn_repl()
102+
output = process.communicate(user_input)[0]
103+
self.assertEqual(process.returncode, 0)
104+
self.assertIn('before close', output)
105+
96106

97107
if __name__ == "__main__":
98108
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix GIL usage in :c:func:`PyOS_Readline`: lock the GIL to set an exception
2+
and pass the Python thread state when checking if there is a pending signal.

Modules/signalmodule.c

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,20 @@ itimer_retval(struct itimerval *iv)
187187
#endif
188188

189189
static int
190-
is_main(_PyRuntimeState *runtime)
190+
is_main_interp(_PyRuntimeState *runtime, PyInterpreterState *interp)
191191
{
192192
unsigned long thread = PyThread_get_thread_ident();
193-
PyInterpreterState *interp = _PyRuntimeState_GetThreadState(runtime)->interp;
194193
return (thread == runtime->main_thread
195194
&& interp == runtime->interpreters.main);
196195
}
197196

197+
static int
198+
is_main(_PyRuntimeState *runtime)
199+
{
200+
PyInterpreterState *interp = _PyRuntimeState_GetThreadState(runtime)->interp;
201+
return is_main_interp(runtime, interp);
202+
}
203+
198204
static PyObject *
199205
signal_default_int_handler(PyObject *self, PyObject *args)
200206
{
@@ -1726,12 +1732,14 @@ PyOS_FiniInterrupts(void)
17261732
finisignal();
17271733
}
17281734

1735+
1736+
// The caller doesn't have to hold the GIL
17291737
int
1730-
PyOS_InterruptOccurred(void)
1738+
_PyOS_InterruptOccurred(PyThreadState *tstate)
17311739
{
17321740
if (_Py_atomic_load_relaxed(&Handlers[SIGINT].tripped)) {
17331741
_PyRuntimeState *runtime = &_PyRuntime;
1734-
if (!is_main(runtime)) {
1742+
if (!is_main_interp(runtime, tstate->interp)) {
17351743
return 0;
17361744
}
17371745
_Py_atomic_store_relaxed(&Handlers[SIGINT].tripped, 0);
@@ -1740,6 +1748,16 @@ PyOS_InterruptOccurred(void)
17401748
return 0;
17411749
}
17421750

1751+
1752+
// The caller must to hold the GIL
1753+
int
1754+
PyOS_InterruptOccurred(void)
1755+
{
1756+
PyThreadState *tstate = _PyThreadState_GET();
1757+
return _PyOS_InterruptOccurred(tstate);
1758+
}
1759+
1760+
17431761
static void
17441762
_clear_pending_signals(void)
17451763
{

0 commit comments

Comments
 (0)