PR #24936 opened by Zhao Zhili (quink)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24936
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24936.patch

# Summary of changes

Remove dependency on pthread_cancel().

Fix issue mentioned in https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24886


>From 4ae2f68d249b860c6da022ac5ee68e21a060c9b9 Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Thu, 8 Oct 2026 14:37:18 +0800
Subject: [PATCH 1/3] avformat/sapdec: disable the udp circular buffer

sap_fetch_packet() polls the raw socket fd to find pending
announcements and reads them itself to detect stream deletion. With
the circular buffer enabled, the rx thread competes for the same
datagrams and usually reads them first, so poll() seldom reports
POLLIN and the check becomes unreliable.

Pass fifo_size=0 as rtpproto.c does.

Signed-off-by: Zhao Zhili <[email protected]>
---
 libavformat/sapdec.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavformat/sapdec.c b/libavformat/sapdec.c
index 715cd0656a..1be8d3234c 100644
--- a/libavformat/sapdec.c
+++ b/libavformat/sapdec.c
@@ -83,8 +83,8 @@ static int sap_read_header(AVFormatContext *s)
         av_strlcpy(host, "224.2.127.254", sizeof(host));
     }
 
-    ff_url_join(url, sizeof(url), "udp", NULL, host, port, "?localport=%d",
-                port);
+    ff_url_join(url, sizeof(url), "udp", NULL, host, port,
+                "?localport=%d&fifo_size=0", port);
     ret = ffurl_open_whitelist(&sap->ann_fd, url, AVIO_FLAG_READ,
                                &s->interrupt_callback, NULL,
                                s->protocol_whitelist, s->protocol_blacklist, 
NULL);
-- 
2.52.0


>From 2a2d13b3ca7f08464c688feff68c0dfe0369aa2c Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Thu, 8 Oct 2026 01:16:22 +0800
Subject: [PATCH 2/3] avformat/udp: stop the rx thread without pthread_cancel()

The rx thread blocks in recvfrom() and udp_close() cancelled it. That
only works if recvfrom() is a cancellation point and if a cancel
request made while cancellation is disabled is still acted on later.
Darwin loses such a request, so closing an input that is receiving
data hangs in pthread_join(). On Win32 recvfrom() is not a
cancellation point at all, which is why udp_close() needed a separate
shutdown() and CancelIoEx() path.

Wait with ff_network_wait_fd() instead, as other protocols do. Closing
an input that receives no data can take up to POLLING_TIME.

Signed-off-by: Zhao Zhili <[email protected]>
---
 libavformat/udp.c | 41 +++++++++++++----------------------------
 1 file changed, 13 insertions(+), 28 deletions(-)

diff --git a/libavformat/udp.c b/libavformat/udp.c
index 0874d18397..4516dab494 100644
--- a/libavformat/udp.c
+++ b/libavformat/udp.c
@@ -530,32 +530,30 @@ static void *circular_buffer_task_rx( void *_URLContext)
 {
     URLContext *h = _URLContext;
     UDPContext *s = h->priv_data;
-    int old_cancelstate;
 
     ff_thread_setname("udp-rx");
 
-    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate);
     pthread_mutex_lock(&s->mutex);
