Browse Source

Manual native memory management for leak detection

Nikita Tsukanov 8 years ago
parent
commit
29791fa241

+ 9 - 0
src/Avalonia.Base/Platform/IRuntimePlatform.cs

@@ -14,6 +14,15 @@ namespace Avalonia.Platform
         IDisposable StartSystemTimer(TimeSpan interval, Action tick);
         string GetStackTrace();
         RuntimePlatformInfo GetRuntimeInfo();
+        IUnmanagedBlob AllocBlob(int size);
+    }
+
+    public interface IUnmanagedBlob : IDisposable
+    {
+        IntPtr Address { get; }
+        int Size { get; }
+        bool IsDisposed { get; }
+        
     }
 
     public struct RuntimePlatformInfo

+ 2 - 1
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@@ -2,6 +2,7 @@
   <PropertyGroup>
     <TargetFramework>netcoreapp2.0</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
   </PropertyGroup>
   <PropertyGroup>
     <DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML</DocumentationFile>
@@ -21,5 +22,5 @@
     <ProjectReference Include="..\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj" />
   </ItemGroup>
   <Import Project="..\..\build\NetCore.props" />
-  <Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
+  <Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" />
 </Project>

+ 10 - 10
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Gtk3
     {
         private readonly WindowBaseImpl _impl;
         private readonly GtkWidget _widget;
-        private CairoSurface _surface;
+        private ManagedCairoSurface _surface;
         private int _factor;
         private object _lock = new object();
         public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height, int factor)
@@ -26,13 +26,13 @@ namespace Avalonia.Gtk3
             _factor = factor;
             width *= _factor;
             height *= _factor;
-            _surface = Native.CairoImageSurfaceCreate(1, width, height);
+            _surface = new ManagedCairoSurface(width, height);
             
             Width = width;
             Height = height;
-            Address = Native.CairoImageSurfaceGetData(_surface);
-            RowBytes = Native.CairoImageSurfaceGetStride(_surface);
-            Native.CairoSurfaceFlush(_surface);
+            Address = _surface.Buffer;
+            RowBytes = _surface.Stride;
+            Native.CairoSurfaceFlush(_surface.Surface);
         }
 
         static void Draw(IntPtr context, CairoSurface surface, double factor)
@@ -83,12 +83,12 @@ namespace Avalonia.Gtk3
         class RenderOp : IDeferredRenderOperation
         {
             private readonly GtkWidget _widget;
-            private CairoSurface _surface;
+            private ManagedCairoSurface _surface;
             private readonly double _factor;
             private readonly int _width;
             private readonly int _height;
 
-            public RenderOp(GtkWidget widget, CairoSurface _surface, double factor, int width, int height)
+            public RenderOp(GtkWidget widget, ManagedCairoSurface _surface, double factor, int width, int height)
             {
                 _widget = widget;
                 this._surface = _surface;
@@ -105,7 +105,7 @@ namespace Avalonia.Gtk3
 
             public void RenderNow()
             {
-                DrawToWidget(_widget, _surface, _width, _height, _factor);
+                DrawToWidget(_widget, _surface.Surface, _width, _height, _factor);
             }
         }
         
@@ -116,9 +116,9 @@ namespace Avalonia.Gtk3
                 if (Dispatcher.UIThread.CheckAccess())
                 {
                     if (_impl.CurrentCairoContext != IntPtr.Zero)
-                        Draw(_impl.CurrentCairoContext, _surface, _factor);
+                        Draw(_impl.CurrentCairoContext, _surface.Surface, _factor);
                     else
-                        DrawToWidget(_widget, _surface, Width, Height, _factor);
+                        DrawToWidget(_widget, _surface.Surface, Width, Height, _factor);
                     _surface.Dispose();
                 }
                 else

+ 38 - 0
src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia.Platform;
+
+namespace Avalonia.Gtk3.Interop
+{
+    class ManagedCairoSurface : IDisposable
+    {
+        public IntPtr Buffer { get; private set; }
+        public CairoSurface Surface { get; private set; }
+        public int Stride { get; private set; }
+        private int _size;
+        private IRuntimePlatform _plat;
+        private IUnmanagedBlob _blob;
+
+        public ManagedCairoSurface(int width, int height)
+        {
+            _plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
+            Stride = width * 4;
+            _size = height * Stride;
+            _blob = _plat.AllocBlob(_size * 2);
+            Buffer = _blob.Address;
+            Surface = Native.CairoImageSurfaceCreateForData(Buffer, 1, width, height, Stride);
+        }
+        
+        public void Dispose()
+        {
+            
+            if (Buffer != IntPtr.Zero)
+            {
+                Surface.Dispose();
+                _blob.Dispose();
+                Buffer = IntPtr.Zero;
+            }
+        }
+
+    }
+}

+ 4 - 0
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -160,6 +160,9 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate CairoSurface cairo_image_surface_create(int format, int width, int height);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
+            public delegate CairoSurface cairo_image_surface_create_for_data(IntPtr data, int format, int width, int height, int stride);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface);
@@ -459,6 +462,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_cairo_create GdkCairoCreate;
         
         public static D.cairo_image_surface_create CairoImageSurfaceCreate;
