Skip to content

Commit a48fa81

Browse files
committed
fix: Provide a warning when running autocomplete in a way that could segfault (deephaven#5954)
Simple mitigation for jedi setting the recursion limit too high for Python 3.9 and 3.10 to correctly handle RecursionErrors. This prevents a segfault for a known bug in jedi, and only applies in cases where we know the bug can take place. Technically it is possible for these python versions to crash in this way without autocomplete or numpy, but it first would require the recursion limit to be raised, so the fix only applies when autocomplete is used. The `deephaven_internal.autocompleter` module has a `set_max_recursion_limit` method to define a different max recursion limit for an application, in case the default of 2000 is not sufficient for all cases. Fixes deephaven#5878
1 parent 604e434 commit a48fa81

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed

py/server/deephaven_internal/auto_completer/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,16 @@
1919
from ._completer import Completer, Mode
2020
from jedi import preload_module, Interpreter
2121

22+
2223
jedi_settings = Completer()
2324
# warm jedi up a little. We could probably off-thread this.
2425
preload_module("deephaven")
2526
Interpreter("", []).complete(1, 0)
27+
28+
29+
def set_max_recursion_limit(limit: int) -> None:
30+
"""
31+
Utility method to raise/lower the limit that our autocompletion will impose on Python 3.9 and 3.10.
32+
"""
33+
from . import _completer
34+
_completer.MAX_RECURSION_LIMIT = limit

py/server/deephaven_internal/auto_completer/_completer.py

+37
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
from typing import Any, Union, List
77
from jedi import Interpreter, Script
88
from jedi.api.classes import Completion, Signature
9+
from importlib.metadata import version
10+
import sys
11+
import warnings
912

1013

1114
class Mode(Enum):
@@ -36,6 +39,13 @@ def __str__(self) -> str:
3639
}
3740

3841

42+
"""
43+
For Python 3.9 and 3.10, there is a bug in recursion which can result in a segfault. Lowering this
44+
limit to 2000 or less seems to mitigate it.
45+
"""
46+
MAX_RECURSION_LIMIT = 2000
47+
48+
3949
def wrap_python(txt: str) -> str:
4050
""" Wraps a string in a Python fenced codeblock for markdown
4151
@@ -80,6 +90,8 @@ def __init__(self):
8090
except ImportError:
8191
self.__can_jedi = False
8292
self.mode = Mode.OFF
93+
self.recursion_limit_already_warned = False
94+
self.check_recursion_limit(True)
8395

8496
@property
8597
def mode(self) -> Mode:
@@ -135,6 +147,7 @@ def do_completion(
135147
Modeled after Jedi language server
136148
https://github.com/pappasam/jedi-language-server/blob/main/jedi_language_server/server.py#L189
137149
"""
150+
self.check_recursion_limit()
138151
if not self._versions[uri] == version:
139152
# if you aren't the newest completion, you get nothing, quickly
140153
return []
@@ -253,3 +266,27 @@ def do_hover(
253266
hoverstring += '\n---\n' + wrap_plaintext(raw_docstring)
254267

255268
return hoverstring.strip()
269+
270+
def check_recursion_limit(self, suppress_warning: bool = False) -> None:
271+
"""
272+
Tests for python+jedi+numpy versions that are susceptible to a RecursionError/segfault issue, and lowers
273+
the recursion limit, warning if the limit is raised externally.
274+
"""
275+
if sys.version_info < (3, 9) or sys.version_info >= (3, 11):
276+
return
277+
278+
if sys.getrecursionlimit() <= MAX_RECURSION_LIMIT:
279+
return
280+
281+
sys.setrecursionlimit(MAX_RECURSION_LIMIT)
282+
283+
# Log a warning if the user (or some user code) seems to have tried to raise the limit again after we lowered it.
284+
# This is not a fool-proof way to keep the limit down, and isn't meant to be, only to guard against the primary
285+
# way we've seen to cause this issue.
286+
if not suppress_warning and not self.recursion_limit_already_warned:
287+
self.recursion_limit_already_warned = True
288+
warnings.warn(f"""Recursion limit has been set to {MAX_RECURSION_LIMIT} to avoid a known segfault in Python 3.9 and 3.10
289+
related to RecursionErrors. This limit will be set to {MAX_RECURSION_LIMIT} whenever autocomplete takes place
290+
to avoid this, because the jedi library sets this to 3000, above the safe limit. Disabling autocomplete
291+
will prevent this check, as it will also prevent jedi from raising the limit.
292+
See https://github.com/deephaven/deephaven-core/issues/5878 for more information.""")

0 commit comments

Comments
 (0)