Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(rpc)!: preseve files when stdio channel is closed #22137

Merged
merged 1 commit into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions runtime/doc/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ The following changes may require adaptations in user config or plugins.

• libiconv is now a required build dependency.

• Unsaved changes are now preserved rather than discarded when |channel-stdio|
is closed.

==============================================================================
NEW FEATURES *news-features*

Expand Down
7 changes: 6 additions & 1 deletion src/nvim/event/process.c
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,12 @@ static void exit_event(void **argv)
}

if (!exiting) {
os_exit(status);
if (ui_client_channel_id) {
os_exit(status);
} else {
assert(status == 0); // Called from rpc_close(), which passes 0 as status.
preserve_exit(NULL);
}
}
}

Expand Down
18 changes: 12 additions & 6 deletions src/nvim/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ void os_exit(int r)
void getout(int exitval)
FUNC_ATTR_NORETURN
{
assert(!ui_client_channel_id);
exiting = true;

// On error during Ex mode, exit with a non-zero code.
Expand Down Expand Up @@ -794,6 +795,7 @@ void getout(int exitval)
}

/// Preserve files, print contents of `errmsg`, and exit 1.
/// @param errmsg If NULL, this function will not print anything.
///
/// May be called from deadly_signal().
void preserve_exit(const char *errmsg)
Expand All @@ -819,27 +821,31 @@ void preserve_exit(const char *errmsg)
// For TUI: exit alternate screen so that the error messages can be seen.
ui_client_stop();
}
os_errmsg(errmsg);
os_errmsg("\n");
if (errmsg != NULL) {
os_errmsg(errmsg);
os_errmsg("\n");
}
if (ui_client_channel_id) {
os_exit(1);
}
ui_flush();

ml_close_notmod(); // close all not-modified buffers

FOR_ALL_BUFFERS(buf) {
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
os_errmsg("Vim: preserving files...\r\n");
ui_flush();
if (errmsg != NULL) {
os_errmsg("Vim: preserving files...\r\n");
}
ml_sync_all(false, false, true); // preserve all swap files
break;
}
}

ml_close_all(false); // close all memfiles, without deleting

os_errmsg("Vim: Finished.\r\n");
if (errmsg != NULL) {
os_errmsg("Vim: Finished.\r\n");
}

getout(1);
}
Expand Down
4 changes: 4 additions & 0 deletions src/nvim/msgpack_rpc/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,10 @@ void rpc_close(Channel *channel)

if (channel->streamtype == kChannelStreamStdio
|| (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) {
if (channel->streamtype == kChannelStreamStdio) {
// Avoid hanging when there are no other UIs and a prompt is triggered on exit.
remote_ui_disconnect(channel->id);
}
exit_from_channel(0);
}
}
Expand Down
5 changes: 5 additions & 0 deletions test/functional/ex_cmds/oldfiles_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')(after_each)

local clear = helpers.clear
local command = helpers.command
local expect_exit = helpers.expect_exit
local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command
local feed, poke_eventloop = helpers.feed, helpers.poke_eventloop
local ok = helpers.ok
Expand All @@ -19,6 +21,7 @@ describe(':oldfiles', function()
before_each(_clear)

after_each(function()
expect_exit(command, 'qall!')
os.remove(shada_file)
end)

Expand All @@ -42,6 +45,7 @@ describe(':oldfiles', function()
|
Press ENTER or type command to continue^ |
]])
feed('<CR>')
end)