-    if (ff_socket_nonblock(s->udp_fd, 0) < 0) {
-        av_log(h, AV_LOG_ERROR, "Failed to set blocking mode");
-        s->circular_buffer_error = AVERROR(EIO);
-        goto end;
-    }
     while(1) {
         UDPQueuedPacketHeader pkt_header;
+        int err = 0;
+
+        if (s->close_req)
+            goto end;
+
         pkt_header.addr_len = sizeof(pkt_header.addr);
 
         pthread_mutex_unlock(&s->mutex);
-        /* Blocking operations are always cancellation points;
-           see "General Information" / "Thread Cancellation Overview"
-           in Single Unix. */
-        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancelstate);
         pkt_header.pkt_size = recvfrom(s->udp_fd, s->tmp + sizeof(pkt_header), 
sizeof(s->tmp) - sizeof(pkt_header), 0, (struct sockaddr *)&pkt_header.addr, 
&pkt_header.addr_len);
-        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate);
+        if (pkt_header.pkt_size < 0) {
+            err = ff_neterrno();
+            if (err == AVERROR(EAGAIN))
+                err = ff_network_wait_fd(s->udp_fd, 0);
+        }
         pthread_mutex_lock(&s->mutex);
         if (pkt_header.pkt_size < 0) {
-            if (ff_neterrno() != AVERROR(EAGAIN) && ff_neterrno() != 
AVERROR(EINTR)) {
-                s->circular_buffer_error = ff_neterrno();
+            if (err < 0 && err != AVERROR(EAGAIN) && err != AVERROR(EINTR)) {
+                s->circular_buffer_error = err;
                 goto end;
             }
             continue;
@@ -1096,8 +1094,7 @@ static int udp_close(URLContext *h)
     UDPContext *s = h->priv_data;
 
 #if HAVE_PTHREAD_CANCEL
-    // Request close once writing is finished
-    if (s->thread_started && !(h->flags & AVIO_FLAG_READ)) {
+    if (s->thread_started) {
         pthread_mutex_lock(&s->mutex);
         s->close_req = 1;
         pthread_cond_signal(&s->cond);
@@ -1111,18 +1108,6 @@ static int udp_close(URLContext *h)
 #if HAVE_PTHREAD_CANCEL
     if (s->thread_started) {
         int ret;
-        // Cancel only read, as write has been signaled as success to the user
-        if (h->flags & AVIO_FLAG_READ) {
-#ifdef _WIN32
-            /* recvfrom() is not a cancellation point for win32, so we shutdown
-             * the socket and abort pending IO, subsequent recvfrom() calls
-             * will fail with WSAESHUTDOWN causing the thread to exit. */
-            shutdown(s->udp_fd, SD_RECEIVE);
-            CancelIoEx((HANDLE)(SOCKET)s->udp_fd, NULL);
-#else
-            pthread_cancel(s->circular_buffer_thread);
-#endif
-        }
         ret = pthread_join(s->circular_buffer_thread, NULL);
         if (ret != 0)
             av_log(h, AV_LOG_ERROR, "pthread_join(): %s\n", strerror(ret));
-- 
2.52.0


>From 913d78bbdb15c88deba48b235b9bc7738ccbc513 Mon Sep 17 00:00:00 2001
From: Zhao Zhili <[email protected]>
Date: Thu, 8 Oct 2026 01:19:10 +0800
Subject: [PATCH 3/3] avformat/udp: use the circular buffer whenever threads
 are available

The rx and tx threads no longer use pthread_cancel(), so gate the
circular buffer on HAVE_THREADS instead of HAVE_PTHREAD_CANCEL. This
also drops the w32threads override.

On targets that have threads but no pthread_cancel(), such as Android,
udp inputs now start a receive thread and allocate the default 7*4096
by 188 byte fifo, as on other platforms. Set fifo_size=0 to get the
previous behavior.

Signed-off-by: Zhao Zhili <[email protected]>
---
 libavformat/udp.c | 29 ++++++++++++-----------------
 1 file changed, 12 insertions(+), 17 deletions(-)

diff --git a/libavformat/udp.c b/libavformat/udp.c
index 4516dab494..74c9896e29 100644
--- a/libavformat/udp.c
+++ b/libavformat/udp.c
@@ -60,12 +60,7 @@
 #define IPPROTO_UDPLITE                                  136
 #endif
 
-#if HAVE_W32THREADS
-#undef HAVE_PTHREAD_CANCEL
-#define HAVE_PTHREAD_CANCEL 1
-#endif
-
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
 #include "libavutil/thread.h"
 #endif
 
@@ -109,7 +104,7 @@ typedef struct UDPContext {
     int64_t bitrate; /* number of bits to send per second */
     int64_t burst_bits;
     int close_req;
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
     pthread_t circular_buffer_thread;
     pthread_mutex_t mutex;
     pthread_cond_t cond;
@@ -146,7 +141,7 @@ static const AVOption options[] = {
     { "ttl",            "Time to live (multicast only)",                   
OFFSET(ttl),            AV_OPT_TYPE_INT,    { .i64 = 16 },     0, 255,     E },
     { "dscp",           "DSCP class for outgoing packets",                 
OFFSET(dscp),           AV_OPT_TYPE_INT,    { .i64 = -1 },    -1, 63,      E },
     { "connect",        "set if connect() should be called on socket",     
OFFSET(is_connected),   AV_OPT_TYPE_BOOL,   { .i64 =  0 },     0, 1,       
.flags = D|E },
-    { "fifo_size",      "set the UDP circular buffer size (in 188-byte 
packets)", OFFSET(circular_buffer_size), AV_OPT_TYPE_INT, {.i64 = 
HAVE_PTHREAD_CANCEL ? 7*4096 : 0}, 0, INT_MAX, D },
+    { "fifo_size",      "set the UDP circular buffer size (in 188-byte 
packets)", OFFSET(circular_buffer_size), AV_OPT_TYPE_INT, {.i64 = HAVE_THREADS 
? 7*4096 : 0}, 0, INT_MAX, D },
     { "overrun_nonfatal", "survive in case of UDP receiving circular buffer 
overrun", OFFSET(overrun_nonfatal), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1,    D },
     { "timeout",        "set raise error timeout, in microseconds (only in 
read mode)",OFFSET(timeout),         AV_OPT_TYPE_INT,  {.i64 = 0}, 0, INT_MAX, 
D },
     { "sources",        "Source list",                                     
OFFSET(sources),        AV_OPT_TYPE_STRING, { .str = NULL },               
.flags = D|E },
@@ -525,7 +520,7 @@ static int udp_get_file_handle(URLContext *h)
     return s->udp_fd;
 }
 
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
 static void *circular_buffer_task_rx( void *_URLContext)
 {
     URLContext *h = _URLContext;
@@ -712,14 +707,14 @@ static int udp_open(URLContext *h, const char *uri, int 
flags)
         if (ret < 0)
             goto fail;
     }
-    if (!HAVE_PTHREAD_CANCEL) {
+    if (!HAVE_THREADS) {
         int64_t      optvals[] = {s->overrun_nonfatal, s->bitrate, 
s->circular_buffer_size};
         const char* optnames[] = {  "overrun_nonfatal",  "bitrate",  
"fifo_size"};
         for (unsigned i = 0; i < FF_ARRAY_ELEMS(optvals); i++) {
             if (optvals[i])
                 av_log(h, AV_LOG_WARNING,
                        "'%s' option was set but it is not supported "
-                       "on this build (pthread support is required)\n", 
optnames[i]);
+                       "on this build (threading support is required)\n", 
optnames[i]);
         }
     }
     if (s->sources) {
@@ -901,7 +896,7 @@ static int udp_open(URLContext *h, const char *uri, int 
flags)
 
     s->udp_fd = udp_fd;
 
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
     /*
       Create thread in case of:
       1. Input and circular_buffer_size is set
@@ -947,7 +942,7 @@ static int udp_open(URLContext *h, const char *uri, int 
flags)
 #endif
 
     return 0;
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
  thread_fail:
     pthread_cond_destroy(&s->cond);
  cond_fail:
@@ -976,7 +971,7 @@ static int udp_read(URLContext *h, uint8_t *buf, int size)
 {
     UDPContext *s = h->priv_data;
     int ret;
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
     int avail, nonblock = h->flags & AVIO_FLAG_NONBLOCK;
 
     if (s->rx_fifo) {
@@ -1044,7 +1039,7 @@ static int udp_write(URLContext *h, const uint8_t *buf, 
int size)
     UDPContext *s = h->priv_data;
     int ret;
 
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
     if (s->tx_fifo) {
         uint8_t tmp[4];
 
@@ -1093,7 +1088,7 @@ static int udp_close(URLContext *h)
 {
     UDPContext *s = h->priv_data;
 
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
     if (s->thread_started) {
         pthread_mutex_lock(&s->mutex);
         s->close_req = 1;
@@ -1105,7 +1100,7 @@ static int udp_close(URLContext *h)
     if (s->is_multicast && (h->flags & AVIO_FLAG_READ))
         udp_leave_multicast_group(s->udp_fd, (struct sockaddr *)&s->dest_addr,
                                   (struct sockaddr *)&s->local_addr_storage, 
h);
-#if HAVE_PTHREAD_CANCEL
+#if HAVE_THREADS
     if (s->thread_started) {
         int ret;
         ret = pthread_join(s->circular_buffer_thread, NULL);
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to