mirror of
https://github.com/games-on-whales/gst-wayland-display.git
synced 2026-10-15 12:51:57 +00:00
[PR #42] [MERGED] fix(cuda): free the transfer-full pool config that pinned the GstCudaContext #42
Labels
No labels
enhancement
enhancement
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
games-on-whales/gst-wayland-display#42
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/games-on-whales/gst-wayland-display/pull/42
Author: @salty2011
Created: 7/26/2026
Status: ✅ Merged
Merged: 8/16/2026
Merged by: @JBailes
Base:
stable← Head:fix/cuda-context-refcount📝 Commits (1)
ce6535dfix(cuda): free the transfer-full pool config that pinned the GstCudaContext📊 Changes
2 files changed (+23 additions, -1 deletions)
View changed files
📝
gst-plugin-wayland-display/src/waylandsrc/imp.rs(+7 -1)📝
wayland-display-core/src/utils/allocator/cuda/mod.rs(+16 -0)📄 Description
CUDABufferPool::get_updated_size()callsgst_buffer_pool_get_config()and never frees theresult. That function is
(transfer full)— it returns a freshgst_structure_copy()of thepool's config and the caller owns it. The sibling
configure()makes the same call but hands itscopy to
gst_buffer_pool_set_config(), which consumes it, soget_updated_size()is the onlyleaking caller.
The leak is not just a stray
GstStructure.gst_structure_copy()copies every field value, andconfigure()has just installed acuda-streamfield viagst_buffer_pool_config_set_cuda_stream().GST_TYPE_CUDA_STREAMis a boxed type whose copy/freeare
gst_mini_object_ref/gst_mini_object_unref, so the copy takes a new reference on theGstCudaStream— andgst_cuda_stream_new()holdsgst_object_ref (context)on itsGstCudaContext, released only in_gst_cuda_stream_free(). The retaining chain:decide_allocation()negotiates more than once per stream, so several config copies leak perstream, each pinning the stream object and through it one reference to the CUDA context.
Impact depends on who owns the context
When
waylanddisplaysrccreates its own context it is process-lifetime anyway and the leak isinvisible — which is probably why this has gone unnoticed. When the application injects one per
session via
gst_element_set_context()(the sharing path this element already supports throughcuda-device-id), that session'sGstCudaContextis never finalized: roughly 500 MiB of VRAMplus one
cuda-EvtHandlrdriver thread leaked per session, growing linearly. I hit it as anunbounded VRAM staircase across sessions on a multi-session host.
Second hunk, and why it must land with the first
Same function: the allocation query's caps was adopted with
Caps::from_glib_full, consuming areference the element never owned and leaving the negotiated caps one short. That is latent while
the config copy leaks, because the copy holds a compensating reference on the same caps.
Fix only the leak and the caps drops below its true count. The failure is quiet and misleading:
streams still negotiate, connect and decode at full frame rate, but buffers are never released and
every session's pipelines are retained. On my leak-only build: 843 MiB VRAM, ~3000 live
GstMemory, ~1000 liveGstBufferand 6 liveGstPipelineafter two sessions, plus onefree_priv_data: object finalizing but still has 1 parentsper session. So the two hunks are onechange; splitting them is worse than neither.
Why it is hard to see with the leaks tracer
Worth recording for the next person: a
GstStructureis not refcounted andGstCudaStreamis aGstMiniObject, soleaks(filters=GstObject)shows oneGstCudaContextalive at ref-count 1with no
GstObjectretaining it — which reads as an impossible leak.filters=also restrictswhat the tracer tracks, so hand-naming candidate types still hides holders you did not guess.
Running
leaksunfiltered is what made the chain visible: after one session it reportedexactly three live objects (
GstCudaStream,GstCudaContext,GstContext).Because the leaked structure is detached from every object, no teardown path can reach it. For
anyone who suspects a lifecycle bug here: the element's own lifecycle is fine — instrumented builds
confirm
CUDAContext::dropruns and the compositor'sGsCUDABufdrops, and clearing the element'scontext reference in
stop()changes nothing.Verification
RTX 5090,
waylanddisplaysrc ! cudaconvert ! nvcudah264encbehind aninterpipeboundary, withthe application injecting one
GstCudaContextper session;leakstracer unfiltered,SIGUSR1dump 35 s after each teardown had settled.
GstCudaStreamafter 1 sessionGstCudaStreamafter 2 sessionsGstCudaContextref-countfree_priv_datawarningsThe residual ref-count of 1 is my own reference to the injected context — by design, not a leak.
Also compile-verified against this branch's base with
--features cuda.Note for #37
The same two lines are present, unchanged, on
feat/vulkan-encode(cuda/mod.rsisbyte-identical between the two branches), and this patch applies cleanly there too. Exposure is
highest on that branch, since Vulkan hosts fall back to the NVENC/CUDA path for AV1. Fixing it
here means #37 inherits it on its next rebase.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.