mirror of
https://github.com/neovim/neovim.git
synced 2026-08-01 05:09:08 +00:00
fix(process): avoid inherited stdio for Windows jobs #40074
Problem:
On Windows, channel jobs inherit Nvim's stdio, so a background job
writing to CON (e.g. gutentags) draws onto the TUI and stays until
redraw.
Solution:
Give Windows job stdin/stderr libuv-created pipes (UV_CREATE_PIPE)
instead of inherited fds, so libuv spawns the child with
CREATE_NO_WINDOW and CON writes no longer leak onto the TUI.
(cherry picked from commit 39e9c1754a)
This commit is contained in:
committed by
github-actions[bot]
parent
ad5720eab9
commit
61765b97fe
@@ -406,6 +406,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
|
||||
proc->cwd = cwd;
|
||||
proc->env = env;
|
||||
proc->overlapped = overlapped;
|
||||
proc->stdio_noinherit = true;
|
||||
|
||||
char *cmd = xstrdup(proc_get_exepath(proc));
|
||||
bool has_out, has_err;
|
||||
|
||||
@@ -170,5 +170,6 @@ struct proc {
|
||||
proc_state_cb state_cb;
|
||||
internal_proc_cb internal_exit_cb, internal_close_cb;
|
||||
bool closed, detach, overlapped, fwd_err;
|
||||
bool stdio_noinherit; ///< MSWIN: channel jobs don't inherit stdio, to prevent CON leaking to TUI
|
||||
MultiQueue *events;
|
||||
};
|
||||
|
||||
@@ -63,23 +63,39 @@ int libuv_proc_spawn(LibuvProc *uvproc)
|
||||
int to_close[3] = { -1, -1, -1 };
|
||||
|
||||
if (!proc->in.closed) {
|
||||
uv_file pipe_pair[2];
|
||||
int client_flags = 0;
|
||||
#ifdef MSWIN
|
||||
client_flags |= proc->overlapped ? UV_NONBLOCK_PIPE : 0;
|
||||
#endif
|
||||
if (proc->stdio_noinherit) {
|
||||
// Let channel jobs use CREATE_NO_WINDOW so CON writes do not leak.
|
||||
uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
|
||||
uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0;
|
||||
uvproc->uvstdio[0].data.stream = (uv_stream_t *)(&proc->in.uv.pipe);
|
||||
} else {
|
||||
uv_file pipe_pair[2];
|
||||
int client_flags = proc->overlapped ? UV_NONBLOCK_PIPE : 0;
|
||||
|
||||
uv_pipe(pipe_pair, client_flags, UV_NONBLOCK_PIPE);
|
||||
|
||||
uvproc->uvstdio[0].flags = UV_INHERIT_FD;
|
||||
uvproc->uvstdio[0].data.fd = pipe_pair[0];
|
||||
to_close[0] = pipe_pair[0];
|
||||
|
||||
uv_pipe_open(&proc->in.uv.pipe, pipe_pair[1]);
|
||||
}
|
||||
#else
|
||||
uv_file pipe_pair[2];
|
||||
|
||||
// As of libuv 1.51, UV_CREATE_PIPE can only create pipes
|
||||
// using socketpair(), not pipe(). We want the latter on linux
|
||||
// as socket pairs behave different in some confusing ways, like
|
||||
// breaking /proc/0/fd/0 which is disowned by the linux socket maintainer.
|
||||
uv_pipe(pipe_pair, client_flags, UV_NONBLOCK_PIPE);
|
||||
uv_pipe(pipe_pair, 0, UV_NONBLOCK_PIPE);
|
||||
|
||||
uvproc->uvstdio[0].flags = UV_INHERIT_FD;
|
||||
uvproc->uvstdio[0].data.fd = pipe_pair[0];
|
||||
to_close[0] = pipe_pair[0];
|
||||
|
||||
uv_pipe_open(&proc->in.uv.pipe, pipe_pair[1]);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!proc->out.s.closed) {
|
||||
@@ -104,6 +120,24 @@ int libuv_proc_spawn(LibuvProc *uvproc)
|
||||
}
|
||||
|
||||
if (!proc->err.s.closed) {
|
||||
#ifdef MSWIN
|
||||
if (proc->stdio_noinherit) {
|
||||
uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
|
||||
// pipe must be readable for IOCP to work on Windows.
|
||||
uvproc->uvstdio[2].flags |= proc->overlapped
|
||||
? (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
|
||||
uvproc->uvstdio[2].data.stream = (uv_stream_t *)(&proc->err.s.uv.pipe);
|
||||
} else {
|
||||
uv_file pipe_pair[2];
|
||||
uv_pipe(pipe_pair, UV_NONBLOCK_PIPE, 0);
|
||||
|
||||
uvproc->uvstdio[2].flags = UV_INHERIT_FD;
|
||||
uvproc->uvstdio[2].data.fd = pipe_pair[1];
|
||||
to_close[2] = pipe_pair[1];
|
||||
|
||||
uv_pipe_open(&proc->err.s.uv.pipe, pipe_pair[0]);
|
||||
}
|
||||
#else
|
||||
uv_file pipe_pair[2];
|
||||
uv_pipe(pipe_pair, UV_NONBLOCK_PIPE, 0);
|
||||
|
||||
@@ -112,6 +146,7 @@ int libuv_proc_spawn(LibuvProc *uvproc)
|
||||
to_close[2] = pipe_pair[1];
|
||||
|
||||
uv_pipe_open(&proc->err.s.uv.pipe, pipe_pair[0]);
|
||||
#endif
|
||||
} else if (proc->fwd_err) {
|
||||
uvproc->uvstdio[2].flags = UV_INHERIT_FD;
|
||||
uvproc->uvstdio[2].data.fd = STDERR_FILENO;
|
||||
|
||||
@@ -31,6 +31,7 @@ static inline Proc proc_init(Loop *loop, ProcType type, void *data)
|
||||
.internal_exit_cb = NULL,
|
||||
.detach = false,
|
||||
.fwd_err = false,
|
||||
.stdio_noinherit = false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,41 @@ describe('TUI', function()
|
||||
feed_data(':')
|
||||
screen:expect(s1)
|
||||
end)
|
||||
|
||||
it('jobstart child writing to CON does not leak to screen', function()
|
||||
-- A child job that writes to CON must not leak onto the TUI screen. The
|
||||
-- embedded server AttachConsole()'s the parent terminal (so server io.stdout
|
||||
-- e.g. SIXEL works), but jobs must get their own (windowless) console so
|
||||
-- writes to CON go nowhere visible.
|
||||
t.skip(not is_os('win'), 'N/A Windows only')
|
||||
n.clear()
|
||||
finally(function()
|
||||
n.check_close()
|
||||
end)
|
||||
|
||||
local screen = tt.setup_child_nvim({
|
||||
'--clean',
|
||||
'--cmd',
|
||||
'colorscheme vim',
|
||||
'--cmd',
|
||||
nvim_set .. ' laststatus=2 background=dark',
|
||||
}, { env = env_notermguicolors })
|
||||
tt.override_screen_expect_for_conpty(screen)
|
||||
|
||||
feed_data('iZZZSENTINEL')
|
||||
|
||||
-- Leave insert mode and run a job that writes directly to CON, blocking
|
||||
-- until it finishes. The CON output must not appear anywhere on screen.
|
||||
feed_data("\027\027:call jobwait([jobstart(['cmd', '/c', 'echo LEAKEDXYZ > CON'])])\013")
|
||||
|
||||
screen:expect([[
|
||||
ZZZSENTINE^L |
|
||||
{100:~ }|*3
|
||||
{3:[No Name] [+] }|
|
||||
|
|
||||
{5:-- TERMINAL --} |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('TUI :detach', function()
|
||||
|
||||
Reference in New Issue
Block a user