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]
