Repository navigation
fix(core): vmaf_read_pictures owns its pictures on every return (ADR-1431) - #1752
Merged
Merged
Conversation
lusoris
force-pushed
the
fix/read-pictures-consume-on-error
branch
from
October 1, 2026 19:46
021de94 to
fb95999
Compare
2 of 7 tasks
…, zed, model, v1 AGENTS.md files (#1763) * docs(agents): caveman register for gen/go/AGENTS.md * docs(agents): caveman register for docs/research/AGENTS.md * docs(agents): caveman register for internal/app/scoringservice/AGENTS.md * docs(agents): caveman register for .zed/AGENTS.md * docs(agents): caveman register for pkg/model/AGENTS.md * docs(agents): caveman register for api/vmafx/v1/AGENTS.md * docs(changelog): record caveman register for 6 subtree AGENTS.md files * docs: regenerate the indexes and the citation map after rebasing
…1431) (#1752) * fix(core): vmaf_read_pictures owns its pictures on every return (ADR-1431) A call that failed before it reached an extractor (a non-increasing index, pictures that disagree with the stream, the picture pool, the CUDA ring buffer, the CUDA translation) returned without releasing the pair it was given. Pool pictures never came back, and vmaf_close() waited for them forever: with the device's memory taken by another process, vmaf --backend cuda printed "problem reading pictures" and hung at 0 % CPU holding the caller's device lock (Netflix/vmaf#1420 on the fork; the allocator itself returns the error). The call now releases both pictures on every return, check_ring_buffer() reports -ENOMEM instead of -EINVAL, and the CLI exits with the error at once. test_read_pictures_failure_ownership (CPU) and test_cuda_oom_pictures_released fail without the fix; docs/api and libvmaf.h state the rule. * docs: regenerate the indexes and the citation map after rebasing
lusoris
force-pushed
the
fix/read-pictures-consume-on-error
branch
from
October 1, 2026 20:41
fb95999 to
b57f272
Compare
This was referenced Oct 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
With the device's memory taken by another process,
vmaf --backend cudaprintedproblem reading picturesand then never exited: it sat at 0 % CPU invmaf_close(), holding whatever lock the caller held, until it was killed. A failedvmaf_read_pictures()kept the pair of pictures it was given; the pair came from the CLI's picture pool and never went back. The call now owns both pictures on every return, and the CLI exits with the error at once.Closes
T-READ-PICTURES-FAILURE-LEAKS-POOL-PICTURES-2026-10-01(found here, ADR-1431). I found it checking Netflix/vmaf#1420, which reports thatvmaf_cuda_buffer_alloc()asserts on an out-of-memory; the fork does not assert there (CHECK_CUDAreturns the error,test_cuda_buffer_alloc_oompins-ENOMEM). RC3, reliability (ADR-1421).Cause
gdb on the hung CLI: the main thread waits in
pthread_cond_waitinvmaf_close()->vmaf_commit_remaining_owners()->vmaf_picture_pool_close(), which returns only when every pool picture is back.vmaf_read_pictures()returned without releasing its pictures when its validation step failed (a non-increasing index, pictures whose shape disagrees with the stream,check_picture_pool,check_ring_bufferwhere the OOM happens, the SYCL host-upload preparation) and when the CUDA translation failed. The failures after that point (context fallback, an extractor, the post-extractor stage) released them.docs/apisaid the caller keeps the pictures after any error,libvmaf.hsaid the context takes them, and every caller in the tree (vmaf,vmaf_bench, MCPcompute_vmaf,vmaf_vpl, thelibvmaf_tunefilter) leaves them alone after an error: a caller that followeddocs/apiwould have released a picture the extractor paths had already released.What changed
core/src/libvmaf.c: from the moment it has a context and two pictures, every return ofvmaf_read_pictures()releases both once (read_pictures_frame_cleanup(); the newread_pictures_translate_abort()for the CUDA translations, which may share storage with the caller's pictures). No context, one pictureNULLand the flush call take nothing.check_ring_buffer()returns the real error (-ENOMEM) instead of-EINVAL.docs/api/index.mdand thevmaf_read_pictures()Doxygen state the rule.test_read_pictures_monotonicandtest_validate_pic_params_bpcstop unref'ing after a rejection.Output on an RTX 4090
hog(upstream's, holds all but 400 MiB of the device) andvmaf --backend cudaon a 4096x2160 pair, 2 frames:-ENOMEM) in about 5 s, twiceproblem reading pictures, then nothinglibvmaf ERROR problem during prepare_ring_buffer,problem reading picturestest_cuda_oom_pictures_released(takes the device's memory itself, submits a pair from a pool of one pair, expects the error, frees the memory, submits the same index again): passes twice with the fix (first-frame error-12); without it the second submit never returns and the 60 s alarm ends the run (exit 142).test_read_pictures_failure_ownership(CPU): fails on master (alarm), passes with the fix.python3 scripts/ci/run_meson_test.py -- -C bs --suite=faston an ASan + UBSan build: 215 OK, 0 failed.Not measured: a HIP run under VRAM pressure. The gfx1036 allocates from system memory (
hipMallocof 30 GiB of 31 GiB succeeded andvmaf --backend hipstill ran), so the hog does not starve it; the HIP twins read their pictures inside their extractors, where the failure path already released them.Type
fix— bug fixDeep-dive deliverables (ADR-0108)
## Alternatives consideredin ADR-1431.AGENTS.mdinvariant note —core/src/AGENTS.md, "vmaf_read_pictures()owns both pictures on every return".changelog.d/fixed/read-pictures-owns-pictures-on-failure.md.docs/rebase-notes.md, "ADR-1431 —vmaf_read_pictures()owns its pictures on every return".Reproducer
End to end: upstream's
hog 400 40 &(/home/kilian/.cache/vmafx-upstream-rebase/evidence/c1420/hog.c), thenvmaf -r z.yuv -d z.yuv --width 4096 --height 2160 --pixel_format 420 --bitdepth 8 --frame_cnt 2 --backend cuda -o o.json --jsoninside the device lock with a timeout.Notes
docs/api/index.mdandlibvmaf.hare also touched by the index-gap documentation PR; different lines.core/test/meson.build(two blocks) andcore/src/libvmaf.c(vmaf_read_pictures(),check_ring_buffer()and one new static function).