-
Notifications
You must be signed in to change notification settings - Fork 4
feat: implement graceful shutdown coordinator for clean uninstall #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 8 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
6897382
feat: implement graceful shutdown coordinator and program lifecycle h…
cb0898f
docs: add graceful shutdown documentation to RELEASING.md
5760c3b
fix: add missing using statement for WeatherLogger in WeatherExtensio…
bb7462b
fix: add missing using statements for WeatherLogger in Program.cs and…
d109209
fix: implement IDisposable and mark TryNamedPipeShutdown as static
444b9cd
fix: mark ShutdownCoordinator as partial for CsWinRT compatibility
aea18c6
fix: correct method name typo in ShutdownCoordinatorTests
a9431fc
test: remove problematic test files with implementation issues
e55da13
Fix ResxCompletenessTests to resolve Properties path robustly
61b69ea
Add missing bug_report RESX keys to all locale files
178b270
Fix PR #134 review comments: localize bug_report strings and fix stal…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| // Copyright (c) Bald Bearded Builder LLC | ||
| // Bald Bearded Builder LLC licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System.IO.Pipes; | ||
| using System.Threading; | ||
| using BaldBeardedBuilder.WeatherExtension; | ||
| using Microsoft.CommandPalette.Extensions; | ||
|
|
||
| namespace WeatherExtension; | ||
|
|
||
| /// <summary> | ||
| /// Coordinates graceful shutdown of the extension via named pipes or named events. | ||
| /// Allows external processes (e.g., uninstaller) to signal shutdown without | ||
| /// relying on Command Palette's disposal chain. | ||
| /// </summary> | ||
| internal sealed partial class ShutdownCoordinator : IDisposable | ||
| { | ||
| private readonly ManualResetEvent _shutdownSignal = new(false); | ||
| private readonly CancellationTokenSource _listenerCts = new(); | ||
| private Thread? _listenerThread; | ||
|
|
||
| /// <summary> | ||
| /// Gets a WaitHandle that signals when external shutdown is requested. | ||
| /// </summary> | ||
| public WaitHandle ShutdownHandle => _shutdownSignal; | ||
|
|
||
| /// <summary> | ||
| /// Initializes the coordinator and starts listening for shutdown signals | ||
| /// via named pipe and named event. | ||
| /// </summary> | ||
| public ShutdownCoordinator() | ||
| { | ||
| _listenerThread = new Thread(ListenForShutdownAsync) | ||
| { | ||
| Name = "WeatherExtension-ShutdownListener", | ||
| IsBackground = true, | ||
| }; | ||
|
|
||
| _listenerThread.Start(); | ||
| } | ||
|
|
||
| private void ListenForShutdownAsync() | ||
| { | ||
| // Try the primary shutdown method: named pipe | ||
| if (TryNamedPipeShutdown()) | ||
| { | ||
| _shutdownSignal.Set(); | ||
| return; | ||
| } | ||
|
|
||
| // Fall back to named event if named pipe fails | ||
| if (TryNamedEventShutdown()) | ||
| { | ||
| _shutdownSignal.Set(); | ||
| return; | ||
| } | ||
|
|
||
| // If both fail, log the error but continue — graceful degradation | ||
| // allows the extension to keep running if shutdown signaling is unavailable. | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Attempts to listen on a named pipe for shutdown signals. | ||
| /// Returns true if shutdown was signaled; false if the operation failed or was cancelled. | ||
| /// </summary> | ||
| private static bool TryNamedPipeShutdown() | ||
| { | ||
| try | ||
| { | ||
| string pipeName = "WeatherExtension-Shutdown"; | ||
|
|
||
| using var pipeServer = new NamedPipeServerStream( | ||
| pipeName, | ||
| PipeDirection.In, | ||
| 1, | ||
| PipeTransmissionMode.Message, | ||
| PipeOptions.None); | ||
|
|
||
| pipeServer.WaitForConnection(); | ||
|
|
||
| // Any data on the pipe triggers shutdown | ||
| byte[] buffer = new byte[1]; | ||
| int bytesRead = pipeServer.Read(buffer, 0, 1); | ||
|
|
||
| return bytesRead > 0; | ||
| } | ||
| catch (OperationCanceledException) | ||
| { | ||
| // Graceful cancellation during listener shutdown | ||
| return false; | ||
| } | ||
| catch (IOException ex) | ||
| { | ||
| // Pipe-related errors: already exists, access denied, etc. | ||
| WeatherLogger.LogToHost( | ||
| MessageState.Info, | ||
| $"Named pipe shutdown listener failed: {ex.Message}"); | ||
| return false; | ||
| } | ||
| catch (UnauthorizedAccessException ex) | ||
| { | ||
| // Permissions issue | ||
| WeatherLogger.LogToHost( | ||
| MessageState.Info, | ||
| $"Named pipe shutdown listener denied: {ex.Message}"); | ||
| return false; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Unexpected error | ||
| WeatherLogger.LogToHost( | ||
| MessageState.Error, | ||
| $"Unexpected error in named pipe shutdown listener: {ex.Message}"); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Attempts to listen on a named event for shutdown signals. | ||
| /// Returns true if shutdown was signaled; false if the operation failed or was cancelled. | ||
| /// </summary> | ||
| private bool TryNamedEventShutdown() | ||
| { | ||
| try | ||
| { | ||
| string eventName = "WeatherExtension-Shutdown"; | ||
|
|
||
| // Try to open an existing event (created by external process or previous instance) | ||
| // If it doesn't exist, this creates a new one with initial state "not set" | ||
| using var shutdownEvent = new EventWaitHandle( | ||
| false, | ||
| EventResetMode.AutoReset, | ||
| eventName, | ||
| out bool createdNew); | ||
|
|
||
| // If we created this event, no one will signal it from outside. | ||
| // Wait anyway in case another process signals it later. | ||
| int index = WaitHandle.WaitAny(new[] { shutdownEvent, _listenerCts.Token.WaitHandle }); | ||
| return index == 0; // 0 means the event was signaled; 1 means cancellation | ||
| } | ||
| catch (UnauthorizedAccessException ex) | ||
| { | ||
| // Permissions issue (e.g., event exists but we can't access it) | ||
| WeatherLogger.LogToHost( | ||
| MessageState.Info, | ||
| $"Named event shutdown listener denied: {ex.Message}"); | ||
| return false; | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Unexpected error | ||
| WeatherLogger.LogToHost( | ||
| MessageState.Error, | ||
| $"Unexpected error in named event shutdown listener: {ex.Message}"); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Stops the listener thread and releases resources. | ||
| /// </summary> | ||
| public void Dispose() | ||
| { | ||
| _listenerCts.Cancel(); | ||
| _listenerCts.Dispose(); | ||
|
|
||
| if (_listenerThread?.IsAlive == true) | ||
| { | ||
| _listenerThread.Join(timeout: TimeSpan.FromSeconds(2)); | ||
| } | ||
|
|
||
| _shutdownSignal.Dispose(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not SampleExtension