Skip to content

gh-111798: Use lower Py_C_RECURSION_LIMIT in debug mode #112124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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