From 407ce5c696049e0aa1ac5adeb4b3c6d3eb0b64c8 Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Thu, 27 Aug 2026 19:48:44 -0500 Subject: [PATCH] Document CmdPal lifecycle contracts Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7a42451c-0ec4-4778-86fd-c77d7ac9d122 --- .../Services/ExtensionTaskCoordinator.cs | 3 +++ .../Services/JsonRpcExtensionService.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ExtensionTaskCoordinator.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ExtensionTaskCoordinator.cs index 780423ad27..1764d2566e 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ExtensionTaskCoordinator.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/ExtensionTaskCoordinator.cs @@ -51,6 +51,9 @@ internal static class ExtensionTaskCoordinator }); var results = await Task.WhenAll(tasks).ConfigureAwait(false); + + // Cancellation is not partial success. The current reload may stop here, and the next + // reload stops every service before rediscovering all providers. cancellationToken.ThrowIfCancellationRequested(); return results.OfType().ToArray(); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/JsonRpcExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/JsonRpcExtensionService.cs index a70de4718c..8ad3ad88de 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/JsonRpcExtensionService.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/JsonRpcExtensionService.cs @@ -82,6 +82,9 @@ public sealed partial class JsonRpcExtensionService : IExtensionService, IDispos private static readonly string ExtensionsPath = GetDefaultExtensionsPath(); private readonly TaskScheduler _taskScheduler; + + // This gate lives for the service lifetime. Disposing it while a start owns a permit + // could make its matching Release throw. private readonly SemaphoreSlim _extensionStartupGate = new(MaxConcurrentExtensionStarts, MaxConcurrentExtensionStarts); private readonly Lock _extensionsLock = new(); private readonly List _extensions = [];