Skip to content

Commit bd3f22a

Browse files
committed
Service: complete staged upgrade after unmount or restart
Add PendingUpgradeHandler to apply staged upgrades using atomic file moves (old files to PreviousVersion, staged files to install dir). Skips GVFS.Service.exe (already replaced by installer, locked by running service). Safety mechanisms: - .ready marker: rejects PendingUpgrade if installer was interrupted - .phase1-complete marker: ensures crash during backup is recoverable (incomplete Phase 1 is restored and retried, not skipped) - Defers if any GVFS.Mount processes are still running Trigger upgrade in two ways: - On service start: checks before automount - After repo unmount: timer-based debounce (5s) so multiple unmounts in quick succession result in a single upgrade attempt Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent ab0ac7f commit bd3f22a

3 files changed

Lines changed: 491 additions & 0 deletions

File tree

GVFS/GVFS.Service/GVFSService.Windows.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ public void Run()
4343
metadata.Add("Version", ProcessHelper.GetCurrentProcessVersion());
4444
this.tracer.RelatedEvent(EventLevel.Informational, $"{nameof(GVFSService)}_{nameof(this.Run)}", metadata);
4545

46+
// Check for a staged upgrade before doing anything else.
47+
// If no GVFS.Mount processes are running (typical at boot or after
48+
// unmount-all), copy staged files in-place and proceed normally.
49+
// If mounts ARE running, the upgrade is deferred to next restart.
50+
PendingUpgradeHandler.TryApplyPendingUpgrade(this.tracer);
51+
4652
this.repoRegistry = new RepoRegistry(
4753
this.tracer,
4854
new PhysicalFileSystem(),

GVFS/GVFS.Service/Handlers/RequestHandler.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using GVFS.Common.NamedPipes;
22
using GVFS.Common.Tracing;
33
using System.Runtime.Serialization;
4+
using System.Threading;
45

56
namespace GVFS.Service.Handlers
67
{
@@ -14,6 +15,8 @@ namespace GVFS.Service.Handlers
1415
/// </summary>
1516
public class RequestHandler
1617
{
18+
private const int PendingUpgradeDelayMs = 5000;
19+
1720
protected const string EnableProjFSRequestDescription = "attach volume";
1821
protected string requestDescription;
1922

@@ -25,6 +28,7 @@ public class RequestHandler
2528
private string etwArea;
2629
private ITracer tracer;
2730
private IRepoRegistry repoRegistry;
31+
private Timer pendingUpgradeTimer;
2832

2933
public RequestHandler(ITracer tracer, string etwArea, IRepoRegistry repoRegistry)
3034
{
@@ -80,6 +84,14 @@ protected virtual void HandleMessage(
8084
UnregisterRepoHandler unmountHandler = new UnregisterRepoHandler(tracer, this.repoRegistry, connection, unmountRequest);
8185
unmountHandler.Run();
8286

87+
// After unmount, check for pending staged upgrade on a
88+
// background thread. The deferred check gives the calling
89+
// GVFS.Mount process time to exit so its executable is no
90+
// longer locked when the upgrade runs.
91+
// Use the long-lived service tracer, not the scoped activity
92+
// tracer which will be disposed when this handler returns.
93+
this.TryDeferredPendingUpgradeCheck(this.tracer);
94+
8395
break;
8496

8597
case NamedPipeMessages.GetActiveRepoListRequest.Header:
@@ -121,5 +133,35 @@ private void TrySendResponse(
121133
tracer.RelatedError($"{nameof(this.TrySendResponse)}: Could not send response to client. Reply Info: {message}");
122134
}
123135
}
136+
137+
private void TryDeferredPendingUpgradeCheck(ITracer tracer)
138+
{
139+
string installDir = Service.Configuration.AssemblyPath;
140+
string pendingUpgradeDir = System.IO.Path.Combine(installDir, PendingUpgradeHandler.PendingUpgradeDirectoryName);
141+
if (!System.IO.Directory.Exists(pendingUpgradeDir))
142+
{
143+
return;
144+
}
145+
146+
// Debounce: reset the timer on each unmount so the check fires
147+
// once after the last unmount settles. If multiple repos unmount
148+
// in quick succession, only one upgrade attempt runs.
149+
if (this.pendingUpgradeTimer == null)
150+
{
151+
this.pendingUpgradeTimer = new Timer(
152+
_ =>
153+
{
154+
tracer.RelatedInfo("TryDeferredPendingUpgradeCheck: Checking pending upgrade after unmount");
155+
PendingUpgradeHandler.TryApplyPendingUpgrade(tracer);
156+
},
157+
null,
158+
PendingUpgradeDelayMs,
159+
Timeout.Infinite);
160+
}
161+
else
162+
{
163+
this.pendingUpgradeTimer.Change(PendingUpgradeDelayMs, Timeout.Infinite);
164+
}
165+
}
124166
}
125167
}

0 commit comments

Comments
 (0)