PR #25546 opened by Jake (jakefineman)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25546
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25546.patch

The TensorFlow backend leaks one LastLevelTaskItem per inference.

fill_model_input_tf() stores the popped lltask in request->lltask, but nothing 
frees it after the inference. The next fill_model_input_tf() on the same 
request overwrites the pointer, so only the last lltask each request held is 
freed, by destroy_request_item() at teardown. The leak is (inferences + 1 
output-size probe) minus the number of requests used, so it grows by one per 
frame, with async=0 and async=1, for every filter on the TF backend.

The patch frees the lltask in infer_completion_callback() before the request 
goes back on request_queue, as the OpenVINO backend does in its completion 
callback. It also frees it on the failure paths of execute_model_tf() and 
dnn_flush_tf(). No inference thread is running on any of these paths, and 
av_freep() clears the pointer, so the free in destroy_request_item() stays safe.

**Testing**

macOS arm64, libtensorflow C API, a [1,-1,-1,1] Identity model. `leaks 
--atExit`, number of leaked 16-byte lltask blocks at 10 / 50 / 100 frames:

| config | master | this PR |
|---|---|---|
| sr, default (async=1) | 1 / 41 / 91 | 0 / 0 / 0 |
| sr, async=0 | 1 / 41 / 91 | 0 / 0 / 0 |
| sr, nireq=1 | 11 / 51 / 101 | 0 / 0 / 0 |
| sr, nireq=4 | 8 / 48 / 98 | 0 / 0 / 0 |
| dnn_processing, async=1 | 3 / 44 / 94 | 0 / 0 / 0 |
| dnn_processing, async=0 | 1 / 41 / 91 | 0 / 0 / 0 |

With frame pointers enabled, every leaked block was allocated by 
extract_lltask_from_task(), from dnn_execute_model_tf() or, once, from 
get_output_tf(). The sr runs on master also show the separate vf_sr frame leak 
fixed by #25543. With both patches applied, sr reports no leaks at all.

framemd5 over 30 frames is identical to master for sr and dnn_processing, with 
gray8 and yuv420p, async=0 and async=1.

Not tested: the two failure-path frees were not triggered at runtime. There is 
no TF FATE coverage.


>From e7ffbc04f42688e568dbb03eb044f5c525a40acf Mon Sep 17 00:00:00 2001
From: Jake Fineman <[email protected]>
Date: Tue, 27 Oct 2026 17:48:23 -0400
Subject: [PATCH] avfilter/dnn_backend_tf: free the last level task after
 inference

fill_model_input_tf() pops a LastLevelTaskItem from lltask_queue and
stores it in request->lltask, but nothing frees it once the inference
has completed. The next fill_model_input_tf() on the same request just
overwrites the pointer, so only the last lltask of each request is
released, by destroy_request_item() when the model is freed. Every
inference therefore leaks one lltask, apart from the last one of each
of the nireq requests, with both async=0 and async=1.

Free it in infer_completion_callback() before the request is pushed
back to request_queue, as the OpenVINO backend does in its completion
callback. Also free it on the failure paths of execute_model_tf() and
dnn_flush_tf(), where the popped lltask is dropped in the same way. No
inference thread is running on any of these paths. av_freep() resets
the pointer, so the av_freep() in destroy_request_item() is still safe.

Signed-off-by: Jake Fineman <[email protected]>
---
 libavfilter/dnn/dnn_backend_tf.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libavfilter/dnn/dnn_backend_tf.c b/libavfilter/dnn/dnn_backend_tf.c
index 6882e0bb311..f722e8bf8df 100644
--- a/libavfilter/dnn/dnn_backend_tf.c
+++ b/libavfilter/dnn/dnn_backend_tf.c
@@ -748,6 +748,7 @@ static void infer_completion_callback(void *args) {
 err:
     tf_free_request(infer_request);
     av_freep(&outputs);
+    av_freep(&request->lltask);
 
     if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) {
         destroy_request_item(&request);
@@ -794,6 +795,7 @@ static int execute_model_tf(TFRequestItem *request, Queue 
*lltask_queue)
     }
 err:
     tf_free_request(request->infer_request);
+    av_freep(&request->lltask);
     if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) {
         destroy_request_item(&request);
     }
@@ -874,6 +876,7 @@ static int dnn_flush_tf(const DNNModel *model)
     ret = fill_model_input_tf(tf_model, request);
     if (ret != 0) {
         av_log(ctx, AV_LOG_ERROR, "Failed to fill model input.\n");
+        av_freep(&request->lltask);
         if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) {
             destroy_request_item(&request);
         }
-- 
2.54.0

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

Reply via email to