From af4eb1c32659dbb734465a7adeb2342a0fd7c1a4 Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Fri, 15 May 2026 10:43:50 -0700 Subject: [PATCH] Fix: --unmount-all now triggers pending staged upgrade 'gvfs service --unmount-all' sets SkipUnregister on each unmount to preserve automount registration. This meant no UnregisterRepoRequest reached the service, so RequestHandler never called TryDeferredPendingUpgradeCheck and staged upgrades were never applied. Add PendingUpgradeCheckRequest message type. ServiceVerb sends it after the --unmount-all loop completes so the service can schedule the same deferred upgrade check that individual unmounts trigger. Also add 'unmount-all-triggers-upgrade' scenario to upgrade-tests.yaml CI matrix to cover this path end-to-end. Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella --- .github/workflows/upgrade-tests.yaml | 30 ++++++++++++ .../NamedPipes/NamedPipeMessages.cs | 23 +++++++++ GVFS/GVFS.Service/Handlers/RequestHandler.cs | 12 +++++ GVFS/GVFS/CommandLine/ServiceVerb.cs | 47 +++++++++++++++++++ 4 files changed, 112 insertions(+) diff --git a/.github/workflows/upgrade-tests.yaml b/.github/workflows/upgrade-tests.yaml index 44eaaac94..a2eb87640 100644 --- a/.github/workflows/upgrade-tests.yaml +++ b/.github/workflows/upgrade-tests.yaml @@ -33,6 +33,7 @@ jobs: - double-staging - staging-then-clean - mount-safety-deferral + - unmount-all-triggers-upgrade fail-fast: false steps: @@ -274,6 +275,35 @@ jobs: Write-Host "PASS: Mount safety deferral works correctly" } + "unmount-all-triggers-upgrade" { + Write-Host "=== Scenario: unmount-all triggers staged upgrade ===" + # Install LKG, mount, staging upgrade, then unmount via + # 'gvfs service --unmount-all' instead of 'gvfs unmount'. + # --unmount-all skips the unregister message, so without the + # PendingUpgradeCheckRequest fix the upgrade would never apply. + Install-GVFS $lkgInstaller + Assert-ServiceRunning + $mountPid = Mount-TestRepo + + Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true") + Assert-MountAlive $mountPid + Assert-PendingUpgrade $true + + # Unmount via --unmount-all (not per-repo unmount) + & "$installDir\gvfs.exe" service --unmount-all 2>&1 | Write-Host + if ($LASTEXITCODE -ne 0) { throw "unmount-all failed" } + + # The deferred upgrade timer fires after ~5 seconds. + # Wait for it to complete. + $deadline = (Get-Date).AddSeconds(30) + while ((Test-Path "$installDir\PendingUpgrade") -and (Get-Date) -lt $deadline) { + Start-Sleep -Seconds 2 + } + + Assert-PendingUpgrade $false + Write-Host "PASS: unmount-all triggers staged upgrade" + } + default { throw "Unknown scenario: ${{ matrix.scenario }}" } diff --git a/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs b/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs index d42c84873..2b3f5f5ca 100644 --- a/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs +++ b/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs @@ -427,6 +427,29 @@ public static Response FromMessage(Message message) } } + public class PendingUpgradeCheckRequest + { + public const string Header = nameof(PendingUpgradeCheckRequest); + + public static PendingUpgradeCheckRequest FromMessage(Message message) + { + return GVFSJsonOptions.Deserialize(message.Body); + } + + public Message ToMessage() + { + return new Message(Header, GVFSJsonOptions.Serialize(this)); + } + + public class Response : BaseResponse + { + public static Response FromMessage(Message message) + { + return GVFSJsonOptions.Deserialize(message.Body); + } + } + } + public class GetActiveRepoListRequest { public const string Header = nameof(GetActiveRepoListRequest); diff --git a/GVFS/GVFS.Service/Handlers/RequestHandler.cs b/GVFS/GVFS.Service/Handlers/RequestHandler.cs index 4d665c416..bafbc0ac9 100644 --- a/GVFS/GVFS.Service/Handlers/RequestHandler.cs +++ b/GVFS/GVFS.Service/Handlers/RequestHandler.cs @@ -95,6 +95,18 @@ protected virtual void HandleMessage( break; + case NamedPipeMessages.PendingUpgradeCheckRequest.Header: + this.requestDescription = "pending upgrade check"; + + this.TryDeferredPendingUpgradeCheck(this.tracer); + + NamedPipeMessages.PendingUpgradeCheckRequest.Response upgradeCheckResponse = + new NamedPipeMessages.PendingUpgradeCheckRequest.Response(); + upgradeCheckResponse.State = NamedPipeMessages.CompletionState.Success; + this.TrySendResponse(tracer, upgradeCheckResponse.ToMessage().ToString(), connection); + + break; + case NamedPipeMessages.GetActiveRepoListRequest.Header: this.requestDescription = RepoListRequestDescription; NamedPipeMessages.GetActiveRepoListRequest repoListRequest = NamedPipeMessages.GetActiveRepoListRequest.FromMessage(message); diff --git a/GVFS/GVFS/CommandLine/ServiceVerb.cs b/GVFS/GVFS/CommandLine/ServiceVerb.cs index 842521fa7..51fbd3dd5 100644 --- a/GVFS/GVFS/CommandLine/ServiceVerb.cs +++ b/GVFS/GVFS/CommandLine/ServiceVerb.cs @@ -136,6 +136,12 @@ public override void Execute() } } + // Notify the service so it can check for a pending staged + // upgrade. Individual unmounts skip unregister (to preserve + // automount registration), so the service's normal unmount + // trigger never fires during --unmount-all. + this.TryNotifyPendingUpgradeCheck(); + if (failedRepoRoots.Count() > 0) { string errorString = $"The following repos failed to unmount:{Environment.NewLine}{string.Join(Environment.NewLine, failedRepoRoots.ToArray())}"; @@ -217,5 +223,46 @@ private bool IsRepoMounted(string repoRoot) return false; } + + private void TryNotifyPendingUpgradeCheck() + { + NamedPipeMessages.PendingUpgradeCheckRequest request = new NamedPipeMessages.PendingUpgradeCheckRequest(); + + try + { + using (NamedPipeClient client = new NamedPipeClient(this.ServicePipeName)) + { + if (!client.Connect()) + { + this.Output.WriteLine(" WARNING: Could not notify GVFS.Service to check for pending upgrade (service not responding)."); + return; + } + + client.SendRequest(request.ToMessage()); + NamedPipeMessages.Message response = client.ReadResponse(); + if (response.Header == NamedPipeMessages.PendingUpgradeCheckRequest.Response.Header) + { + NamedPipeMessages.PendingUpgradeCheckRequest.Response typedResponse = + NamedPipeMessages.PendingUpgradeCheckRequest.Response.FromMessage(response); + if (typedResponse.State != NamedPipeMessages.CompletionState.Success) + { + this.Output.WriteLine(" WARNING: Pending upgrade check failed: " + typedResponse.ErrorMessage); + } + } + else + { + this.Output.WriteLine(" WARNING: GVFS.Service responded with unexpected message: " + response.Header); + } + } + } + catch (BrokenPipeException) + { + this.Output.WriteLine(" WARNING: Could not notify GVFS.Service to check for pending upgrade."); + } + catch (Exception ex) + { + this.Output.WriteLine(" WARNING: Error notifying GVFS.Service to check for pending upgrade: " + ex.Message); + } + } } }