pygls: `RecursionError: maximum recursion depth exceeded` on certain Python versions

👋 I’ve received a few reports of this over on the Ruff LSP repo (https://github.com/charliermarsh/ruff-vscode/issues/116). I started debugging and reduced it down to an MRE in pygls.

Given server.py:

from pygls import server

LSP_SERVER = server.LanguageServer(name="Example", version="0.1.0")

def start() -> None:
    LSP_SERVER.start_io()

if __name__ == "__main__":
    start()

And the following Dockerfile:

# syntax=docker/dockerfile:1

FROM python:3.7.4-slim-buster

RUN pip3 install pygls
COPY server.py server.py

RUN python -m server

Running docker build . gives me an infinite RecursionError:

 > [4/4] RUN python -m server:
#10 0.367 Traceback (most recent call last):
#10 0.367   File "/usr/local/lib/python3.7/runpy.py", line 193, in _run_module_as_main
#10 0.367     "__main__", mod_spec)
#10 0.367   File "/usr/local/lib/python3.7/runpy.py", line 85, in _run_code
#10 0.367     exec(code, run_globals)
#10 0.367   File "/server.py", line 7, in <module>
#10 0.367     LSP_SERVER = server.LanguageServer(name="Example", version="0.1.0")
#10 0.367   File "/usr/local/lib/python3.7/site-packages/pygls/server.py", line 357, in __init__
#10 0.367     super().__init__(protocol_cls, converter_factory, loop, max_workers)
#10 0.367   File "/usr/local/lib/python3.7/site-packages/pygls/server.py", line 199, in __init__
#10 0.367     self.lsp = protocol_cls(self, converter_factory())
#10 0.367   File "/usr/local/lib/python3.7/site-packages/pygls/protocol.py", line 163, in default_converter
#10 0.367     converter = converters.get_converter()
#10 0.367   File "/usr/local/lib/python3.7/site-packages/lsprotocol/converters.py", line 17, in get_converter
#10 0.367     return _hooks.register_hooks(converter)
#10 0.367   File "/usr/local/lib/python3.7/site-packages/lsprotocol/_hooks.py", line 35, in register_hooks
#10 0.367     _resolve_forward_references()
#10 0.367   File "/usr/local/lib/python3.7/site-packages/lsprotocol/_hooks.py", line 30, in _resolve_forward_references
#10 0.367     attrs.resolve_types(value, lsp_types.ALL_TYPES_MAP, {})
#10 0.367   File "/usr/local/lib/python3.7/site-packages/attr/_funcs.py", line 408, in resolve_types
#10 0.367     hints = typing.get_type_hints(cls, globalns=globalns, localns=localns)
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 976, in get_type_hints
#10 0.367     value = _eval_type(value, base_globals, localns)
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 265, in _eval_type
#10 0.367     ev_args = tuple(_eval_type(a, globalns, localns) for a in t.__args__)
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 265, in <genexpr>
#10 0.367     ev_args = tuple(_eval_type(a, globalns, localns) for a in t.__args__)
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 266, in _eval_type
#10 0.367     if ev_args == t.__args__:
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 661, in __eq__
#10 0.367     return frozenset(self.__args__) == frozenset(other.__args__)
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 667, in __hash__
#10 0.367     return hash((self.__origin__, self.__args__))
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 666, in __hash__
...
#10 0.367     return hash((Union, frozenset(self.__args__)))
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 667, in __hash__
#10 0.367     return hash((self.__origin__, self.__args__))
#10 0.367   File "/usr/local/lib/python3.7/typing.py", line 480, in __hash__
#10 0.367     return hash((self.__forward_arg__, self.__forward_value__))
#10 0.367 RecursionError: maximum recursion depth exceeded while calling a Python object

Thanks as always for all your work on pygls!

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 30 (11 by maintainers)

Commits related to this issue

Most upvoted comments

The above-mentioned change seems to work, I ran the pygls test suite, and tested a few other servers on it. I plan on releasing the fix next week.

(Closing as this is resolved for me!)

Great idea @karthiknadig, thank you.

So I think we can close this issue then. The fix is essentially to reinstall Pygls. Or, for an end user, perhaps even just lsprotocl, eg: pip install lsprotocol==2023.0.0a1.

I think there’s a broader issue here in terms of version pinning. Ideally I think Pygls should have more control of the lsprotocol version it uses. I’m leaning towards pinning to a specific version, even though that means we’ll have to make new releases for every lsprotocol change. @karthiknadig I wonder what you think about semantic versioning for lsprotocol?

Yup! All good from my perspective.

@tombh I need to push out another release I will be doing it today.

Published lsprotocol to pypi with the fix.

Replace the entire file from lsprotocol main: https://github.com/microsoft/lsprotocol/blob/main/lsprotocol/types.py that should be enough.

@tombh I will need to investigate and see if fully resolving this is really needed or if it can be done on the fly. If it turns out that we have to resolve all types before we can build the converters then yes this means it won’t work for < 3.7.7.

I’ll reply back with my findings.