[memory-leak] SKGLView (Blazor) leaks native GPU resources — Dispose() never disposes surface/renderTarget/context/glInterface
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
Research direction
Open source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKGLView.razor.cs and inspect OnRenderFrame() alongside Dispose(). Compare the cleanup with the sibling SKCanvasView.Dispose() implementation, then verify that the four GPU resources are released in reverse creation order without changing the public signature. A complete change should address deterministic cleanup while preserving the existing interop teardown.
Written by the indexing model from the issue text.
Description
🤖 AI-generated finding. Produced by the
memory-leak-fixeragentic workflow using thememory-leak-fixerskill. Scope note below states what is empirically proven vs statically reasoned.
Focus area
Managed retention (Views / handlers) — a view's Dispose failing to release the native resources it owns.
Leak / ownership path
source/SkiaSharp.Views/SkiaSharp.Views.Blazor/SKGLView.razor.cs
In OnRenderFrame() the view creates and stores four native SKObjects as private fields:
glInterface = GRGlInterface.Create();(line 102)context = GRContext.CreateGl(glInterface);(line 103) — thencontext.SetResourceCacheLimit(256 MB)(line 106)renderTarget = new GRBackendRenderTarget(...)(line 129)surface = SKSurface.Create(context, renderTarget, ...)(line 135)
But Dispose() (lines 190–195) only tears down the JS-interop helpers:
public void Dispose()
{
dpiWatcher.Unsubscribe(OnDpiChanged);
sizeWatcher.Dispose();
interop.Dispose();
}
The surface, renderTarget, context, and glInterface fields are never disposed, so each SKGLView instance leaks a GRContext (carrying a 256 MB resource-cache budget), a GRBackendRenderTarget, an SKSurface, and a GRGlInterface until the finalizer runs non-deterministically. In a Blazor app that navigates between pages hosting an SKGLView, this leaks a GL context + GPU surface on every navigation — the classic "repeated navigation leaks a surface each time" pattern.
Asymmetry that confirms the oversight: the sibling CPU control SKCanvasView.Dispose() (same package) correctly calls FreeBitmap() to release its pinned buffer. The GL control has no equivalent cleanup of its native GPU objects.
Evidence
Statically reasoned by code inspection and by the asymmetry with SKCanvasView. An empirical WeakReference/forced-GC red→green probe was not run because instantiating these fields requires a live WebGL context (GRGlInterface.Create() / GRContext.CreateGl(...) return null without real GL), which is unavailable on a headless CI runner. This is why the finding is filed as an issue rather than a validated PR.
Proposed fix (managed C#, ABI-safe)
Dispose the owned GPU resources in Dispose(), in reverse creation order, before the interop teardown:
public void Dispose()
{
dpiWatcher.Unsubscribe(OnDpiChanged);
sizeWatcher.Dispose();
interop.Dispose();
surface?.Dispose();
surface = null;
canvas = null;
renderTarget?.Dispose();
renderTarget = null;
context?.Dispose();
context = null;
glInterface?.Dispose();
glInterface = null;
}
This mirrors the existing surface?.Dispose() / renderTarget?.Dispose() recreation logic already in OnRenderFrame(). It changes no public signature (adds cleanup only), so it is ABI-safe. (Related but distinct: #2374 requests making Dispose virtual for subclassing — a separate concern; either can be done independently.)
Watch out: dispose in Dispose(), not a finalizer, and don't null a field before disposing it.
Scope note
- Type: framework bug — the view owns native GPU resources and must release them on
Dispose. - Proof: statically reasoned (not empirically proven red→green here, due to the headless-runner/WebGL limitation noted above).
- ABI impact: none — cleanup-only change inside
source/SkiaSharp.Views.Blazor, no signature change. - Labels:
tenet/performance,perf/memory-leak.
Generated by Fixer - Memory Leak · ● 5.6M · ◷
- Dominant language
- C#
- Stars
- 5.6k
- Forks
- 647
- Avg merge
- 23h 1m
- Merged PRs (30d)
- 130
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from mono/SkiaSharp
-
agentic-workflows area/Build tenet/reliability triage/triaged type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
agentic-workflows
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
partner/agentic-workflows perf/memory-leak tenet/performance
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
partner/agentic-workflows perf/throughput tenet/performance
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
partner/agentic-workflows perf/throughput tenet/performance
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Similar issues
-
type/automation type/tech-debt
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
t/bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
ci-failure-cause test-failure
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
area:auth FE mvp P3
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
klasolsson81/jobbliggaren#1788 ·