mirror of
https://github.com/neovim/neovim.git
synced 2026-07-10 03:19:44 +00:00
feat(channel): add ChanClose event #40568
Problem: Plugins using RPC sockets cannot detect when the peer closes a `sockconnect()` channel, so reconnect logic has no reliable trigger. Solution: Add a `ChanClose` event with channel info before the channel is removed, matching the existing `ChanOpen`/`ChanInfo` event model.
This commit is contained in:
@@ -397,6 +397,13 @@ BufWriteCmd Before writing the whole buffer to a file.
|
||||
BufWritePost After writing the whole buffer to a file
|
||||
(should undo the commands for BufWritePre).
|
||||
|
||||
*ChanClose*
|
||||
ChanClose After a channel was closed, before it is removed.
|
||||
This is triggered even when inside an
|
||||
autocommand defined without |autocmd-nested|.
|
||||
Sets these |v:event| keys:
|
||||
info as from |nvim_get_chan_info()|
|
||||
|
||||
*ChanInfo*
|
||||
ChanInfo State of channel changed, for instance the
|
||||
client of a RPC channel described itself.
|
||||
|
||||
@@ -219,6 +219,7 @@ EVENTS
|
||||
|
||||
• |:delmarks| now triggers the |MarkSet| autocommand with line==col==0, same
|
||||
as |nvim_buf_del_mark()|
|
||||
• |ChanClose| is triggered after a channel is closed, before it is removed.
|
||||
• |SessionWritePre| event emits just before |:mksession|.
|
||||
• |TextPutPre| and |TextPutPost| are triggered before/after putting text.
|
||||
• |TabMoved| is triggered when tabs are reordered.
|
||||
|
||||
@@ -2544,6 +2544,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
Note: The following events are considered to happen outside of a
|
||||
window context and thus cannot be ignored by 'eventignorewin':
|
||||
|
||||
|ChanClose|,
|
||||
|ChanInfo|,
|
||||
|ChanOpen|,
|
||||
|CmdUndefined|,
|
||||
|
||||
1
runtime/lua/vim/_meta/api_keysets.gen.lua
generated
1
runtime/lua/vim/_meta/api_keysets.gen.lua
generated
@@ -102,6 +102,7 @@ error('Cannot require a meta file')
|
||||
--- |'BufWriteCmd'
|
||||
--- |'BufWritePost'
|
||||
--- |'BufWritePre'
|
||||
--- |'ChanClose'
|
||||
--- |'ChanInfo'
|
||||
--- |'ChanOpen'
|
||||
--- |'CmdUndefined'
|
||||
|
||||
1
runtime/lua/vim/_meta/options.gen.lua
generated
1
runtime/lua/vim/_meta/options.gen.lua
generated
@@ -2191,6 +2191,7 @@ vim.go.ei = vim.go.eventignore
|
||||
--- Note: The following events are considered to happen outside of a
|
||||
--- window context and thus cannot be ignored by 'eventignorewin':
|
||||
---
|
||||
--- `ChanClose`,
|
||||
--- `ChanInfo`,
|
||||
--- `ChanOpen`,
|
||||
--- `CmdUndefined`,
|
||||
|
||||
@@ -22,6 +22,7 @@ return {
|
||||
BufWriteCmd = true, -- write buffer using command
|
||||
BufWritePost = true, -- after writing a buffer
|
||||
BufWritePre = true, -- before writing a buffer
|
||||
ChanClose = false,
|
||||
ChanInfo = false, -- info was received about channel
|
||||
ChanOpen = false, -- channel was opened
|
||||
CmdUndefined = false, -- command undefined
|
||||
|
||||
@@ -263,7 +263,7 @@ void channel_create_event(Channel *chan, const char *ext_source)
|
||||
(void)ext_source;
|
||||
#endif
|
||||
|
||||
channel_info_changed(chan, true);
|
||||
channel_event(chan, EVENT_CHANOPEN);
|
||||
}
|
||||
|
||||
void channel_incref(Channel *chan)
|
||||
@@ -273,6 +273,11 @@ void channel_incref(Channel *chan)
|
||||
|
||||
void channel_decref(Channel *chan)
|
||||
{
|
||||
if (chan->refcount == 1 && !chan->did_close_event) {
|
||||
chan->did_close_event = true;
|
||||
channel_event(chan, EVENT_CHANCLOSE);
|
||||
}
|
||||
|
||||
if (!(--chan->refcount)) {
|
||||
// delay free, so that libuv is done with the handles
|
||||
multiqueue_put(main_loop.events, free_channel_event, chan);
|
||||
@@ -951,9 +956,8 @@ static void term_close(void *data)
|
||||
multiqueue_put(chan->events, term_delayed_free, data);
|
||||
}
|
||||
|
||||
void channel_info_changed(Channel *chan, bool new_chan)
|
||||
void channel_event(Channel *chan, event_T event)
|
||||
{
|
||||
event_T event = new_chan ? EVENT_CHANOPEN : EVENT_CHANINFO;
|
||||
if (has_event(event)) {
|
||||
channel_incref(chan);
|
||||
multiqueue_put(main_loop.events, set_info_event, chan, (void *)(intptr_t)event);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "nvim/autocmd_defs.h"
|
||||
#include "nvim/channel_defs.h" // IWYU pragma: keep
|
||||
#include "nvim/eval/typval_defs.h"
|
||||
#include "nvim/event/defs.h"
|
||||
@@ -44,6 +45,7 @@ struct Channel {
|
||||
|
||||
bool callback_busy;
|
||||
bool callback_scheduled;
|
||||
bool did_close_event;
|
||||
};
|
||||
|
||||
#include "channel.h.generated.h"
|
||||
|
||||
@@ -675,7 +675,7 @@ void rpc_set_client_info(uint64_t id, Dict info)
|
||||
chan->rpc.client_type = kClientTypeUnknown;
|
||||
}
|
||||
|
||||
channel_info_changed(chan, false);
|
||||
channel_event(chan, EVENT_CHANINFO);
|
||||
}
|
||||
|
||||
Dict rpc_client_info(Channel *chan)
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('channels', function()
|
||||
source(init)
|
||||
end)
|
||||
|
||||
pending('can connect to socket', function()
|
||||
it('can connect to socket', function()
|
||||
local server = n.new_session(true)
|
||||
set_session(server)
|
||||
local address = fn.serverlist()[1]
|
||||
@@ -62,6 +62,46 @@ describe('channels', function()
|
||||
eq({ 'notification', 'data', { id, { '' } } }, next_msg())
|
||||
end)
|
||||
|
||||
it('emits ChanClose when RPC socket reaches EOF', function()
|
||||
local client = get_session()
|
||||
local server = n.new_session(true)
|
||||
finally(function()
|
||||
set_session(client)
|
||||
if server then
|
||||
server:close()
|
||||
end
|
||||
end)
|
||||
set_session(server)
|
||||
local address = fn.serverlist()[1]
|
||||
set_session(client)
|
||||
|
||||
api.nvim_set_var('address', address)
|
||||
exec_lua(function()
|
||||
_G.closed_event = nil
|
||||
vim.api.nvim_create_autocmd('ChanClose', {
|
||||
callback = function()
|
||||
_G.closed_event = vim.deepcopy(vim.v.event.info)
|
||||
end,
|
||||
})
|
||||
end)
|
||||
command("let g:id = sockconnect('pipe', address, {'rpc': v:true})")
|
||||
local id = eval('g:id')
|
||||
ok(id > 0)
|
||||
eq(5, eval("rpcrequest(g:id, 'nvim_eval', '2+3')"))
|
||||
|
||||
server:close()
|
||||
server = nil
|
||||
set_session(client)
|
||||
retry(nil, 3000, function()
|
||||
local info = exec_lua(function()
|
||||
return _G.closed_event
|
||||
end)
|
||||
eq(id, info.id)
|
||||
eq('socket', info.stream)
|
||||
eq('rpc', info.mode)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('dont crash due to garbage in rpc #23781', function()
|
||||
local client = get_session()
|
||||
local server = n.new_session(true)
|
||||
|
||||
Reference in New Issue
Block a user