Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit ecf00da

Browse files
committed
Fix race condition in SystemEvents tests
When attaching a handler from two different threads at once, the first starts creating the HWND and waits for the event thread to spin up and create the window. The problem is that it will wait *after* initializing the SystemEvents instance singleton so the second thread will just skip the initialization and return since it sees the instance is already created. This is probably not an issue for the API itself since it doesn't make any garuntees about how soon after you attach to the handler you can receive events, but the tests assume that they can find the window and send messages to it immediately after attaching to an event. Fix this by waiting on the same manualresetevent that the first thread is waiting on.
1 parent fc1a878 commit ecf00da

File tree

1 file changed

+19
-7
lines changed

1 file changed

+19
-7
lines changed

src/Microsoft.Win32.SystemEvents/tests/SystemEventsTest.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Threading;
67
using Xunit;
78
using static Interop;
89

@@ -17,26 +18,37 @@ public abstract class SystemEventsTest
1718
public const int UnexpectedEventMultiplier = 10;
1819

1920
protected IntPtr SendMessage(int msg, IntPtr wParam, IntPtr lParam)
21+
{
22+
EnsureHwnd();
23+
return User32.SendMessageW(s_hwnd, msg, wParam, lParam);
24+
}
25+
26+
private void EnsureHwnd()
2027
{
2128
if (s_hwnd == IntPtr.Zero)
2229
{
23-
// locate the hwnd used by SystemEvents in this domain
24-
var windowClassNameField = typeof(SystemEvents).GetField("s_className", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
25-
if (windowClassNameField == null)
30+
// wait for the window to be created
31+
var windowReadyField = typeof(SystemEvents).GetField("s_eventWindowReady", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic) ?? // corefx
32+
typeof(SystemEvents).GetField("eventWindowReady", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); // desktop
33+
Assert.NotNull(windowReadyField);
34+
var windowReadyEvent = windowReadyField.GetValue(null) as ManualResetEvent;
35+
if (windowReadyEvent != null)
2636
{
27-
// desktop doesn't use the s_ prefix
28-
windowClassNameField = typeof(SystemEvents).GetField("className", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
37+
// on an STA thread the HWND is created in the same thread synchronously when attaching to an event handler
38+
// if we're on an MTA thread, a new thread is created to handle events and that thread creates the window, wait for it to complete.
39+
Assert.True(windowReadyEvent.WaitOne(PostMessageWait * ExpectedEventMultiplier));
2940
}
3041

42+
// locate the hwnd used by SystemEvents in this domain
43+
var windowClassNameField = typeof(SystemEvents).GetField("s_className", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic) ?? // corefx
44+
typeof(SystemEvents).GetField("className", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); // desktop
3145
Assert.NotNull(windowClassNameField);
3246
var windowClassName = windowClassNameField.GetValue(null) as string;
3347
Assert.NotNull(windowClassName);
3448

3549
s_hwnd = User32.FindWindowW(windowClassName, null);
3650
Assert.NotEqual(s_hwnd, IntPtr.Zero);
3751
}
38-
39-
return User32.SendMessageW(s_hwnd, msg, wParam, lParam);
4052
}
4153
}
4254
}

0 commit comments

Comments
 (0)