Skip to content

lsp_shutdown iterates over _request_futures while it may be mutated concurrently #606

@meymchen

Description

@meymchen

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

  1. Have multiple in-flight requests running in the thread pool.
  2. Trigger shutdown while some of those requests finish concurrently.
  3. 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()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions