Commit 2f74158ddf for ffmpeg
commit 2f74158ddf8c6fc33df94f867cf5b58d72c0e62f
Author: Zhao Zhili <quinkblack@foxmail.com>
Date: Sat Oct 10 00:47:15 2026 +0800
avformat/network: add a wakeup channel for blocking waits
ff_network_wait_fd() can only return after its poll() slice, so a
thread blocked in it notices a request from another thread up to
POLLING_TIME late. Add a one-shot channel that a second thread can
signal to end the wait at once, together with a wait function that
polls it next to the socket.
The channel is an eventfd where available and a pipe otherwise. On
Windows neither can be waited on together with sockets, so it is a
loopback datagram socket connected to itself.
Signed-off-by: Zhao Zhili <zhilizhao@tencent.com>
diff --git a/configure b/configure
index 362e7b12f7..3bb4d9ef80 100755
--- a/configure
+++ b/configure
@@ -2492,6 +2492,7 @@ SYSTEM_FUNCS="
closesocket
CommandLineToArgvW
elf_aux_info
+ eventfd
fcntl
getaddrinfo
getauxval
@@ -6893,6 +6894,7 @@ check_func mmap
check_func mprotect
# Solaris has nanosleep in -lrt, OpenSolaris no longer needs that
check_func_headers time.h nanosleep || check_lib nanosleep time.h nanosleep -lrt
+check_func_headers sys/eventfd.h eventfd
check_func_headers sys/prctl.h prctl
check_func sched_getaffinity
check_func setrlimit
diff --git a/libavformat/network.c b/libavformat/network.c
index 2b4c78cfb1..4d2a2a9654 100644
--- a/libavformat/network.c
+++ b/libavformat/network.c
@@ -29,6 +29,9 @@
#endif
#include <fcntl.h>
+#if HAVE_EVENTFD
+#include <sys/eventfd.h>
+#endif
#include "network.h"
#include "tls.h"
#include "url.h"
@@ -100,6 +103,121 @@ int ff_network_wait_fd_timeout(int fd, int write, int64_t timeout, AVIOInterrupt
}
}
+int ff_network_wakeup_open(FFNetWakeup *w)
+{
+#if HAVE_WINSOCK2_H
+ /* select() and WSAPoll() only accept sockets, so use a datagram socket
+ * connected to itself: sending a datagram to it makes it readable.
+ */
+ struct sockaddr_in addr = { .sin_family = AF_INET };
+ socklen_t len = sizeof(addr);
+ int ret, fd = ff_socket(AF_INET, SOCK_DGRAM, 0, NULL);
+
+ if (fd < 0)
+ return ff_neterrno();
+
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0 ||
+ getsockname(fd, (struct sockaddr *)&addr, &len) < 0 ||
+ connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ ret = ff_neterrno();
+ closesocket(fd);
+ return ret;
+ }
+
+ w->rfd = w->wfd = fd;
+#elif HAVE_EVENTFD
+ int fd = eventfd(0, EFD_CLOEXEC);
+
+ if (fd < 0)
+ return AVERROR(errno);
+
+ w->rfd = w->wfd = fd;
+#else
+ int fds[2];
+
+ if (pipe(fds) < 0)
+ return AVERROR(errno);
+
+#if HAVE_FCNTL
+ for (int i = 0; i < 2; i++) {
+ if (fcntl(fds[i], F_SETFD, FD_CLOEXEC) < 0) {
+ int ret = AVERROR(errno);
+ closesocket(fds[0]);
+ closesocket(fds[1]);
+ return ret;
+ }
+ }
+#endif
+
+ w->rfd = fds[0];
+ w->wfd = fds[1];
+#endif
+
+ atomic_init(&w->signaled, 0);
+
+ return 0;
+}
+
+int ff_network_wakeup_signal(FFNetWakeup *w)
+{
+ /* An eventfd only accepts 8-byte writes; a pipe or a socket takes any
+ * payload, so the same value serves every channel type.
+ */
+ const uint64_t one = 1;
+ int ret;
+
+ /* wfd is blocking; writing at most once keeps the write from blocking.
+ * The flag publishes no other data, so relaxed ordering is enough.
+ */
+ if (atomic_exchange_explicit(&w->signaled, 1, memory_order_relaxed))
+ return 0;
+
+#if HAVE_WINSOCK2_H
+ ret = send(w->wfd, (const char *)&one, sizeof(one), 0);
+#else
+ ret = write(w->wfd, &one, sizeof(one));
+#endif
+
+ return ret < 0 ? ff_neterrno() : 0;
+}
+
+void ff_network_wakeup_close(FFNetWakeup *w)
+{
+ if (w->wfd != w->rfd)
+ closesocket(w->wfd);
+ closesocket(w->rfd);
+
+ w->rfd = w->wfd = -1;
+}
+
+int ff_network_wait_fd_wakeup(int fd, int write, int timeout_ms,
+ const FFNetWakeup *w)
+{
+ int ev = write ? POLLOUT : POLLIN;
+ struct pollfd p[2] = {
+ { .fd = w->rfd, .events = POLLIN },
+ { .fd = fd, .events = ev },
+ };
+ int ret;
+
+ /* Return without polling once signaled. This also ends the wait when
+ * the write in ff_network_wakeup_signal() failed and rfd never became
+ * readable.
+ */
+ if (atomic_load_explicit(&w->signaled, memory_order_relaxed))
+ return AVERROR_EXIT;
+
+ ret = poll(p, 2, timeout_ms);
+ if (ret < 0)
+ return ff_neterrno();
+
+ if (p[0].revents & POLLIN)
+ return AVERROR_EXIT;
+
+ return p[1].revents & (ev | POLLERR | POLLHUP) ? 0 : AVERROR(EAGAIN);
+}
+
int ff_network_sleep_interruptible(int64_t timeout, AVIOInterruptCB *int_cb)
{
int64_t wait_start = av_gettime_relative();
diff --git a/libavformat/network.h b/libavformat/network.h
index 7c72c902aa..f48b4d7b2a 100644
--- a/libavformat/network.h
+++ b/libavformat/network.h
@@ -22,6 +22,7 @@
#define AVFORMAT_NETWORK_H
#include <errno.h>
+#include <stdatomic.h>
#include <stdint.h>
#include "config.h"
@@ -107,6 +108,68 @@ int ff_network_wait_fd_timeout(int fd, int write, int64_t timeout, AVIOInterrupt
*/
int ff_network_sleep_interruptible(int64_t timeout, AVIOInterruptCB *int_cb);
+/**
+ * A channel for waking a thread that is blocked in
+ * ff_network_wait_fd_wakeup(), from another thread.
+ *
+ * Usage: the waiting thread calls ff_network_wait_fd_wakeup(); another
+ * thread calls ff_network_wakeup_signal() to end that wait; once the
+ * waiting thread has been joined, ff_network_wakeup_close() is called.
+ *
+ * rfd is pollable together with sockets on every platform. It is an
+ * eventfd, the read end of a pipe, or a loopback datagram socket on
+ * Windows, where only sockets can be waited on together with sockets.
+ * wfd is the end ff_network_wakeup_signal() writes to; it equals rfd
+ * unless a pipe is in use. On systems other than Windows both fds are
+ * close-on-exec.
+ */
+typedef struct FFNetWakeup {
+ int rfd;
+ int wfd;
+ atomic_int signaled;
+} FFNetWakeup;
+
+/**
+ * Create a wakeup channel. On Windows ff_network_init() must have been
+ * called first.
+ *
+ * @return 0 on success, a negative AVERROR code on failure
+ */
+int ff_network_wakeup_open(FFNetWakeup *w);
+
+/**
+ * Wake the thread blocked in ff_network_wait_fd_wakeup() on this channel,
+ * or the next one that waits on it: the signal is not consumed, so a
+ * signal sent before the wait starts is not lost. Only the first call has
+ * an effect; the function may be called any number of times, from any
+ * thread.
+ *
+ * @return 0 on success, a negative AVERROR code if the first call failed
+ * to write to the channel
+ */
+int ff_network_wakeup_signal(FFNetWakeup *w);
+
+/**
+ * Close the channel. Call it after the waiting thread has been joined and
+ * no thread can signal any more.
+ */
+void ff_network_wakeup_close(FFNetWakeup *w);
+
+/**
+ * Like ff_network_wait_fd(), but also returns as soon as the wakeup
+ * channel is signaled, and with a caller chosen timeout.
+ *
+ * @param fd Socket descriptor
+ * @param write Set 1 to wait for the socket to be writable, 0 for readable
+ * @param timeout_ms Timeout in milliseconds, or -1 to wait indefinitely
+ * @param w Wakeup channel to watch, must have been opened
+ * @return 0 if fd is ready, AVERROR(EAGAIN) if the timeout expired,
+ * AVERROR_EXIT if w was signaled (also when fd is ready),
+ * or a negative error code
+ */
+int ff_network_wait_fd_wakeup(int fd, int write, int timeout_ms,
+ const FFNetWakeup *w);
+
#if !HAVE_STRUCT_SOCKADDR_STORAGE
struct sockaddr_storage {
#if HAVE_STRUCT_SOCKADDR_SA_LEN