mirror of https://github.com/python/cpython
gh-123321: Fix Parser/myreadline.c to prevent a segfault during a multi-threaded race (#123323)
This commit is contained in:
parent
c530ce1e9d
commit
a4562fedad
|
@ -7,11 +7,12 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
import unittest
|
import unittest
|
||||||
from test.support import verbose
|
from test.support import requires_gil_enabled, verbose
|
||||||
from test.support.import_helper import import_module
|
from test.support.import_helper import import_module
|
||||||
from test.support.os_helper import unlink, temp_dir, TESTFN
|
from test.support.os_helper import unlink, temp_dir, TESTFN
|
||||||
from test.support.pty_helper import run_pty
|
from test.support.pty_helper import run_pty
|
||||||
from test.support.script_helper import assert_python_ok
|
from test.support.script_helper import assert_python_ok
|
||||||
|
from test.support.threading_helper import requires_working_threading
|
||||||
|
|
||||||
# Skip tests if there is no readline module
|
# Skip tests if there is no readline module
|
||||||
readline = import_module('readline')
|
readline = import_module('readline')
|
||||||
|
@ -349,6 +350,31 @@ readline.write_history_file(history_file)
|
||||||
self.assertEqual(len(lines), history_size)
|
self.assertEqual(len(lines), history_size)
|
||||||
self.assertEqual(lines[-1].strip(), b"last input")
|
self.assertEqual(lines[-1].strip(), b"last input")
|
||||||
|
|
||||||
|
@requires_working_threading()
|
||||||
|
@requires_gil_enabled()
|
||||||
|
def test_gh123321_threadsafe(self):
|
||||||
|
"""gh-123321: readline should be thread-safe and not crash"""
|
||||||
|
script = textwrap.dedent(r"""
|
||||||
|
import threading
|
||||||
|
from test.support.threading_helper import join_thread
|
||||||
|
|
||||||
|
def func():
|
||||||
|
input()
|
||||||
|
|
||||||
|
thread1 = threading.Thread(target=func)
|
||||||
|
thread2 = threading.Thread(target=func)
|
||||||
|
thread1.start()
|
||||||
|
thread2.start()
|
||||||
|
join_thread(thread1)
|
||||||
|
join_thread(thread2)
|
||||||
|
print("done")
|
||||||
|
""")
|
||||||
|
|
||||||
|
output = run_pty(script, input=b"input1\rinput2\r")
|
||||||
|
|
||||||
|
self.assertIn(b"done", output)
|
||||||
|
|
||||||
|
|
||||||
def test_write_read_limited_history(self):
|
def test_write_read_limited_history(self):
|
||||||
previous_length = readline.get_history_length()
|
previous_length = readline.get_history_length()
|
||||||
self.addCleanup(readline.set_history_length, previous_length)
|
self.addCleanup(readline.set_history_length, previous_length)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Prevent Parser/myreadline race condition from segfaulting on multi-threaded
|
||||||
|
use. Patch by Bar Harel and Amit Wienner.
|
|
@ -392,9 +392,14 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_PyOS_ReadlineTState = tstate;
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
|
||||||
|
// GH-123321: We need to acquire the lock before setting
|
||||||
|
// _PyOS_ReadlineTState and after the release of the GIL, otherwise
|
||||||
|
// the variable may be nullified by a different thread or a deadlock
|
||||||
|
// may occur if the GIL is taken in any sub-function.
|
||||||
PyThread_acquire_lock(_PyOS_ReadlineLock, 1);
|
PyThread_acquire_lock(_PyOS_ReadlineLock, 1);
|
||||||
|
_PyOS_ReadlineTState = tstate;
|
||||||
|
|
||||||
/* This is needed to handle the unlikely case that the
|
/* This is needed to handle the unlikely case that the
|
||||||
* interpreter is in interactive mode *and* stdin/out are not
|
* interpreter is in interactive mode *and* stdin/out are not
|
||||||
|
@ -418,11 +423,13 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
|
||||||
else {
|
else {
|
||||||
rv = (*PyOS_ReadlineFunctionPointer)(sys_stdin, sys_stdout, prompt);
|
rv = (*PyOS_ReadlineFunctionPointer)(sys_stdin, sys_stdout, prompt);
|
||||||
}
|
}
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
|
|
||||||
|
// gh-123321: Must set the variable and then release the lock before
|
||||||
|
// taking the GIL. Otherwise a deadlock or segfault may occur.
|
||||||
|
_PyOS_ReadlineTState = NULL;
|
||||||
PyThread_release_lock(_PyOS_ReadlineLock);
|
PyThread_release_lock(_PyOS_ReadlineLock);
|
||||||
|
|
||||||
_PyOS_ReadlineTState = NULL;
|
Py_END_ALLOW_THREADS
|
||||||
|
|
||||||
if (rv == NULL)
|
if (rv == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
Loading…
Reference in New Issue