From 5b08ccaff82d5e8a3d7576a2f04e448fedac8839 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Thu, 24 Apr 2025 16:56:22 +0400 Subject: [PATCH 1/2] fix: fix mutagen controller to release process on exception --- App/Services/MutagenController.cs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/App/Services/MutagenController.cs b/App/Services/MutagenController.cs index 3a68962..8563eca 100644 --- a/App/Services/MutagenController.cs +++ b/App/Services/MutagenController.cs @@ -16,9 +16,12 @@ using Coder.Desktop.Vpn.Utilities; using Grpc.Core; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using Serilog; using DaemonTerminateRequest = Coder.Desktop.MutagenSdk.Proto.Service.Daemon.TerminateRequest; using MutagenProtocol = Coder.Desktop.MutagenSdk.Proto.Url.Protocol; using SynchronizationTerminateRequest = Coder.Desktop.MutagenSdk.Proto.Service.Synchronization.TerminateRequest; +using Microsoft.Extensions.Hosting; namespace Coder.Desktop.App.Services; @@ -110,6 +113,8 @@ public sealed class MutagenController : ISyncSessionController // Protects all private non-readonly class members. private readonly RaiiSemaphoreSlim _lock = new(1, 1); + private readonly ILogger _logger; + private readonly CancellationTokenSource _stateUpdateCts = new(); private Task? _stateUpdateTask; @@ -139,15 +144,19 @@ public sealed class MutagenController : ISyncSessionController private string MutagenDaemonLog => Path.Combine(_mutagenDataDirectory, "daemon.log"); - public MutagenController(IOptions config) + public MutagenController(IOptions config, ILogger logger) { _mutagenExecutablePath = config.Value.MutagenExecutablePath; + _logger = logger; } public MutagenController(string executablePath, string dataDirectory) { _mutagenExecutablePath = executablePath; _mutagenDataDirectory = dataDirectory; + var builder = Host.CreateApplicationBuilder(); + builder.Services.AddSerilog(); + _logger = (ILogger)builder.Build().Services.GetService(typeof(ILogger))!; } public event EventHandler? StateChanged; @@ -440,9 +449,9 @@ private async Task EnsureDaemon(CancellationToken ct) { await StopDaemon(cts.Token); } - catch + catch (Exception stopEx) { - // ignored + _logger.LogError(stopEx, "failed to stop daemon"); } ReplaceState(new SyncSessionControllerStateModel @@ -494,6 +503,8 @@ private async Task StartDaemon(CancellationToken ct) } catch (Exception e) when (e is not OperationCanceledException) { + _logger.LogWarning(e, "failed to start daemon process, attempt {attempt} of {maxAttempts}", attempts, + maxAttempts); if (attempts == maxAttempts) throw; // back off a little and try again. @@ -548,8 +559,11 @@ private void StartDaemonProcess() // https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo.environment?view=net-8.0 _daemonProcess.StartInfo.UseShellExecute = false; _daemonProcess.StartInfo.RedirectStandardError = true; - // TODO: log exited process - // _daemonProcess.Exited += ... + _daemonProcess.EnableRaisingEvents = true; + _daemonProcess.Exited += (object? sender, EventArgs e) => + { + _logger.LogInformation("mutagen daemon exited with code {exitCode}", _daemonProcess?.ExitCode); + }; if (!_daemonProcess.Start()) throw new InvalidOperationException("Failed to start mutagen daemon process, Start returned false"); From 8dc446a86cab25c4f9889d29995d463a1ae3a5de Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Tue, 29 Apr 2025 12:13:24 +0400 Subject: [PATCH 2/2] chore: add debug logging to mutagen stop daemon --- App/Services/MutagenController.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/App/Services/MutagenController.cs b/App/Services/MutagenController.cs index 8563eca..1af2fca 100644 --- a/App/Services/MutagenController.cs +++ b/App/Services/MutagenController.cs @@ -578,6 +578,7 @@ private void StartDaemonProcess() /// private async Task StopDaemon(CancellationToken ct) { + _logger.LogDebug("stopping mutagen daemon"); var process = _daemonProcess; var client = _mutagenClient; var writer = _logWriter; @@ -590,17 +591,21 @@ private async Task StopDaemon(CancellationToken ct) if (client == null) { if (process == null) return; + _logger.LogDebug("no client; killing daemon process"); process.Kill(true); } else { try { + _logger.LogDebug("sending DaemonTerminateRequest"); await client.Daemon.TerminateAsync(new DaemonTerminateRequest(), cancellationToken: ct); } - catch + catch (Exception e) { + _logger.LogError(e, "failed to gracefully terminate agent"); if (process == null) return; + _logger.LogDebug("killing daemon process after failed graceful termination"); process.Kill(true); } } @@ -608,10 +613,12 @@ private async Task StopDaemon(CancellationToken ct) if (process == null) return; var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(TimeSpan.FromSeconds(5)); + _logger.LogDebug("waiting for process to exit"); await process.WaitForExitAsync(cts.Token); } finally { + _logger.LogDebug("cleaning up daemon process objects"); client?.Dispose(); process?.Dispose(); writer?.Dispose();