diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 21ad81c09c..311344cf06 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -66,14 +66,32 @@ void signal_init(void) signal_start(); } +#ifdef MSWIN +/// Swallows console control events during shutdown. +static BOOL WINAPI signal_ctrl_handler(DWORD type) +{ + switch (type) { + case CTRL_CLOSE_EVENT: + // Returning true is not enough; Windows terminates the process as soon as the handler returns. + // So block instead, like libuv's own handler does, and let the exit finish. + Sleep(INFINITE); + return true; + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + return true; + default: + return false; + } +} +#endif + /// During shutdown, we don't want the default actions of these signals. -/// -/// Note: Windows still has the race. libuv's console control handler dispatches CTRL_CLOSE_EVENT -/// as SIGHUP, but only while a watcher is registered; signal_teardown() closed ours, so the -/// console terminates Nvim with STATUS_CONTROL_C_EXIT (-1073741510). static void signal_ignore_deadly(void) { -#ifndef MSWIN +#ifdef MSWIN + // Handlers run in reverse registration order, so this one runs before libuv's. + SetConsoleCtrlHandler(signal_ctrl_handler, true); +#else signal(SIGHUP, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN); diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index ead5f55e35..1c8e73e5e6 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -3669,10 +3669,7 @@ describe('TUI', function() retry(nil, 2000, function() eq(vim.NIL, api.nvim_get_proc(pid)) end) - -- On Windows the console terminates the child with STATUS_CONTROL_C_EXIT (-1073741510). - screen:expect({ - any = is_os('win') and '%[Process exited %-?%d+%]' or '%[Process exited 1%]', - }) + screen:expect({ any = '%[Process exited 1%]' }) -- Closing stdin must skip the DA1 wait. t.assert_nolog('timed out waiting for DA1 response', testlog, 100) end)