+        public static D.cairo_image_surface_create_for_data CairoImageSurfaceCreateForData;
         public static D.cairo_image_surface_get_data CairoImageSurfaceGetData;
         public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride;
         public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty;

+ 131 - 1
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@@ -2,8 +2,11 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Collections.Generic;
 using System.Reflection;
 using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using System.Threading;
 using Avalonia.Platform;
 
@@ -18,8 +21,135 @@ namespace Avalonia.Shared.PlatformSupport
             return new Timer(_ => tick(), null, interval, interval);
         }
 
+        public string GetStackTrace() => Environment.StackTrace;
+
+        public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size);
+        
+        class UnmanagedBlob : IUnmanagedBlob
+        {
+            private readonly StandardRuntimePlatform _plat;
+#if DEBUG
+            private static readonly List<string> Backtraces = new List<string>();
+            private static Thread GCThread;
+            private readonly string _backtrace;
 
 
-        public string GetStackTrace() => Environment.StackTrace;
+            class GCThreadDetector
+            {
+                ~GCThreadDetector()
+                {
+                    GCThread = Thread.CurrentThread;
+                }
+            }
+
+            [MethodImpl(MethodImplOptions.NoInlining)]
+            static void Spawn() => new GCThreadDetector();
+            
+            static UnmanagedBlob()
+            {
+                Spawn();
+                GC.WaitForPendingFinalizers();
+            }
+            
+#endif
+            
+            public UnmanagedBlob(StandardRuntimePlatform plat, int size)
+            {
+                _plat = plat;
+                Address = plat.Alloc(size);
+                GC.AddMemoryPressure(size);
+                Size = size;
+#if DEBUG
+                _backtrace = Environment.StackTrace;
+                Backtraces.Add(_backtrace);
+#endif
+            }
+
+            void DoDispose()
+            {
+                if (!IsDisposed)
+                {
+                    Backtraces.Remove(_backtrace);
+                    _plat.Free(Address, Size);
+                    GC.RemoveMemoryPressure(Size);
+                    IsDisposed = true;
+                    Address = IntPtr.Zero;
+                }
+            }
+
+            public void Dispose()
+            {
+#if DEBUG
+                if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
+                {
+                    Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+                                            + Environment.StackTrace
+                                            + "\n\nBlob created by " + _backtrace);
+                }
+#endif
+                DoDispose();
+                GC.SuppressFinalize(this);
+            }
+
+            ~UnmanagedBlob()
+            {
+#if DEBUG
+                Console.Error.WriteLine("Undisposed native blob created by " + _backtrace);
+#endif
+                DoDispose();
+            }
+
+            public IntPtr Address { get; private set; }
+            public int Size { get; private set; }
+            public bool IsDisposed { get; private set; }
+        }
+        
+        
+        
+#if FULLDOTNET || DOTNETCORE
+        [DllImport("libc", SetLastError = true)]
+        private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset);
+        [DllImport("libc", SetLastError = true)]
+        private static extern int munmap(IntPtr addr, IntPtr length);
+        [DllImport("libc", SetLastError = true)]
+        private static extern long sysconf(int name);
+
+        private bool? _useMmap;
+        private bool UseMmap 
+            => _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value;
+        
+        IntPtr Alloc(int size)
+        {
+            if (UseMmap)
+            {
+                var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero);
+                if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff)
+                {
+                    var errno = Marshal.GetLastWin32Error();
+                    throw new Exception("Unable to allocate memory: " + errno);
+                }
+                return rv;
+            }
+            else
+                return Marshal.AllocHGlobal(size);
+        }
+
+        void Free(IntPtr ptr, int len)
+        {
+            if (UseMmap)
+            {
+                if (munmap(ptr, new IntPtr(len)) == -1)
+                {
+                    var errno = Marshal.GetLastWin32Error();
+                    throw new Exception("Unable to free memory: " + errno);
+                }
+            }
+            else
+                Marshal.FreeHGlobal(ptr);
+        }
+#else
+        IntPtr Alloc(int size) => Marshal.AllocHGlobal(size);
+        void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
+#endif
     }
 }

+ 13 - 0
src/Skia/Avalonia.Skia/BitmapImpl.cs

@@ -20,6 +20,13 @@ namespace Avalonia.Skia
             _dpi = new Vector(96, 96);
         }
 
+        static void ReleaseProc(IntPtr address, object ctx)
+        {
+            ((IUnmanagedBlob) ctx).Dispose();
+        }
+
+        private static readonly SKBitmapReleaseDelegate ReleaseDelegate = ReleaseProc;
+        
         public BitmapImpl(int width, int height, Vector dpi, PixelFormat? fmt = null)
         {
             PixelHeight = height;
@@ -29,6 +36,12 @@ namespace Avalonia.Skia
             var runtime = AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo();
             if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux)
                 colorType = SKColorType.Bgra8888;
+            
+            Bitmap = new SKBitmap();
+            var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul);
+            var plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
+            var blob = plat.AllocBlob(nfo.BytesSize);
+            Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob);
             Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul);
             Bitmap.Erase(SKColor.Empty);
         }