Skip to content

Adapt VS Code Parametric Language server to become a forwarder and start a new actual parametric server per rascal version #1010

@toinehartman

Description

@toinehartman

At the time of writing (Rascal LSP 2.22.4 & VS Code 0.13.4), the situation is as follows.

The Rascal VS Code extension is shipped with specific versions of Rascal and Rascal-LSP. Language projects separately define dependencies on specific versions of Rascal and Rascal-LSP in their pom.xml (POM). When registering a language in the extension with registerLanguage, its contributions (IDE features) will run with the Rascal and Rascal-LSP versions from the extension.

This has several drawbacks:

  • Differences between the versions in the contributions with other contexts, that consider the versions in the POM, like the VS Code REPL and Maven (compile, rascal:console, etc.). This leads to confusing discrepancies between the different contexts and unpredictable results.
  • Users are forced to update to a new Rascal-LSP version when the extension is updated, possibly requiring changes to their projects.

Solution: run the contributions with the versions as supplied in the POM. This document outlines the required changes to enable this.

Existing implementation

In the following examples, for simplicity, we only show the most important classes related to parametric languages. We consider a case with two registered languages: Language 1 #00ff00 and Language 2 #0000ff.

Current parametric language server implementation

Currently, the components of the parametric language server related to the language contributions run within a single process. This schematic shows how the classes are related to each other.

Since everything lives in the same process, they share the same Java classloader by default, and therefore the same versions of classes. However, we want different versions for different instances of ILanguageContributions.

Design

To separate the dependency versions of the language contributions from the versions of the VS Code extension, we propose to run the contributions in separate processes (1 per language). For inter-process communication, we forward LSP requests to the right process using JSON-RPC (which is already in use in many places in the Rascal/LSP infrastructure).
We use a parametric language router that manages language processes (and dependency versions/classpaths) and dispatches LSP calls to the right process over JSON-RPC. On the receiving end, we use a single language server that routes the incoming requests to the responsible service (document or workspace). This allows us to re-use many existing components without many changes.

Proposed parametric language server implementation

Since this requires certain compatibility between the versions running in the main and delegate processes, LSP will put constraints on the minimal versions of DSL dependencies (Rascal and Rascal LSP).

Pros

  • Relatively simple
    • LSP requests can be forwarded verbatim
    • Not many extra moving parts are needed
  • Isolation of languages; a crash in one language does not influence others

Cons

  • Memory usage grows linearly with number of registered languages

Tests

To test this properly, we should consider adding some more UI tests. Some example scenarios:

  1. Register/unregister multiple languages and check that IDE features for both work at the same time.
  2. Register multiple languages and check their dependency versions from the language contributions.
  3. Register a language, check its versions, change the pom, unregister, re-register, and check that the versions changed.

Alternatives considered

Demultiplex by version

The above, but with one process per version combo and multiple languages per process.

Pros

  • Less memory overhead/more sharing in the case of multiple languages that use the same versions.

Cons

  • More complexity
    • Every request going out of the demultiplexer needs the language name.
    • The version-specific process needs another demultiplexer to differentiate between languages.
  • Interference of languages; a crash in one language might influence another

Custom classloader

Pros

  • Less processes, more memory sharing
  • No hard-to-debug JSON-RPC bridge

Cons

  • Hard to implement the class loader
  • Tricky to get right:
    • Object equality between instances from different class loaders/versions
    • Backwards compatibility between different versions
  • Interference of languages; a crash in one language might influence another

OSGI

Many of the same properties as using a custom class loader.

Metadata

Metadata

Assignees

Labels

dependenciesPull requests that update a dependency file

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions