Skip to content

Commit 1be9cdb

Browse files
tyrielvCopilot
andcommitted
Merge upstream/master into tyrielv/hydration-status-1-itracer
Resolve conflicts in EnlistmentHydrationSummary.cs and GitStatusCache.cs by combining ITracer + Stopwatch timing from this branch with CancellationToken support from PR microsoft#1914. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents d695489 + 6cbd3dc commit 1be9cdb

5 files changed

Lines changed: 930 additions & 38 deletions

File tree

GVFS/GVFS.Common/GitStatusCache.cs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public class GitStatusCache : IDisposable
4747
private bool isStopping;
4848
private bool isInitialized;
4949
private StatusStatistics statistics;
50+
private CancellationTokenSource shutdownTokenSource;
51+
private Task activeHydrationTask;
5052

5153
private volatile CacheState cacheState = CacheState.Dirty;
5254

@@ -65,6 +67,7 @@ public GitStatusCache(GVFSContext context, TimeSpan backoffTime)
6567
this.backoffTime = backoffTime;
6668
this.serializedGitStatusFilePath = this.context.Enlistment.GitStatusCachePath;
6769
this.statistics = new StatusStatistics();
70+
this.shutdownTokenSource = new CancellationTokenSource();
6871

6972
this.wakeUpThread = new AutoResetEvent(false);
7073
}
@@ -79,6 +82,7 @@ public virtual void Initialize()
7982
public virtual void Shutdown()
8083
{
8184
this.isStopping = true;
85+
this.shutdownTokenSource.Cancel();
8286

8387
if (this.isInitialized && this.updateStatusCacheThread != null)
8488
{
@@ -177,6 +181,27 @@ public virtual void Dispose()
177181
{
178182
this.Shutdown();
179183

184+
// Wait for the hydration task to complete before disposing the
185+
// token source it may still be using.
186+
if (this.activeHydrationTask != null)
187+
{
188+
try
189+
{
190+
this.activeHydrationTask.Wait();
191+
}
192+
catch (AggregateException)
193+
{
194+
}
195+
196+
this.activeHydrationTask = null;
197+
}
198+
199+
if (this.shutdownTokenSource != null)
200+
{
201+
this.shutdownTokenSource.Dispose();
202+
this.shutdownTokenSource = null;
203+
}
204+
180205
if (this.wakeUpThread != null)
181206
{
182207
this.wakeUpThread.Dispose();
@@ -317,10 +342,29 @@ private void RebuildStatusCacheIfNeeded(bool ignoreBackoff)
317342
if (needToRebuild)
318343
{
319344
this.statistics.RecordBackgroundStatusScanRun();
320-
this.UpdateHydrationSummary();
345+
346+
// Run hydration summary in parallel with git status — they are independent
347+
// operations and neither should delay the other.
348+
Task hydrationTask = Task.Run(() => this.UpdateHydrationSummary());
349+
this.activeHydrationTask = hydrationTask;
321350

322351
bool rebuildStatusCacheSucceeded = this.TryRebuildStatusCache();
323352

353+
// Wait for hydration to complete before logging final stats.
354+
try
355+
{
356+
hydrationTask.Wait();
357+
}
358+
catch (AggregateException ex)
359+
{
360+
EventMetadata errorMetadata = new EventMetadata();
361+
errorMetadata.Add("Area", EtwArea);
362+
errorMetadata.Add("Exception", ex.InnerException?.ToString());
363+
this.context.Tracer.RelatedError(
364+
errorMetadata,
365+
$"{nameof(GitStatusCache)}.{nameof(RebuildStatusCacheIfNeeded)}: Unhandled exception in hydration summary task.");
366+
}
367+
324368
TimeSpan delayedTime = startTime - this.initialDelayTime;
325369
TimeSpan statusRunTime = DateTime.UtcNow - startTime;
326370

@@ -356,7 +400,7 @@ private void UpdateHydrationSummary()
356400
* and this is also a convenient place to log telemetry for it.
357401
*/
358402
EnlistmentHydrationSummary hydrationSummary =
359-
EnlistmentHydrationSummary.CreateSummary(this.context.Enlistment, this.context.FileSystem, this.context.Tracer);
403+
EnlistmentHydrationSummary.CreateSummary(this.context.Enlistment, this.context.FileSystem, this.context.Tracer, cancellationToken: this.shutdownTokenSource.Token);
360404
EventMetadata metadata = new EventMetadata();
361405
metadata.Add("Area", EtwArea);
362406
if (hydrationSummary.IsValid)
@@ -372,14 +416,20 @@ private void UpdateHydrationSummary()
372416
metadata,
373417
Keywords.Telemetry);
374418
}
375-
else
419+
else if (hydrationSummary.Error != null)
376420
{
377-
metadata["Exception"] = hydrationSummary.Error?.ToString();
421+
metadata["Exception"] = hydrationSummary.Error.ToString();
378422
this.context.Tracer.RelatedWarning(
379423
metadata,
380424
$"{nameof(GitStatusCache)}{nameof(RebuildStatusCacheIfNeeded)}: hydration summary could not be calculated.",
381425
Keywords.Telemetry);
382426
}
427+
else
428+
{
429+
// Invalid summary with no error — likely cancelled during shutdown
430+
this.context.Tracer.RelatedInfo(
431+
$"{nameof(GitStatusCache)}{nameof(RebuildStatusCacheIfNeeded)}: hydration summary was cancelled.");
432+
}
383433
}
384434
catch (Exception ex)
385435
{

GVFS/GVFS.Common/HealthCalculator/EnlistmentHydrationSummary.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
using System;
55
using System.Diagnostics;
66
using System.IO;
7-
using System.Linq;
7+
using System.Linq;
8+
using System.Threading;
89

910
namespace GVFS.Common
1011
{
@@ -43,7 +44,8 @@ public string ToMessage()
4344
public static EnlistmentHydrationSummary CreateSummary(
4445
GVFSEnlistment enlistment,
4546
PhysicalFileSystem fileSystem,
46-
ITracer tracer)
47+
ITracer tracer,
48+
CancellationToken cancellationToken = default)
4749
{
4850
Stopwatch totalStopwatch = Stopwatch.StartNew();
4951
Stopwatch phaseStopwatch = new Stopwatch();
@@ -56,17 +58,23 @@ public static EnlistmentHydrationSummary CreateSummary(
5658
int totalFileCount = GetIndexFileCount(enlistment, fileSystem);
5759
long indexReadMs = phaseStopwatch.ElapsedMilliseconds;
5860

61+
cancellationToken.ThrowIfCancellationRequested();
62+
5963
EnlistmentPathData pathData = new EnlistmentPathData();
6064

6165
/* FUTURE: These could be optimized to only deal with counts instead of full path lists */
6266
phaseStopwatch.Restart();
6367
pathData.LoadPlaceholdersFromDatabase(enlistment);
6468
long placeholderLoadMs = phaseStopwatch.ElapsedMilliseconds;
6569

70+
cancellationToken.ThrowIfCancellationRequested();
71+
6672
phaseStopwatch.Restart();
6773
pathData.LoadModifiedPaths(enlistment, tracer);
6874
long modifiedPathsLoadMs = phaseStopwatch.ElapsedMilliseconds;
6975

76+
cancellationToken.ThrowIfCancellationRequested();
77+
7078
int hydratedFileCount = pathData.ModifiedFilePaths.Count + pathData.PlaceholderFilePaths.Count;
7179
int hydratedFolderCount = pathData.ModifiedFolderPaths.Count + pathData.PlaceholderFolderPaths.Count;
7280

@@ -108,6 +116,16 @@ public static EnlistmentHydrationSummary CreateSummary(
108116
TotalFileCount = totalFileCount,
109117
TotalFolderCount = totalFolderCount,
110118
};
119+
}
120+
catch (OperationCanceledException)
121+
{
122+
return new EnlistmentHydrationSummary()
123+
{
124+
HydratedFileCount = -1,
125+
HydratedFolderCount = -1,
126+
TotalFileCount = -1,
127+
TotalFolderCount = -1,
128+
};
111129
}
112130
catch (Exception e)
113131
{

0 commit comments

Comments
 (0)