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); + } + } } }