[memory-leak] SKGLView (Blazor) leaks native GPU resources — Dispose() never disposes surface/renderTarget/context/glInterface

Open Beginner friendly
#4,580 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
csharp
Domain
frontend

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

partner/agentic-workflows perf/memory-leak tenet/performance

🤖 AI-generated finding. Produced by the memory-leak-fixer agentic workflow using the memory-leak-fixer skill. 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) — then context.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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from mono/SkiaSharp

All issues in mono/SkiaSharp

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.