mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 19:27:56 +01:00
Run: handle DWM issues more gracefully (#42588)
## Summary of the Pull Request This PR expands the scenarios where Run handles exceptions silently, reducing the number of exception dialogs displayed to users and creating a smoother experience. The premise remains unchanged: most exceptions occur when Run is in the background, and the app can recover as the DWM recovers. This should cover most of the reports commonly encountered in the wild. - Extended silent handling of DWM exceptions to include `DWM_E_COMPOSITIONDISABLED` (0x80263001). - Added silent handling for `STATUS_MESSAGE_LOST` error (NTSTATUS 0xC0000701, HR 0xD0000701). - Implemented exception handling and retry logic for failures during manual app theme changes (triggered by `PowerLauncher.Helper.ThemeManager`). I can't reproduce the error on its own; it only occurs alongside a fatal render thread failure (`UCEERR_RENDERTHREADFAILURE`), which is unrecoverable. Even if caught, the UI stops rendering, leaving the end-user unable to proceed. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Related to: #31226 - [x] Related to: #30769 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
This commit is contained in:
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -258,6 +258,7 @@ cominterop
|
||||
commandnotfound
|
||||
commandpalette
|
||||
compmgmt
|
||||
COMPOSITIONDISABLED
|
||||
COMPOSITIONFULL
|
||||
CONFIGW
|
||||
CONFLICTINGMODIFIERKEY
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace PowerLauncher.Helper
|
||||
// Many bug reports because users see the "Report problem UI" after "the" crash with System.Runtime.InteropServices.COMException 0xD0000701 or 0x80263001.
|
||||
// However, displaying this "Report problem UI" during WPF crashes, especially when DWM composition is changing, is not ideal; some users reported it hangs for up to a minute before the "Report problem UI" appears.
|
||||
// This change modifies the behavior to log the exception instead of showing the "Report problem UI".
|
||||
if (IsDwmCompositionException(e as System.Runtime.InteropServices.COMException))
|
||||
if (ExceptionHelper.IsRecoverableDwmCompositionException(e as System.Runtime.InteropServices.COMException))
|
||||
{
|
||||
var logger = LogManager.GetLogger(LoggerName);
|
||||
logger.Error($"From {(isNotUIThread ? "non" : string.Empty)} UI thread's exception: {ExceptionFormatter.FormatException(e)}");
|
||||
@@ -91,22 +91,5 @@ namespace PowerLauncher.Helper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDwmCompositionException(System.Runtime.InteropServices.COMException comException)
|
||||
{
|
||||
if (comException == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var stackTrace = comException.StackTrace;
|
||||
if (string.IsNullOrEmpty(stackTrace))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for common DWM composition changed patterns in the stack trace
|
||||
return stackTrace.Contains("DwmCompositionChanged");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
src/modules/launcher/PowerLauncher/Helper/ExceptionHelper.cs
Normal file
46
src/modules/launcher/PowerLauncher/Helper/ExceptionHelper.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PowerLauncher.Helper
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Win32 naming conventions")]
|
||||
internal static class ExceptionHelper
|
||||
{
|
||||
private const string PresentationFrameworkExceptionSource = "PresentationFramework";
|
||||
|
||||
private const int DWM_E_COMPOSITIONDISABLED = unchecked((int)0x80263001);
|
||||
|
||||
// HRESULT for NT STATUS STATUS_MESSAGE_LOST (0xC0000701 | 0x10000000 == 0xD0000701)
|
||||
private const int STATUS_MESSAGE_LOST_HR = unchecked((int)0xD0000701);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the exception is a recoverable DWM composition exception.
|
||||
/// </summary>
|
||||
internal static bool IsRecoverableDwmCompositionException(Exception exception)
|
||||
{
|
||||
if (exception is not COMException comException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (comException.HResult is DWM_E_COMPOSITIONDISABLED)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (comException.HResult is STATUS_MESSAGE_LOST_HR && comException.Source == PresentationFrameworkExceptionSource)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for common DWM composition changed patterns in the stack trace
|
||||
var stackTrace = comException.StackTrace;
|
||||
return !string.IsNullOrEmpty(stackTrace) &&
|
||||
stackTrace.Contains("DwmCompositionChanged");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,16 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Win32;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.UserSettings;
|
||||
using Wox.Plugin.Logger;
|
||||
|
||||
namespace PowerLauncher.Helper
|
||||
{
|
||||
@@ -20,6 +23,9 @@ namespace PowerLauncher.Helper
|
||||
private readonly ThemeHelper _themeHelper = new();
|
||||
|
||||
private bool _disposed;
|
||||
private CancellationTokenSource _themeUpdateTokenSource;
|
||||
private const int MaxRetries = 5;
|
||||
private const int InitialDelayMs = 2000;
|
||||
|
||||
public Theme CurrentTheme { get; private set; }
|
||||
|
||||
@@ -108,10 +114,80 @@ namespace PowerLauncher.Helper
|
||||
{
|
||||
Theme newTheme = _themeHelper.DetermineTheme(_settings.Theme);
|
||||
|
||||
_mainWindow.Dispatcher.Invoke(() =>
|
||||
// Cancel any existing theme update operation
|
||||
_themeUpdateTokenSource?.Cancel();
|
||||
_themeUpdateTokenSource?.Dispose();
|
||||
_themeUpdateTokenSource = new CancellationTokenSource();
|
||||
|
||||
// Start theme update with retry logic in the background
|
||||
_ = UpdateThemeWithRetryAsync(newTheme, _themeUpdateTokenSource.Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the theme with retry logic for desktop composition errors.
|
||||
/// </summary>
|
||||
/// <param name="theme">The theme to apply.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
private async Task UpdateThemeWithRetryAsync(Theme theme, CancellationToken cancellationToken)
|
||||
{
|
||||
var delayMs = 0;
|
||||
const int maxAttempts = MaxRetries + 1;
|
||||
|
||||
for (var attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
SetSystemTheme(newTheme);
|
||||
});
|
||||
try
|
||||
{
|
||||
if (delayMs > 0)
|
||||
{
|
||||
await Task.Delay(delayMs, cancellationToken);
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Log.Debug("Theme update operation was cancelled.", typeof(ThemeManager));
|
||||
return;
|
||||
}
|
||||
|
||||
await _mainWindow.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
SetSystemTheme(theme);
|
||||
});
|
||||
|
||||
if (attempt > 1)
|
||||
{
|
||||
Log.Info($"Successfully applied theme after {attempt - 1} retry attempt(s).", typeof(ThemeManager));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
catch (COMException ex) when (ExceptionHelper.IsRecoverableDwmCompositionException(ex))
|
||||
{
|
||||
switch (attempt)
|
||||
{
|
||||
case 1:
|
||||
Log.Warn($"Desktop composition is disabled (HRESULT: 0x{ex.HResult:X}). Scheduling retries for theme update.", typeof(ThemeManager));
|
||||
delayMs = InitialDelayMs;
|
||||
break;
|
||||
case < maxAttempts:
|
||||
Log.Warn($"Retry {attempt - 1}/{MaxRetries} failed: Desktop composition still disabled. Retrying in {delayMs * 2}ms...", typeof(ThemeManager));
|
||||
delayMs *= 2;
|
||||
break;
|
||||
default:
|
||||
Log.Exception($"Failed to set theme after {MaxRetries} retry attempts. Desktop composition remains disabled.", ex, typeof(ThemeManager));
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Log.Debug("Theme update operation was cancelled.", typeof(ThemeManager));
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Exception($"Unexpected error during theme update (attempt {attempt}/{maxAttempts}): {ex.Message}", ex, typeof(ThemeManager));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -130,6 +206,8 @@ namespace PowerLauncher.Helper
|
||||
if (disposing)
|
||||
{
|
||||
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
|
||||
_themeUpdateTokenSource?.Cancel();
|
||||
_themeUpdateTokenSource?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
|
||||
Reference in New Issue
Block a user