From d65ba7f348703737fa75d9f1026447f6485518ce Mon Sep 17 00:00:00 2001 From: leileizhang Date: Thu, 26 Jun 2025 23:50:17 +0800 Subject: [PATCH] [CmdPal] Refactor ActionRuntime initialization to avoid repeated delays on failure (#40229) ## Summary of the Pull Request This PR addresses an issue where ActionRuntime initialization may repeatedly incur delays and fail during runtime usage, especially when the OS supports the API but action creation fails. ### Fix: Introduced ActionRuntimeManager to: - Manage a shared static ActionRuntime instance - Perform initialization once, at extension loading time - Store the result (success or failure) to avoid repeated creation attempts Consumers now: - ActionRuntimeManager.InstanceAsync.GetAwaiter().GetResult() Initialization will only be attempted up to 3 times. After that, runtime is marked as unavailable and no further attempts are made. ## PR Checklist - [x] **Closes:** #40228 - [ ] **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 ## Detailed Description of the Pull Request / Additional comments ## Validation Steps Performed --- .../Actions/ActionRuntimeManager.cs | 47 +++++++++++++++++++ .../IndexerCommandsProvider.cs | 6 +++ .../Pages/ActionsListContextItem.cs | 43 ++++++++++++----- 3 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeManager.cs diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeManager.cs new file mode 100644 index 0000000000..995c8a5f6c --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Actions/ActionRuntimeManager.cs @@ -0,0 +1,47 @@ +// 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.Threading; +using System.Threading.Tasks; +using ManagedCommon; +using Windows.AI.Actions; + +namespace Microsoft.CmdPal.Ext.Indexer.Data; + +public static class ActionRuntimeManager +{ + private static readonly Lazy> _lazyRuntime = new(InitializeAsync); + + public static Task InstanceAsync => _lazyRuntime.Value; + + private static async Task InitializeAsync() + { + // If we tried 3 times and failed, should we think the action runtime is not working? + // then we should not use it anymore. + const int maxAttempts = 3; + + for (var attempt = 1; attempt <= maxAttempts; attempt++) + { + try + { + var runtime = ActionRuntimeFactory.CreateActionRuntime(); + await Task.Delay(500); + + return runtime; + } + catch (Exception ex) + { + Logger.LogError($"Attempt {attempt} to initialize ActionRuntime failed: {ex.Message}"); + + if (attempt == maxAttempts) + { + Logger.LogError($"Failed to initialize ActionRuntime: {ex.Message}"); + } + } + } + + return null; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/IndexerCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/IndexerCommandsProvider.cs index c31d7c5176..2633607e11 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/IndexerCommandsProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/IndexerCommandsProvider.cs @@ -2,9 +2,11 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.CmdPal.Ext.Indexer.Data; using Microsoft.CmdPal.Ext.Indexer.Properties; using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; +using Windows.Foundation.Metadata; namespace Microsoft.CmdPal.Ext.Indexer; @@ -17,6 +19,10 @@ public partial class IndexerCommandsProvider : CommandProvider Id = "Files"; DisplayName = Resources.IndexerCommandsProvider_DisplayName; Icon = Icons.FileExplorer; + if (ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4)) + { + _ = ActionRuntimeManager.InstanceAsync; + } } public override ICommandItem[] TopLevelCommands() diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/ActionsListContextItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/ActionsListContextItem.cs index d191a5a08c..d8a9d15316 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/ActionsListContextItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Pages/ActionsListContextItem.cs @@ -2,10 +2,12 @@ // 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.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using ManagedCommon; using Microsoft.CmdPal.Ext.Indexer.Commands; using Microsoft.CmdPal.Ext.Indexer.Data; using Microsoft.CmdPal.Ext.Indexer.Properties; @@ -15,7 +17,7 @@ using Windows.System; namespace Microsoft.CmdPal.Ext.Indexer.Pages; -internal sealed partial class ActionsListContextItem : CommandContextItem +internal sealed partial class ActionsListContextItem : CommandContextItem, IDisposable { private readonly string fullPath; private readonly List actions = []; @@ -41,20 +43,24 @@ internal sealed partial class ActionsListContextItem : CommandContextItem private void UpdateMoreCommands() { - try + lock (UpdateMoreCommandsLock) { - lock (UpdateMoreCommandsLock) + if (actionRuntime == null) { - if (actionRuntime == null) - { - actionRuntime = ActionRuntimeFactory.CreateActionRuntime(); - Task.Delay(500).Wait(); - } - - actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed; - actionRuntime.ActionCatalog.Changed += ActionCatalog_Changed; + actionRuntime = ActionRuntimeManager.InstanceAsync.GetAwaiter().GetResult(); } + if (actionRuntime == null) + { + return; + } + + actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed; + actionRuntime.ActionCatalog.Changed += ActionCatalog_Changed; + } + + try + { var extension = System.IO.Path.GetExtension(fullPath).ToLower(CultureInfo.InvariantCulture); ActionEntity entity = null; if (extension != null) @@ -85,9 +91,20 @@ internal sealed partial class ActionsListContextItem : CommandContextItem MoreCommands = [.. actions]; } } - catch + catch (Exception ex) { - actionRuntime = null; + Logger.LogError($"Error updating commands: {ex.Message}"); + } + } + + public void Dispose() + { + lock (UpdateMoreCommandsLock) + { + if (actionRuntime != null) + { + actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed; + } } } }