diff options
-rw-r--r-- | Lib/test/test_capi.py | 16 | ||||
-rw-r--r-- | Modules/_testcapimodule.c | 12 | ||||
-rw-r--r-- | Python/errors.c | 11 |
3 files changed, 35 insertions, 4 deletions
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 29f7a71..7a6870d 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -2,9 +2,10 @@ # these are all functions _testcapi exports whose name begins with 'test_'. from __future__ import with_statement +import random +import subprocess import sys import time -import random import unittest from test import support try: @@ -35,6 +36,19 @@ class CAPITest(unittest.TestCase): self.assertEqual(testfunction.attribute, "test") self.assertRaises(AttributeError, setattr, inst.testfunction, "attribute", "test") + def test_no_FatalError_infinite_loop(self): + p = subprocess.Popen([sys.executable, "-c", + 'import _testcapi;' + '_testcapi.crash_no_current_thread()'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (out, err) = p.communicate() + self.assertEqual(out, b'') + # This used to cause an infinite loop. + self.assertEqual(err, + b'Fatal Python error:' + b' PyThreadState_Get: no current thread\n') + @unittest.skipUnless(threading, 'Threading required for this test.') class TestPendingCalls(unittest.TestCase): diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 2868014..ba81f71 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2005,6 +2005,17 @@ make_exception_with_doc(PyObject *self, PyObject *args, PyObject *kwargs) return PyErr_NewExceptionWithDoc(name, doc, base, dict); } +/* Test that the fatal error from not having a current thread doesn't + cause an infinite loop. Run via Lib/test/test_capi.py */ +static PyObject * +crash_no_current_thread(PyObject *self) +{ + Py_BEGIN_ALLOW_THREADS + PyErr_SetString(PyExc_SystemError, "bork bork bork"); + Py_END_ALLOW_THREADS + return NULL; +} + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, {"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS}, @@ -2069,6 +2080,7 @@ static PyMethodDef TestMethods[] = { {"code_newempty", code_newempty, METH_VARARGS}, {"make_exception_with_doc", (PyCFunction)make_exception_with_doc, METH_VARARGS | METH_KEYWORDS}, + {"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/errors.c b/Python/errors.c index e98d7a9..3766973 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -130,9 +130,14 @@ PyErr_SetString(PyObject *exception, const char *string) PyObject * PyErr_Occurred(void) { - PyThreadState *tstate = PyThreadState_GET(); - - return tstate->curexc_type; + /* If there is no thread state, PyThreadState_GET calls + Py_FatalError, which calls PyErr_Occurred. To avoid the + resulting infinite loop, we inline PyThreadState_GET here and + treat no thread as no error. */ + PyThreadState *tstate = + ((PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current)); + + return tstate == NULL ? NULL : tstate->curexc_type; } |