CmdPal: Improve error handling and logging in activation process (#41344)

## Summary of the Pull Request

This PR makes passing arguments from a new instance to the existing one
more resilient:


- Fixes situations where `x-cmdpal://` links might not work as expected.
Instead of performing the intended action (e.g., `x-cmdpal://background`
or `x-cmdpal://settings`), they could incorrectly just summon the main
window.

- Refactors the `AppInstance.Activated` handler to be synchronous.  
- The handler blocks `AppInstance.RedirectActivationToAsync` in the
caller.
- If it runs asynchronously (or offloads work to another thread,
including the UI thread), the calling instance may exit too soon,
preventing the activation arguments from being read.

- Adds a timeout and ensures the semaphore is always released so the
application can exit gracefully under all conditions.

- Adjusts handling for cases where the source application exits before
passing arguments by lowering the log severity to **Warning** and
providing a clearer, more descriptive message.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** yop
- [x] **Localization:** no need
- [x] **Dev docs:** no need
- [x] **New binaries:** none
- [x] **Documentation updated:** nope

<!-- 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
Tested with x-cmdpal://settings under normal conditions, and with CmdPal
deliberately slowed down to take its sweet time handling the arguments
(so the calling instance times out).
This commit is contained in:
Jiří Polášek
2025-09-02 22:39:59 +02:00
committed by GitHub
parent 7931f14bd5
commit d153f3473a
3 changed files with 57 additions and 13 deletions

View File

@@ -107,12 +107,33 @@ internal sealed class Program
{
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
var redirectSemaphore = new Semaphore(0, 1);
Task.Run(() =>
using var redirectSemaphore = new Semaphore(0, 1);
var redirectTimeout = TimeSpan.FromSeconds(32);
_ = Task.Run(() =>
{
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
redirectSemaphore.Release();
using var cts = new CancellationTokenSource(redirectTimeout);
try
{
keyInstance.RedirectActivationToAsync(args)
.AsTask(cts.Token)
.GetAwaiter()
.GetResult();
}
catch (OperationCanceledException)
{
Logger.LogError($"Failed to activate existing instance; timed out after {redirectTimeout}.");
}
catch (Exception ex)
{
Logger.LogError("Failed to activate existing instance", ex);
}
finally
{
redirectSemaphore.Release();
}
});
_ = PInvoke.CoWaitForMultipleObjects(
(uint)CWMO_FLAGS.CWMO_DEFAULT,
PInvoke.INFINITE,
@@ -124,13 +145,14 @@ internal sealed class Program
{
// If we already have a form, display the message now.
// Otherwise, add it to the collection for displaying later.
if (App.Current is App thisApp)
if (App.Current?.AppWindow is MainWindow mainWindow)
{
if (thisApp.AppWindow is not null and
MainWindow mainWindow)
{
uiContext?.Post(_ => mainWindow.HandleLaunch(args), null);
}
// LOAD BEARING
// This must be synchronous to ensure the method does not return
// before the activation is fully handled and the parameters are processed.
// The sending instance remains blocked until this returns; afterward it may quit,
// causing the activation arguments to be lost.
mainWindow.HandleLaunchNonUI(args);
}
}
}