Repository navigation
Fix single-threaded decoder picture-queue leak (#3872) - #4025
Open
glamberson wants to merge 1 commit into
Open
glamberson wants to merge 1 commit into
glamberson wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The reference-count handling and regression test setup require correction before approval.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Fixes the single-threaded decoder picture-queue leak and adds regression coverage using a captured bitstream.
Changes:
- Adjusts previous-picture reference handling.
- Adds direct decode/flush regression coverage.
| File | Summary |
|---|---|
test/api/decoder_ec_test.cpp |
Adds regression coverage. Moderate findings (1 vote each): configure one thread before initialization and prevent output locals from going out of scope. |
codec/decoder/core/src/decoder_core.cpp |
Adds the reference-count decrement. Critical finding (3 votes): the decrement may double-release pictures already handled by the normal release path. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
glamberson
force-pushed
the
fix-3872-decoder-refcount-leak
branch
2 times, most recently
from
September 22, 2026 14:31
4a8c6dc to
14804df
Compare
BufferingReadyPicture() (welsDecoderExt.cpp) pins the previous decoded picture for possible error concealment on the single-threaded decode path (++pPrevPic->iRefCount, gated on GetThreadCount(pCtx) <= 1), but nothing released that pin, so every decoded picture leaked one picture-queue slot until the next IDR reset state. This is not a queue size problem: Neither of the fixes discussed in cisco#3872 (iNumRefFrames * 2, iNumRefFrames + 4) closes the leak, they only delay the failure. It also explains why ffplay and mpv could not reproduce it: They default to multi-threaded decode, which never takes the pin. The pin is now recorded in SWelsLastDecPicInfo (pECPinnedPicture), next to pPreviousDecodedPictureInDpb, at the place BufferingReadyPicture() takes it. The next time a different picture is pinned, the previous pin is released first, and a repeat call for the same picture does nothing. Releasing it from decoder_core.cpp is not sufficient: Whether a picture is ever pinned depends on ReorderPicturesInDisplay()'s immediate-output fast path, which is wrapper-only state that decoder_core.cpp cannot see, and on B-slice streams that released pins that were never taken. The decoder core clears the pin wherever it frees the picture pool or zeroes the reference counts (WelsRequestMem(), DecreasePicBuff(), and the context defaults), the same places it already clears pPreviousDecodedPictureInDpb. A pointer kept anywhere else dangles after a resolution change, which is a heap-use-after-free under AddressSanitizer. Adds three regression tests to decoder_ec_test.cpp: A captured stream with max_num_ref_frames=1 that fails at picture 3 with dsOutOfMemory without the fix, a real B-slice and B-pyramid stream covering the immediate-output path, and back-to-back clips of different sizes on one decoder covering the pool rebuild. The last one is meant for the asan job.
glamberson
force-pushed
the
fix-3872-decoder-refcount-leak
branch
from
October 12, 2026 20:11
14804df to
252fc70
Compare
naunglinthuaung-creator
approved these changes
Oct 14, 2026
naunglinthuaung-creator
approved these changes
Oct 14, 2026
naunglinthuaung-creator
approved these changes
Oct 14, 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.

Fixes #3872.
BufferingReadyPicture() in welsDecoderExt.cpp pins the previous decoded picture for possible error concealment when decoding single-threaded (++pPrevPic->iRefCount, only when GetThreadCount(pCtx) <= 1). Nothing released that pin, so every decoded picture leaked one picture-queue slot until the next IDR reset state. This is not a queue size problem: Neither iNumRefFrames * 2 nor iNumRefFrames + 4, both discussed in the issue, closes it. They only delay the failure. It also explains why maintainers testing with ffplay or mpv could not reproduce it past 2.5.0: Those default to multi-threaded decoding, which never takes the pin.
The fix records the pinned picture in SWelsLastDecPicInfo, next to pPreviousDecodedPictureInDpb, at the place the pin is taken. The next time BufferingReadyPicture() pins a different picture it releases the previous pin first, and it does nothing when it runs again for the same picture. Releasing the pin from decoder_core.cpp instead is not safe, because whether a picture is ever pinned depends on the immediate-output fast path in ReorderPicturesInDisplay(), which that file cannot see. The decoder core clears the pin wherever it frees the picture pool or zeroes the reference counts (WelsRequestMem() and DecreasePicBuff()), the same places it already clears pPreviousDecodedPictureInDpb, so the pin never refers to a freed picture.
Tests, all in test/api/decoder_ec_test.cpp:
Verified on current master (1bca623): The first two tests fail without the fix and pass with it. The full unit test suite passes (667 tests), including when built with the same command as the asan job (LDFLAGS=-fsanitize=address CFLAGS=-fsanitize=address make), and the meson build with --werror compiles cleanly.