[PATCH v3 2/4] wrapper: add support for timeout and deadline in read helpers
From: Siddh Raman Pant <hidden>
Date: 2026-05-23 10:39:30
Subsystem:
the rest · Maintainer:
Linus Torvalds
Add read helpers which allow a caller to enforce a timeout per read, and a deadline for the read in case multiple reads have to be done under a common timeout. Assisted-by: Codex:gpt-5.5-xhigh-fast Signed-off-by: Siddh Raman Pant <redacted> --- strbuf.c | 26 +++++++++- strbuf.h | 4 ++ wrapper.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++---- wrapper.h | 23 +++++++++ 4 files changed, 182 insertions(+), 10 deletions(-)
diff --git a/strbuf.c b/strbuf.c
index 3e04addc22fe..b3fc7c624aa2 100644
--- a/strbuf.c
+++ b/strbuf.c@@ -749,13 +749,15 @@ int strbuf_getline_nul(struct strbuf *sb, FILE *fp) return strbuf_getdelim(sb, fp, '\0'); } -int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term) +static int strbuf_getwholeline_fd_with(struct strbuf *sb, int fd, int term, + xread_cb_t xread_cb, + void *cb_data) { strbuf_reset(sb); while (1) { char ch; - ssize_t len = xread(fd, &ch, 1); + ssize_t len = xread_cb(fd, &ch, 1, cb_data); if (len <= 0) return EOF; strbuf_addch(sb, ch);
@@ -765,6 +767,26 @@ int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term) return 0; } +int strbuf_getwholeline_fd_deadline(struct strbuf *sb, int fd, int term, + uint64_t deadline_ns) +{ + return strbuf_getwholeline_fd_with(sb, fd, term, xread_deadline_fn, + &deadline_ns); +} + +int strbuf_getwholeline_fd_timeout(struct strbuf *sb, int fd, int term, + int timeout_ms) +{ + return strbuf_getwholeline_fd_with(sb, fd, term, xread_timeout_fn, + &timeout_ms); +} + +/* Non-timeout version for compatibility. */ +int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term) +{ + return strbuf_getwholeline_fd_timeout(sb, fd, term, 0); +} + ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint) { int fd;
diff --git a/strbuf.h b/strbuf.h
index 06e284f9cca4..f896da1277a6 100644
--- a/strbuf.h
+++ b/strbuf.h@@ -535,6 +535,10 @@ int strbuf_appendwholeline(struct strbuf *sb, FILE *file, int term); * descriptor. */ int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term); +int strbuf_getwholeline_fd_timeout(struct strbuf *sb, int fd, int term, + int timeout_ms); +int strbuf_getwholeline_fd_deadline(struct strbuf *sb, int fd, int term, + uint64_t deadline_ns); /** * Set the buffer to the path of the current working directory.
diff --git a/wrapper.c b/wrapper.c
index 16f5a63fbb61..1f42845e031e 100644
--- a/wrapper.c
+++ b/wrapper.c@@ -9,6 +9,7 @@ #include "parse.h" #include "gettext.h" #include "strbuf.h" +#include "trace.h" #include "trace2.h" #ifdef HAVE_RTLGENRANDOM
@@ -220,28 +221,129 @@ static int handle_nonblock(int fd, short poll_events, int err) return 1; } -/* - * xread() is the same a read(), but it automatically restarts read() - * operations with a recoverable error (EAGAIN and EINTR). xread() +static int wait_for_fd(int fd, short poll_events, int timeout_ms) +{ + struct pollfd pfd; + + if (timeout_ms < 0) { + /* Negative timeout makes no sense. */ + errno = EINVAL; + return -1; + } + + pfd.fd = fd; + pfd.events = poll_events; + + while(1) { + int ret = poll(&pfd, 1, timeout_ms); + + if (ret <= 0) { + /* Retry if interrupted. */ + if (ret < 0 && errno == EINTR) + continue; + + /* Set errno if timeout happened. */ + if (ret == 0) + errno = ETIMEDOUT; + + return -1; + } + + /* Invalid FD passed. */ + if (pfd.revents & POLLNVAL) { + errno = EBADF; + return -1; + } + + /* Some error happened. */ + if (pfd.revents & POLLERR) { + errno = EIO; + return -1; + } + + /* HangUp => We are ready to consume output till EOF. */ + if (pfd.revents & (poll_events | POLLHUP)) + return 0; + } +} + +/** + * xread_timeout() is the same as read(), but it automatically restarts read() + * operations with a recoverable error (EAGAIN and EINTR). xread_timeout() * DOES NOT GUARANTEE that "len" bytes is read even if the data is available. + * + * Fails with ETIMEDOUT when no bytes become available within timeout_ms + * milliseconds. A zero timeout disables timeout handling, so reads can + * block until the file descriptor is readable. Negative timeouts are invalid. */ -ssize_t xread(int fd, void *buf, size_t len) +ssize_t xread_timeout(int fd, void *buf, size_t len, int timeout_ms) { ssize_t nr; + if (len > MAX_IO_SIZE) len = MAX_IO_SIZE; + while (1) { + if (timeout_ms && wait_for_fd(fd, POLLIN, timeout_ms)) + return -1; + nr = read(fd, buf, len); + if (nr < 0) { if (errno == EINTR) continue; - if (handle_nonblock(fd, POLLIN, errno)) - continue; + + if (timeout_ms) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + } else { + if (handle_nonblock(fd, POLLIN, errno)) + continue; + } } + return nr; } } +/* Non-timeout version for compatibility. */ +ssize_t xread(int fd, void *buf, size_t len) +{ + return xread_timeout(fd, buf, len, 0); +} + +static int remaining_timeout_ms(uint64_t deadline_ns) +{ + uint64_t now, remaining_ns; + + if (!deadline_ns) + return 0; + + now = getnanotime(); + if (now >= deadline_ns) { + errno = ETIMEDOUT; + return -1; + } + + remaining_ns = deadline_ns - now; + return (int)((remaining_ns + 999999ULL) / 1000000ULL); +} + +/* (deadline_ns = 0) disables the deadline and short-circuits to xread(). */ +ssize_t xread_deadline(int fd, void *buf, size_t len, uint64_t deadline_ns) +{ + int timeout_ms; + + if (deadline_ns == 0) + return xread(fd, buf, len); + + timeout_ms = remaining_timeout_ms(deadline_ns); + if (timeout_ms < 0) + return -1; + + return xread_timeout(fd, buf, len, timeout_ms); +} + /* * xwrite() is the same a write(), but it automatically restarts write() * operations with a recoverable error (EAGAIN and EINTR). xwrite() DOES NOT
@@ -283,13 +385,15 @@ ssize_t xpread(int fd, void *buf, size_t len, off_t offset) } } -ssize_t read_in_full(int fd, void *buf, size_t count) +static ssize_t read_in_full_with(int fd, void *buf, size_t count, + xread_cb_t xread_cb, + void *cb_data) { char *p = buf; ssize_t total = 0; while (count > 0) { - ssize_t loaded = xread(fd, p, count); + ssize_t loaded = xread_cb(fd, p, count, cb_data); if (loaded < 0) return -1; if (loaded == 0)
@@ -302,6 +406,25 @@ ssize_t read_in_full(int fd, void *buf, size_t count) return total; } +ssize_t read_in_full_deadline(int fd, void *buf, size_t count, + uint64_t deadline_ns) +{ + return read_in_full_with(fd, buf, count, xread_deadline_fn, + &deadline_ns); +} + +ssize_t read_in_full_timeout(int fd, void *buf, size_t count, int timeout_ms) +{ + return read_in_full_with(fd, buf, count, xread_timeout_fn, + &timeout_ms); +} + +/* Non-timeout version for compatibility. */ +ssize_t read_in_full(int fd, void *buf, size_t count) +{ + return read_in_full_timeout(fd, buf, count, 0); +} + ssize_t write_in_full(int fd, const void *buf, size_t count) { const char *p = buf;
diff --git a/wrapper.h b/wrapper.h
index 15ac3bab6e97..10d85c467b86 100644
--- a/wrapper.h
+++ b/wrapper.h@@ -15,6 +15,8 @@ const char *mmap_os_err(void); void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset); int xopen(const char *path, int flags, ...); ssize_t xread(int fd, void *buf, size_t len); +ssize_t xread_timeout(int fd, void *buf, size_t len, int timeout_ms); +ssize_t xread_deadline(int fd, void *buf, size_t len, uint64_t deadline_ns); ssize_t xwrite(int fd, const void *buf, size_t len); ssize_t xpread(int fd, void *buf, size_t len, off_t offset); int xdup(int fd);
@@ -44,9 +46,30 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode); int git_mkstemp_mode(char *pattern, int mode); ssize_t read_in_full(int fd, void *buf, size_t count); +ssize_t read_in_full_timeout(int fd, void *buf, size_t count, int timeout_ms); +ssize_t read_in_full_deadline(int fd, void *buf, size_t count, + uint64_t deadline_ns); ssize_t write_in_full(int fd, const void *buf, size_t count); ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset); +typedef ssize_t xread_cb_t(int fd, void *buf, size_t len, const void *cb_data); + +static inline ssize_t xread_timeout_fn(int fd, void *buf, size_t len, + const void *cb_data) +{ + const int *timeout_ms = cb_data; + + return xread_timeout(fd, buf, len, *timeout_ms); +} + +static inline ssize_t xread_deadline_fn(int fd, void *buf, size_t len, + const void *cb_data) +{ + const uint64_t *deadline_ns = cb_data; + + return xread_deadline(fd, buf, len, *deadline_ns); +} + static inline ssize_t write_str_in_full(int fd, const char *str) { return write_in_full(fd, str, strlen(str));
--
2.53.0