Skip to content

Commit afec6a5

Browse files
[3.13] gh-134163: Fix an infinite loop when the process runs out of memory in a try block (GH-138491)
Signed-off-by: yihong0618 <[email protected]> Co-authored-by: Peter Bierma <[email protected]>
1 parent 443d4af commit afec6a5

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

Lib/test/test_exceptions.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,6 +1843,38 @@ def test_memory_error_in_subinterp(self):
18431843
rc, _, err = script_helper.assert_python_ok("-c", code)
18441844
self.assertIn(b'MemoryError', err)
18451845

1846+
@cpython_only
1847+
# Python built with Py_TRACE_REFS fail with a fatal error in
1848+
# _PyRefchain_Trace() on memory allocation error.
1849+
@unittest.skipIf(support.Py_TRACE_REFS, 'cannot test Py_TRACE_REFS build')
1850+
def test_exec_set_nomemory_hang(self):
1851+
import_module("_testcapi")
1852+
# gh-134163: A MemoryError inside code that was wrapped by a try/except
1853+
# block would lead to an infinite loop.
1854+
1855+
# The frame_lasti needs to be greater than 257 to prevent
1856+
# PyLong_FromLong() from returning cached integers, which
1857+
# don't require a memory allocation. Prepend some dummy code
1858+
# to artificially increase the instruction index.
1859+
warmup_code = "a = list(range(0, 1))\n" * 20
1860+
user_input = warmup_code + dedent("""
1861+
try:
1862+
import _testcapi
1863+
_testcapi.set_nomemory(0)
1864+
b = list(range(1000, 2000))
1865+
except Exception as e:
1866+
import traceback
1867+
traceback.print_exc()
1868+
""")
1869+
with SuppressCrashReport():
1870+
with script_helper.spawn_python('-c', user_input) as p:
1871+
p.wait()
1872+
output = p.stdout.read()
1873+
1874+
self.assertIn(p.returncode, (0, 1))
1875+
self.assertGreater(len(output), 0) # At minimum, should not hang
1876+
self.assertIn(b"MemoryError", output)
1877+
18461878

18471879
class NameErrorTests(unittest.TestCase):
18481880
def test_name_error_has_name(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a hang when the process is out of memory inside an exception handler.

Python/ceval.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
912912
int frame_lasti = _PyInterpreterFrame_LASTI(frame);
913913
PyObject *lasti = PyLong_FromLong(frame_lasti);
914914
if (lasti == NULL) {
915-
goto exception_unwind;
915+
// Instead of going back to exception_unwind (which would cause
916+
// infinite recursion), directly exit to let the original exception
917+
// propagate up and hopefully be handled at a higher level.
918+
_PyFrame_SetStackPointer(frame, stack_pointer);
919+
goto exit_unwind;
916920
}
917921
PUSH(lasti);
918922
}

0 commit comments

Comments
 (0)