Browse Source

Merge pull request #4242 from AvaloniaUI/win32-sync-context-fix

Use custom WaitForMultipleObjectsEx to prevent STA/COM reentrancy
Nikita Tsukanov 5 years ago
parent
commit
5ed130e07d

+ 26 - 1
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@@ -1,3 +1,5 @@
+using System;
+using System.Runtime.ConstrainedExecution;
 using System.Threading;
 
 namespace Avalonia.Threading
@@ -7,6 +9,20 @@ namespace Avalonia.Threading
     /// </summary>
     public class AvaloniaSynchronizationContext : SynchronizationContext
     {
+        public interface INonPumpingPlatformWaitProvider
+        {
+            int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
+        }
+
+        private readonly INonPumpingPlatformWaitProvider _waitProvider;
+
+        public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider)
+        {
+            _waitProvider = waitProvider;
+            if (_waitProvider != null)
+                SetWaitNotificationRequired();
+        }
+
         /// <summary>
         /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer.
         /// </summary>
@@ -22,7 +38,8 @@ namespace Avalonia.Threading
                 return;
             }
 
-            SetSynchronizationContext(new AvaloniaSynchronizationContext());
+            SetSynchronizationContext(new AvaloniaSynchronizationContext(AvaloniaLocator.Current
+                .GetService<INonPumpingPlatformWaitProvider>()));
         }
 
         /// <inheritdoc/>
@@ -39,5 +56,13 @@ namespace Avalonia.Threading
             else
                 Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
         }
+
+        [PrePrepareMethod]
+        public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
+        {
+            if (_waitProvider != null)
+                return _waitProvider.Wait(waitHandles, waitAll, millisecondsTimeout);
+            return base.Wait(waitHandles, waitAll, millisecondsTimeout);
+        }
     }
 }

+ 1 - 1
src/Avalonia.FreeDesktop/DBusHelper.cs

@@ -43,7 +43,7 @@ namespace Avalonia.FreeDesktop
             public void Initialized()
             {
                 lock (_lock)
-                    _ctx = new AvaloniaSynchronizationContext();
+                    _ctx = new AvaloniaSynchronizationContext(null);
             }
         }
         public static Connection Connection { get; private set; }

+ 17 - 0
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -1,4 +1,5 @@
 using System;
+using System.ComponentModel;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
@@ -1382,6 +1383,22 @@ namespace Avalonia.Win32.Interop
                 throw new Exception("RtlGetVersion failed!");
             }
         }
+        
+        [DllImport("kernel32", EntryPoint="WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)]
+        private static extern int IntWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable);
+
+        public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF);
+
+        internal static int WaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable)
+        {
+            int result = IntWaitForMultipleObjectsEx(nCount, pHandles, bWaitAll, dwMilliseconds, bAlertable);
+            if(result ==  WAIT_FAILED)
+            {
+                throw new Win32Exception();
+            }
+
+            return result;
+        }
 
         [DllImport("user32.dll")]
         internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);

+ 15 - 0
src/Windows/Avalonia.Win32/NonPumpingWaitProvider.cs

@@ -0,0 +1,15 @@
+using System;
+using Avalonia.Threading;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    internal class NonPumpingWaitProvider : AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider
+    {
+        public int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
+        {
+            return UnmanagedMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll,
+                millisecondsTimeout, false);
+        }
+    }
+}

+ 1 - 0
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -93,6 +93,7 @@ namespace Avalonia.Win32
                 .Bind<IWindowingPlatform>().ToConstant(s_instance)
                 .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
                 .Bind<IPlatformIconLoader>().ToConstant(s_instance)
+                .Bind<AvaloniaSynchronizationContext.INonPumpingPlatformWaitProvider>().ToConstant(new NonPumpingWaitProvider())
                 .Bind<IMountedVolumeInfoProvider>().ToConstant(new WindowsMountedVolumeInfoProvider());
 
             if (options.AllowEglInitialization)