commit 0862c1036a8e45c594ed279dd4576719f93614c5
parent df345503ebae919f27649ffd273043de24442ffb
Author: Eike <56123922+moguls753@users.noreply.github.com>
Date: Sun, 4 May 2025 23:38:01 +0200
fix(lsp): check if client is stopping before reuse #33796
Problem:
Stopping a language server and then calling vim.lsp.start() with the same name/root will return the old language server that's in the middle of shutting down. vim.lsp.start() won't return a new server until the old process has terminated.
Solution:
Introducing a client._is_stopping field that tracks the shutdown phase, preventing the client from being reused.
Diffstat:
3 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
@@ -148,7 +148,7 @@ end
--- @param config vim.lsp.ClientConfig
--- @return boolean
local function reuse_client_default(client, config)
- if client.name ~= config.name then
+ if client.name ~= config.name or client:is_stopped() then
return false
end
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
@@ -387,6 +387,7 @@ function Client.create(config)
capabilities = config.capabilities,
workspace_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir),
root_dir = config.root_dir,
+ _is_stopping = false,
_before_init_cb = config.before_init,
_on_init_cbs = vim._ensure_list(config.on_init),
_on_exit_cbs = vim._ensure_list(config.on_exit),
@@ -804,12 +805,13 @@ end
---
--- @param force? boolean
function Client:stop(force)
- local rpc = self.rpc
-
- if rpc.is_closing() then
+ if self:is_stopped() then
return
end
+ self._is_stopping = true
+ local rpc = self.rpc
+
vim.lsp._watchfiles.cancel(self.id)
if force or not self.initialized or self._graceful_shutdown_failed then
@@ -936,7 +938,7 @@ end
--- @return boolean # true if client is stopped or in the process of being
--- stopped; false otherwise
function Client:is_stopped()
- return self.rpc.is_closing()
+ return self.rpc.is_closing() or self._is_stopping
end
--- Execute a lsp command, either via client command function (if available)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
@@ -218,6 +218,25 @@ describe('LSP', function()
)
end)
end)
+
+ it('does not reuse an already-stopping client #33616', function()
+ -- we immediately try to start a second client with the same name/root
+ -- before the first one has finished shutting down; we must get a new id.
+ local clients = exec_lua([[
+ local client1 = vim.lsp.start({
+ name = 'dup-test',
+ cmd = { vim.v.progpath, '-l', fake_lsp_code, 'basic_init' },
+ }, { attach = false })
+ vim.lsp.get_client_by_id(client1):stop()
+ local client2 = vim.lsp.start({
+ name = 'dup-test',
+ cmd = { vim.v.progpath, '-l', fake_lsp_code, 'basic_init' },
+ }, { attach = false })
+ return { client1, client2 }
+ ]])
+ local c1, c2 = clients[1], clients[2]
+ eq(false, c1 == c2, 'Expected a fresh client while the old one is stopping')
+ end)
end)
describe('basic_init test', function()