mirror of
https://github.com/neovim/neovim.git
synced 2026-08-28 18:11:47 +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->cwd = cwd;
|
||||||
proc->env = env;
|
proc->env = env;
|
||||||
proc->overlapped = overlapped;
|
proc->overlapped = overlapped;
|
||||||
|
proc->stdio_noinherit = true;
|
||||||
|
|
||||||
char *cmd = xstrdup(proc_get_exepath(proc));
|
char *cmd = xstrdup(proc_get_exepath(proc));
|
||||||
bool has_out, has_err;
|
bool has_out, has_err;
|
||||||
|
|||||||
@@ -170,5 +170,6 @@ struct proc {
|
|||||||
proc_state_cb state_cb;
|
proc_state_cb state_cb;
|
||||||
internal_proc_cb internal_exit_cb, internal_close_cb;
|
internal_proc_cb internal_exit_cb, internal_close_cb;
|
||||||
bool closed, detach, overlapped, fwd_err;
|
bool closed, detach, overlapped, fwd_err;
|
||||||
|
bool stdio_noinherit; ///< MSWIN: channel jobs don't inherit stdio, to prevent CON leaking to TUI
|
||||||
MultiQueue *events;
|
MultiQueue *events;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,23 +63,39 @@ int libuv_proc_spawn(LibuvProc *uvproc)
|
|||||||
int to_close[3] = { -1, -1, -1 };
|
int to_close[3] = { -1, -1, -1 };
|
||||||
|
|
||||||
if (!proc->in.closed) {
|
if (!proc->in.closed) {
|
||||||
uv_file pipe_pair[2];
|
|
||||||
int client_flags = 0;
|
|
||||||
#ifdef MSWIN
|
#ifdef MSWIN
|
||||||
client_flags |= proc->overlapped ? UV_NONBLOCK_PIPE : 0;
|
if (proc->stdio_noinherit) {
|
||||||
#endif
|
// 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
|
// As of libuv 1.51, UV_CREATE_PIPE can only create pipes
|
||||||
// using socketpair(), not pipe(). We want the latter on linux
|
// using socketpair(), not pipe(). We want the latter on linux
|
||||||
// as socket pairs behave different in some confusing ways, like
|
// as socket pairs behave different in some confusing ways, like
|
||||||
// breaking /proc/0/fd/0 which is disowned by the linux socket maintainer.
|
// 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].flags = UV_INHERIT_FD;
|
||||||
uvproc->uvstdio[0].data.fd = pipe_pair[0];
|
uvproc->uvstdio[0].data.fd = pipe_pair[0];
|
||||||
to_close[0] = pipe_pair[0];
|
to_close[0] = pipe_pair[0];
|
||||||
|
|
||||||
uv_pipe_open(&proc->in.uv.pipe, pipe_pair[1]);
|
uv_pipe_open(&proc->in.uv.pipe, pipe_pair[1]);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!proc->out.s.closed) {
|
if (!proc->out.s.closed) {
|
||||||
@@ -104,6 +120,24 @@ int libuv_proc_spawn(LibuvProc *uvproc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!proc->err.s.closed) {
|
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_file pipe_pair[2];
|
||||||
uv_pipe(pipe_pair, UV_NONBLOCK_PIPE, 0);
|
uv_pipe(pipe_pair, UV_NONBLOCK_PIPE, 0);
|
||||||
|
|
||||||
@@ -112,6 +146,7 @@ int libuv_proc_spawn(LibuvProc *uvproc)
|
|||||||
to_close[2] = pipe_pair[1];
|
to_close[2] = pipe_pair[1];
|
||||||
|
|
||||||
uv_pipe_open(&proc->err.s.uv.pipe, pipe_pair[0]);
|
uv_pipe_open(&proc->err.s.uv.pipe, pipe_pair[0]);
|
||||||
|
#endif
|
||||||
} else if (proc->fwd_err) {
|
} else if (proc->fwd_err) {
|
||||||
uvproc->uvstdio[2].flags = UV_INHERIT_FD;
|
uvproc->uvstdio[2].flags = UV_INHERIT_FD;
|
||||||
uvproc->uvstdio[2].data.fd = STDERR_FILENO;
|
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,
|
.internal_exit_cb = NULL,
|
||||||
.detach = false,
|
.detach = false,
|
||||||
.fwd_err = false,
|
.fwd_err = false,
|
||||||
|
.stdio_noinherit = false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,41 @@ describe('TUI', function()
|
|||||||
feed_data(':')
|
feed_data(':')
|
||||||
screen:expect(s1)
|
screen:expect(s1)
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
describe('TUI :detach', function()
|
describe('TUI :detach', function()
|
||||||
|
|||||||
Reference in New Issue
Block a user