diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index d9d10f5dddcca..0e3f10d2df6c3 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -113,6 +113,7 @@ The PR that reveals the implementation of the ` $(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) + true diff --git a/src/libraries/Microsoft.Win32.SystemEvents/src/CompatibilitySuppressions.xml b/src/libraries/Microsoft.Win32.SystemEvents/src/CompatibilitySuppressions.xml index 038640c945743..c12a83ef6de8a 100644 --- a/src/libraries/Microsoft.Win32.SystemEvents/src/CompatibilitySuppressions.xml +++ b/src/libraries/Microsoft.Win32.SystemEvents/src/CompatibilitySuppressions.xml @@ -1,6 +1,13 @@  + + CP0014 + E:Microsoft.Win32.SystemEvents.LowMemory:[T:System.ObsoleteAttribute] + lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll + lib/netstandard2.0/Microsoft.Win32.SystemEvents.dll + true + CP0015 E:Microsoft.Win32.SystemEvents.LowMemory:[T:System.ObsoleteAttribute] diff --git a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj index b6921f386a31d..411c1132b65ca 100644 --- a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj +++ b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj @@ -6,6 +6,7 @@ true false true + true Provides access to Windows system event notifications. Commonly Used Types: @@ -104,6 +105,8 @@ Microsoft.Win32.SystemEvents Link="Common\Interop\Windows\WtsApi32\Interop.WTSUnRegisterSessionNotification.cs" /> + diff --git a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs index 3067cd053bb4c..786ab50e0742b 100644 --- a/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs +++ b/src/libraries/Microsoft.Win32.SystemEvents/src/Microsoft/Win32/SystemEvents.cs @@ -130,17 +130,12 @@ public static event EventHandler? DisplaySettingsChanged /// Occurs before the thread that listens for system events is terminated. /// Delegates will be invoked on the events thread. /// + [Obsolete(Obsoletions.SystemEventsEventsThreadShutdownMessage, DiagnosticId = Obsoletions.SystemEventsEventsThreadShutdownDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] public static event EventHandler? EventsThreadShutdown { // Really only here for GDI+ initialization and shut down - add - { - AddEventHandler(s_onEventsThreadShutdownEvent, value); - } - remove - { - RemoveEventHandler(s_onEventsThreadShutdownEvent, value); - } + add => AddEventHandler(s_onEventsThreadShutdownEvent, value); + remove => RemoveEventHandler(s_onEventsThreadShutdownEvent, value); } /// @@ -464,39 +459,43 @@ private void Dispose() /// private static void EnsureSystemEvents(bool requireHandle) { - if (s_systemEvents == null) + if (s_systemEvents is not null) { - lock (s_procLockObject) + return; + } + + lock (s_procLockObject) + { + if (s_systemEvents is not null) { - if (s_systemEvents == null) - { - // Create a new pumping thread. We always create one even if the current thread - // is STA, as there are no guarantees this thread will pump nor still be alive - // for the desired duration. + return; + } - s_eventWindowReady = new ManualResetEvent(false); - SystemEvents systemEvents = new SystemEvents(); - s_windowThread = new Thread(new ThreadStart(systemEvents.WindowThreadProc)) - { - IsBackground = true, - Name = ".NET System Events" - }; - s_windowThread.Start(); - s_eventWindowReady.WaitOne(); + // Create a new pumping thread. We always create one even if the current thread + // is STA, as there are no guarantees this thread will pump nor still be alive + // for the desired duration. - // ensure this is initialized last as that will force concurrent threads calling - // this method to block until after we've initialized. - s_systemEvents = systemEvents; + s_eventWindowReady = new ManualResetEvent(false); + SystemEvents systemEvents = new SystemEvents(); + s_windowThread = new Thread(new ThreadStart(systemEvents.WindowThreadProc)) + { + IsBackground = true, + Name = ".NET System Events" + }; + s_windowThread.Start(); + s_eventWindowReady.WaitOne(); - if (requireHandle && s_systemEvents._windowHandle == IntPtr.Zero) - { - // In theory, it's not the end of the world that - // we don't get system events. Unfortunately, the main reason windowHandle == 0 - // is CreateWindowEx failed for mysterious reasons, and when that happens, - // subsequent (and more important) CreateWindowEx calls also fail. - throw new ExternalException(SR.ErrorCreateSystemEvents); - } - } + // Ensure this is initialized last as that will force concurrent threads calling + // this method to block until after we've initialized. + s_systemEvents = systemEvents; + + if (requireHandle && s_systemEvents._windowHandle == IntPtr.Zero) + { + // In theory, it's not the end of the world that + // we don't get system events. Unfortunately, the main reason windowHandle == 0 + // is CreateWindowEx failed for mysterious reasons, and when that happens, + // subsequent (and more important) CreateWindowEx calls also fail. + throw new ExternalException(SR.ErrorCreateSystemEvents); } } } @@ -694,8 +693,6 @@ private unsafe void Initialize() hInstance, IntPtr.Zero); } } - - AppDomain.CurrentDomain.ProcessExit += new EventHandler(Shutdown); } /// @@ -759,16 +756,15 @@ public static void InvokeOnEventsThread(Delegate method) { int pid; int thread = Interop.User32.GetWindowThreadProcessId(s_systemEvents!._windowHandle, &pid); - GC.KeepAlive(s_systemEvents); Debug.Assert(s_windowThread == null || thread != Interop.Kernel32.GetCurrentThreadId(), "Don't call MarshaledInvoke on the system events thread"); } #endif - if (s_threadCallbackList == null) + if (s_threadCallbackList is null) { lock (s_eventLockObject) { - if (s_threadCallbackList == null) + if (s_threadCallbackList is null) { s_threadCallbackMessage = Interop.User32.RegisterWindowMessageW("SystemEventsThreadCallbackMessage"); s_threadCallbackList = new Queue(); @@ -784,7 +780,6 @@ public static void InvokeOnEventsThread(Delegate method) } Interop.User32.PostMessageW(s_systemEvents!._windowHandle, s_threadCallbackMessage, IntPtr.Zero, IntPtr.Zero); - GC.KeepAlive(s_systemEvents); } /// @@ -1073,56 +1068,6 @@ private static void RemoveEventHandler(object key, Delegate? value) } } - private static void Shutdown() - { - if (s_systemEvents != null) - { - lock (s_procLockObject) - { - if (s_systemEvents != null) - { - // If we are using system events from another thread, request that it terminate - if (s_windowThread != null) - { -#if DEBUG - unsafe - { - int pid; - int thread = Interop.User32.GetWindowThreadProcessId(s_systemEvents._windowHandle, &pid); - Debug.Assert(thread != Interop.Kernel32.GetCurrentThreadId(), "Don't call Shutdown on the system events thread"); - - } -#endif - // The handle could be valid, Zero or invalid depending on the state of the thread - // that is processing the messages. We optimistically expect it to be valid to - // notify the thread to shutdown. The Zero or invalid values should be present - // only when the thread is already shutting down due to external factors. - if (s_systemEvents._windowHandle != IntPtr.Zero) - { - Interop.User32.PostMessageW(s_systemEvents._windowHandle, Interop.User32.WM_QUIT, IntPtr.Zero, IntPtr.Zero); - GC.KeepAlive(s_systemEvents); - } - - s_windowThread.Join(); - } - else - { - s_systemEvents.Dispose(); - s_systemEvents = null; - } - } - } - } - } - -#if FEATURE_CER - [PrePrepareMethod] -#endif - private static void Shutdown(object? sender, EventArgs e) - { - Shutdown(); - } - /// /// A standard Win32 window proc for our broadcast window. /// @@ -1250,8 +1195,8 @@ private IntPtr WindowProc(IntPtr hWnd, int msg, nint wParam, nint lParam) } /// - /// This is the method that runs our window thread. This method - /// creates a window and spins up a message loop. The window + /// This is the method that runs our window thread. This method + /// creates a window and spins up a message loop. The window /// is made visible with a size of 0, 0, so that it will trap /// global broadcast messages. /// @@ -1264,7 +1209,7 @@ private void WindowThreadProc() if (_windowHandle != IntPtr.Zero) { - Interop.User32.MSG msg = default(Interop.User32.MSG); + Interop.User32.MSG msg = default; while (Interop.User32.GetMessageW(ref msg, _windowHandle, 0, 0) > 0) { @@ -1277,11 +1222,11 @@ private void WindowThreadProc() } catch (Exception e) { - // In case something very very wrong happend during the creation action. + // In case something very very wrong happened during the creation action. // This will unblock the calling thread. s_eventWindowReady!.Set(); - if (!((e is ThreadInterruptedException) || (e is ThreadAbortException))) + if (e is not (ThreadInterruptedException or ThreadAbortException)) { Debug.Fail("Unexpected thread exception in system events window thread proc", e.ToString()); } diff --git a/src/libraries/Microsoft.Win32.SystemEvents/tests/ShutdownTest.cs b/src/libraries/Microsoft.Win32.SystemEvents/tests/ShutdownTest.cs index d8012da78db70..f2febc7942398 100644 --- a/src/libraries/Microsoft.Win32.SystemEvents/tests/ShutdownTest.cs +++ b/src/libraries/Microsoft.Win32.SystemEvents/tests/ShutdownTest.cs @@ -2,32 +2,37 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Threading; using Microsoft.DotNet.RemoteExecutor; using Xunit; using static Interop; -namespace Microsoft.Win32.SystemEventsTests +namespace Microsoft.Win32.SystemEventsTests; + +public class ShutdownTest : SystemEventsTest { - public abstract class ShutdownTest : SystemEventsTest + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoNorServerCore))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void ShutdownThroughRestartManager() { - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoNorServerCore))] - [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] - public void ShutdownThroughRestartManager() + RemoteExecutor.Invoke(() => { - RemoteExecutor.Invoke(() => - { - // Register any event to ensure that SystemEvents get initialized - SystemEvents.TimeChanged += (o, e) => { }; + // Register any event to ensure that SystemEvents get initialized + SystemEvents.TimeChanged += (o, e) => { }; - // Fake Restart Manager behavior by sending external WM_CLOSE message - SendMessage(Interop.User32.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + // Fake Restart Manager behavior by sending external WM_CLOSE message + SendMessage(Interop.User32.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + }).Dispose(); + } - // Emulate calling the Shutdown event - var shutdownMethod = typeof(SystemEvents).GetMethod("Shutdown", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic, null, new Type[0], null); - Assert.NotNull(shutdownMethod); - shutdownMethod.Invoke(null, null); - }).Dispose(); - } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoNorServerCore))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void ShutdownSuccessDespiteThreadBlock() + { + RemoteExecutor.Invoke(() => + { + // Block the SystemEvents thread. Regression test for https://github.com/dotnet/winforms/issues/11944 + SystemEvents.UserPreferenceChanged += (o, e) => { while (true) { } }; + SendMessage(User32.WM_SETTINGCHANGE, IntPtr.Zero, IntPtr.Zero); + }).Dispose(); } }