mirror of
https://github.com/neovim/neovim.git
synced 2026-02-21 09:50:19 +10:00
fix(rpc): don't overwrite already received results on error (#37339)
This fixes a regression fromcf6f60ce4d(possibly), as before that commit a frame is popped from the call stack immediately after its response is received. Also fix leaking the allocated error messages. (cherry picked from commit39d8aa0a1a)
This commit is contained in:
committed by
github-actions[bot]
parent
ba600c495f
commit
2a3cd8dc80
@@ -163,7 +163,9 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem
|
||||
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned || rpc->closed);
|
||||
(void)kv_pop(rpc->call_stack);
|
||||
|
||||
if (rpc->closed) {
|
||||
// !frame.returned implies rpc->closed.
|
||||
// If frame.returned is true, its memory needs to be freed, so don't return here.
|
||||
if (!frame.returned) {
|
||||
api_set_error(err, kErrorTypeException, "Invalid channel: %" PRIu64, id);
|
||||
channel_decref(channel);
|
||||
return NIL;
|
||||
@@ -526,9 +528,13 @@ static void chan_close_on_err(Channel *channel, char *msg, int loglevel)
|
||||
{
|
||||
for (size_t i = 0; i < kv_size(channel->rpc.call_stack); i++) {
|
||||
ChannelCallFrame *frame = kv_A(channel->rpc.call_stack, i);
|
||||
if (frame->returned) {
|
||||
continue; // Don't overwrite an already received result.
|
||||
}
|
||||
frame->returned = true;
|
||||
frame->errored = true;
|
||||
frame->result = CSTR_TO_OBJ(msg);
|
||||
frame->result = CSTR_TO_ARENA_OBJ(&channel->rpc.unpacker->arena, msg);
|
||||
frame->result_mem = arena_finish(&channel->rpc.unpacker->arena);
|
||||
}
|
||||
|
||||
channel_close(channel->id, kChannelPartRpc, NULL);
|
||||
|
||||
@@ -362,7 +362,7 @@ describe('server -> client', function()
|
||||
connect_test(server, 'tcp', address)
|
||||
end)
|
||||
|
||||
it('does not crash on receiving UI events', function()
|
||||
local function start_server_and_client()
|
||||
local server = n.new_session(false)
|
||||
set_session(server)
|
||||
local address = fn.serverlist()[1]
|
||||
@@ -370,11 +370,53 @@ describe('server -> client', function()
|
||||
set_session(client)
|
||||
|
||||
local id = fn.sockconnect('pipe', address, { rpc = true })
|
||||
|
||||
finally(function()
|
||||
server:close()
|
||||
client:close()
|
||||
end)
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
it('does not crash on receiving UI events', function()
|
||||
local id = start_server_and_client()
|
||||
fn.rpcrequest(id, 'nvim_ui_attach', 80, 24, {})
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
server:close()
|
||||
client:close()
|
||||
it('does not leak memory with channel closed before response', function()
|
||||
local id = start_server_and_client()
|
||||
eq(
|
||||
('ch %d was closed by the peer'):format(id),
|
||||
pcall_err(n.exec_lua, function()
|
||||
vim.rpcrequest(id, 'nvim_command', 'qall!')
|
||||
end)
|
||||
)
|
||||
eq({}, api.nvim_get_chan_info(id)) -- Channel is closed.
|
||||
end)
|
||||
|
||||
it('response works with channel closed just after response', function()
|
||||
local id = start_server_and_client()
|
||||
eq(
|
||||
'RESPONSE',
|
||||
n.exec_lua(function()
|
||||
local prepare = vim.uv.new_prepare()
|
||||
-- Block the event loop after writing the request but before polling for I/O
|
||||
-- so that response and EOF arrive at the same uv_run() call.
|
||||
prepare:start(function()
|
||||
vim.uv.sleep(50)
|
||||
prepare:close()
|
||||
end)
|
||||
return vim.rpcrequest(
|
||||
id,
|
||||
'nvim_exec_lua',
|
||||
[[vim.schedule(function() vim.cmd('qall!') end); return 'RESPONSE']],
|
||||
{}
|
||||
)
|
||||
end)
|
||||
)
|
||||
eq({}, api.nvim_get_chan_info(id)) -- Channel is closed.
|
||||
end)
|
||||
|
||||
it('via stdio, with many small flushes does not crash #23781', function()
|
||||
|
||||
Reference in New Issue
Block a user