summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_capi.py16
-rw-r--r--Modules/_testcapimodule.c12
-rw-r--r--Python/errors.c11
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;
}