Component
pygls/protocol/language_server.py
Summary
lsp_shutdown loops over self._request_futures.items() and calls future.cancel() on each. If a future completes in another thread and its done-callback pops it from _request_futures during this iteration, Python raises RuntimeError: dictionary changed size during iteration, crashing the server.
Steps to Reproduce
- Have multiple in-flight requests running in the thread pool.
- Trigger
shutdown while some of those requests finish concurrently.
- Server crashes with
RuntimeError.
Expected Behavior
Shutdown should gracefully cancel pending requests without crashing.
Actual Behavior
Dictionary mutation during iteration causes a fatal RuntimeError.
Affected Code (pygls/protocol/language_server.py, ~L209-211)
for msg_id, future in self._request_futures.items():
if msg_id != current_id and not future.done():
future.cancel()
Proposed Fix
Iterate over a snapshot:
for msg_id, future in list(self._request_futures.items()):
if msg_id != current_id and not future.done():
future.cancel()
Component
pygls/protocol/language_server.py
Summary
lsp_shutdownloops overself._request_futures.items()and callsfuture.cancel()on each. If a future completes in another thread and its done-callback pops it from_request_futuresduring this iteration, Python raisesRuntimeError: dictionary changed size during iteration, crashing the server.Steps to Reproduce
shutdownwhile some of those requests finish concurrently.RuntimeError.Expected Behavior
Shutdown should gracefully cancel pending requests without crashing.
Actual Behavior
Dictionary mutation during iteration causes a fatal
RuntimeError.Affected Code (
pygls/protocol/language_server.py, ~L209-211)Proposed Fix
Iterate over a snapshot: