Skip to content

Commit c98644b

Browse files
committed
gh-111798: Use lower Py_C_RECURSION_LIMIT in debug mode
* Run again test_ast_recursion_limit() on WASI platform. * Add _testinternalcapi.get_c_recursion_remaining(). * Fix test_ast and test_sys_settrace: test_ast_recursion_limit() and test_trace_unpack_long_sequence() now adjust the maximum recursion depth depending on the the remaining C recursion.
1 parent 81ab0e8 commit c98644b

File tree

5 files changed

+37
-5
lines changed

5 files changed

+37
-5
lines changed

Include/cpython/pystate.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,11 @@ struct _ts {
214214

215215
};
216216

217-
#ifdef __wasi__
217+
#ifdef Py_DEBUG
218+
// A debug build is likely built with low optimization level which implies
219+
// higher stack memory usage than a release build: use a lower limit.
220+
# define Py_C_RECURSION_LIMIT 500
221+
#elif defined(__wasi__)
218222
// WASI has limited call stack. Python's recursion limit depends on code
219223
// layout, optimization, and WASI runtime. Wasmtime can handle about 700
220224
// recursions, sometimes less. 500 is a more conservative limit.

Lib/test/test_ast.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
import weakref
1313
from functools import partial
1414
from textwrap import dedent
15+
try:
16+
import _testinternalcapi
17+
except ImportError:
18+
_testinternalcapi = None
1519

1620
from test import support
1721
from test.support.import_helper import import_fresh_module
@@ -1118,12 +1122,14 @@ def next(self):
11181122
return self
11191123
enum._test_simple_enum(_Precedence, ast._Precedence)
11201124

1121-
@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
11221125
@support.cpython_only
11231126
def test_ast_recursion_limit(self):
11241127
fail_depth = support.EXCEEDS_RECURSION_LIMIT
11251128
crash_depth = 100_000
11261129
success_depth = 1200
1130+
if _testinternalcapi is not None:
1131+
remaining = _testinternalcapi.get_c_recursion_remaining()
1132+
success_depth = min(success_depth, remaining)
11271133

11281134
def check_limit(prefix, repeated):
11291135
expect_ok = prefix + repeated * success_depth

Lib/test/test_sys_settrace.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
import textwrap
1515
import subprocess
1616
import warnings
17+
try:
18+
import _testinternalcapi
19+
except ImportError:
20+
_testinternalcapi = None
1721

1822
support.requires_working_socket(module=True)
1923

@@ -3033,16 +3037,21 @@ def test_trace_unpack_long_sequence(self):
30333037
self.assertEqual(counts, {'call': 1, 'line': 301, 'return': 1})
30343038

30353039
def test_trace_lots_of_globals(self):
3040+
count = 1000
3041+
if _testinternalcapi is not None:
3042+
remaining = _testinternalcapi.get_c_recursion_remaining()
3043+
count = min(count, remaining)
3044+
30363045
code = """if 1:
30373046
def f():
30383047
return (
30393048
{}
30403049
)
3041-
""".format("\n+\n".join(f"var{i}\n" for i in range(1000)))
3042-
ns = {f"var{i}": i for i in range(1000)}
3050+
""".format("\n+\n".join(f"var{i}\n" for i in range(count)))
3051+
ns = {f"var{i}": i for i in range(count)}
30433052
exec(code, ns)
30443053
counts = self.count_traces(ns["f"])
3045-
self.assertEqual(counts, {'call': 1, 'line': 2000, 'return': 1})
3054+
self.assertEqual(counts, {'call': 1, 'line': count * 2, 'return': 1})
30463055

30473056

30483057
class TestEdgeCases(unittest.TestCase):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
When Python is built in debug mode, set the C recursion limit to 500 instead
2+
of 1500. A debug build is likely built with low optimization level which
3+
implies higher stack memory usage than a release build. Patch by Victor
4+
Stinner.

Modules/_testinternalcapi.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args))
109109
}
110110

111111

112+
static PyObject*
113+
get_c_recursion_remaining(PyObject *self, PyObject *Py_UNUSED(args))
114+
{
115+
PyThreadState *tstate = _PyThreadState_GET();
116+
return PyLong_FromLong(tstate->c_recursion_remaining);
117+
}
118+
119+
112120
static PyObject*
113121
test_bswap(PyObject *self, PyObject *Py_UNUSED(args))
114122
{
@@ -1611,6 +1619,7 @@ perf_trampoline_set_persist_after_fork(PyObject *self, PyObject *args)
16111619
static PyMethodDef module_functions[] = {
16121620
{"get_configs", get_configs, METH_NOARGS},
16131621
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
1622+
{"get_c_recursion_remaining", get_c_recursion_remaining, METH_NOARGS},
16141623
{"test_bswap", test_bswap, METH_NOARGS},
16151624
{"test_popcount", test_popcount, METH_NOARGS},
16161625
{"test_bit_length", test_bit_length, METH_NOARGS},

0 commit comments

Comments
 (0)