commit 97509aa2be00b366f10fa4e97506e7b96d514345
parent ec24746406c4f02a6933e046a87b72d5129042a9
Author: zeertzjq <zeertzjq@outlook.com>
Date: Mon, 23 Feb 2026 12:28:17 +0800
fix(process): handle poll() interrupted by a signal (#38024)
It is indeed possible that a signal may arrive during flush_stream()
(e.g. SIGCHLD from another child process), so poll() again on EINTR.
Fixes the following Coverity warning:
_____________________________________________________________________________________________
*** CID 644345: Error handling issues (CHECKED_RETURN)
/src/nvim/event/proc.c: 405 in flush_stream()
399
400 #ifdef __linux__
401 // On Linux, libuv's polling (which uses epoll) doesn't flush PTY master's pending
402 // work on kernel workqueue, so use an explcit poll() before that. #37982
403 if (proc->type == kProcTypePty && !stream->did_eof) {
404 struct pollfd pollfd = { .fd = ((PtyProc *)proc)->tty_fd, .events = POLLIN };
>>> CID 644345: Error handling issues (CHECKED_RETURN)
>>> Calling "poll(&pollfd, 1UL, 0)" without checking return value. This library function may fail and return an error code.
405 poll(&pollfd, 1, 0);
406 }
407 #endif
408 // Poll for data and process the generated events.
409 loop_poll_events(proc->loop, 0);
410 if (stream->s.events) {
Another possible error is ENOMEM, which is probably not worth handling.
For reference, #23308 previously removed a case of ENOMEM handling, and
this PR also removes an outdated mention of that.
Also reduce the number of #ifdefs in non-OS-specific files.
Diffstat:
5 files changed, 34 insertions(+), 13 deletions(-)
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
@@ -849,10 +849,8 @@ static void term_resize(uint16_t width, uint16_t height, void *data)
static void term_resume(void *data)
{
-#ifdef UNIX
Channel *chan = data;
pty_proc_resume(&chan->stream.pty);
-#endif
}
static inline void term_delayed_free(void **argv)
diff --git a/src/nvim/event/proc.c b/src/nvim/event/proc.c
@@ -3,9 +3,6 @@
#include <signal.h>
#include <string.h>
#include <uv.h>
-#ifdef __linux__
-# include <poll.h>
-#endif
#include "klib/kvec.h"
#include "nvim/channel.h"
@@ -397,14 +394,9 @@ static void flush_stream(Proc *proc, RStream *stream)
// Remember number of bytes before polling.
size_t num_bytes = stream->num_bytes;
-#ifdef __linux__
- // On Linux, libuv's polling (which uses epoll) doesn't flush PTY master's pending
- // work on kernel workqueue, so use an explcit poll() before that. #37982
if (proc->type == kProcTypePty && !stream->did_eof) {
- struct pollfd pollfd = { .fd = ((PtyProc *)proc)->tty_fd, .events = POLLIN };
- poll(&pollfd, 1, 0);
+ pty_proc_flush_master((PtyProc *)proc);
}
-#endif
// Poll for data and process the generated events.
loop_poll_events(proc->loop, 0);
if (stream->s.events) {
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
@@ -563,7 +563,7 @@ int os_open_stdin_fd(void)
/// Read from a file
///
-/// Handles EINTR and ENOMEM, but not other errors.
+/// Handles EINTR, but not other errors.
///
/// @param[in] fd File descriptor to read from.
/// @param[out] ret_eof Is set to true if EOF was encountered, otherwise set
diff --git a/src/nvim/os/pty_proc_unix.c b/src/nvim/os/pty_proc_unix.c
@@ -31,6 +31,9 @@ int forkpty(int *, char *, const struct termios *, const struct winsize *);
#ifdef __APPLE__
# include <crt_externs.h>
#endif
+#ifdef __linux__
+# include <poll.h>
+#endif
#include "auto/config.h"
#include "klib/kvec.h"
@@ -240,12 +243,29 @@ void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
}
void pty_proc_resume(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
{
// Send SIGCONT to the entire process group, as some shells (e.g. fish) don't
// propagate SIGCONT to suspended child processes.
killpg(((Proc *)ptyproc)->pid, SIGCONT);
}
+/// On Linux, libuv's polling (which uses epoll) doesn't flush PTY master's pending
+/// work on kernel workqueue, so use an explcit poll() before that. #37982
+/// Note that poll() only flushes pending work if no data is immediately available,
+/// so this function is needed before every libuv poll in flush_stream().
+void pty_proc_flush_master(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+#ifdef __linux__
+ struct pollfd pollfd = { .fd = ptyproc->tty_fd, .events = POLLIN };
+ int n = 0;
+ do {
+ n = poll(&pollfd, 1, 0);
+ } while (n < 0 && errno == EINTR);
+#endif
+}
+
void pty_proc_close(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
@@ -256,7 +276,8 @@ void pty_proc_close(PtyProc *ptyproc)
}
}
-void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL
+void pty_proc_close_master(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
{
if (ptyproc->tty_fd >= 0) {
close(ptyproc->tty_fd);
diff --git a/src/nvim/os/pty_proc_win.c b/src/nvim/os/pty_proc_win.c
@@ -180,6 +180,16 @@ void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
os_conpty_set_size(ptyproc->conpty, width, height);
}
+void pty_proc_resume(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+}
+
+void pty_proc_flush_master(PtyProc *ptyproc)
+ FUNC_ATTR_NONNULL_ALL
+{
+}
+
void pty_proc_close(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{