AvaloniaUI/Avalonia · critical · InvalidOperationException

Cannot dispose while memory is pinned.

Error message

Cannot dispose while memory is pinned.

What it means

UnmanagedFontMemory.Dispose takes a write lock and refuses to free the native allocation while _pinCount > 0. Pinning marks the buffer as in use by native code (GCHandle-style ref count), and freeing it would cause use-after-free in the glyph cache or rasterizer. The guard is checked under a write lock so the check and free are atomic.

Source

Thrown at src/Avalonia.Base/Media/Fonts/UnmanagedFontMemory.cs:338

            // Decrement pin count
            Interlocked.Decrement(ref _pinCount);
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected override void Dispose(bool disposing)
        {
            // Always use lock for disposal since we don't have a finalizer
            _lock.EnterWriteLock();

            try
            {
                if (Volatile.Read(ref _pinCount) > 0)
                {
                    throw new InvalidOperationException("Cannot dispose while memory is pinned.");
                }

                if (_ptr != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(_ptr);
                    _ptr = IntPtr.Zero;
                }

                _length = 0;
            }
            finally
            {
                _lock.ExitWriteLock();
                _lock.Dispose();
            }
        }
    }
}

View on GitHub (pinned to 11c5427268)

Solutions

  1. Stop issuing Dispose on font memory that is shared; let the GlyphTypeface/FontCollection own its lifetime.
  2. Ensure all consumers of the pinned buffer (renderers, glyph caches) are torn down before disposing the source font.
  3. If pinning manually, decrement/release the pin (unpin) before calling Dispose.
  4. Move ownership to a single long-lived cache (e.g. FontManager) so Dispose only fires on application shutdown when nothing else can pin.

Example fix

// before
using (var typeface = new GlyphTypeface(uri))
{
    RenderGlyphs(typeface, codepoints); // pins buffer asynchronously
} // Dispose may fire while a render task still holds the pin -> throws

// after
var typeface = _fontCache.GetOrAdd(uri, u => new GlyphTypeface(u));
RenderGlyphs(typeface, codepoints); // cache owns lifetime; never disposed mid-use
Defensive patterns

Strategy: validation

Validate before calling

// Caller must guarantee no pinned spans are outstanding before dispose.
// Prefer to never call Dispose on shared font memory; let the cache own it.

Try / catch

// Avoid catching: this is a correctness bug. Refactor ownership instead.
// If you must, log and leak rather than risk use-after-free:
// try { mem.Dispose(); } catch (InvalidOperationException ex)
// { Log.FontStillPinned(ex); /* leak intentionally, do NOT retry */ }

Prevention

When it happens

Trigger: Disposing a GlyphTypeface while its UnmanagedFontMemory is still referenced by a pinned Span/GCHandle used in active rendering; calling Dispose from a finalizer-ordering race where another thread is mid-blit; explicit Dispose issued before the rendering pipeline has released its pin.

Common situations: Using-statement scoping of a GlyphTypeface that is still cached in a TextRun; multithreaded UI where one thread disposes a shared font and another composes text; incorrect ownership where a transient owner disposes memory owned by a long-lived cache.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/fa9cc2f14642cd82. Report an issue: GitHub.