Release the decoded image in decodeDynamic - #16
Open
frathe wants to merge 1 commit into
Open
Conversation
heif_decode_image allocates a heif_image that was never released, leaking
libheif's decoded buffer on every still-image decode - about 18.7 MB for a
12 MP photo, one full YCbCr 4:2:0 plane set. The context, image handle and
decoding options were all released correctly; only the image was missed.
The sequence decoder already releases its images.
The leak is in libheif's allocations rather than Go memory, so the Go GC
and pprof never see it: HeapAlloc stays flat while RSS grows without bound.
Deferring the release is safe because every colorspace branch copies the
pixel planes into Go-owned memory before returning, so nothing references
the native buffer once decodeDynamic returns.
Decoding one 12 MP HEIC 60 times, with two forced GCs before each reading:
decodes before after
20 433 MB 84 MB
40 785 MB 84 MB
60 1137 MB 90 MB
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5 tasks
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 #15.
decodeDynamiccallsheif_decode_imagebut never callsheif_image_releaseon the result, so libheif's decoded image buffer is leaked on every still-image decode. The context, image handle and decoding options are all released correctly — only the decoded image is missed. The sequence decoder already does this correctly (heifImageRelease(himg)further down the same file).Because the leak is in libheif's own allocations rather than Go memory, neither the Go GC nor
pprofcan see it:HeapAllocstays completely flat while process RSS grows without bound. On macOS it shows up invmmapasMALLOC_LARGE.Measurements
Decoding a single 12 MP HEIC (4032×3024) 60 times, with two forced
runtime.GC()calls before each reading so nothing reachable from Go is counted:The Go heap reads a flat 1 MB throughout both runs. ~18.7 MB was leaked per decode, which is exactly one full YCbCr 4:2:0 buffer for that resolution (4032 × 3024 × 1.5 = 18.3 MB) — the entire decoded image. With the fix, RSS settles after libheif's one-time codec initialisation and stops growing.
Reproducer and full details are in #15.
Why
deferis safe hereEvery colorspace branch copies the pixel planes into Go-owned memory (
copy(i.Y, ...),copy(i.Pix, ...)) before returning, so nothing references the native buffer past the end of the function.heifImageReleaseis already declared and registered — it was simply never called on this path.go build,go vet,gofmtandgo test ./...all pass.🤖 Generated with Claude Code