Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spec should discourage abuse of initializationOptions and didChangeConfiguration #567

Closed
mickaelistria opened this issue Sep 10, 2018 · 40 comments
Labels
clarification feature-request Request for new features or functionality initialization

Comments

@mickaelistria
Copy link

I'm working on having Eclipse IDE adopting some language servers.
I see a possible bad trend for Language Servers to heavily rely on initializationOptions and didChangeConfigurations to enable/disable features. The issue with those is that they are unspecified placeholders and that whatever usage is made of it requires all clients to write code specific to this language server to support those options.
The main example I have in mind right now is RLS that, by abusing those settings is progressively, and most likely without really willing it, breaking rich compatibility with other IDEs: rust-lang/rls#1047
Abuse of those properties should be deprecated in the spec, with some disclaimer explaining how relying on those make the LS integration less likely to be trivially portable from an editor to another.

@rcjsuen
Copy link
Contributor

rcjsuen commented Sep 10, 2018

Interesting. I use workspace/didChangeConfiguration as that's what was there but did not consider this case.

At what point would you consider the use of this "abuse" though?

@mickaelistria
Copy link
Author

As an adopter of LS I don't develop, I consider any usage is abuse, as long as it requires to create a specific UI or workflow to interact with it in the client ;) But I hope this discussion can lead to a more flexible definition.

@LaurentTreguier
Copy link
Contributor

As I see it, a server initialized without initializationOptions should just work in any editor. If the editor allows servers to be started by custom extensions with possible deeper integration (like VSCode or Atom for example), then initializationOptions can be used to enable specific features that can't be used in other editors.

@mickaelistria
Copy link
Author

@LaurentTreguier: so your proposal is that the settings should be limited to only client-specific configuration? I think it'd be fair.

@LaurentTreguier
Copy link
Contributor

This is how I understood it. The spec describes it as User provided initialization options, which can be misleading; it makes it sound like the user should change it themselves when it should be up to a specific client to do this.

@dbaeumer
Copy link
Member

IMO the spec should encourage all server providers to spec the following to things:

  • the initialization options it supports. I agree with @LaurentTreguier that a server should work with an empty literal as well and simply assume a set of defaults.
  • which workspace/configuration request it sends.

Having a push model for configurations was a mistake and got basically replaced by the pull model where the server sends workspace/configuration requests.

The client should still send workspace/didChangeConfiguration notification so that the server can clear caches if it caches configurations. But it should not send any values since values can differ based on the scope used in the workspace/configuration request.

@mickaelistria
Copy link
Author

mickaelistria commented Sep 12, 2018 via email

@dbaeumer
Copy link
Member

I disagree here. The reason is that the fact that initializationOptions is in the spec say that this property should be used and not any other random property.

I still think that it is a fair thing to require a server to depend on some initializationOptions. But I do fully agree that these need to be speced by the server (and not be reverse engineered from code) and that server should work with a resonable default set if they are not provided. Same is true for settings.

I do fully agree that the spec need to spec this assumptions.

@aeschli any comments on why the CSS language server can't work with a reasonable default set ?

@felixfbecker
Copy link

felixfbecker commented Nov 16, 2018

One pattern I saw emerge is that many language servers interpret initializationOptions as containing user configuration, i.e. the same that is sent in workspace/didChangeConfiguration or in response to workspace/configuration. But LSP doesn't actually really say this, it is very vague on what initializationOptions actually means:

User provided initialization options.

It doesn't use the term "configuration", but it does say "user provided" (not client provided).

The reason why language servers use it for configuration is because there is otherwise no way to read configuration in initialize. workspace/didChangeConfiguration is only sent after initialize returned (if at all), and workspace/configuration is not among the whitelisted requests allowed during initialize.
The problem is that since initializationOptions is not clearly defined, most clients do not send configuration in it.
Could LSP just be clearer about what initializationOptions is intended to be used for (maybe with an example in the spec)? And could workspace/configuration be whitelisted to be used during initialize?

@dbaeumer
Copy link
Member

I will clarify the spec in a way that the initializationOptions is typically something that could be passed on the command line when starting the server. It shouldn't be user configurations.

I am actually against whitelisting workspace/configuration. If a server needs the configuration to register providers it should use dynamic registration instead of static registration which allows to mix workspace/configuration with registration calls.

@dbaeumer
Copy link
Member

Please ping if you think dynamic registration is not the right path to go.

@mickaelistria
Copy link
Author

mickaelistria commented Dec 18, 2018 via email

@felixfbecker
Copy link

felixfbecker commented Dec 18, 2018

I am actually against whitelisting workspace/configuration. If a server needs the configuration to register providers it should use dynamic registration instead of static registration which allows to mix workspace/configuration with registration calls.

