[PR] avfilter/dnn: require threads dependency and fix teardown abort (PR #23981)
Zhao Zhili via ffmpeg-devel <[email protected]> Sun, 02 Aug 2026 13:11:57 -0000
| Newsgroups | gmane.comp.video.ffmpeg.devel |
|---|---|
| Message-ID | <178567631982.59.17739261726892515163@29965ddac10e> |
PR #23981 opened by Zhao Zhili (quink) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23981 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23981.patch # Summary of changes DNN backends (TensorFlow, OpenVINO, libtorch, ONNX Runtime) all require multithreading, and the async inference path uses pthreads directly. Disabling threads while building DNN is not a real use case, so add `threads` to `dnn_deps`. `HAVE_PTHREAD_CANCEL` only guards `pthread_cancel` availability, not thread support; it was misused to guard `pthread_cond_*`, `pthread_create`, `pthread_join`, `pthread_t` and `pthread_attr_t`. With threads now required, remove the guards and the sync fallback they protected. This also fixes an ONNX backend teardown abort. <!-- If this PR requires new FATE test samples, attach them to the PR and list their target paths below (relative to the fate-suite root). Attached filenames must match the sample's filename: ```fate-samples # e.g. vorbis/new-sample.ogg ``` --> >From ba03c7362639a4297a411fd439efda0e33d46352 Mon Sep 17 00:00:00 2001 From: Zhao Zhili <[email protected]> Date: Sun, 2 Aug 2026 15:00:05 +0800 Subject: [PATCH 1/5] avfilter/dnn: replace DNNCond by AVCond The DNNCond / dnn_cond_* macros in safe_queue.c were guarded by HAVE_PTHREAD_CANCEL, but none of them call pthread_cancel(). The guard was semantically wrong. AVCond provides both a threads and a no-thread fallback path, so the conditional compilation is no longer needed here. --- libavfilter/dnn/safe_queue.c | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/libavfilter/dnn/safe_queue.c b/libavfilter/dnn/safe_queue.c index f7aa31d42e..1dedac8970 100644 --- a/libavfilter/dnn/safe_queue.c +++ b/libavfilter/dnn/safe_queue.c @@ -22,31 +22,12 @@ #include "queue.h" #include "safe_queue.h" #include "libavutil/mem.h" -#include "libavutil/avassert.h" #include "libavutil/thread.h" -#if HAVE_PTHREAD_CANCEL -#define DNNCond pthread_cond_t -#define dnn_cond_init pthread_cond_init -#define dnn_cond_destroy pthread_cond_destroy -#define dnn_cond_signal pthread_cond_signal -#define dnn_cond_wait pthread_cond_wait -#else -#define DNNCond char -static inline int dnn_cond_init(DNNCond *cond, const void *attr) { return 0; } -static inline int dnn_cond_destroy(DNNCond *cond) { return 0; } -static inline int dnn_cond_signal(DNNCond *cond) { return 0; } -static inline int dnn_cond_wait(DNNCond *cond, AVMutex *mutex) -{ - av_assert0(!"should not reach here"); - return 0; -} -#endif - struct SafeQueue { Queue *q; AVMutex mutex; - DNNCond cond; + AVCond cond; }; SafeQueue *ff_safe_queue_create(void) @@ -62,7 +43,7 @@ SafeQueue *ff_safe_queue_create(void) } ff_mutex_init(&sq->mutex, NULL); - dnn_cond_init(&sq->cond, NULL); + ff_cond_init(&sq->cond, NULL); return sq; } @@ -73,7 +54,7 @@ void ff_safe_queue_destroy(SafeQueue *sq) ff_queue_destroy(sq->q); ff_mutex_destroy(&sq->mutex); - dnn_cond_destroy(&sq->cond); + ff_cond_destroy(&sq->cond); av_freep(&sq); } @@ -89,7 +70,7 @@ void ff_safe_queue_wait_for_size(SafeQueue *sq, size_t min_size) ff_mutex_lock(&sq->mutex); while (ff_queue_size(sq->q) < min_size) - dnn_cond_wait(&sq->cond, &sq->mutex); + ff_cond_wait(&sq->cond, &sq->mutex); ff_mutex_unlock(&sq->mutex); } @@ -98,7 +79,7 @@ int ff_safe_queue_push_front(SafeQueue *sq, void *v) int ret; ff_mutex_lock(&sq->mutex); ret = ff_queue_push_front(sq->q, v); - dnn_cond_signal(&sq->cond); + ff_cond_signal(&sq->cond); ff_mutex_unlock(&sq->mutex); return ret; } @@ -108,7 +89,7 @@ int ff_safe_queue_push_back(SafeQueue *sq, void *v) int ret; ff_mutex_lock(&sq->mutex); ret = ff_queue_push_back(sq->q, v); - dnn_cond_signal(&sq->cond); + ff_cond_signal(&sq->cond); ff_mutex_unlock(&sq->mutex); return ret; } @@ -118,10 +99,10 @@ void *ff_safe_queue_pop_front(SafeQueue *sq) void *value; ff_mutex_lock(&sq->mutex); while (ff_queue_size(sq->q) == 0) { - dnn_cond_wait(&sq->cond, &sq->mutex); + ff_cond_wait(&sq->cond, &sq->mutex); } value = ff_queue_pop_front(sq->q); - dnn_cond_signal(&sq->cond); + ff_cond_signal(&sq->cond); ff_mutex_unlock(&sq->mutex); return value; } -- 2.52.0 >From 5591285d4894d3a56c7291959241efceb1a18c2d Mon Sep 17 00:00:00 2001 From: Zhao Zhili <[email protected]> Date: Sun, 2 Aug 2026 19:06:06 +0800 Subject: [PATCH 2/5] configure: make threads a hard dependency for dnn All DNN backends (TensorFlow, OpenVINO, libtorch, ONNX Runtime) require multithreading at runtime, and the DNN async inference path uses pthread APIs directly. Disabling thread support while building DNN is not a real use case. --- configure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure b/configure index 61a948d14d..b91d5b03f5 100755 --- a/configure +++ b/configure @@ -3054,7 +3054,7 @@ deflate_wrapper_deps="zlib" dirac_parse_select="golomb" dovi_rpudec_select="golomb" dovi_rpuenc_select="golomb" -dnn_deps="avformat swscale" +dnn_deps="avformat swscale threads" dnn_deps_any="libtensorflow libopenvino libtorch libonnxruntime" error_resilience_select="me_cmp" evcparse_select="golomb" -- 2.52.0 >From dc6d97aa865d30d603be1bf3b6c57d619d86df61 Mon Sep 17 00:00:00 2001 From: Zhao Zhili <[email protected]> Date: Sun, 2 Aug 2026 19:07:13 +0800 Subject: [PATCH 3/5] libavfilter/dnn: remove incorrect HAVE_PTHREAD_CANCEL guards HAVE_PTHREAD_CANCEL checks whether pthread_cancel() is available, not whether thread support exists at all. It was used in dnn to guard pthread API, none of which depend on pthread_cancel. Platforms can have a working pthread implementation without pthread_cancel (e.g., Android), so the guard could disable the async path on systems that actually support threads. Now that threads is a hard dependency of dnn, remove the guards and the sync fallback path they protected. --- libavfilter/dnn/dnn_backend_common.c | 10 ---------- libavfilter/dnn/dnn_backend_common.h | 6 ------ libavfilter/dnn/dnn_backend_tf.c | 7 ------- 3 files changed, 23 deletions(-) diff --git a/libavfilter/dnn/dnn_backend_common.c b/libavfilter/dnn/dnn_backend_common.c index 82627c7a96..4d90efecd4 100644 --- a/libavfilter/dnn/dnn_backend_common.c +++ b/libavfilter/dnn/dnn_backend_common.c @@ -89,13 +89,11 @@ int ff_dnn_async_module_cleanup(DNNAsyncExecModule *async_module) if (!async_module) { return AVERROR(EINVAL); } -#if HAVE_PTHREAD_CANCEL pthread_join(async_module->thread_id, &status); if (status == DNN_ASYNC_FAIL) { av_log(NULL, AV_LOG_ERROR, "Last Inference Failed.\n"); return DNN_GENERIC_ERROR; } -#endif async_module->start_inference = NULL; async_module->callback = NULL; async_module->args = NULL; @@ -120,7 +118,6 @@ int ff_dnn_start_inference_async(void *ctx, DNNAsyncExecModule *async_module) return AVERROR(EINVAL); } -#if HAVE_PTHREAD_CANCEL pthread_join(async_module->thread_id, &status); if (status == DNN_ASYNC_FAIL) { av_log(ctx, AV_LOG_ERROR, "Unable to start inference as previous inference failed.\n"); @@ -131,13 +128,6 @@ int ff_dnn_start_inference_async(void *ctx, DNNAsyncExecModule *async_module) av_log(ctx, AV_LOG_ERROR, "Unable to start async inference.\n"); return ret; } -#else - ret = async_module->start_inference(async_module->args); - if (ret != 0) { - return ret; - } - async_module->callback(async_module->args); -#endif return 0; } diff --git a/libavfilter/dnn/dnn_backend_common.h b/libavfilter/dnn/dnn_backend_common.h index f0b8b47397..acb4256749 100644 --- a/libavfilter/dnn/dnn_backend_common.h +++ b/libavfilter/dnn/dnn_backend_common.h @@ -82,10 +82,7 @@ typedef struct DNNAsyncExecModule { * i.e. Request item for the backend. */ void *args; -#if HAVE_PTHREAD_CANCEL pthread_t thread_id; - pthread_attr_t thread_attr; -#endif } DNNAsyncExecModule; int ff_check_exec_params(void *ctx, DNNBackendType backend, DNNFunctionType func_type, DNNExecBaseParams *exec_params); @@ -129,9 +126,6 @@ void ff_dnn_wait_requests(SafeQueue *request_queue, int nireq); * after the inference completes. Completion callback and inference * function must be set before calling this function. * - * If POSIX threads aren't supported, the execution rolls back - * to synchronous mode, calling completion callback after inference. - * * @param ctx pointer to the backend context * @param async_module pointer to DNNAsyncExecModule module * diff --git a/libavfilter/dnn/dnn_backend_tf.c b/libavfilter/dnn/dnn_backend_tf.c index f8a4d9bc62..6c72783f77 100644 --- a/libavfilter/dnn/dnn_backend_tf.c +++ b/libavfilter/dnn/dnn_backend_tf.c @@ -541,13 +541,6 @@ static DNNModel *dnn_load_model_tf(DnnContext *ctx, DNNFunctionType func_type, A ctx->nireq = av_cpu_count() / 2 + 1; } -#if !HAVE_PTHREAD_CANCEL - if (ctx->async) { - ctx->async = 0; - av_log(filter_ctx, AV_LOG_WARNING, "pthread is not supported, roll back to sync.\n"); - } -#endif - tf_model->request_queue = ff_safe_queue_create(); if (!tf_model->request_queue) { goto err; -- 2.52.0 >From a1ad6b545a702c08bb52ab4b53f69c5d5989d44f Mon Sep 17 00:00:00 2001 From: Zhao Zhili <[email protected]> Date: Sun, 2 Aug 2026 19:07:49 +0800 Subject: [PATCH 4/5] libavfilter/dnn: fix ONNX backend teardown abort ONNX is synchronous and never starts the async thread, so exec_module.thread_id stays zeroed from av_mallocz. Joining it via ff_dnn_async_module_cleanup returns error, which the strict pthread wrappers in libavutil/thread.h turn into abort(). --- libavfilter/dnn/dnn_backend_onnx.c | 1 - 1 file changed, 1 deletion(-) diff --git a/libavfilter/dnn/dnn_backend_onnx.c b/libavfilter/dnn/dnn_backend_onnx.c index 6c75d6eb24..9429b774bb 100644 --- a/libavfilter/dnn/dnn_backend_onnx.c +++ b/libavfilter/dnn/dnn_backend_onnx.c @@ -140,7 +140,6 @@ static inline void destroy_request_item(ONNXRequestItem **arg) onnx_free_request(item->infer_request); av_freep(&item->infer_request); av_freep(&item->lltask); - ff_dnn_async_module_cleanup(&item->exec_module); av_freep(arg); } -- 2.52.0 >From 4ef83b9d6557865372c295017c9c9b7e00c0789c Mon Sep 17 00:00:00 2001 From: Zhao Zhili <[email protected]> Date: Sun, 2 Aug 2026 20:27:27 +0800 Subject: [PATCH 5/5] libavfilter/dnn: guard pthread_join on never-started async threads ff_dnn_async_module_cleanup and ff_dnn_start_inference_async unconditionally pthread_join() thread_id, which is zeroed by av_mallocz until pthread_create runs. The first call to either helper joins a never-created handle, which strict_pthread_join turns into abort(). --- libavfilter/dnn/dnn_backend_common.c | 24 ++++++++++++++++-------- libavfilter/dnn/dnn_backend_common.h | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/libavfilter/dnn/dnn_backend_common.c b/libavfilter/dnn/dnn_backend_common.c index 4d90efecd4..6c52d6e86b 100644 --- a/libavfilter/dnn/dnn_backend_common.c +++ b/libavfilter/dnn/dnn_backend_common.c @@ -89,10 +89,13 @@ int ff_dnn_async_module_cleanup(DNNAsyncExecModule *async_module) if (!async_module) { return AVERROR(EINVAL); } - pthread_join(async_module->thread_id, &status); - if (status == DNN_ASYNC_FAIL) { - av_log(NULL, AV_LOG_ERROR, "Last Inference Failed.\n"); - return DNN_GENERIC_ERROR; + if (async_module->thread_started) { + pthread_join(async_module->thread_id, &status); + async_module->thread_started = 0; + if (status == DNN_ASYNC_FAIL) { + av_log(NULL, AV_LOG_ERROR, "Last Inference Failed.\n"); + return DNN_GENERIC_ERROR; + } } async_module->start_inference = NULL; async_module->callback = NULL; @@ -118,16 +121,21 @@ int ff_dnn_start_inference_async(void *ctx, DNNAsyncExecModule *async_module) return AVERROR(EINVAL); } - pthread_join(async_module->thread_id, &status); - if (status == DNN_ASYNC_FAIL) { - av_log(ctx, AV_LOG_ERROR, "Unable to start inference as previous inference failed.\n"); - return DNN_GENERIC_ERROR; + if (async_module->thread_started) { + pthread_join(async_module->thread_id, &status); + async_module->thread_started = 0; + if (status == DNN_ASYNC_FAIL) { + av_log(ctx, AV_LOG_ERROR, "Unable to start inference as previous inference failed.\n"); + return DNN_GENERIC_ERROR; + } } ret = pthread_create(&async_module->thread_id, NULL, async_thread_routine, async_module); if (ret != 0) { av_log(ctx, AV_LOG_ERROR, "Unable to start async inference.\n"); return ret; } + async_module->thread_started = 1; + return 0; } diff --git a/libavfilter/dnn/dnn_backend_common.h b/libavfilter/dnn/dnn_backend_common.h index acb4256749..9cef469f6d 100644 --- a/libavfilter/dnn/dnn_backend_common.h +++ b/libavfilter/dnn/dnn_backend_common.h @@ -83,6 +83,7 @@ typedef struct DNNAsyncExecModule { */ void *args; pthread_t thread_id; + int thread_started; } DNNAsyncExecModule; int ff_check_exec_params(void *ctx, DNNBackendType backend, DNNFunctionType func_type, DNNExecBaseParams *exec_params); -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]