-
-
Notifications
You must be signed in to change notification settings - Fork 33.6k
GH-91079: Decouple C stack overflow checks from Python recursion checks. #96510
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
Changes from 9 commits
de4085f
6b380eb
b64c83b
e29a4cb
4897476
e8a1bed
7e21a74
65cca7d
d75797c
d1abed5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,16 +12,7 @@ extern "C" { | |
| struct pyruntimestate; | ||
| struct _ceval_runtime_state; | ||
|
|
||
| /* WASI has limited call stack. Python's recursion limit depends on code | ||
| layout, optimization, and WASI runtime. Wasmtime can handle about 700-750 | ||
| recursions, sometimes less. 600 is a more conservative limit. */ | ||
| #ifndef Py_DEFAULT_RECURSION_LIMIT | ||
| # ifdef __wasi__ | ||
| # define Py_DEFAULT_RECURSION_LIMIT 600 | ||
| # else | ||
| # define Py_DEFAULT_RECURSION_LIMIT 1000 | ||
| # endif | ||
| #endif | ||
| #define Py_DEFAULT_RECURSION_LIMIT 1000 | ||
|
||
|
|
||
| #include "pycore_interp.h" // PyInterpreterState.eval_frame | ||
| #include "pycore_pystate.h" // _PyThreadState_GET() | ||
|
|
@@ -118,19 +109,22 @@ extern void _PyEval_DeactivateOpCache(void); | |
| /* With USE_STACKCHECK macro defined, trigger stack checks in | ||
| _Py_CheckRecursiveCall() on every 64th call to _Py_EnterRecursiveCall. */ | ||
| static inline int _Py_MakeRecCheck(PyThreadState *tstate) { | ||
| return (tstate->recursion_remaining-- <= 0 | ||
| || (tstate->recursion_remaining & 63) == 0); | ||
| return (tstate->c_recursion_remaining-- <= 0 | ||
| || (tstate->c_recursion_remaining & 63) == 0); | ||
| } | ||
| #else | ||
| static inline int _Py_MakeRecCheck(PyThreadState *tstate) { | ||
| return tstate->recursion_remaining-- <= 0; | ||
| return tstate->c_recursion_remaining-- <= 0; | ||
| } | ||
| #endif | ||
|
|
||
| PyAPI_FUNC(int) _Py_CheckRecursiveCall( | ||
| PyThreadState *tstate, | ||
| const char *where); | ||
|
|
||
| int _Py_CheckRecursiveCallPy( | ||
| PyThreadState *tstate); | ||
|
|
||
| static inline int _Py_EnterRecursiveCallTstate(PyThreadState *tstate, | ||
| const char *where) { | ||
| return (_Py_MakeRecCheck(tstate) && _Py_CheckRecursiveCall(tstate, where)); | ||
|
|
@@ -142,7 +136,7 @@ static inline int _Py_EnterRecursiveCall(const char *where) { | |
| } | ||
|
|
||
| static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) { | ||
| tstate->recursion_remaining++; | ||
| tstate->c_recursion_remaining++; | ||
| } | ||
|
|
||
| static inline void _Py_LeaveRecursiveCall(void) { | ||
|
|
@@ -157,6 +151,7 @@ extern PyObject* _Py_MakeCoro(PyFunctionObject *func); | |
| extern int _Py_HandlePending(PyThreadState *tstate); | ||
|
|
||
|
|
||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -140,11 +140,11 @@ class MyGlobals(dict): | |
| def __missing__(self, key): | ||
| return int(key.removeprefix("_number_")) | ||
|
|
||
| # 1,000 on most systems | ||
| limit = sys.getrecursionlimit() | ||
| code = "lambda: " + "+".join(f"_number_{i}" for i in range(limit)) | ||
| # Need more than 256 variables to use EXTENDED_ARGS | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume EXTENDED_ARGS has stack implications? explaining the "why" of this here would be useful. |
||
| variables = 400 | ||
| code = "lambda: " + "+".join(f"_number_{i}" for i in range(variables)) | ||
| sum_func = eval(code, MyGlobals()) | ||
| expected = sum(range(limit)) | ||
| expected = sum(range(variables)) | ||
| # Warm up the the function for quickening (PEP 659) | ||
| for _ in range(30): | ||
| self.assertEqual(sum_func(), expected) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1399,13 +1399,8 @@ def gen(): | |
| generator = gen() | ||
| next(generator) | ||
| recursionlimit = sys.getrecursionlimit() | ||
| depth = get_recursion_depth() | ||
| try: | ||
| # Upon the last recursive invocation of recurse(), | ||
| # tstate->recursion_depth is equal to (recursion_limit - 1) | ||
| # and is equal to recursion_limit when _gen_throw() calls | ||
| # PyErr_NormalizeException(). | ||
| recurse(setrecursionlimit(depth + 2) - depth) | ||
| recurse(5000) | ||
|
||
| finally: | ||
| sys.setrecursionlimit(recursionlimit) | ||
| print('Done.') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| Separate Python recursion checking from C recursion checking which reduces | ||
| the chance of C stack overflow and allows the recursion limit to be | ||
| increased safely. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ifndef C_RECURSION_LIMIT