Skip to content

Commit

Permalink
gh-111798: Use lower Py_C_RECURSION_LIMIT in debug mode (#112124)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
vstinner authored Nov 16, 2023
1 parent 81ab0e8 commit bd89bca
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 5 deletions.
6 changes: 5 additions & 1 deletion Include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ struct _ts {

};

#ifdef __wasi__
#ifdef Py_DEBUG
// A debug build is likely built with low optimization level which implies
// higher stack memory usage than a release build: use a lower limit.
# define Py_C_RECURSION_LIMIT 500
#elif defined(__wasi__)
// WASI has limited call stack. Python's recursion limit depends on code
// layout, optimization, and WASI runtime. Wasmtime can handle about 700
// recursions, sometimes less. 500 is a more conservative limit.
Expand Down
8 changes: 7 additions & 1 deletion Lib/test/test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
import weakref
from functools import partial
from textwrap import dedent
try:
import _testinternalcapi
except ImportError:
_testinternalcapi = None

from test import support
from test.support.import_helper import import_fresh_module
Expand Down Expand Up @@ -1118,12 +1122,14 @@ def next(self):
return self
enum._test_simple_enum(_Precedence, ast._Precedence)

@unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
@support.cpython_only
def test_ast_recursion_limit(self):
fail_depth = support.EXCEEDS_RECURSION_LIMIT
crash_depth = 100_000
success_depth = 1200
if _testinternalcapi is not None:
remaining = _testinternalcapi.get_c_recursion_remaining()
success_depth = min(success_depth, remaining)

def check_limit(prefix, repeated):
expect_ok = prefix + repeated * success_depth
Expand Down
15 changes: 12 additions & 3 deletions Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
import textwrap
import subprocess
import warnings
try:
import _testinternalcapi
except ImportError:
_testinternalcapi = None

support.requires_working_socket(module=True)

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

def test_trace_lots_of_globals(self):
count = 1000
if _testinternalcapi is not None:
remaining = _testinternalcapi.get_c_recursion_remaining()
count = min(count, remaining)

code = """if 1:
def f():
return (
{}
)
""".format("\n+\n".join(f"var{i}\n" for i in range(1000)))
ns = {f"var{i}": i for i in range(1000)}
""".format("\n+\n".join(f"var{i}\n" for i in range(count)))
ns = {f"var{i}": i for i in range(count)}
exec(code, ns)
counts = self.count_traces(ns["f"])
self.assertEqual(counts, {'call': 1, 'line': 2000, 'return': 1})
self.assertEqual(counts, {'call': 1, 'line': count * 2, 'return': 1})


class TestEdgeCases(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
When Python is built in debug mode, set the C recursion limit to 500 instead
of 1500. A debug build is likely built with low optimization level which
implies higher stack memory usage than a release build. Patch by Victor
Stinner.
9 changes: 9 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args))
}


static PyObject*
get_c_recursion_remaining(PyObject *self, PyObject *Py_UNUSED(args))
{
PyThreadState *tstate = _PyThreadState_GET();
return PyLong_FromLong(tstate->c_recursion_remaining);
}


static PyObject*
test_bswap(PyObject *self, PyObject *Py_UNUSED(args))
{
Expand Down Expand Up @@ -1611,6 +1619,7 @@ perf_trampoline_set_persist_after_fork(PyObject *self, PyObject *args)
static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
{"get_c_recursion_remaining", get_c_recursion_remaining, METH_NOARGS},
{"test_bswap", test_bswap, METH_NOARGS},
{"test_popcount", test_popcount, METH_NOARGS},
{"test_bit_length", test_bit_length, METH_NOARGS},
Expand Down

0 comments on commit bd89bca

Please sign in to comment.