mirror of
https://github.com/games-on-whales/gst-wayland-display.git
synced 2026-10-15 12:51:57 +00:00
[PR #43] [CLOSED] fix(cuda): free the transfer-full pool config that pinned the GstCudaContext (for feat/vulkan-encode) #44
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#44
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/43
Author: @salty2011
Created: 7/26/2026
Status: ❌ Closed
Base:
feat/vulkan-encode← Head:fix/cuda-context-refcount-vulkan-encode📝 Commits (1)
b3da88efix(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
Same fix as #42, applied directly to
feat/vulkan-encodeso this branch does not have to wait for a rebase to pick it up. Take whichever is more convenient — or close this one if you would rather let #42 flow in naturally.Both bugs predate this branch (they entered in
63688e6, 2025-10-08, andwayland-display-core/src/utils/allocator/cuda/mod.rsis byte-identical betweenstableand this branch), but exposure is highest here, because a Vulkan host falls back to the NVENC/CUDA path for AV1.The leak
CUDABufferPool::get_updated_size()callsgst_buffer_pool_get_config()and never frees the result. That function is(transfer full)— a freshgst_structure_copy()of the pool's config that the caller owns. The siblingconfigure()makes the same call but hands its copy togst_buffer_pool_set_config(), which consumes it, soget_updated_size()is the only leaking caller.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/free aregst_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():decide_allocation()negotiates more than once per stream, so several config copies leak per stream, each pinning the stream object and through it one reference to the CUDA context. When the element creates its own context this is invisible (process-lifetime anyway). When the application injects one per session viagst_element_set_context(), that session's context is never finalized: roughly 500 MiB of VRAM plus onecuda-EvtHandlrdriver thread leaked per session, growing linearly.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 a reference 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: streams still negotiate, connect and decode at full frame rate, but buffers are never released and every session's pipelines are retained (measured 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).Verification — on this branch
Built from this branch's head with
--features cudaand run on an RTX 5090:waylanddisplaysrc ! cudaconvert ! nvcudah264encbehind aninterpipeboundary, application-injected per-sessionGstCudaContext,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.
Finding it
Worth recording, because the standard recipe hides it: a
GstStructureis not refcounted andGstCudaStreamis aGstMiniObject, soleaks(filters=GstObject)shows oneGstCudaContextalive at ref-count 1 with noGstObjectretaining it, which reads as an impossible leak.filters=also restricts what the tracer tracks, so hand-naming candidate types still hides holders you did not guess. Runningleaksunfiltered is what made the chain visible.Because the leaked structure is detached from every object, no teardown path can reach it — the element's own lifecycle is fine (
CUDAContext::dropruns,GsCUDABufdrops, and clearing the element's context reference instop()changes nothing).🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.