mirror of
https://github.com/neovim/neovim.git
synced 2026-06-17 17:21:16 +00:00
fix(rpc): trigger UILeave earlier on channel close (#38846)
Problem:
On exit, rpc_free() is called when processing main_loop.events after
libuv calls close callbacks of the channel's stream. However, when there
are no child processes, these libuv callbacks are called in loop_close()
instead of proc_teardown(), and main_loop.events isn't processed after
loop_close(). As a result, calling remote_ui_disconnect() in rpc_free()
causes UILeave to depend on the presence of child processes.
Solution:
Always call remote_ui_disconnect() in rpc_close_event(), and remove the
call in rpc_free().
(cherry picked from commit 5d66ef188f)
This commit is contained in:
committed by
github-actions[bot]
parent
a358b9be64
commit
eee2d10fd2
@@ -2684,11 +2684,6 @@ void do_autocmd_uienter(uint64_t chanid, bool attached)
|
|||||||
{
|
{
|
||||||
static bool recursive = false;
|
static bool recursive = false;
|
||||||
|
|
||||||
#ifdef EXITFREE
|
|
||||||
if (entered_free_all_mem) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (starting == NO_SCREEN) {
|
if (starting == NO_SCREEN) {
|
||||||
return; // user config hasn't been sourced yet
|
return; // user config hasn't been sourced yet
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -494,32 +494,31 @@ static void rpc_close_event(void **argv)
|
|||||||
|
|
||||||
channel_decref(channel);
|
channel_decref(channel);
|
||||||
|
|
||||||
|
// No more I/O can happen on this channel. Remove UI if there is one attached.
|
||||||
|
// Do this here instead of in rpc_free() which isn't always called on exit, so that
|
||||||
|
// UILeave events behave consistently.
|
||||||
|
remote_ui_disconnect(channel->id, NULL, false);
|
||||||
|
|
||||||
bool is_ui_client = ui_client_channel_id && channel->id == ui_client_channel_id;
|
bool is_ui_client = ui_client_channel_id && channel->id == ui_client_channel_id;
|
||||||
if (is_ui_client || channel->streamtype == kChannelStreamStdio) {
|
if (is_ui_client) {
|
||||||
if (!is_ui_client) {
|
ui_client_attach_to_restarted_server();
|
||||||
// Avoid hanging when there are no other UIs and a prompt is triggered on exit.
|
if (ui_client_channel_id != channel->id) {
|
||||||
remote_ui_disconnect(channel->id, NULL, false);
|
// Attached to new server. Don't exit.
|
||||||
} else {
|
return;
|
||||||
ui_client_attach_to_restarted_server();
|
|
||||||
if (ui_client_channel_id != channel->id) {
|
|
||||||
// Attached to new server. Don't exit.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!channel->detach) {
|
if (channel->streamtype == kChannelStreamProc && ui_client_error_exit < 0) {
|
||||||
if (channel->streamtype == kChannelStreamProc && ui_client_error_exit < 0) {
|
// Wait for the embedded server to exit instead of exiting immediately,
|
||||||
// Wait for the embedded server to exit instead of exiting immediately,
|
// as it's necessary to get the server's exit code in on_proc_exit().
|
||||||
// as it's necessary to get the server's exit code in on_proc_exit().
|
return;
|
||||||
} else {
|
|
||||||
exit_on_closed_chan(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
exit_on_closed_chan(0);
|
||||||
|
} else if (channel->streamtype == kChannelStreamStdio && !channel->detach) {
|
||||||
|
exit_on_closed_chan(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rpc_free(Channel *channel)
|
void rpc_free(Channel *channel)
|
||||||
{
|
{
|
||||||
remote_ui_disconnect(channel->id, NULL, false);
|
|
||||||
unpacker_teardown(channel->rpc.unpacker);
|
unpacker_teardown(channel->rpc.unpacker);
|
||||||
xfree(channel->rpc.unpacker);
|
xfree(channel->rpc.unpacker);
|
||||||
|
|
||||||
|
|||||||
@@ -162,16 +162,65 @@ it('autocmds UIEnter/UILeave', function()
|
|||||||
autocmd UIEnter * call add(g:evs, "UIEnter") | let g:uienter_ev = deepcopy(v:event)
|
autocmd UIEnter * call add(g:evs, "UIEnter") | let g:uienter_ev = deepcopy(v:event)
|
||||||
autocmd UILeave * call add(g:evs, "UILeave") | let g:uileave_ev = deepcopy(v:event)
|
autocmd UILeave * call add(g:evs, "UILeave") | let g:uileave_ev = deepcopy(v:event)
|
||||||
autocmd VimEnter * call add(g:evs, "VimEnter")
|
autocmd VimEnter * call add(g:evs, "VimEnter")
|
||||||
|
autocmd VimLeave * call add(g:evs, "VimLeave")
|
||||||
]])
|
]])
|
||||||
|
|
||||||
local screen = Screen.new()
|
local screen = Screen.new()
|
||||||
eq({ chan = 1 }, eval('g:uienter_ev'))
|
eq({ chan = 1 }, eval('g:uienter_ev'))
|
||||||
|
eq({ 'VimEnter', 'UIEnter' }, eval('g:evs'))
|
||||||
|
|
||||||
screen:detach()
|
screen:detach()
|
||||||
eq({ chan = 1 }, eval('g:uileave_ev'))
|
eq({ chan = 1 }, eval('g:uileave_ev'))
|
||||||
eq({
|
eq({ 'VimEnter', 'UIEnter', 'UILeave' }, eval('g:evs'))
|
||||||
'VimEnter',
|
|
||||||
'UIEnter',
|
local servername = api.nvim_get_vvar('servername')
|
||||||
'UILeave',
|
|
||||||
}, eval('g:evs'))
|
local session2 = n.connect(servername)
|
||||||
|
local status2, chan2 = session2:request('nvim_get_chan_info', 0)
|
||||||
|
t.ok(status2)
|
||||||
|
|
||||||
|
local session3 = n.connect(servername)
|
||||||
|
local status3, chan3 = session3:request('nvim_get_chan_info', 0)
|
||||||
|
t.ok(status3)
|
||||||
|
|
||||||
|
local screen2 = Screen.new(nil, nil, nil, session2)
|
||||||
|
eq({ chan = chan2.id }, eval('g:uienter_ev'))
|
||||||
|
eq({ 'VimEnter', 'UIEnter', 'UILeave', 'UIEnter' }, eval('g:evs'))
|
||||||
|
|
||||||
|
screen2:detach()
|
||||||
|
eq({ chan = chan2.id }, eval('g:uileave_ev'))
|
||||||
|
eq({ 'VimEnter', 'UIEnter', 'UILeave', 'UIEnter', 'UILeave' }, eval('g:evs'))
|
||||||
|
|
||||||
|
command('let g:evs = ["…"]')
|
||||||
|
|
||||||
|
screen2:attach(session2)
|
||||||
|
eq({ chan = chan2.id }, eval('g:uienter_ev'))
|
||||||
|
eq({ '…', 'UIEnter' }, eval('g:evs'))
|
||||||
|
|
||||||
|
Screen.new(nil, nil, nil, session3)
|
||||||
|
eq({ chan = chan3.id }, eval('g:uienter_ev'))
|
||||||
|
eq({ '…', 'UIEnter', 'UIEnter' }, eval('g:evs'))
|
||||||
|
|
||||||
|
screen:attach(n.get_session())
|
||||||
|
eq({ chan = 1 }, eval('g:uienter_ev'))
|
||||||
|
eq({ '…', 'UIEnter', 'UIEnter', 'UIEnter' }, eval('g:evs'))
|
||||||
|
|
||||||
|
session3:close()
|
||||||
|
t.retry(nil, 1000, function()
|
||||||
|
eq({}, api.nvim_get_chan_info(chan3.id))
|
||||||
|
end)
|
||||||
|
eq({ chan = chan3.id }, eval('g:uileave_ev'))
|
||||||
|
eq({ '…', 'UIEnter', 'UIEnter', 'UIEnter', 'UILeave' }, eval('g:evs'))
|
||||||
|
|
||||||
|
command('let g:evs = ["…"]')
|
||||||
|
command('autocmd UILeave * call writefile(g:evs, "Xevents.log")')
|
||||||
|
finally(function()
|
||||||
|
os.remove('Xevents.log')
|
||||||
|
end)
|
||||||
|
n.expect_exit(command, 'qall!')
|
||||||
|
n.check_close() -- Wait for process exit.
|
||||||
|
-- UILeave should have been triggered for both remaining UIs.
|
||||||
|
eq('…\nVimLeave\nUILeave\nUILeave\n', t.read_file('Xevents.log'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('autocmds VimSuspend/VimResume #22041', function()
|
it('autocmds VimSuspend/VimResume #22041', function()
|
||||||
|
|||||||
Reference in New Issue
Block a user