mirror of
https://github.com/neovim/neovim.git
synced 2025-11-15 14:59:20 +00:00
fix(lsp): stop repeatedly resuming dead coroutine #35743
Problem: Error extracting content-length causes all future coroutine resumes to fail. Solution: Replace coroutine.wrap with coroutine.create in create_read_loop so that we can check its status and catch any errors, allowing us to stop the lsp client and avoid repeatedly resuming the dead coroutine.
This commit is contained in:
@@ -175,27 +175,34 @@ end
|
|||||||
--- @private
|
--- @private
|
||||||
--- @param handle_body fun(body: string)
|
--- @param handle_body fun(body: string)
|
||||||
--- @param on_exit? fun()
|
--- @param on_exit? fun()
|
||||||
--- @param on_error fun(err: any)
|
--- @param on_error? fun(err: any, errkind: vim.lsp.rpc.ClientErrors)
|
||||||
function M.create_read_loop(handle_body, on_exit, on_error)
|
function M.create_read_loop(handle_body, on_exit, on_error)
|
||||||
local parse_chunk = coroutine.wrap(request_parser_loop) --[[@as fun(chunk: string?): string]]
|
on_exit = on_exit or function() end
|
||||||
parse_chunk()
|
on_error = on_error or function() end
|
||||||
|
local co = coroutine.create(request_parser_loop)
|
||||||
|
coroutine.resume(co)
|
||||||
return function(err, chunk)
|
return function(err, chunk)
|
||||||
if err then
|
if err then
|
||||||
on_error(err)
|
on_error(err, M.client_errors.READ_ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not chunk then
|
if not chunk then
|
||||||
if on_exit then
|
on_exit()
|
||||||
on_exit()
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if coroutine.status(co) == 'dead' then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local body = parse_chunk(chunk)
|
local ok, res = coroutine.resume(co, chunk)
|
||||||
if body then
|
if not ok then
|
||||||
handle_body(body)
|
on_error(res, M.client_errors.INVALID_SERVER_MESSAGE)
|
||||||
|
break
|
||||||
|
elseif res then
|
||||||
|
handle_body(res)
|
||||||
chunk = ''
|
chunk = ''
|
||||||
else
|
else
|
||||||
break
|
break
|
||||||
@@ -547,8 +554,12 @@ local function create_client_read_loop(client, on_exit)
|
|||||||
client:handle_body(body)
|
client:handle_body(body)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function on_error(err)
|
--- @param errkind vim.lsp.rpc.ClientErrors
|
||||||
client:on_error(M.client_errors.READ_ERROR, err)
|
local function on_error(err, errkind)
|
||||||
|
client:on_error(errkind, err)
|
||||||
|
if errkind == M.client_errors.INVALID_SERVER_MESSAGE then
|
||||||
|
client.transport:terminate()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return M.create_read_loop(handle_body, on_exit, on_error)
|
return M.create_read_loop(handle_body, on_exit, on_error)
|
||||||
|
|||||||
@@ -1971,6 +1971,43 @@ describe('LSP', function()
|
|||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('should catch error while parsing invalid header', function()
|
||||||
|
local header = 'Content-Length: \r\n'
|
||||||
|
local called = false
|
||||||
|
exec_lua(function()
|
||||||
|
local server = assert(vim.uv.new_tcp())
|
||||||
|
server:bind('127.0.0.1', 0)
|
||||||
|
server:listen(1, function(e)
|
||||||
|
assert(not e, e)
|
||||||
|
local socket = assert(vim.uv.new_tcp())
|
||||||
|
server:accept(socket)
|
||||||
|
socket:write(header .. '\r\n', function()
|
||||||
|
socket:shutdown()
|
||||||
|
server:close()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
local client = assert(vim.uv.new_tcp())
|
||||||
|
local on_read = require('vim.lsp.rpc').create_read_loop(function() end, function()
|
||||||
|
client:close()
|
||||||
|
end, function(err, code)
|
||||||
|
vim.rpcnotify(1, 'error', err, code)
|
||||||
|
end)
|
||||||
|
client:connect('127.0.0.1', server:getsockname().port, function()
|
||||||
|
client:read_start(on_read)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
n.run(nil, function(method, args)
|
||||||
|
local err, code = unpack(args) --- @type string, number
|
||||||
|
eq('error', method)
|
||||||
|
eq(1, code)
|
||||||
|
matches(vim.pesc('Content-Length not found in header: ' .. header) .. '$', err)
|
||||||
|
called = true
|
||||||
|
stop()
|
||||||
|
return NIL
|
||||||
|
end, nil, 1000)
|
||||||
|
eq(true, called)
|
||||||
|
end)
|
||||||
|
|
||||||
it('should not trim vim.NIL from the end of a list', function()
|
it('should not trim vim.NIL from the end of a list', function()
|
||||||
local expected_handlers = {
|
local expected_handlers = {
|
||||||
{ NIL, {}, { method = 'shutdown', client_id = 1 } },
|
{ NIL, {}, { method = 'shutdown', client_id = 1 } },
|
||||||
|
|||||||
Reference in New Issue
Block a user