Client: duplicate messages sent after server restart
nrc opened this issue · 13 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.
I tired to reproduce this using the ESLint extension but failed. Which version of the client library are you using?
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!
@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 start
ed 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!