{"record":{"id":"fa9cc2f14642cd82","repo":"AvaloniaUI/Avalonia","slug":"cannot-dispose-while-memory-is-pinned","errorCode":null,"errorMessage":"Cannot dispose while memory is pinned.","messagePattern":"Cannot dispose while memory is pinned\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"src/Avalonia.Base/Media/Fonts/UnmanagedFontMemory.cs","lineNumber":338,"sourceCode":"            // Decrement pin count\n            Interlocked.Decrement(ref _pinCount);\n        }\n\n        public void Dispose()\n        {\n            Dispose(true);\n        }\n\n        protected override void Dispose(bool disposing)\n        {\n            // Always use lock for disposal since we don't have a finalizer\n            _lock.EnterWriteLock();\n\n            try\n            {\n                if (Volatile.Read(ref _pinCount) > 0)\n                {\n                    throw new InvalidOperationException(\"Cannot dispose while memory is pinned.\");\n                }\n\n                if (_ptr != IntPtr.Zero)\n                {\n                    Marshal.FreeHGlobal(_ptr);\n                    _ptr = IntPtr.Zero;\n                }\n\n                _length = 0;\n            }\n            finally\n            {\n                _lock.ExitWriteLock();\n                _lock.Dispose();\n            }\n        }\n    }\n}","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/AvaloniaUI/Avalonia/blob/11c542726898ae954a1ef668c65ec79ec92ab17d/src/Avalonia.Base/Media/Fonts/UnmanagedFontMemory.cs#L320-L356","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Stop issuing Dispose on font memory that is shared; let the GlyphTypeface/FontCollection own its lifetime.","Ensure all consumers of the pinned buffer (renderers, glyph caches) are torn down before disposing the source font.","If pinning manually, decrement/release the pin (unpin) before calling Dispose.","Move ownership to a single long-lived cache (e.g. FontManager) so Dispose only fires on application shutdown when nothing else can pin."],"exampleFix":"// before\nusing (var typeface = new GlyphTypeface(uri))\n{\n    RenderGlyphs(typeface, codepoints); // pins buffer asynchronously\n} // Dispose may fire while a render task still holds the pin -> throws\n\n// after\nvar typeface = _fontCache.GetOrAdd(uri, u => new GlyphTypeface(u));\nRenderGlyphs(typeface, codepoints); // cache owns lifetime; never disposed mid-use","handlingStrategy":"validation","validationCode":"// Caller must guarantee no pinned spans are outstanding before dispose.\n// Prefer to never call Dispose on shared font memory; let the cache own it.","typeGuard":null,"tryCatchPattern":"// Avoid catching: this is a correctness bug. Refactor ownership instead.\n// If you must, log and leak rather than risk use-after-free:\n// try { mem.Dispose(); } catch (InvalidOperationException ex)\n// { Log.FontStillPinned(ex); /* leak intentionally, do NOT retry */ }","preventionTips":["Give font memory a single long-lived owner (FontManager/Cache).","Never dispose GlyphTypeface while text runs or renderers reference it.","Make pinned spans short-lived and complete synchronously before the using-scope exits.","Audit finalizers for ordering races against pinned memory."],"tags":["fonts","lifecycle","dispose","concurrency","pinning","native-memory"],"backgroundTag":null,"analyzedSha":"11c542726898ae954a1ef668c65ec79ec92ab17d","analyzedAt":"2026-08-13T11:57:40.261Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}