@dbaeumer I agree for determining whether a provider should be registered or not, but a server might need configuration during initialisation for a lot of reasons. For example, a loglevel or logfile, whether file watchers should be set up with polling or OS events, the HTTP endpoint of a service that needs to be contacted, whether dependencies should be installed, if yes an access token for that, the path of an external tool that needs to be shelled into, something like JAVA_HOME or GOPATH, ...
Requiring to delay all of these after initialize complicates a lot of things for no apparent reason. Also not every client supports dynamic registration, and these clients should gracefully degrade in functionality, i.e. they should work with a static set of capabilities from server initialize and work with restarting the server instead of not working at all.

@dbaeumer
Copy link
Member

@felixfbecker the list you provided don't seem to be user configuration which is something that should come through workspace/configuration anyways. For example a logLevel or an access token is something that you usually would pass as a command line option and therefore I have no problem if something like this comes through the initialize options. I want to makes sure that the initialize options are static.

@felixfbecker
Copy link

Could you explain why a user-defined access token should come through a command line option and not user settings? Lots of VS Code extensions configure access tokens through settings, or JAVA_HOME/GOPATH, ... It seems like an arbitrary distinction if the extension had to filter out specific settings to pass them through initializationOptions (and prevent the server from reacting to their changes) while other settings go through didChangeConfiguration?

@mickaelistria
Copy link
Author

that should come through workspace/configuration anyways.

Multiplying the entry-points for configuration/options is making harder and harder to adopt the protocol.
For example, see how current VSCode CSS language server requires enablement for SCSS, SASS and so on in initializationOptions. Are those user config or not? (a user could decide to disable SASS)
The line between user config and initializationOptions is IMO way to blurry to be specified and pragmatically, initializationOptions are also very good to pass some user settings and should be spec'd as being deprecated for this case.

@LaurentTreguier
Copy link
Contributor

@dbaeumer dynamic registration is not always available, as clients aren't forced to implement it. Atom, with its official atom-languageclient for example, doesn't support any dynamic registration; so right now initializationOptions is actually the only reliable way to get any kind of configuration at server startup.

@dbaeumer
Copy link
Member

This is exactly what I want to avoid by making it more clear. The initializationOptions should be static in the sense of a server run (e.g. like command line parameters). User settings should come through workspace/configuration.

@felixfbecker if the user token is a user setting then it should come through workspace/configuration. If we have more such configuration I am open to rethink to white list it in the initialize call. But that would be something clients need to opt in.

@dbaeumer
Copy link
Member

@LaurentTreguier I am not against having initializationOptions. But as others pointed out this should not be used to pass user configuration since this is something a server needs to handle dynamically anyways.

@felixfbecker
Copy link

@felixfbecker if the user token is a user setting then it should come through workspace/configuration. If we have more such configuration I am open to rethink to white list it in the initialize call.

I think me and others gave a lot of examples of this kind of config in this thread - it really is a very common use case.

But that would be something clients need to opt in.

The server could just make the request and if it returns an error fall back to a different mechanism. But the whole whitelist could also be announced in ClientCapabilities.

@mickaelistria
Copy link
Author

But as others pointed out this should not be used to pass user configuration since this is something a server needs to handle dynamically anyways.

static vs dynamic is not absolute and depends a lot on the client and the server. Some clients tend to allow/encourage a lot of user configuration, some others using similar LS would prefer providing a pre-defined config and not let user tweak it. As such, I don't think using the static vs dynamic distinction is reliable here.

@dbaeumer
Copy link
Member

@felixfbecker and @mickaelistria can you both please write what you would like to see written in the spec. I have to say I get puzzled here since it is not clear to me what kind of settings you want to allow and what kind of settings you want to forbid. The original idea of the initializationOptions option was to pass parameters that are usually parameters pass on the command line to ease that.

I will revert the changes and wait for you to make concrete wording proposals.

@dbaeumer dbaeumer reopened this Dec 18, 2018
@mickaelistria
Copy link
Author

mickaelistria commented Dec 18, 2018

I would like to see specified something like

It's recommended that initializationOptions can read a superset of didChangeConfiguration parameters, and interpret them as default/initial values.

and/or the other way round

parameters of didChangeConfiguration should also be valid as input to initialization, where they would be interpreted as default/initial values.

@mickaelistria
Copy link
Author

and also something like

initializationOptions and didChangeConfiguration can receive any values. This flexibility increase the difficulty for clients to adopt the Language Servers as they need to learn about the Language Server specific value that can be set there and implement support for them. It's recommended to rely on those as less as possible to ease integration.

@dbaeumer
Copy link
Member

It's recommended that initializationOptions can read a superset of didChangeConfiguration parameters, and interpret them as default/initial values.

I thought this is exactly what we don't want. We shouldn't push server developers in updating a configuration using didChangeConfiguration. Configuration should always be pull from the client and didChangeConfiguration should only be used to signal a change. So may be in a first step I will clarify this.

