bpo-26806: add 30 to the recursion limit in IDLE's shell (GH-13944)
This is done to compensate for the extra stack frames added by
IDLE itself, which cause problems when setting the recursion limit
to low values.
This wraps sys.setrecursionlimit() and sys.getrecursionlimit()
as invisibly as possible.
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index 6b3928b..c6ed76b 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -4,10 +4,12 @@
f'''{sys.executable} -c "__import__('idlelib.run').run.main()"'''
'.run' is needed because __import__ returns idlelib, not idlelib.run.
"""
+import functools
import io
import linecache
import queue
import sys
+import textwrap
import time
import traceback
import _thread as thread
@@ -305,6 +307,64 @@
font['size'] = round(-0.75*size)
+RECURSIONLIMIT_DELTA = 30
+def install_recursionlimit_wrappers():
+ """Install wrappers to always add 30 to the recursion limit."""
+ # see: bpo-26806
+
+ @functools.wraps(sys.setrecursionlimit)
+ def setrecursionlimit(*args, **kwargs):
+ # mimic the original sys.setrecursionlimit()'s input handling
+ if kwargs:
+ raise TypeError(
+ "setrecursionlimit() takes no keyword arguments")
+ try:
+ limit, = args
+ except ValueError:
+ raise TypeError(f"setrecursionlimit() takes exactly one "
+ f"argument ({len(args)} given)")
+ if not limit > 0:
+ raise ValueError(
+ "recursion limit must be greater or equal than 1")
+
+ return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA)
+
+ setrecursionlimit.__doc__ += "\n\n" + textwrap.fill(textwrap.dedent(f"""\
+ This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible
+ uninterruptible loops.
+ """).strip())
+
+ @functools.wraps(sys.getrecursionlimit)
+ def getrecursionlimit():
+ return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA
+
+ getrecursionlimit.__doc__ += "\n\n" + textwrap.fill(textwrap.dedent(f"""\
+ This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate for
+ the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.
+ """).strip())
+
+ # add the delta to the default recursion limit, to compensate
+ sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA)
+
+ sys.setrecursionlimit = setrecursionlimit
+ sys.getrecursionlimit = getrecursionlimit
+
+
+def uninstall_recursionlimit_wrappers():
+ """Uninstall the recursion limit wrappers from the sys module.
+
+ IDLE only uses this for tests. Users can import run and call
+ this to remove the wrapping.
+ """
+ if (
+ getattr(sys.setrecursionlimit, '__wrapped__', None) and
+ getattr(sys.getrecursionlimit, '__wrapped__', None)
+ ):
+ sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__
+ sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__
+ sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA)
+
+
class MyRPCServer(rpc.RPCServer):
def handle_error(self, request, client_address):
@@ -448,6 +508,8 @@
# sys.stdin gets changed from within IDLE's shell. See issue17838.
self._keep_stdin = sys.stdin
+ install_recursionlimit_wrappers()
+
self.interp = self.get_remote_proxy("interp")
rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)