From 4e30ed3af06ae655f4cb8aad8cba21f341384250 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 5 May 2020 16:52:52 +0200 Subject: bpo-40513: Per-interpreter recursion_limit (GH-19929) Move recursion_limit member from _PyRuntimeState.ceval to PyInterpreterState.ceval. * Py_SetRecursionLimit() now only sets _Py_CheckRecursionLimit of ceval.c if the current Python thread is part of the main interpreter. * Inline _Py_MakeEndRecCheck() into _Py_LeaveRecursiveCall(). * Convert _Py_RecursionLimitLowerWaterMark() macro into a static inline function. --- Include/internal/pycore_ceval.h | 24 +++++++++++++----------- Include/internal/pycore_interp.h | 1 + Include/internal/pycore_runtime.h | 1 - Python/ceval.c | 24 ++++++++++++++---------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 2df796d..18c8f02 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -65,12 +65,12 @@ PyAPI_DATA(int) _Py_CheckRecursionLimit; /* 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_depth > _Py_CheckRecursionLimit + return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit || ++tstate->stackcheck_counter > 64); } #else static inline int _Py_MakeRecCheck(PyThreadState *tstate) { - return (++tstate->recursion_depth > _Py_CheckRecursionLimit); + return (++tstate->recursion_depth > tstate->interp->ceval.recursion_limit); } #endif @@ -90,20 +90,22 @@ static inline int _Py_EnterRecursiveCall_inline(const char *where) { #define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_inline(where) - /* Compute the "lower-water mark" for a recursion limit. When * Py_LeaveRecursiveCall() is called with a recursion depth below this mark, * the overflowed flag is reset to 0. */ -#define _Py_RecursionLimitLowerWaterMark(limit) \ - (((limit) > 200) \ - ? ((limit) - 50) \ - : (3 * ((limit) >> 2))) - -#define _Py_MakeEndRecCheck(x) \ - (--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit)) +static inline int _Py_RecursionLimitLowerWaterMark(int limit) { + if (limit > 200) { + return (limit - 50); + } + else { + return (3 * (limit >> 2)); + } +} static inline void _Py_LeaveRecursiveCall(PyThreadState *tstate) { - if (_Py_MakeEndRecCheck(tstate->recursion_depth)) { + tstate->recursion_depth--; + int limit = tstate->interp->ceval.recursion_limit; + if (tstate->recursion_depth < _Py_RecursionLimitLowerWaterMark(limit)) { tstate->overflowed = 0; } } diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index fafc72e..0829101 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -33,6 +33,7 @@ struct _pending_calls { }; struct _ceval_state { + int recursion_limit; /* Records whether tracing is on for any thread. Counts the number of threads for which tstate->c_tracefunc is non-NULL, so if the value is 0, we know we don't have to check this thread's diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index c597335..8ca1dfb 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -14,7 +14,6 @@ extern "C" { /* ceval state */ struct _ceval_runtime_state { - int recursion_limit; struct _gil_runtime_state gil; }; diff --git a/Python/ceval.c b/Python/ceval.c index 6b00273..601e21a 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -699,7 +699,6 @@ int _Py_CheckRecursionLimit = Py_DEFAULT_RECURSION_LIMIT; void _PyEval_InitRuntimeState(struct _ceval_runtime_state *ceval) { - ceval->recursion_limit = Py_DEFAULT_RECURSION_LIMIT; _Py_CheckRecursionLimit = Py_DEFAULT_RECURSION_LIMIT; _gil_initialize(&ceval->gil); } @@ -707,6 +706,8 @@ _PyEval_InitRuntimeState(struct _ceval_runtime_state *ceval) int _PyEval_InitState(struct _ceval_state *ceval) { + ceval->recursion_limit = Py_DEFAULT_RECURSION_LIMIT; + struct _pending_calls *pending = &ceval->pending; assert(pending->lock == NULL); @@ -730,16 +731,18 @@ _PyEval_FiniState(struct _ceval_state *ceval) int Py_GetRecursionLimit(void) { - struct _ceval_runtime_state *ceval = &_PyRuntime.ceval; - return ceval->recursion_limit; + PyThreadState *tstate = _PyThreadState_GET(); + return tstate->interp->ceval.recursion_limit; } void Py_SetRecursionLimit(int new_limit) { - struct _ceval_runtime_state *ceval = &_PyRuntime.ceval; - ceval->recursion_limit = new_limit; - _Py_CheckRecursionLimit = new_limit; + PyThreadState *tstate = _PyThreadState_GET(); + tstate->interp->ceval.recursion_limit = new_limit; + if (_Py_IsMainInterpreter(tstate)) { + _Py_CheckRecursionLimit = new_limit; + } } /* The function _Py_EnterRecursiveCall() only calls _Py_CheckRecursiveCall() @@ -750,8 +753,7 @@ Py_SetRecursionLimit(int new_limit) int _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) { - _PyRuntimeState *runtime = tstate->interp->runtime; - int recursion_limit = runtime->ceval.recursion_limit; + int recursion_limit = tstate->interp->ceval.recursion_limit; #ifdef USE_STACKCHECK tstate->stackcheck_counter = 0; @@ -760,8 +762,10 @@ _Py_CheckRecursiveCall(PyThreadState *tstate, const char *where) _PyErr_SetString(tstate, PyExc_MemoryError, "Stack overflow"); return -1; } - /* Needed for ABI backwards-compatibility (see bpo-31857) */ - _Py_CheckRecursionLimit = recursion_limit; + if (_Py_IsMainInterpreter(tstate)) { + /* Needed for ABI backwards-compatibility (see bpo-31857) */ + _Py_CheckRecursionLimit = recursion_limit; + } #endif if (tstate->recursion_critical) /* Somebody asked that we don't check for recursion. */ -- cgit v0.12