In my opinion to make this easier for clients to handle we should say:

  • no configuration / setting in initializationOptions
  • settings should be fetch using workspace/configuration and a server needs to spec which configuiration settings it expects and can handle.
  • didChangeConfiguration should only be used to signal a change in configuration not to push data.

@felixfbecker
Copy link

I agree that they shouldn’t be in initialisationOptions, and the docs should clarify that. The term used for user-defined settings is “configuration”, not “options”.
I am proposing to whitelist workspace/configuration during initialize.

This seems like the simplest solution to me to solve the use case and without any drawbacks.

DJMcNab added a commit to DJMcNab/rust-analyzer that referenced this issue Dec 21, 2018
See microsoft/language-server-protocol#567
for motivations to not require `InitializationOptions`

TODO: Check if there is any other custom clientside code we use which should be disabled if not implemented
@dbaeumer
Copy link
Member

It is possible to send getConfiguration requests in the initialized notification.

I will close the issue since I am really not a fan of having another property during initialization. Please ping if you think otherwise.

@dbaeumer dbaeumer removed this from the Backlog milestone Nov 2, 2021
@michaelpj
Copy link
Contributor

It is possible to send getConfiguration requests in the initialized notification.

The current spec says

In addition the server is not allowed to send any requests or notifications to the client until it has responded with an InitializeResult, with the exception that during the initialize request the server is allowed to send the notifications window/showMessage, window/logMessage and telemetry/event as well as the window/showMessageRequest request to the client.

Which seems to contradict what you said, @dbaeumer ?

@rwols
Copy link
Contributor

rwols commented Mar 7, 2022

No this contradicts nothing.

  1. initialize is a request sent from client to server.
  2. initialized is a notification from the client. Clients are expected to send this notification after receiving a response to the initialize request.

A server can therefore do a workspace/configuration request in its handler for initialized notifications.

@michaelpj
Copy link
Contributor

Gotcha, thanks!

@nayeemrmn
Copy link

I will look into white listing workspace/configuration.

It is possible to send getConfiguration requests in the initialized notification.

@dbaeumer The latest solution doesn't seem sufficient and I think workspace/configuration should be allow-listed during initialize.

Good servers should pull relevant workspace settings by namespace, for each workspace folder if interested, and incorporate that before they start processing normal document notifications/requests. Currently you can only do that in the initialized handler. This means every server must have a boilerplate initializer lock which blocks out other message handling until that part of the initialized handler is completed.

Is that the basic recommended approach? Or are servers supposed to initialize once with default settings, initialize again with pulled config slightly later while possibly receiving doc info in between? I don't see any other interpretation.

Well, extension authors have avoided this and are still heavily relying on initializationOptions for startup workspace settings despite using pull-based config when it comes to receiving workspace/didChangeConfiguration. IMO that should only be a fallback for clients that don't support pull-based config, if they're worth the effort. My problem is that initializationOptions has no convention for per-workspace-folder settings.

Is there a good reason to not allow-list workspace/configuration?

@michaelpj
Copy link
Contributor

Is that the basic recommended approach? Or are servers supposed to initialize once with default settings, initialize again with pulled config slightly later while possibly receiving doc info in between?

This is what we're doing in HLS. In fact we do all of what you said:

  • Start with default config
  • Take the config from intializationOptions if it's there
  • Fire off workspace/configuration in the initialized handler

I agree that this is not very good, and it certainly seems that if you do the recommended thing you can start getting sent requests before you have your config set up.

@aon012345
Copy link

good

@dbaeumer
Copy link
Member

dbaeumer commented May 6, 2024

Is there a good reason to not allow-list workspace/configuration?

In general I try to keep the message that can be sent to the client in initialized as small as possible to make the initialization phase on the client simple.

Currently you can only do that in the initialized handler

I usually do that when actually processing a request and then cache the result of the workspace/configuration. When I receive a workspace/didChangeConfiguration I simply clear the cache.

@nayeemrmn
Copy link

I usually do that when actually processing a request and then cache the result of the workspace/configuration.

This is not a good candidate for lazy init because:

  • workspace/configuration generally is behind an async binding, and accessing config shouldn't need to be.
  • It spreads to other structures that depend on config. Now everything must be computed lazily and asynchronously.
  • Lazy computation is used to make things more responsive. In this case it's less responsive compared to acting on workspace/didChangeConfiguration.

In general I try to keep the message that can be sent to the client in initialized as small as possible to make the initialization phase on the client simple.

I think that would mean just storing the initialize params and returning server info and capabilities, since technically everything else can be done lazily. But server authors won't use it that way.

A more meaningful differentiation is that initialize is the request that all other requests are blocked on, hinting that it should get up-to-date everything required for handling textDocument/* notifications. That makes more sense so server authors are using it that way (but relying on initializationOptions).

Please reconsider allow-listing workspace/configuration during initialize.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clarification feature-request Request for new features or functionality initialization
Projects
None yet
Development

No branches or pull requests

10 participants