it('can be filtered with :filter', function()
Expand Down Expand Up @@ -107,6 +111,7 @@ describe(':browse oldfiles', function()
end)

after_each(function()
expect_exit(command, 'qall!')
os.remove(shada_file)
end)

Expand Down
1 change: 1 addition & 0 deletions test/functional/ex_cmds/profile_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe(':profile', function()
before_each(helpers.clear)

after_each(function()
helpers.expect_exit(command, 'qall!')
if lfs.attributes(tempfile, 'uid') ~= nil then
os.remove(tempfile)
end
Expand Down
63 changes: 47 additions & 16 deletions test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ local nvim_prog = helpers.nvim_prog
local ok = helpers.ok
local rmdir = helpers.rmdir
local new_argv = helpers.new_argv
local new_pipename = helpers.new_pipename
local pesc = helpers.pesc
local os_kill = helpers.os_kill
local set_session = helpers.set_session
Expand All @@ -37,10 +38,21 @@ describe(':recover', function()

end)

describe(':preserve', function()
describe("preserve and (R)ecover with custom 'directory'", function()
local swapdir = lfs.currentdir()..'/Xtest_recover_dir'
local testfile = 'Xtest_recover_file1'
-- Put swapdir at the start of the 'directory' list. #1836
-- Note: `set swapfile` *must* go after `set directory`: otherwise it may
-- attempt to create a swapfile in different directory.
local init = [[
set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
set swapfile fileformat=unix undolevels=-1
]]

local nvim1
before_each(function()
clear()
nvim1 = spawn(new_argv())
set_session(nvim1)
rmdir(swapdir)
lfs.mkdir(swapdir)
end)
Expand All @@ -49,25 +61,15 @@ describe(':preserve', function()
rmdir(swapdir)
end)

it("saves to custom 'directory' and (R)ecovers #1836", function()
local testfile = 'Xtest_recover_file1'
-- Put swapdir at the start of the 'directory' list. #1836
-- Note: `set swapfile` *must* go after `set directory`: otherwise it may
-- attempt to create a swapfile in different directory.
local init = [[
set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
set swapfile fileformat=unix undolevels=-1
]]

local function setup_swapname()
exec(init)
command('edit! '..testfile)
feed('isometext<esc>')
command('preserve')
exec('redir => g:swapname | silent swapname | redir END')
return eval('g:swapname')
end

local swappath1 = eval('g:swapname')

os_kill(eval('getpid()'))
local function test_recover(swappath1)
-- Start another Nvim instance.
local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
true)
Expand All @@ -90,6 +92,35 @@ describe(':preserve', function()
-- Verify that :swapname was not truncated (:help 'shortmess').
ok(nil == string.find(swappath1, '%.%.%.'))
ok(nil == string.find(swappath2, '%.%.%.'))
end

it('with :preserve and SIGKILL', function()
local swappath1 = setup_swapname()
command('preserve')
os_kill(eval('getpid()'))
test_recover(swappath1)
end)

it('closing stdio channel without :preserve #22096', function()
local swappath1 = setup_swapname()
nvim1:close()
test_recover(swappath1)
end)

it('killing TUI process without :preserve #22096', function()
helpers.skip(helpers.is_os('win'))
local screen = Screen.new()
screen:attach()
local child_server = new_pipename()
funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server})
screen:expect({any = pesc('[No Name]')}) -- Wait for the child process to start.
local child_session = helpers.connect(child_server)
set_session(child_session)
local swappath1 = setup_swapname()
set_session(nvim1)
command('call chanclose(&channel)') -- Kill the child process.
screen:expect({any = pesc('[Process exited 1]')}) -- Wait for the child process to stop.
test_recover(swappath1)
end)

end)
Expand Down
1 change: 1 addition & 0 deletions test/functional/shada/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ local function reset(o)
end

local clear = function()
helpers.expect_exit(helpers.command, 'qall!')
os.remove(tmpname)
end

Expand Down
1 change: 0 additions & 1 deletion test/functional/shada/history_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('ShaDa support code', function()
nvim_command('rshada')
eq('" Test 2', funcs.histget(':', -1))
eq('" Test', funcs.histget(':', -2))
expect_exit(nvim_command, 'qall')
end)

it('respects &history when dumping',
Expand Down
38 changes: 20 additions & 18 deletions test/functional/shada/shada_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -136,24 +136,6 @@ describe('ShaDa support code', function()
eq(#msgpack, found)
end)

it('does not write NONE file', function()
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
'--headless', '--cmd', 'qall'}, true)
session:close()
eq(nil, lfs.attributes('NONE'))
eq(nil, lfs.attributes('NONE.tmp.a'))
end)

it('does not read NONE file', function()
write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
'--headless'}, true)
set_session(session)
eq('', funcs.getreg('a'))
session:close()
os.remove('NONE')
end)

local marklike = {[7]=true, [8]=true, [10]=true, [11]=true}
local find_file = function(fname)
local found = {}
Expand Down Expand Up @@ -263,3 +245,23 @@ describe('ShaDa support code', function()
meths.set_option('shada', '')
end)
end)

describe('ShaDa support code', function()
it('does not write NONE file', function()
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
'--headless', '--cmd', 'qall'}, true)
session:close()
eq(nil, lfs.attributes('NONE'))
eq(nil, lfs.attributes('NONE.tmp.a'))
end)

it('does not read NONE file', function()
write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
'--headless'}, true)
set_session(session)
eq('', funcs.getreg('a'))
session:close()
os.remove('NONE')
end)
end)