From cedeeaa3ba68e86d4aa0ec3e5d453b8c033f7f6b Mon Sep 17 00:00:00 2001 From: William Baker Date: Thu, 8 Aug 2019 14:46:40 -0700 Subject: [PATCH] Remove dead GVFSLock and GitStatusCache code --- GVFS/GVFS.Common/GVFSConstants.cs | 5 - GVFS/GVFS.Common/GVFSLock.cs | 308 +--------- GVFS/GVFS.Common/GVFSPlatform.cs | 2 - GVFS/GVFS.Common/GitStatusCache.cs | 581 ------------------ GVFS/GVFS.Common/GitStatusCacheConfig.cs | 89 --- .../NamedPipes/LockNamedPipeMessages.cs | 420 ------------- .../NamedPipes/NamedPipeMessages.cs | 49 +- GVFS/GVFS.Mount/InProcessMount.cs | 15 +- GVFS/GVFS.Mount/InProcessMountVerb.cs | 9 +- GVFS/GVFS.Platform.POSIX/POSIXPlatform.cs | 6 - GVFS/GVFS.Platform.Windows/WindowsPlatform.cs | 5 - .../Common/GitStatusCacheTests.cs | 195 ------ GVFS/GVFS.UnitTests/Common/NamedPipeTests.cs | 56 -- .../Mock/Common/MockGitStatusCache.cs | 59 -- .../Mock/Common/MockPlatform.cs | 5 - GVFS/GVFS/CommandLine/GVFSVerb.cs | 11 - GVFS/GVFS/CommandLine/StatusVerb.cs | 1 - 17 files changed, 51 insertions(+), 1765 deletions(-) delete mode 100644 GVFS/GVFS.Common/GitStatusCache.cs delete mode 100644 GVFS/GVFS.Common/GitStatusCacheConfig.cs delete mode 100644 GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs delete mode 100644 GVFS/GVFS.UnitTests/Common/GitStatusCacheTests.cs delete mode 100644 GVFS/GVFS.UnitTests/Common/NamedPipeTests.cs delete mode 100644 GVFS/GVFS.UnitTests/Mock/Common/MockGitStatusCache.cs diff --git a/GVFS/GVFS.Common/GVFSConstants.cs b/GVFS/GVFS.Common/GVFSConstants.cs index 0764e74aa3..faac54bad5 100644 --- a/GVFS/GVFS.Common/GVFSConstants.cs +++ b/GVFS/GVFS.Common/GVFSConstants.cs @@ -46,11 +46,6 @@ public static class LocalGVFSConfig public const string OrgInfoServerUrl = "upgrade.orgInfoServerUrl"; } - public static class GitStatusCache - { - public const string EnableGitStatusCacheTokenFile = "EnableGitStatusCacheToken.dat"; - } - public static class Service { public const string ServiceName = "GVFS.Service"; diff --git a/GVFS/GVFS.Common/GVFSLock.cs b/GVFS/GVFS.Common/GVFSLock.cs index d29197e9f9..d5561c92d6 100644 --- a/GVFS/GVFS.Common/GVFSLock.cs +++ b/GVFS/GVFS.Common/GVFSLock.cs @@ -1,16 +1,13 @@ -using GVFS.Common.NamedPipes; using GVFS.Common.Tracing; -using System; using System.Diagnostics; using System.Threading; namespace GVFS.Common { - public partial class GVFSLock + public class GVFSLock { private readonly object acquisitionLock = new object(); private readonly ITracer tracer; - private readonly LockHolder currentLockHolder = new LockHolder(); public GVFSLock(ITracer tracer) { @@ -24,238 +21,6 @@ public ActiveGitCommandStats Stats private set; } - /// - /// Allows external callers (non-GVFS) to acquire the lock. - /// - /// The data for the external acquisition request. - /// The current holder of the lock if the acquisition fails. - /// True if the lock was acquired, false otherwise. - public bool TryAcquireLockForExternalRequestor( - NamedPipeMessages.LockData requestor, - out NamedPipeMessages.LockData existingExternalHolder) - { - EventMetadata metadata = new EventMetadata(); - EventLevel eventLevel = EventLevel.Verbose; - metadata.Add("LockRequest", requestor.ToString()); - metadata.Add("IsElevated", requestor.IsElevated); - - existingExternalHolder = null; - - try - { - lock (this.acquisitionLock) - { - if (this.currentLockHolder.IsGVFS) - { - metadata.Add("CurrentLockHolder", "GVFS"); - metadata.Add("Result", "Denied"); - - return false; - } - - existingExternalHolder = this.GetExternalHolder(); - if (existingExternalHolder != null) - { - metadata.Add("CurrentLockHolder", existingExternalHolder.ToString()); - metadata.Add("Result", "Denied"); - - return false; - } - - metadata.Add("Result", "Accepted"); - eventLevel = EventLevel.Informational; - - this.currentLockHolder.AcquireForExternalRequestor(requestor); - this.Stats = new ActiveGitCommandStats(); - - return true; - } - } - finally - { - this.tracer.RelatedEvent(eventLevel, "TryAcquireLockExternal", metadata); - } - } - - /// - /// Allow GVFS to acquire the lock. - /// - /// True if GVFS was able to acquire the lock or if it already held it. False othwerwise. - public bool TryAcquireLockForGVFS() - { - EventMetadata metadata = new EventMetadata(); - try - { - lock (this.acquisitionLock) - { - if (this.currentLockHolder.IsGVFS) - { - return true; - } - - NamedPipeMessages.LockData existingExternalHolder = this.GetExternalHolder(); - if (existingExternalHolder != null) - { - metadata.Add("CurrentLockHolder", existingExternalHolder.ToString()); - metadata.Add("Result", "Denied"); - return false; - } - - this.currentLockHolder.AcquireForGVFS(); - metadata.Add("Result", "Accepted"); - return true; - } - } - finally - { - this.tracer.RelatedEvent(EventLevel.Verbose, "TryAcquireLockInternal", metadata); - } - } - - public void ReleaseLockHeldByGVFS() - { - lock (this.acquisitionLock) - { - if (!this.currentLockHolder.IsGVFS) - { - throw new InvalidOperationException("Cannot release lock that is not held by GVFS"); - } - - this.tracer.RelatedEvent(EventLevel.Verbose, nameof(this.ReleaseLockHeldByGVFS), new EventMetadata()); - this.currentLockHolder.Release(); - } - } - - public bool ReleaseLockHeldByExternalProcess(int pid) - { - return this.ReleaseExternalLock(pid, nameof(this.ReleaseLockHeldByExternalProcess)); - } - - public NamedPipeMessages.LockData GetExternalHolder() - { - NamedPipeMessages.LockData externalHolder; - this.IsLockAvailable(checkExternalHolderOnly: true, existingExternalHolder: out externalHolder); - - return externalHolder; - } - - public bool IsLockAvailableForExternalRequestor(out NamedPipeMessages.LockData existingExternalHolder) - { - return this.IsLockAvailable(checkExternalHolderOnly: false, existingExternalHolder: out existingExternalHolder); - } - - public string GetLockedGitCommand() - { - // In this code path, we don't care if the process terminated without releasing the lock. The calling code - // is asking us about this lock so that it can determine if git was the cause of certain IO events. Even - // if the git process has terminated, the answer to that question does not change. - NamedPipeMessages.LockData currentHolder = this.currentLockHolder.GetExternalHolder(); - - if (currentHolder != null) - { - return currentHolder.ParsedCommand; - } - - return null; - } - - public string GetStatus() - { - lock (this.acquisitionLock) - { - if (this.currentLockHolder.IsGVFS) - { - return "Held by GVFS."; - } - - NamedPipeMessages.LockData externalHolder = this.GetExternalHolder(); - if (externalHolder != null) - { - return string.Format("Held by {0} (PID:{1})", externalHolder.ParsedCommand, externalHolder.PID); - } - } - - return "Free"; - } - - private bool IsLockAvailable(bool checkExternalHolderOnly, out NamedPipeMessages.LockData existingExternalHolder) - { - lock (this.acquisitionLock) - { - if (!checkExternalHolderOnly && - this.currentLockHolder.IsGVFS) - { - existingExternalHolder = null; - return false; - } - - bool externalHolderTerminatedWithoutReleasingLock; - existingExternalHolder = this.currentLockHolder.GetExternalHolder( - out externalHolderTerminatedWithoutReleasingLock); - - if (externalHolderTerminatedWithoutReleasingLock) - { - this.ReleaseLockForTerminatedProcess(existingExternalHolder.PID); - this.tracer.SetGitCommandSessionId(string.Empty); - existingExternalHolder = null; - } - - return existingExternalHolder == null; - } - } - - private bool ReleaseExternalLock(int pid, string eventName) - { - lock (this.acquisitionLock) - { - EventMetadata metadata = new EventMetadata(); - - try - { - if (this.currentLockHolder.IsGVFS) - { - metadata.Add("IsLockedByGVFS", "true"); - return false; - } - - // We don't care if the process has already terminated. We're just trying to record the info for the last holder. - NamedPipeMessages.LockData previousExternalHolder = this.currentLockHolder.GetExternalHolder(); - - if (previousExternalHolder == null) - { - metadata.Add("Result", "Failed (no current holder, requested PID=" + pid + ")"); - return false; - } - - metadata.Add("CurrentLockHolder", previousExternalHolder.ToString()); - metadata.Add("IsElevated", previousExternalHolder.IsElevated); - metadata.Add(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId); - - if (previousExternalHolder.PID != pid) - { - metadata.Add("pid", pid); - metadata.Add("Result", "Failed (wrong PID)"); - return false; - } - - this.currentLockHolder.Release(); - metadata.Add("Result", "Released"); - this.Stats.AddStatsToTelemetry(metadata); - - return true; - } - finally - { - this.tracer.RelatedEvent(EventLevel.Informational, eventName, metadata, Keywords.Telemetry); - } - } - } - - private void ReleaseLockForTerminatedProcess(int pid) - { - this.ReleaseExternalLock(pid, "ExternalLockHolderExited"); - } - // The lock release event is a convenient place to record stats about things that happened while a git command was running, // such as duration/count of object downloads during a git command, cache hits during a git command, etc. public class ActiveGitCommandStats @@ -364,76 +129,5 @@ public void AddStatsToTelemetry(EventMetadata metadata) metadata.Add("SizeQueryTimeMS", this.sizeQueryTimeMs); } } - - /// - /// This class manages the state of which process currently owns the GVFS lock. This code is complicated because - /// the lock can be held by us or by an external process, and because the external process that holds the lock - /// can terminate without releasing the lock. If that happens, we implicitly release the lock the next time we - /// check to see who is holding it. - /// - /// The goal of this class is to make it impossible for the rest of GVFSLock to read the external holder without being - /// aware of the fact that it could have terminated. - /// - /// This class assumes that the caller is handling all synchronization. - /// - private class LockHolder - { - private NamedPipeMessages.LockData externalLockHolder; - - public bool IsFree - { - get { return !this.IsGVFS && this.externalLockHolder == null; } - } - - public bool IsGVFS - { - get; private set; - } - - public void AcquireForGVFS() - { - if (this.externalLockHolder != null) - { - throw new InvalidOperationException("Cannot acquire for GVFS because there is an external holder"); - } - - this.IsGVFS = true; - } - - public void AcquireForExternalRequestor(NamedPipeMessages.LockData externalLockHolder) - { - if (this.IsGVFS || - this.externalLockHolder != null) - { - throw new InvalidOperationException("Cannot acquire a lock that is already held"); - } - - this.externalLockHolder = externalLockHolder; - } - - public void Release() - { - this.IsGVFS = false; - this.externalLockHolder = null; - } - - public NamedPipeMessages.LockData GetExternalHolder() - { - return this.externalLockHolder; - } - - public NamedPipeMessages.LockData GetExternalHolder(out bool externalHolderTerminatedWithoutReleasingLock) - { - externalHolderTerminatedWithoutReleasingLock = false; - - if (this.externalLockHolder != null) - { - int pid = this.externalLockHolder.PID; - externalHolderTerminatedWithoutReleasingLock = !GVFSPlatform.Instance.IsProcessActive(pid); - } - - return this.externalLockHolder; - } - } } } diff --git a/GVFS/GVFS.Common/GVFSPlatform.cs b/GVFS/GVFS.Common/GVFSPlatform.cs index 4c9e660ff7..f12b759c5a 100644 --- a/GVFS/GVFS.Common/GVFSPlatform.cs +++ b/GVFS/GVFS.Common/GVFSPlatform.cs @@ -105,8 +105,6 @@ public static void Register(GVFSPlatform platform) public abstract bool TryGetGVFSEnlistmentRoot(string directory, out string enlistmentRoot, out string errorMessage); public abstract bool TryGetDefaultLocalCacheRoot(string enlistmentRoot, out string localCacheRoot, out string localCacheRootError); - public abstract bool IsGitStatusCacheSupported(); - public abstract FileBasedLock CreateFileBasedLock( PhysicalFileSystem fileSystem, ITracer tracer, diff --git a/GVFS/GVFS.Common/GitStatusCache.cs b/GVFS/GVFS.Common/GitStatusCache.cs deleted file mode 100644 index 0b633b6dfa..0000000000 --- a/GVFS/GVFS.Common/GitStatusCache.cs +++ /dev/null @@ -1,581 +0,0 @@ -using GVFS.Common.Git; -using GVFS.Common.NamedPipes; -using GVFS.Common.Tracing; -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace GVFS.Common -{ - /// - /// Responsible for orchestrating the Git Status Cache interactions. This is a cache of the results of running - /// the "git status" command. - /// - /// Consumers are responsible for invalidating the cache and directing it to rebuild. - /// - public class GitStatusCache : IDisposable - { - private const string EtwArea = nameof(GitStatusCache); - private const int DelayBeforeRunningLoopAgainMs = 1000; - - private readonly TimeSpan backoffTime; - - // arbitrary value used when deciding whether to print - // a message about a delayed status scan. - private readonly TimeSpan delayThreshold = TimeSpan.FromSeconds(0.5); - - private string serializedGitStatusFilePath; - - /// - /// The last time that the refresh loop noticed an - /// invalidation. - /// - private DateTime lastInvalidationTime = DateTime.MinValue; - - /// - /// This is the time the GitStatusCache started delaying refreshes. - /// - private DateTime initialDelayTime = DateTime.MinValue; - - private GVFSContext context; - - private AutoResetEvent wakeUpThread; - private Task updateStatusCacheThread; - private bool isStopping; - private bool isInitialized; - private StatusStatistics statistics; - - private volatile CacheState cacheState = CacheState.Dirty; - - private object cacheFileLock = new object(); - - public GitStatusCache(GVFSContext context, GitStatusCacheConfig config) - : this(context, config.BackoffTime) - { - } - - public GitStatusCache(GVFSContext context, TimeSpan backoffTime) - { - this.context = context; - this.backoffTime = backoffTime; - this.serializedGitStatusFilePath = this.context.Enlistment.GitStatusCachePath; - this.statistics = new StatusStatistics(); - - this.wakeUpThread = new AutoResetEvent(false); - } - - public virtual void Initialize() - { - this.isInitialized = true; - this.updateStatusCacheThread = Task.Factory.StartNew(this.SerializeStatusMainThread, TaskCreationOptions.LongRunning); - this.Invalidate(); - } - - public virtual void Shutdown() - { - this.isStopping = true; - - if (this.isInitialized && this.updateStatusCacheThread != null) - { - this.wakeUpThread.Set(); - this.updateStatusCacheThread.Wait(); - } - } - - /// - /// Invalidate the status cache. Does not cause the cache to refresh - /// If caller also wants to signal the refresh, they must call - /// . - /// - public virtual void Invalidate() - { - this.lastInvalidationTime = DateTime.UtcNow; - this.cacheState = CacheState.Dirty; - } - - public virtual bool IsCacheReadyAndUpToDate() - { - return this.cacheState == CacheState.Clean; - } - - public virtual void RefreshAsynchronously() - { - this.wakeUpThread.Set(); - } - - public void RefreshAndWait() - { - this.RebuildStatusCacheIfNeeded(ignoreBackoff: true); - } - - /// - /// The GitStatusCache gets a chance to approve / deny requests for a - /// command to take the GVFS lock. The GitStatusCache will only block - /// if the command is a status command and there is a blocking error - /// that might affect the correctness of the result. - /// - public virtual bool IsReadyForExternalAcquireLockRequests( - NamedPipeMessages.LockData requester, - out string infoMessage) - { - infoMessage = null; - if (!this.isInitialized) - { - return true; - } - - GitCommandLineParser gitCommand = new GitCommandLineParser(requester.ParsedCommand); - if (!gitCommand.IsVerb(GitCommandLineParser.Verbs.Status) || - gitCommand.IsSerializedStatus()) - { - return true; - } - - bool shouldAllowExternalRequest = true; - bool isCacheReady = false; - - lock (this.cacheFileLock) - { - if (this.IsCacheReadyAndUpToDate()) - { - isCacheReady = true; - } - else - { - if (!this.TryDeleteStatusCacheFile()) - { - shouldAllowExternalRequest = false; - infoMessage = string.Format("Unable to delete stale status cache file at: {0}", this.serializedGitStatusFilePath); - } - } - } - - if (isCacheReady) - { - this.statistics.RecordCacheReady(); - } - else - { - this.statistics.RecordCacheNotReady(); - } - - if (!shouldAllowExternalRequest) - { - this.statistics.RecordBlockedRequest(); - this.context.Tracer.RelatedWarning("GitStatusCache.IsReadyForExternalAcquireLockRequests: request blocked"); - } - - return shouldAllowExternalRequest; - } - - public virtual void Dispose() - { - this.Shutdown(); - - if (this.wakeUpThread != null) - { - this.wakeUpThread.Dispose(); - this.wakeUpThread = null; - } - - if (this.updateStatusCacheThread != null) - { - this.updateStatusCacheThread.Dispose(); - this.updateStatusCacheThread = null; - } - } - - public virtual bool WriteTelemetryandReset(EventMetadata metadata) - { - bool wroteTelemetry = false; - if (!this.isInitialized) - { - return wroteTelemetry; - } - - StatusStatistics statusStatistics = Interlocked.Exchange(ref this.statistics, new StatusStatistics()); - - if (statusStatistics.BackgroundStatusScanCount > 0) - { - wroteTelemetry = true; - metadata.Add("GitStatusCache.StatusScanCount", statusStatistics.BackgroundStatusScanCount); - } - - if (statusStatistics.BackgroundStatusScanErrorCount > 0) - { - wroteTelemetry = true; - metadata.Add("GitStatusCache.StatusScanErrorCount", statusStatistics.BackgroundStatusScanErrorCount); - } - - if (statusStatistics.CacheReadyCount > 0) - { - wroteTelemetry = true; - metadata.Add("GitStatusCache.CacheReadyCount", statusStatistics.CacheReadyCount); - } - - if (statusStatistics.CacheNotReadyCount > 0) - { - wroteTelemetry = true; - metadata.Add("GitStatusCache.CacheNotReadyCount", statusStatistics.CacheNotReadyCount); - } - - if (statusStatistics.BlockedRequestCount > 0) - { - wroteTelemetry = true; - metadata.Add("GitStatusCache.BlockedRequestCount", statusStatistics.BlockedRequestCount); - } - - return wroteTelemetry; - } - - private void SerializeStatusMainThread() - { - while (true) - { - try - { - this.wakeUpThread.WaitOne(); - - if (this.isStopping) - { - break; - } - - this.RebuildStatusCacheIfNeeded(ignoreBackoff: false); - - // Delay to throttle the rate of how often status is run. - // Do not run status again for at least this timeout. - Thread.Sleep(DelayBeforeRunningLoopAgainMs); - } - catch (Exception ex) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Area", EtwArea); - if (ex != null) - { - metadata.Add("Exception", ex.ToString()); - } - - this.context.Tracer.RelatedError(metadata, "Unhandled exception encountered on GitStatusCache background thread."); - Environment.Exit(1); - } - } - } - - private void RebuildStatusCacheIfNeeded(bool ignoreBackoff) - { - bool needToRebuild = false; - DateTime startTime; - - lock (this.cacheFileLock) - { - CacheState cacheState = this.cacheState; - startTime = DateTime.UtcNow; - - if (cacheState == CacheState.Clean) - { - this.context.Tracer.RelatedInfo("GitStatusCache.RebuildStatusCacheIfNeeded: Status Cache up-to-date."); - } - else if (!this.TryDeleteStatusCacheFile()) - { - // The cache is dirty, but we failed to delete the previous on disk cache. - // Do not rebuild the cache this time. Wait for the next invalidation - // to cause the thread to run again, or the on-disk cache will be deleted - // if a status command is run. - } - else if (!ignoreBackoff && - (startTime - this.lastInvalidationTime) < this.backoffTime) - { - // The approriate backoff time has not elapsed yet, - // If this is the 1st time we are delaying the background - // status scan (indicated by the initialDelayTime being set to - // DateTime.MinValue), mark the current time. We can then track - // how long the scan was delayed for. - if (this.initialDelayTime == DateTime.MinValue) - { - this.initialDelayTime = startTime; - } - - // Signal the background thread to run again, so it - // can check if the backoff time has elapsed and it should - // rebuild the status cache. - this.wakeUpThread.Set(); - } - else - { - // The cache is dirty, and we succeeded in deleting the previous on disk cache and the minimum - // backoff time has passed, so now we can rebuild the status cache. - needToRebuild = true; - } - } - - if (needToRebuild) - { - this.statistics.RecordBackgroundStatusScanRun(); - - bool rebuildStatusCacheSucceeded = this.TryRebuildStatusCache(); - - TimeSpan delayedTime = startTime - this.initialDelayTime; - TimeSpan statusRunTime = DateTime.UtcNow - startTime; - - string message = string.Format( - "GitStatusCache.RebuildStatusCacheIfNeeded: Done generating status. Cache state: {0}. Status scan time: {1:0.##}s.", - this.cacheState, - statusRunTime.TotalSeconds); - if (delayedTime > this.backoffTime + this.delayThreshold) - { - message += string.Format(" Status scan was delayed for: {0:0.##}s.", delayedTime.TotalSeconds); - } - - this.context.Tracer.RelatedInfo(message); - - this.initialDelayTime = DateTime.MinValue; - } - } - - /// - /// Rebuild the status cache. This will run the background status to - /// generate status results, and update the serialized status cache - /// file. - /// - private bool TryRebuildStatusCache() - { - try - { - this.context.FileSystem.CreateDirectory(this.context.Enlistment.GitStatusCacheFolder); - } - catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Area", EtwArea); - metadata.Add("Exception", ex.ToString()); - - this.context.Tracer.RelatedWarning( - metadata, - string.Format("GitStatusCache is unable to create git status cache folder at {0}.", this.context.Enlistment.GitStatusCacheFolder)); - return false; - } - - // The status cache is regenerated on mount. This means that even if the write to temp file - // and rename operation doesn't complete (due to a system crash), and there is a torn write, - // GVFS is still protected because a new status cache file will be generated on mount. - string tmpStatusFilePath = Path.Combine(this.context.Enlistment.GitStatusCacheFolder, Path.GetRandomFileName() + "_status.tmp"); - - GitProcess.Result statusResult = null; - - // Do not modify this block unless you completely understand the comments and code within - { - // We MUST set the state to Rebuilding _immediately before_ we call the `git status` command. That allows us to - // check afterwards if anything happened during the status command that should invalidate the cache, and we - // can discard its results if that happens. - this.cacheState = CacheState.Rebuilding; - - GitProcess git = this.context.Enlistment.CreateGitProcess(); - statusResult = git.SerializeStatus( - allowObjectDownloads: true, - serializePath: tmpStatusFilePath); - } - - bool rebuildSucceeded = false; - if (statusResult.ExitCodeIsSuccess) - { - lock (this.cacheFileLock) - { - // Only update the cache if our state is still Rebuilding. Otherwise, this indicates that another call - // to Invalidate came in, and moved the state back to Dirty. - if (this.cacheState == CacheState.Rebuilding) - { - rebuildSucceeded = this.MoveCacheFileToFinalLocation(tmpStatusFilePath); - if (rebuildSucceeded) - { - // We have to check the state once again, because it could have been invalidated while we were - // copying the file in the previous step. Here we do it as a CompareExchange to minimize any further races. - if (Interlocked.CompareExchange(ref this.cacheState, CacheState.Clean, CacheState.Rebuilding) != CacheState.Rebuilding) - { - // We did not succeed in setting the state to Clean. Note that we have already overwritten the on disk cache, - // but all users of the cache file first check the cacheState, and since the cacheState is not Clean, no one - // should ever read it. - - rebuildSucceeded = false; - } - } - - if (!rebuildSucceeded) - { - this.cacheState = CacheState.Dirty; - } - } - } - - if (!rebuildSucceeded) - { - try - { - this.context.FileSystem.DeleteFile(tmpStatusFilePath); - } - catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Area", EtwArea); - metadata.Add("Exception", ex.ToString()); - - this.context.Tracer.RelatedError( - metadata, - string.Format("GitStatusCache is unable to delete temporary status cache file at {0}.", tmpStatusFilePath)); - } - } - } - else - { - this.statistics.RecordBackgroundStatusScanError(); - this.context.Tracer.RelatedInfo("GitStatusCache.TryRebuildStatusCache: Error generating status: {0}", statusResult.Errors); - } - - return rebuildSucceeded; - } - - private bool TryDeleteStatusCacheFile() - { - Debug.Assert(Monitor.IsEntered(this.cacheFileLock), "Attempting to delete the git status cache file without the cacheFileLock"); - - try - { - if (this.context.FileSystem.FileExists(this.serializedGitStatusFilePath)) - { - this.context.FileSystem.DeleteFile(this.serializedGitStatusFilePath); - } - } - catch (IOException ex) when (ex is FileNotFoundException || ex is DirectoryNotFoundException) - { - // Unexpected, but maybe something deleted the file out from underneath us... - // As the file is deleted, lets continue with the status generation.. - } - catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Area", EtwArea); - metadata.Add("Exception", ex.ToString()); - - this.context.Tracer.RelatedWarning( - metadata, - string.Format("GitStatusCache encountered exception attempting to delete cache file at {0}.", this.serializedGitStatusFilePath), - Keywords.Telemetry); - - return false; - } - - return true; - } - - /// - /// Move (and overwrite) status cache file from the temporary location to the - /// expected location for the status cache file. - /// - /// True on success, False on failure - private bool MoveCacheFileToFinalLocation(string tmpStatusFilePath) - { - Debug.Assert(Monitor.IsEntered(this.cacheFileLock), "Attempting to update the git status cache file without the cacheFileLock"); - - try - { - this.context.FileSystem.MoveAndOverwriteFile(tmpStatusFilePath, this.serializedGitStatusFilePath); - return true; - } - catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is Win32Exception) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Area", EtwArea); - metadata.Add("Exception", ex.ToString()); - - this.context.Tracer.RelatedError( - metadata, - string.Format("GitStatusCache encountered exception attempting to update status cache file at {0} with {1}.", this.serializedGitStatusFilePath, tmpStatusFilePath)); - } - - return false; - } - - private class StatusStatistics - { - public int BackgroundStatusScanCount { get; private set; } - - public int BackgroundStatusScanErrorCount { get; private set; } - - public int CacheReadyCount { get; private set; } - - public int CacheNotReadyCount { get; private set; } - - public int BlockedRequestCount { get; private set; } - - /// - /// Record that a background status scan was run. This is the - /// status command that is run to populate the status cache. - /// - public void RecordBackgroundStatusScanRun() - { - this.BackgroundStatusScanCount++; - } - - /// - /// Record that an error was encountered while running - /// the background status scan. - /// - public void RecordBackgroundStatusScanError() - { - this.BackgroundStatusScanErrorCount++; - } - - /// - /// Record that a status command was run from the repository, - /// and the cache was not ready to answer it. - /// - public void RecordCacheNotReady() - { - this.CacheNotReadyCount++; - } - - /// - /// Record that a status command was run from the repository, - /// and the cache was ready to answer it. - /// - public void RecordCacheReady() - { - this.CacheReadyCount++; - } - - /// - /// Record that a status command was run from the repository, - /// and the cache blocked the request. This only happens - /// if there is a stale status cache file and it cannot be deleted. - /// - public void RecordBlockedRequest() - { - this.BlockedRequestCount++; - } - } - - // This should really be an enum, but because we need to CompareExchange it, - // we have to create a reference type that looks like an enum instead. - private class CacheState - { - public static readonly CacheState Dirty = new CacheState("Dirty"); - public static readonly CacheState Clean = new CacheState("Clean"); - public static readonly CacheState Rebuilding = new CacheState("Rebuilding"); - - private string name; - - private CacheState(string name) - { - this.name = name; - } - - public override string ToString() - { - return this.name; - } - } - } -} diff --git a/GVFS/GVFS.Common/GitStatusCacheConfig.cs b/GVFS/GVFS.Common/GitStatusCacheConfig.cs deleted file mode 100644 index 82598fb4ac..0000000000 --- a/GVFS/GVFS.Common/GitStatusCacheConfig.cs +++ /dev/null @@ -1,89 +0,0 @@ -using GVFS.Common.Git; -using GVFS.Common.Tracing; -using System; -using System.Linq; - -namespace GVFS.Common -{ - /// - /// Manage the reading of GitStatusCache configuration data from git config. - /// - public class GitStatusCacheConfig - { - private const string EtwArea = nameof(GitStatusCacheConfig); - - private static readonly TimeSpan DefaultBackoffTime = TimeSpan.FromSeconds(2); - - public GitStatusCacheConfig(TimeSpan backOffTime) - { - this.BackoffTime = backOffTime; - } - - public static GitStatusCacheConfig DefaultConfig { get; } = new GitStatusCacheConfig(DefaultBackoffTime); - - public TimeSpan BackoffTime { get; private set; } - - public static bool TryLoadFromGitConfig(ITracer tracer, Enlistment enlistment, out GitStatusCacheConfig gitStatusCacheConfig, out string error) - { - return TryLoadFromGitConfig(tracer, new GitProcess(enlistment), out gitStatusCacheConfig, out error); - } - - public static bool TryLoadFromGitConfig(ITracer tracer, GitProcess git, out GitStatusCacheConfig gitStatusCacheConfig, out string error) - { - gitStatusCacheConfig = DefaultConfig; - - int backOffTimeSeconds = (int)DefaultBackoffTime.TotalSeconds; - if (!TryLoadBackOffTime(git, out backOffTimeSeconds, out error)) - { - if (tracer != null) - { - tracer.RelatedError( - new EventMetadata - { - { "Area", EtwArea }, - { "error", error } - }, - $"{nameof(GitStatusCacheConfig.TryLoadFromGitConfig)}: TryLoadBackOffTime failed"); - } - - return false; - } - - gitStatusCacheConfig = new GitStatusCacheConfig(TimeSpan.FromSeconds(backOffTimeSeconds)); - - if (tracer != null) - { - tracer.RelatedEvent( - EventLevel.Informational, - "GitStatusCacheConfig_Loaded", - new EventMetadata - { - { "Area", EtwArea }, - { "BackOffTime", gitStatusCacheConfig.BackoffTime }, - { TracingConstants.MessageKey.InfoMessage, "GitStatusCacheConfigLoaded" } - }); - } - - return true; - } - - private static bool TryLoadBackOffTime(GitProcess git, out int backoffTimeSeconds, out string error) - { - bool returnVal = TryGetFromGitConfig( - git: git, - configName: GVFSConstants.GitConfig.GitStatusCacheBackoffConfig, - defaultValue: (int)DefaultBackoffTime.TotalSeconds, - minValue: 0, - value: out backoffTimeSeconds, - error: out error); - - return returnVal; - } - - private static bool TryGetFromGitConfig(GitProcess git, string configName, int defaultValue, int minValue, out int value, out string error) - { - GitProcess.ConfigResult result = git.GetFromConfig(configName); - return result.TryParseAsInt(defaultValue, minValue, out value, out error); - } - } -} diff --git a/GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs b/GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs deleted file mode 100644 index f4139c5272..0000000000 --- a/GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs +++ /dev/null @@ -1,420 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace GVFS.Common.NamedPipes -{ - /// - /// Define messages used to communicate via the named-pipe in GVFS. - /// - /// - /// This file contains only the message types that GVFS.Hooks is interested - /// in using. For all other messages see GVFS.Common/NamedPipeMessages. - /// - public static partial class NamedPipeMessages - { - public const string UnknownRequest = "UnknownRequest"; - - private const char MessageSeparator = '|'; - - public static class AcquireLock - { - public const string AcquireRequest = "AcquireLock"; - public const string DenyGVFSResult = "LockDeniedGVFS"; - public const string DenyGitResult = "LockDeniedGit"; - public const string AcceptResult = "LockAcquired"; - public const string AvailableResult = "LockAvailable"; - public const string MountNotReadyResult = "MountNotReady"; - public const string UnmountInProgressResult = "UnmountInProgress"; - - public class Response - { - public Response(string result, LockData responseData = null, string denyGVFSMessage = null) - { - this.Result = result; - this.ResponseData = responseData; - this.DenyGVFSMessage = denyGVFSMessage; - } - - public Response(Message message) - { - this.Result = message.Header; - - if (this.Result == DenyGVFSResult) - { - this.DenyGVFSMessage = message.Body; - } - else - { - this.ResponseData = LockData.FromBody(message.Body); - } - } - - public string Result { get; } - - public string DenyGVFSMessage { get; } - - public LockData ResponseData { get; } - - public Message CreateMessage() - { - string messageBody = null; - if (this.ResponseData != null) - { - messageBody = this.ResponseData.ToMessage(); - } - else if (this.DenyGVFSMessage != null) - { - messageBody = this.DenyGVFSMessage; - } - - return new Message(this.Result, messageBody); - } - } - } - - public static class ReleaseLock - { - public const string Request = "ReleaseLock"; - public const string SuccessResult = "LockReleased"; - public const string FailureResult = "ReleaseDenied"; - - public class Response - { - public Response(string result, ReleaseLockData responseData = null) - { - this.Result = result; - this.ResponseData = responseData; - } - - public Response(Message message) - { - this.Result = message.Header; - this.ResponseData = ReleaseLockData.FromBody(message.Body); - } - - public string Result { get; } - - public ReleaseLockData ResponseData { get; } - - public Message CreateMessage() - { - string messageBody = null; - if (this.ResponseData != null) - { - messageBody = this.ResponseData.ToMessage(); - } - - return new Message(this.Result, messageBody); - } - } - - public class ReleaseLockData - { - // Message Format - // FailedUpdateCount failedToUpdateFileList, List failedToDeleteFileList) - : this( - failedToUpdateFileList.Count, - failedToDeleteFileList.Count, - (failedToUpdateFileList.Count + failedToDeleteFileList.Count <= MaxReportedFileNames) ? failedToUpdateFileList : new List(), - (failedToUpdateFileList.Count + failedToDeleteFileList.Count <= MaxReportedFileNames) ? failedToDeleteFileList : new List()) - { - } - - private ReleaseLockData( - int failedToUpdateCount, - int failedToDeleteCount, - List failedToUpdateFileList, - List failedToDeleteFileList) - { - this.FailedToUpdateCount = failedToUpdateCount; - this.FailedToDeleteCount = failedToDeleteCount; - this.FailedToUpdateFileList = failedToUpdateFileList; - this.FailedToDeleteFileList = failedToDeleteFileList; - } - - public bool HasFailures - { - get - { - return this.FailedToUpdateCount > 0 || this.FailedToDeleteCount > 0; - } - } - - public int FailedToUpdateCount { get; private set; } - public int FailedToDeleteCount { get; private set; } - public bool FailureCountExceedsMaxFileNames - { - get - { - return (this.FailedToUpdateCount + this.FailedToDeleteCount) > MaxReportedFileNames; - } - } - - public List FailedToUpdateFileList { get; private set; } - public List FailedToDeleteFileList { get; private set; } - - /// - /// Parses ReleaseLockData from the provided string. - /// - /// Message body (containing ReleaseLockData in string format) - /// - /// - ReleaseLockData when body is successfully parsed - /// - null when there is a parsing error - /// - internal static ReleaseLockData FromBody(string body) - { - if (!string.IsNullOrEmpty(body)) - { - string[] sections = body.Split(new char[] { SectionSeparator }); - - if (sections.Length != 4) - { - return null; - } - - int failedToUpdateCount; - if (!int.TryParse(sections[0], out failedToUpdateCount)) - { - return null; - } - - int failedToDeleteCount; - if (!int.TryParse(sections[1], out failedToDeleteCount)) - { - return null; - } - - List failedToUpdateFileList = null; - string[] updateParts = sections[2].Split(new char[] { MessageSeparator }, StringSplitOptions.RemoveEmptyEntries); - if (updateParts.Length > 0) - { - failedToUpdateFileList = new List(updateParts); - } - - List failedToDeleteFileList = null; - string[] deleteParts = sections[3].Split(new char[] { MessageSeparator }, StringSplitOptions.RemoveEmptyEntries); - if (deleteParts.Length > 0) - { - failedToDeleteFileList = new List(deleteParts); - } - - return new ReleaseLockData(failedToUpdateCount, failedToDeleteCount, failedToUpdateFileList, failedToDeleteFileList); - } - - return new ReleaseLockData(failedToUpdateCount: 0, failedToDeleteCount: 0, failedToUpdateFileList: null, failedToDeleteFileList: null); - } - - internal string ToMessage() - { - return - this.FailedToUpdateCount.ToString() + - SectionSeparator + - this.FailedToDeleteCount.ToString() + - SectionSeparator + - string.Join(MessageSeparator.ToString(), this.FailedToUpdateFileList) + - SectionSeparator + - string.Join(MessageSeparator.ToString(), this.FailedToDeleteFileList); - } - } - } - - public class LockRequest - { - public LockRequest(string messageBody) - { - this.RequestData = LockData.FromBody(messageBody); - } - - public LockRequest(int pid, bool isElevated, bool checkAvailabilityOnly, string parsedCommand, string gitCommandSessionId) - { - this.RequestData = new LockData(pid, isElevated, checkAvailabilityOnly, parsedCommand, gitCommandSessionId); - } - - public LockData RequestData { get; } - - public Message CreateMessage(string header) - { - return new Message(header, this.RequestData.ToMessage()); - } - } - - public class LockData - { - public LockData(int pid, bool isElevated, bool checkAvailabilityOnly, string parsedCommand, string gitCommandSessionId) - { - this.PID = pid; - this.GitCommandSessionId = gitCommandSessionId; - this.IsElevated = isElevated; - this.CheckAvailabilityOnly = checkAvailabilityOnly; - this.ParsedCommand = parsedCommand; - } - - public int PID { get; set; } - - public string GitCommandSessionId { get; set; } - - public bool IsElevated { get; set; } - - /// - /// Should the command actually acquire the GVFSLock or - /// only check if the lock is available. - /// - public bool CheckAvailabilityOnly { get; set; } - - /// - /// The command line requesting the lock, built internally for parsing purposes. - /// e.g. "git status", "git rebase" - /// - public string ParsedCommand { get; set; } - - public override string ToString() - { - return this.ParsedCommand + " (" + this.PID + ")"; - } - - internal static LockData FromBody(string body) - { - if (!string.IsNullOrEmpty(body)) - { - // This mesage is stored using the MessageSeperator delimiter for performance reasons - // Format of the body uses length prefixed string so that the strings can have the delimiter in them - // Examples: - // "123|true|false|13|parsedCommand|9|sessionId" - // "321|false|true|30|parsedCommand with | delimiter|26|sessionId with | delimiter" - string[] dataParts = body.Split(MessageSeparator); - int pid; - bool isElevated = false; - bool checkAvailabilityOnly = false; - string parsedCommand = null; - - if (dataParts.Length < 7) - { - throw new InvalidOperationException(string.Format("Invalid lock message. Expected at least 7 parts, got: {0} from message: '{1}'", dataParts.Length, body)); - } - - if (!int.TryParse(dataParts[0], out pid)) - { - throw new InvalidOperationException(string.Format("Invalid lock message. Expected PID, got: {0} from message: '{1}'", dataParts[0], body)); - } - - if (!bool.TryParse(dataParts[1], out isElevated)) - { - throw new InvalidOperationException(string.Format("Invalid lock message. Expected bool for isElevated, got: {0} from message: '{1}'", dataParts[1], body)); - } - - if (!bool.TryParse(dataParts[2], out checkAvailabilityOnly)) - { - throw new InvalidOperationException(string.Format("Invalid lock message. Expected bool for checkAvailabilityOnly, got: {0} from message: '{1}'", dataParts[2], body)); - } - - if (!int.TryParse(dataParts[3], out int parsedCommandLength)) - { - throw new InvalidOperationException(string.Format("Invalid lock message. Expected command length, got: {0} from message: '{1}'", dataParts[3], body)); - } - - // ParsedCommandLength should be the length of the string at the end of the message - // Add the length of the previous parts, plus delimiters - int commandStartingSpot = dataParts[0].Length + dataParts[1].Length + dataParts[2].Length + dataParts[3].Length + 4; - if ((commandStartingSpot + parsedCommandLength) >= body.Length) - { - throw new InvalidOperationException(string.Format("Invalid lock message. The parsedCommand is an unexpected length, got: {0} from message: '{1}'", parsedCommandLength, body)); - } - - parsedCommand = body.Substring(commandStartingSpot, parsedCommandLength); - - // The session Id is after the parsed command with the length of the session Id string coming first - // Use the string after the parsed command string to get the session Id data - string sessionIdSubString = body.Substring(commandStartingSpot + parsedCommandLength + 1); - string[] sessionIdParts = sessionIdSubString.Split(MessageSeparator); - if (!int.TryParse(sessionIdParts[0], out int sessionIdLength)) - { - throw new InvalidOperationException(string.Format("Invalid lock message. Expected session id length, got: {0} from message: '{1}'", sessionIdParts[0], body)); - } - - // Validate the session Id data does not exceed the body of the message by using the previous - // command starting position and length and adding length of the part for the size of the session id plus the 2 delimiters - int sessionIdStartingSpot = commandStartingSpot + parsedCommandLength + sessionIdParts[0].Length + 2; - if ((sessionIdStartingSpot + sessionIdLength) != body.Length) - { - throw new InvalidOperationException(string.Format("Invalid lock message. The sessionId is an unexpected length, got: {0} from message: '{1}'", sessionIdLength, body)); - } - - string sessionId = body.Substring(sessionIdStartingSpot, sessionIdLength); - - return new LockData(pid, isElevated, checkAvailabilityOnly, parsedCommand, sessionId); - } - - return null; - } - - internal string ToMessage() - { - return string.Join( - MessageSeparator.ToString(), - this.PID, - this.IsElevated, - this.CheckAvailabilityOnly, - this.ParsedCommand.Length, - this.ParsedCommand, - this.GitCommandSessionId.Length, - this.GitCommandSessionId); - } - } - - public class Message - { - public Message(string header, string body) - { - this.Header = header; - this.Body = body; - } - - public string Header { get; } - - public string Body { get; } - - public static Message FromString(string message) - { - string header = null; - string body = null; - if (!string.IsNullOrEmpty(message)) - { - string[] parts = message.Split(new[] { NamedPipeMessages.MessageSeparator }, count: 2); - header = parts[0]; - if (parts.Length > 1) - { - body = parts[1]; - } - } - - return new Message(header, body); - } - - public override string ToString() - { - string result = string.Empty; - if (!string.IsNullOrEmpty(this.Header)) - { - result = this.Header; - } - - if (this.Body != null) - { - result = result + NamedPipeMessages.MessageSeparator + this.Body; - } - - return result; - } - } - } -} diff --git a/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs b/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs index 29c3ed0b9c..0e473e9c63 100644 --- a/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs +++ b/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs @@ -13,10 +13,12 @@ namespace GVFS.Common.NamedPipes /// public static partial class NamedPipeMessages { + public const string UnknownRequest = "UnknownRequest"; public const string UnknownGVFSState = "UnknownGVFSState"; public const string MountNotReadyResult = "MountNotReady"; private const string ResponseSuffix = "Response"; + private const char MessageSeparator = '|'; public enum CompletionState { @@ -41,7 +43,6 @@ public class Response public string RepoUrl { get; set; } public string CacheServer { get; set; } public int BackgroundOperationCount { get; set; } - public string LockStatus { get; set; } public string DiskLayoutVersion { get; set; } public static Response FromJson(string json) @@ -261,6 +262,52 @@ public Message ToMessage() } } + public class Message + { + public Message(string header, string body) + { + this.Header = header; + this.Body = body; + } + + public string Header { get; } + + public string Body { get; } + + public static Message FromString(string message) + { + string header = null; + string body = null; + if (!string.IsNullOrEmpty(message)) + { + string[] parts = message.Split(new[] { NamedPipeMessages.MessageSeparator }, count: 2); + header = parts[0]; + if (parts.Length > 1) + { + body = parts[1]; + } + } + + return new Message(header, body); + } + + public override string ToString() + { + string result = string.Empty; + if (!string.IsNullOrEmpty(this.Header)) + { + result = this.Header; + } + + if (this.Body != null) + { + result = result + NamedPipeMessages.MessageSeparator + this.Body; + } + + return result; + } + } + public class UnregisterRepoRequest { public const string Header = nameof(UnregisterRepoRequest); diff --git a/GVFS/GVFS.Mount/InProcessMount.cs b/GVFS/GVFS.Mount/InProcessMount.cs index a9568d64ac..1536b489a9 100644 --- a/GVFS/GVFS.Mount/InProcessMount.cs +++ b/GVFS/GVFS.Mount/InProcessMount.cs @@ -24,7 +24,6 @@ public class InProcessMount private CacheServerInfo cacheServer; private RetryConfig retryConfig; - private GitStatusCacheConfig gitStatusCacheConfig; private GVFSContext context; private GVFSGitObjects gitObjects; @@ -32,11 +31,10 @@ public class InProcessMount private MountState currentState; private ManualResetEvent unmountEvent; - public InProcessMount(ITracer tracer, GVFSEnlistment enlistment, CacheServerInfo cacheServer, RetryConfig retryConfig, GitStatusCacheConfig gitStatusCacheConfig, bool showDebugWindow) + public InProcessMount(ITracer tracer, GVFSEnlistment enlistment, CacheServerInfo cacheServer, RetryConfig retryConfig, bool showDebugWindow) { this.tracer = tracer; this.retryConfig = retryConfig; - this.gitStatusCacheConfig = gitStatusCacheConfig; this.cacheServer = cacheServer; this.enlistment = enlistment; this.showDebugWindow = showDebugWindow; @@ -307,7 +305,6 @@ private void HandleGetStatusRequest(NamedPipeServer.Connection connection) response.LocalCacheRoot = !string.IsNullOrWhiteSpace(this.enlistment.LocalCacheRoot) ? this.enlistment.LocalCacheRoot : this.enlistment.GitObjectsRoot; response.RepoUrl = this.enlistment.RepoUrl; response.CacheServer = this.cacheServer.ToString(); - response.LockStatus = this.context?.Repository.GVFSLock != null ? this.context.Repository.GVFSLock.GetStatus() : "Unavailable"; response.DiskLayoutVersion = $"{GVFSPlatform.Instance.DiskLayoutUpgrade.Version.CurrentMajorVersion}.{GVFSPlatform.Instance.DiskLayoutUpgrade.Version.CurrentMinorVersion}"; switch (this.currentState) @@ -381,16 +378,6 @@ private void MountAndStartWorkingDirectoryCallbacks(CacheServerInfo cache) GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(this.context.Tracer, this.context.Enlistment, cache, this.retryConfig); this.gitObjects = new GVFSGitObjects(this.context, objectRequestor); - GitStatusCache gitStatusCache = (!this.context.Unattended && GVFSPlatform.Instance.IsGitStatusCacheSupported()) ? new GitStatusCache(this.context, this.gitStatusCacheConfig) : null; - if (gitStatusCache != null) - { - this.tracer.RelatedInfo("Git status cache enabled. Backoff time: {0}ms", this.gitStatusCacheConfig.BackoffTime.TotalMilliseconds); - } - else - { - this.tracer.RelatedInfo("Git status cache is not enabled"); - } - this.maintenanceScheduler = this.CreateOrReportAndExit(() => new GitMaintenanceScheduler(this.context, this.gitObjects), "Failed to start maintenance scheduler"); int majorVersion; diff --git a/GVFS/GVFS.Mount/InProcessMountVerb.cs b/GVFS/GVFS.Mount/InProcessMountVerb.cs index 17d373b7c2..9776c8dfe0 100644 --- a/GVFS/GVFS.Mount/InProcessMountVerb.cs +++ b/GVFS/GVFS.Mount/InProcessMountVerb.cs @@ -122,14 +122,7 @@ public void Execute() this.ReportErrorAndExit(tracer, "Failed to determine GVFS timeout and max retries: " + error); } - GitStatusCacheConfig gitStatusCacheConfig; - if (!GitStatusCacheConfig.TryLoadFromGitConfig(tracer, enlistment, out gitStatusCacheConfig, out error)) - { - tracer.RelatedWarning("Failed to determine GVFS status cache backoff time: " + error); - gitStatusCacheConfig = GitStatusCacheConfig.DefaultConfig; - } - - InProcessMount mountHelper = new InProcessMount(tracer, enlistment, cacheServer, retryConfig, gitStatusCacheConfig, this.ShowDebugWindow); + InProcessMount mountHelper = new InProcessMount(tracer, enlistment, cacheServer, retryConfig, this.ShowDebugWindow); try { diff --git a/GVFS/GVFS.Platform.POSIX/POSIXPlatform.cs b/GVFS/GVFS.Platform.POSIX/POSIXPlatform.cs index 17cef7795d..e31ae5d373 100644 --- a/GVFS/GVFS.Platform.POSIX/POSIXPlatform.cs +++ b/GVFS/GVFS.Platform.POSIX/POSIXPlatform.cs @@ -217,12 +217,6 @@ public override bool TryGetDefaultLocalCacheRoot(string enlistmentRoot, out stri } } - public override bool IsGitStatusCacheSupported() - { - // Git status cache is only supported on Windows - return false; - } - public override bool TryKillProcessTree(int processId, out int exitCode, out string error) { ProcessResult result = ProcessHelper.Run("pkill", $"-P {processId}"); diff --git a/GVFS/GVFS.Platform.Windows/WindowsPlatform.cs b/GVFS/GVFS.Platform.Windows/WindowsPlatform.cs index efdab3cacc..6217ea059d 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsPlatform.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsPlatform.cs @@ -316,11 +316,6 @@ public override bool IsConsoleOutputRedirectedToFile() return WindowsPlatform.IsConsoleOutputRedirectedToFileImplementation(); } - public override bool IsGitStatusCacheSupported() - { - return File.Exists(Path.Combine(GVFSPlatform.Instance.GetDataRootForGVFSComponent(GVFSConstants.Service.ServiceName), GVFSConstants.GitStatusCache.EnableGitStatusCacheTokenFile)); - } - public override FileBasedLock CreateFileBasedLock( PhysicalFileSystem fileSystem, ITracer tracer, diff --git a/GVFS/GVFS.UnitTests/Common/GitStatusCacheTests.cs b/GVFS/GVFS.UnitTests/Common/GitStatusCacheTests.cs deleted file mode 100644 index 75e754afea..0000000000 --- a/GVFS/GVFS.UnitTests/Common/GitStatusCacheTests.cs +++ /dev/null @@ -1,195 +0,0 @@ -using GVFS.Common; -using GVFS.Common.Git; -using GVFS.Common.NamedPipes; -using GVFS.Tests.Should; -using GVFS.UnitTests.Category; -using GVFS.UnitTests.Mock.Common; -using GVFS.UnitTests.Mock.FileSystem; -using GVFS.UnitTests.Mock.Git; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.IO; - -namespace GVFS.UnitTests.Common -{ - [TestFixture] - public class GitStatusCacheTests - { - private static NamedPipeMessages.LockData statusCommandLockData = new NamedPipeMessages.LockData(123, false, false, "git status", "123"); - - private MockFileSystem fileSystem; - private MockGitProcess gitProcess; - private GVFSContext context; - private string gitParentPath; - private string gvfsMetadataPath; - private MockDirectory enlistmentDirectory; - - public static IEnumerable ExceptionsThrownByCreateDirectory - { - get - { - yield return new IOException("Error creating directory"); - yield return new UnauthorizedAccessException("Error creating directory"); - } - } - - [SetUp] - public void SetUp() - { - MockTracer tracer = new MockTracer(); - - string enlistmentRoot = Path.Combine("mock:", "GVFS", "UnitTests", "Repo"); - string statusCachePath = Path.Combine("mock:", "GVFS", "UnitTests", "Repo", GVFSPlatform.Instance.Constants.DotGVFSRoot, "gitStatusCache"); - - this.gitProcess = new MockGitProcess(); - this.gitProcess.SetExpectedCommandResult($"--no-optional-locks status \"--serialize={statusCachePath}", () => new GitProcess.Result(string.Empty, string.Empty, 0), true); - MockGVFSEnlistment enlistment = new MockGVFSEnlistment(enlistmentRoot, "fake://repoUrl", "fake://gitBinPath", this.gitProcess); - enlistment.InitializeCachePathsFromKey("fake:\\gvfsSharedCache", "fakeCacheKey"); - - this.gitParentPath = enlistment.WorkingDirectoryBackingRoot; - this.gvfsMetadataPath = enlistment.DotGVFSRoot; - - this.enlistmentDirectory = new MockDirectory( - enlistmentRoot, - new MockDirectory[] - { - new MockDirectory(this.gitParentPath, folders: null, files: null), - }, - null); - - this.enlistmentDirectory.CreateFile(Path.Combine(this.gitParentPath, ".git", "config"), ".git config Contents", createDirectories: true); - this.enlistmentDirectory.CreateFile(Path.Combine(this.gitParentPath, ".git", "HEAD"), ".git HEAD Contents", createDirectories: true); - this.enlistmentDirectory.CreateFile(Path.Combine(this.gitParentPath, ".git", "logs", "HEAD"), "HEAD Contents", createDirectories: true); - this.enlistmentDirectory.CreateFile(Path.Combine(this.gitParentPath, ".git", "info", "always_exclude"), "always_exclude Contents", createDirectories: true); - this.enlistmentDirectory.CreateDirectory(Path.Combine(this.gitParentPath, ".git", "objects", "pack")); - - this.fileSystem = new MockFileSystem(this.enlistmentDirectory); - this.fileSystem.AllowMoveFile = true; - this.fileSystem.DeleteNonExistentFileThrowsException = false; - - this.context = new GVFSContext( - tracer, - this.fileSystem, - new MockGitRepo(tracer, enlistment, this.fileSystem), - enlistment); - } - - [TearDown] - public void TearDown() - { - this.fileSystem = null; - this.gitProcess = null; - this.context = null; - this.gitParentPath = null; - this.gvfsMetadataPath = null; - this.enlistmentDirectory = null; - } - - [TestCase] - public void CanInvalidateCleanCache() - { - this.enlistmentDirectory.CreateFile(Path.Combine(this.gvfsMetadataPath, GVFSConstants.DotGVFS.GitStatusCache.CachePath), "Git status cache contents", createDirectories: true); - using (GitStatusCache statusCache = new GitStatusCache(this.context, TimeSpan.Zero)) - { - statusCache.Initialize(); - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - // Refresh the cache to put it into the clean state. - statusCache.RefreshAndWait(); - - bool result = statusCache.IsReadyForExternalAcquireLockRequests(statusCommandLockData, out _); - - result.ShouldBeTrue(); - statusCache.IsCacheReadyAndUpToDate().ShouldBeTrue(); - - // Invalidate the cache, and make sure that it transistions into - // the dirty state, and that commands are still allowed through. - statusCache.Invalidate(); - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - result = statusCache.IsReadyForExternalAcquireLockRequests(statusCommandLockData, out _); - result.ShouldBeTrue(); - - // After checking if we are ready for external lock requests, cache should still be dirty - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - statusCache.Shutdown(); - } - } - - [TestCase] - [Category(CategoryConstants.ExceptionExpected)] - public void CacheFileErrorShouldBlock() - { - this.fileSystem.DeleteFileThrowsException = true; - this.enlistmentDirectory.CreateFile(Path.Combine(this.gvfsMetadataPath, GVFSConstants.DotGVFS.GitStatusCache.CachePath), "Git status cache contents", createDirectories: true); - - using (GitStatusCache statusCache = new GitStatusCache(this.context, TimeSpan.Zero)) - { - statusCache.Initialize(); - - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - bool isReady = statusCache.IsReadyForExternalAcquireLockRequests(statusCommandLockData, out _); - isReady.ShouldBeFalse(); - - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - statusCache.Shutdown(); - } - } - - [TestCase] - public void CanRefreshCache() - { - this.enlistmentDirectory.CreateFile(Path.Combine(this.gvfsMetadataPath, GVFSConstants.DotGVFS.GitStatusCache.CachePath), "Git status cache contents", createDirectories: true); - using (GitStatusCache statusCache = new GitStatusCache(this.context, TimeSpan.Zero)) - { - statusCache.Initialize(); - - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - string message; - bool result = statusCache.IsReadyForExternalAcquireLockRequests(statusCommandLockData, out message); - result.ShouldBeTrue(); - - statusCache.RefreshAndWait(); - - result = statusCache.IsReadyForExternalAcquireLockRequests(statusCommandLockData, out message); - result.ShouldBeTrue(); - - statusCache.IsCacheReadyAndUpToDate().ShouldBeTrue(); - - statusCache.Shutdown(); - } - } - - [TestCaseSource("ExceptionsThrownByCreateDirectory")] - [Category(CategoryConstants.ExceptionExpected)] - public void HandlesExceptionsCreatingDirectory(Exception exceptionToThrow) - { - this.enlistmentDirectory.CreateFile(Path.Combine(this.gvfsMetadataPath, GVFSConstants.DotGVFS.GitStatusCache.CachePath), "Git status cache contents", createDirectories: true); - this.fileSystem.ExceptionThrownByCreateDirectory = exceptionToThrow; - using (GitStatusCache statusCache = new GitStatusCache(this.context, TimeSpan.Zero)) - { - statusCache.Initialize(); - - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - string message; - bool result = statusCache.IsReadyForExternalAcquireLockRequests(statusCommandLockData, out message); - result.ShouldBeTrue(); - - statusCache.RefreshAndWait(); - - result = statusCache.IsReadyForExternalAcquireLockRequests(statusCommandLockData, out message); - result.ShouldBeTrue(); - - statusCache.IsCacheReadyAndUpToDate().ShouldBeFalse(); - - statusCache.Shutdown(); - } - } - } -} diff --git a/GVFS/GVFS.UnitTests/Common/NamedPipeTests.cs b/GVFS/GVFS.UnitTests/Common/NamedPipeTests.cs deleted file mode 100644 index 8f7ab07137..0000000000 --- a/GVFS/GVFS.UnitTests/Common/NamedPipeTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using GVFS.Tests.Should; -using GVFS.UnitTests.Category; -using NUnit.Framework; -using System; -using static GVFS.Common.NamedPipes.NamedPipeMessages; - -namespace GVFS.UnitTests.Common -{ - [TestFixture] - public class NamedPipeTests - { - [TestCase] - public void LockData_FromBody_Simple() - { - // Verify simple vanilla parsing - LockData lockDataBefore = new LockData(1, true, true, "git status --serialize=D:\\Sources\\tqoscy2l.ud0_status.tmp --ignored=matching --untracked-files=complete", "123"); - LockData lockDataAfter = LockData.FromBody(lockDataBefore.ToMessage()); - lockDataAfter.PID.ShouldEqual(1); - lockDataAfter.IsElevated.ShouldEqual(true); - lockDataAfter.CheckAvailabilityOnly.ShouldEqual(true); - lockDataAfter.ParsedCommand.ShouldEqual("git status --serialize=D:\\Sources\\tqoscy2l.ud0_status.tmp --ignored=matching --untracked-files=complete"); - lockDataAfter.GitCommandSessionId.ShouldEqual("123"); - } - - [TestCase] - public void LockData_FromBody_WithDelimiters() - { - // Verify strings with "|" will work - LockData lockDataWithPipeBefore = new LockData(1, true, true, "git commit -m 'message with a | and another |'", "123|321"); - LockData lockDataWithPipeAfter = LockData.FromBody(lockDataWithPipeBefore.ToMessage()); - lockDataWithPipeAfter.PID.ShouldEqual(1); - lockDataWithPipeAfter.IsElevated.ShouldEqual(true); - lockDataWithPipeAfter.CheckAvailabilityOnly.ShouldEqual(true); - lockDataWithPipeAfter.ParsedCommand.ShouldEqual("git commit -m 'message with a | and another |'"); - lockDataWithPipeAfter.GitCommandSessionId.ShouldEqual("123|321"); - } - - [TestCase("1|true|true", "Invalid lock message. Expected at least 7 parts, got: 3 from message: '1|true|true'")] - [TestCase("123|true|true|10|git status", "Invalid lock message. Expected at least 7 parts, got: 5 from message: '123|true|true|10|git status'")] - [TestCase("blah|true|true|10|git status|9|sessionId", "Invalid lock message. Expected PID, got: blah from message: 'blah|true|true|10|git status|9|sessionId'")] - [TestCase("1|true|1|10|git status|9|sessionId", "Invalid lock message. Expected bool for checkAvailabilityOnly, got: 1 from message: '1|true|1|10|git status|9|sessionId'")] - [TestCase("1|1|true|10|git status|9|sessionId", "Invalid lock message. Expected bool for isElevated, got: 1 from message: '1|1|true|10|git status|9|sessionId'")] - [TestCase("1|true|true|true|git status|9|sessionId", "Invalid lock message. Expected command length, got: true from message: '1|true|true|true|git status|9|sessionId'")] - [TestCase("1|true|true|5|git status|9|sessionId", "Invalid lock message. Expected session id length, got: atus from message: '1|true|true|5|git status|9|sessionId'")] - [TestCase("1|true|true|10|git status|bad|sessionId", "Invalid lock message. Expected session id length, got: bad from message: '1|true|true|10|git status|bad|sessionId'")] - [TestCase("1|true|true|20|git status|9|sessionId", "Invalid lock message. Expected session id length, got: d from message: '1|true|true|20|git status|9|sessionId'")] - [TestCase("1|true|true|10|git status|5|sessionId", "Invalid lock message. The sessionId is an unexpected length, got: 5 from message: '1|true|true|10|git status|5|sessionId'")] - [TestCase("1|true|true|10|git status|20|sessionId", "Invalid lock message. The sessionId is an unexpected length, got: 20 from message: '1|true|true|10|git status|20|sessionId'")] - [Category(CategoryConstants.ExceptionExpected)] - public void LockData_FromBody_Exception(string body, string exceptionMessage) - { - InvalidOperationException exception = Assert.Throws(() => LockData.FromBody(body)); - exception.Message.ShouldEqual(exceptionMessage); - } - } -} diff --git a/GVFS/GVFS.UnitTests/Mock/Common/MockGitStatusCache.cs b/GVFS/GVFS.UnitTests/Mock/Common/MockGitStatusCache.cs deleted file mode 100644 index 4f4a27dd38..0000000000 --- a/GVFS/GVFS.UnitTests/Mock/Common/MockGitStatusCache.cs +++ /dev/null @@ -1,59 +0,0 @@ -using GVFS.Common; -using GVFS.Common.NamedPipes; -using GVFS.Common.Tracing; -using System; - -namespace GVFS.UnitTests.Mock.Common -{ - public class MockGitStatusCache : GitStatusCache - { - public MockGitStatusCache(GVFSContext context, TimeSpan backoff) - : base(context, backoff) - { - } - - public int InvalidateCallCount { get; private set; } - - public void ResetCalls() - { - this.InvalidateCallCount = 0; - } - - public override void Dispose() - { - } - - public override void Initialize() - { - } - - public override void Invalidate() - { - this.InvalidateCallCount++; - } - - public override bool IsReadyForExternalAcquireLockRequests(NamedPipeMessages.LockData requester, out string infoMessage) - { - infoMessage = string.Empty; - return true; - } - - public override bool IsCacheReadyAndUpToDate() - { - return false; - } - - public override void RefreshAsynchronously() - { - } - - public override void Shutdown() - { - } - - public override bool WriteTelemetryandReset(EventMetadata metadata) - { - return false; - } - } -} diff --git a/GVFS/GVFS.UnitTests/Mock/Common/MockPlatform.cs b/GVFS/GVFS.UnitTests/Mock/Common/MockPlatform.cs index 3224d02176..cec5455526 100644 --- a/GVFS/GVFS.UnitTests/Mock/Common/MockPlatform.cs +++ b/GVFS/GVFS.UnitTests/Mock/Common/MockPlatform.cs @@ -151,11 +151,6 @@ public override void PrepareProcessToRunInBackground() throw new NotSupportedException(); } - public override bool IsGitStatusCacheSupported() - { - return true; - } - public override FileBasedLock CreateFileBasedLock(PhysicalFileSystem fileSystem, ITracer tracer, string lockPath) { return new MockFileBasedLock(fileSystem, tracer, lockPath); diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs index d45acdbbdc..8199ce64ce 100644 --- a/GVFS/GVFS/CommandLine/GVFSVerb.cs +++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs @@ -101,17 +101,6 @@ public static bool TrySetRequiredGitConfigSettings(Enlistment enlistment) string expectedHooksPath = Path.Combine(enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Hooks.Root); expectedHooksPath = Paths.ConvertPathToGitFormat(expectedHooksPath); - string gitStatusCachePath = null; - if (!GVFSEnlistment.IsUnattended(tracer: null) && GVFSPlatform.Instance.IsGitStatusCacheSupported()) - { - gitStatusCachePath = Path.Combine( - enlistment.EnlistmentRoot, - GVFSPlatform.Instance.Constants.DotGVFSRoot, - GVFSConstants.DotGVFS.GitStatusCache.CachePath); - - gitStatusCachePath = Paths.ConvertPathToGitFormat(gitStatusCachePath); - } - // These settings are required for normal GVFS functionality. // They will override any existing local configuration values. // diff --git a/GVFS/GVFS/CommandLine/StatusVerb.cs b/GVFS/GVFS/CommandLine/StatusVerb.cs index 8be1bfb559..5906acc351 100644 --- a/GVFS/GVFS/CommandLine/StatusVerb.cs +++ b/GVFS/GVFS/CommandLine/StatusVerb.cs @@ -34,7 +34,6 @@ protected override void Execute(GVFSEnlistment enlistment) this.Output.WriteLine("Cache Server: " + getStatusResponse.CacheServer); this.Output.WriteLine("Local Cache: " + getStatusResponse.LocalCacheRoot); this.Output.WriteLine("Mount status: " + getStatusResponse.MountStatus); - this.Output.WriteLine("GVFS Lock: " + getStatusResponse.LockStatus); this.Output.WriteLine("Background operations: " + getStatusResponse.BackgroundOperationCount); this.Output.WriteLine("Disk layout version: " + getStatusResponse.DiskLayoutVersion); }