diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h index 6d322b203e..2f0a041899 100644 --- a/src/nvim/event/defs.h +++ b/src/nvim/event/defs.h @@ -93,7 +93,7 @@ struct stream { uv_file fd; ///< When the stream is a file, this is its file descriptor int64_t fpos; ///< When the stream is a file, this is the position in file void *cb_data; - stream_close_cb close_cb, internal_close_cb; + stream_close_cb before_close_cb, close_cb, internal_close_cb; void *close_cb_data, *internal_data; size_t pending_reqs; MultiQueue *events; diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 538373e1ad..7cae0448cf 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -140,6 +140,11 @@ void stream_close_handle(Stream *stream) assert(handle != NULL); + if (stream->before_close_cb) { + stream->pending_reqs++; + stream->before_close_cb(stream, stream->close_cb_data); + stream->pending_reqs--; + } if (!uv_is_closing(handle)) { uv_close(handle, close_cb); } diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c index d511a5b88a..5e0253089b 100644 --- a/src/nvim/os/pty_conpty_win.c +++ b/src/nvim/os/pty_conpty_win.c @@ -174,8 +174,9 @@ void os_conpty_set_size(conpty_t *conpty_object, uint16_t width, uint16_t height } } -void os_conpty_free(conpty_t *conpty_object) +void os_conpty_free(void *data) { + conpty_t *conpty_object = data; if (conpty_object != NULL) { if (conpty_object->si_ex.lpAttributeList != NULL) { DeleteProcThreadAttributeList(conpty_object->si_ex.lpAttributeList); diff --git a/src/nvim/os/pty_proc_win.c b/src/nvim/os/pty_proc_win.c index 2930f55f95..14aacc9800 100644 --- a/src/nvim/os/pty_proc_win.c +++ b/src/nvim/os/pty_proc_win.c @@ -17,35 +17,35 @@ static void CALLBACK pty_proc_terminate_cb(void *context, BOOLEAN unused) FUNC_ATTR_NONNULL_ALL { - PtyProc *ptyproc = (PtyProc *)context; - Proc *proc = (Proc *)ptyproc; - - os_conpty_free(ptyproc->conpty); + Proc *proc = (Proc *)context; // NB: pty_proc_terminate_cb() is called on a separate thread, // but finishing up the process needs to be done on the main thread. - loop_schedule_fast(proc->loop, event_create(pty_proc_finish_when_eof, ptyproc)); + loop_schedule_fast(proc->loop, event_create(pty_proc_finish, context)); } -static void pty_proc_finish_when_eof(void **argv) - FUNC_ATTR_NONNULL_ALL -{ - PtyProc *ptyproc = (PtyProc *)argv[0]; - - if (ptyproc->finish_wait != NULL) { - if (pty_proc_can_finish(ptyproc)) { - pty_proc_finish(ptyproc); - } else { - uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200); - } - } -} - -static bool pty_proc_can_finish(PtyProc *ptyproc) +/// Create a separate thread to call ClosePseudoConsole, +/// which allows us to flush the final frame on the main thread. +/// See https://learn.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#ending-the-pseudoconsole-session +static void pty_proc_close_console(Stream *stream, void *data) { + PtyProc *ptyproc = stream->internal_data; Proc *proc = (Proc *)ptyproc; - - assert(ptyproc->finish_wait != NULL); - return proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream); + if (ptyproc->conpty == NULL) { + return; + } + // On Windows 11, closing a terminal immediately after opening it can leave orphan conhost + // and shell processes, e.g. `during TermClose event` case in autocmd_spec.lua + // Checking `num_bytes` is an attempt to detect that, until we find a better way. + if (proc->out.num_bytes == 0) { + TerminateProcess(ptyproc->proc_handle, 0); + } + uv_thread_t tid; + uv_thread_create(&tid, os_conpty_free, ptyproc->conpty); + while (!proc->out.did_eof && WaitForSingleObject(tid, 0) == WAIT_TIMEOUT) { + uv_run(&proc->loop->uv, UV_RUN_ONCE); + } + uv_thread_detach(&tid); + ptyproc->conpty = NULL; } /// @returns zero on success, or negative error code. @@ -126,8 +126,6 @@ int pty_proc_spawn(PtyProc *ptyproc) } proc->pid = (int)GetProcessId(proc_handle); - uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer); - ptyproc->wait_eof_timer.data = (void *)ptyproc; if (!RegisterWaitForSingleObject(&ptyproc->finish_wait, proc_handle, pty_proc_terminate_cb, @@ -143,6 +141,7 @@ int pty_proc_spawn(PtyProc *ptyproc) uv_run(&proc->loop->uv, UV_RUN_ONCE); } + proc->out.s.before_close_cb = pty_proc_close_console; ptyproc->conpty = conpty_object; ptyproc->proc_handle = proc_handle; conpty_object = NULL; @@ -200,7 +199,6 @@ void pty_proc_close(PtyProc *ptyproc) if (ptyproc->finish_wait != NULL) { UnregisterWaitEx(ptyproc->finish_wait, NULL); ptyproc->finish_wait = NULL; - uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL); } if (ptyproc->proc_handle != NULL) { CloseHandle(ptyproc->proc_handle); @@ -229,19 +227,10 @@ static void pty_proc_connect_cb(uv_connect_t *req, int status) req->handle = NULL; } -static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) - FUNC_ATTR_NONNULL_ALL -{ - PtyProc *ptyproc = wait_eof_timer->data; - if (pty_proc_can_finish(ptyproc)) { - uv_timer_stop(&ptyproc->wait_eof_timer); - pty_proc_finish(ptyproc); - } -} - -static void pty_proc_finish(PtyProc *ptyproc) +static void pty_proc_finish(void **argv) FUNC_ATTR_NONNULL_ALL { + PtyProc *ptyproc = (PtyProc *)argv[0]; Proc *proc = (Proc *)ptyproc; DWORD exit_code = 0; diff --git a/src/nvim/os/pty_proc_win.h b/src/nvim/os/pty_proc_win.h index 6faf68bdd0..148f2bddea 100644 --- a/src/nvim/os/pty_proc_win.h +++ b/src/nvim/os/pty_proc_win.h @@ -13,7 +13,6 @@ typedef struct pty_process { conpty_t *conpty; HANDLE finish_wait; HANDLE proc_handle; - uv_timer_t wait_eof_timer; } PtyProc; // Structure used by build_cmd_line()