[Python-checkins] cpython (merge 3.5 -> default): Merge 3.5 (sys.setrecursionlimit)

victor.stinner python-checkins at python.org
Mon Oct 12 18:16:34 EDT 2015


https://hg.python.org/cpython/rev/978b7dfcb6f1
changeset: 98719:978b7dfcb6f1
parent: 98717:370af83da32c
parent: 98718:eb0c76442cee
user: Victor Stinner <victor.stinner at gmail.com>
date: Tue Oct 13 00:16:07 2015 +0200
summary:
 Merge 3.5 (sys.setrecursionlimit)
files:
 Doc/library/sys.rst | 7 +++
 Include/ceval.h | 12 ++++-
 Lib/test/test_sys.py | 55 ++++++++++++++++++++++----
 Misc/NEWS | 5 ++
 Modules/_testcapimodule.c | 10 ++++
 Python/sysmodule.c | 29 ++++++++++++-
 6 files changed, 102 insertions(+), 16 deletions(-)
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -975,6 +975,13 @@
 that supports a higher limit. This should be done with care, because a too-high
 limit can lead to a crash.
 
+ If the new limit is too low at the current recursion depth, a
+ :exc:`RecursionError` exception is raised.
+
+ .. versionchanged:: 3.5.1
+ A :exc:`RecursionError` exception is now raised if the new limit is too
+ low at the current recursion depth.
+
 
 .. function:: setswitchinterval(interval)
 
diff --git a/Include/ceval.h b/Include/ceval.h
--- a/Include/ceval.h
+++ b/Include/ceval.h
@@ -94,10 +94,16 @@
 # define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit)
 #endif
 
+/* 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_CheckRecursionLimit > 100) \
- ? (_Py_CheckRecursionLimit - 50) \
- : (3 * (_Py_CheckRecursionLimit >> 2))))
+ (--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit))
 
 #define Py_ALLOW_RECURSION \
 do { unsigned char _old = PyThreadState_GET()->recursion_critical;\
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -201,25 +201,60 @@
 if hasattr(sys, 'gettrace') and sys.gettrace():
 self.skipTest('fatal error if run with a trace function')
 
- # NOTE: this test is slightly fragile in that it depends on the current
- # recursion count when executing the test being low enough so as to
- # trigger the recursion recovery detection in the _Py_MakeEndRecCheck
- # macro (see ceval.h).
 oldlimit = sys.getrecursionlimit()
 def f():
 f()
 try:
- # FIXME: workaround crash for the issue #25274
- # FIXME: until the crash is fixed
- #for i in (50, 1000):
- for i in (150, 1000):
- # Issue #5392: stack overflow after hitting recursion limit twice
- sys.setrecursionlimit(i)
+ for depth in (10, 25, 50, 75, 100, 250, 1000):
+ try:
+ sys.setrecursionlimit(depth)
+ except RecursionError:
+ # Issue #25274: The recursion limit is too low at the
+ # current recursion depth
+ continue
+
+ # Issue #5392: test stack overflow after hitting recursion
+ # limit twice
 self.assertRaises(RecursionError, f)
 self.assertRaises(RecursionError, f)
 finally:
 sys.setrecursionlimit(oldlimit)
 
+ @test.support.cpython_only
+ def test_setrecursionlimit_recursion_depth(self):
+ # Issue #25274: Setting a low recursion limit must be blocked if the
+ # current recursion depth is already higher than the "lower-water
+ # mark". Otherwise, it may not be possible anymore to
+ # reset the overflowed flag to 0.
+
+ from _testcapi import get_recursion_depth
+
+ def set_recursion_limit_at_depth(depth, limit):
+ recursion_depth = get_recursion_depth()
+ if recursion_depth >= depth:
+ with self.assertRaises(RecursionError) as cm:
+ sys.setrecursionlimit(limit)
+ self.assertRegex(str(cm.exception),
+ "cannot set the recursion limit to [0-9]+ "
+ "at the recursion depth [0-9]+: "
+ "the limit is too low")
+ else:
+ set_recursion_limit_at_depth(depth, limit)
+
+ oldlimit = sys.getrecursionlimit()
+ try:
+ sys.setrecursionlimit(1000)
+
+ for limit in (10, 25, 50, 75, 100, 150, 200):
+ # formula extracted from _Py_RecursionLimitLowerWaterMark()
+ if limit > 200:
+ depth = limit - 50
+ else:
+ depth = limit * 3 // 4
+ set_recursion_limit_at_depth(depth, limit)
+ finally:
+ sys.setrecursionlimit(oldlimit)
+
 def test_recursionlimit_fatalerror(self):
 # A fatal error occurs if a second recursion limit is hit when recovering
 # from a first one.
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,11 @@
 Core and Builtins
 -----------------
 
+- Issue #25274: sys.setrecursionlimit() now raises a RecursionError if the new
+ recursion limit is too low depending at the current recursion depth. Modify
+ also the "lower-water mark" formula to make it monotonic. This mark is used
+ to decide when the overflowed flag of the thread state is reset.
+
 - Issue #24402: Fix input() to prompt to the redirected stdout when
 sys.stdout.fileno() fails.
 
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -3520,6 +3520,15 @@
 return _PyTime_AsNanosecondsObject(ms);
 }
 
+static PyObject*
+get_recursion_depth(PyObject *self, PyObject *args)
+{
+ PyThreadState *tstate = PyThreadState_GET();
+
+ /* substract one to ignore the frame of the get_recursion_depth() call */
+ return PyLong_FromLong(tstate->recursion_depth - 1);
+}
+
 
 static PyMethodDef TestMethods[] = {
 {"raise_exception", raise_exception, METH_VARARGS},
@@ -3696,6 +3705,7 @@
 #endif
 {"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS},
 {"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS},
+ {"get_recursion_depth", get_recursion_depth, METH_NOARGS},
 {NULL, NULL} /* sentinel */
 };
 
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -632,14 +632,37 @@
 static PyObject *
 sys_setrecursionlimit(PyObject *self, PyObject *args)
 {
- int new_limit;
+ int new_limit, mark;
+ PyThreadState *tstate;
+
 if (!PyArg_ParseTuple(args, "i:setrecursionlimit", &new_limit))
 return NULL;
- if (new_limit <= 0) {
+
+ if (new_limit < 1) {
 PyErr_SetString(PyExc_ValueError,
- "recursion limit must be positive");
+ "recursion limit must be greater or equal than 1");
 return NULL;
 }
+
+ /* Issue #25274: When the recursion depth hits the recursion limit in
+ _Py_CheckRecursiveCall(), the overflowed flag of the thread state is
+ set to 1 and a RecursionError is raised. The overflowed flag is reset
+ to 0 when the recursion depth goes below the low-water mark: see
+ Py_LeaveRecursiveCall().
+
+ Reject too low new limit if the current recursion depth is higher than
+ the new low-water mark. Otherwise it may not be possible anymore to
+ reset the overflowed flag to 0. */
+ mark = _Py_RecursionLimitLowerWaterMark(new_limit);
+ tstate = PyThreadState_GET();
+ if (tstate->recursion_depth >= mark) {
+ PyErr_Format(PyExc_RecursionError,
+ "cannot set the recursion limit to %i at "
+ "the recursion depth %i: the limit is too low",
+ new_limit, tstate->recursion_depth);
+ return NULL;
+ }
+
 Py_SetRecursionLimit(new_limit);
 Py_INCREF(Py_None);
 return Py_None;
-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /