microsoft / vscode-languageserver-node

Language server protocol implementation for VSCode. This allows implementing language services in JS/TS running on node.js

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Client: duplicate messages sent after server restart

nrc opened this issue · comments

STR: start an extension with a language server (I use the Rust Language Server but I believe any extension/language server would do). Kill the language server process. The client restarts the server (as it should), but now every message (notification or request) is duplicated. If you kill the server again, you get three of each message, and so forth.

From looking at the source code for the client, it looks to me that when the client restarts the server it should properly cleanup the connection, but does not (so handlers are not removed). The connection is re-initialized, but by calling onRequest, onNotification, etc. the client is installing extra copies of the handlers into the connection, not replacing the previous handlers. So, when a message is sent to the connection it is dispatched multiple times to the server.

cc microsoft/vscode#48309

I tired to reproduce this using the ESLint extension but failed. Which version of the client library are you using?

I know there were issues when restarting the server (see issues here).

Which version of the client library are you using?

4.0.0

There is a PR to upgrade us to 4.1.0, so I'll investigate with that too.

I can still repro with 4.1.0

Is there any logging I can provide which would be useful?

You can try to debug this. The code that is invoked on connection close and does the restart is here:

https://github.com/Microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L2787

And the cleanup of the listeners happens here:

https://github.com/Microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L2704

Do these methods get executed when you kill the server?

This issue has been closed automatically because it needs more information and has not had recent activity. See also our issue reporting guidelines.

Happy Coding!

@nrc any information ?

@dbaeumer Hi, sorry I haven't had a chance to try and debug yet. Hopefully I'll give it a go tomorrow.

@dbaeumer Hi, sorry it is a bit later than expected, but I had a look and I have (I think) diagnosed what is going on.

The BaseLanguageClient has an array of _features. This array is initialised when the client is constructed, and never destroyed, it is not created/destroyed on start/stop like the connection.

When the client is started it calls initialize which calls initializeFeatures (https://github.com/Microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L2704). That in turn calls initialize, then register for each feature, which for DidChangeTextDocumentFeature creates an entry in the _changeData Map (https://github.com/Microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L2704).

Because the features are not reset as part of the start/stop lifecycle, the _changeData map is never reset, so after n restarts there will be n+1 entries in the map.

Now, when a change happens, the DidChangeTextDocumentFeature's callback method is called (once), but that loops over _changeData (https://github.com/Microsoft/vscode-languageserver-node/blob/master/client/src/client.ts#L2704) and so sends the notification multiple times when it should be sent once.

I believe the fix is to move the call to initializeFeatures from the BaseLanguageClient's initialize method to its constructor. However, perhaps features are added the end of the ctor (registerBuiltinFeatures) but before initializeFeatures is called, in which case an alternative fix would be to 'un-initialise' (i.e., empty _changeData) each feature in BaseLanguageClient::cleanup.

@nrc thanks a lot for the very good analysis. Very good catch.

I didn't see this with ESLint since it uses a full text sync and these are debounced on the client side hence they are only send once.

The fix is quite simple. Each feature has a dispose method which is called when the server goes down. The DidChangeTextDocumentFeature simply doesn't reset the map in dispose :-(.

@nrc I published a new client and server. Please let me know if using these fixes the problem.

I updated and checked and it seems to be working fine now. Thanks for the fix!

@nrc and thanks again for the analysis.