diff --git a/GVFS/GVFS.Common/FileBasedLock.cs b/GVFS/GVFS.Common/FileBasedLock.cs index 9f709f2cf..e87fc7eb6 100644 --- a/GVFS/GVFS.Common/FileBasedLock.cs +++ b/GVFS/GVFS.Common/FileBasedLock.cs @@ -20,7 +20,23 @@ public FileBasedLock( protected string LockPath { get; } protected ITracer Tracer { get; } - public abstract bool TryAcquireLock(); + public bool TryAcquireLock() + { + return this.TryAcquireLock(out _); + } + + /// + /// Attempts to acquire the lock, providing the exception that prevented acquisition. + /// + /// + /// When the method returns false, contains the exception that prevented lock acquisition. + /// Callers can pattern-match on the exception type to distinguish lock contention + /// (e.g. with a sharing violation HResult) from + /// permission errors () or other failures. + /// Null when the method returns true. + /// + /// True if the lock was acquired, false otherwise. + public abstract bool TryAcquireLock(out Exception lockException); public abstract void Dispose(); } diff --git a/GVFS/GVFS.Common/GVFSConstants.cs b/GVFS/GVFS.Common/GVFSConstants.cs index cf52e1fbb..308ee91c6 100644 --- a/GVFS/GVFS.Common/GVFSConstants.cs +++ b/GVFS/GVFS.Common/GVFSConstants.cs @@ -116,6 +116,7 @@ public static class DotGVFS { public const string CorruptObjectsName = "CorruptObjects"; public const string LogName = "logs"; + public const string MountLock = "mount.lock"; public static class Databases { diff --git a/GVFS/GVFS.Common/ReturnCode.cs b/GVFS/GVFS.Common/ReturnCode.cs index f99f3875d..5243cb2f5 100644 --- a/GVFS/GVFS.Common/ReturnCode.cs +++ b/GVFS/GVFS.Common/ReturnCode.cs @@ -10,5 +10,6 @@ public enum ReturnCode NullRequestData = 5, UnableToRegisterForOfflineIO = 6, DehydrateFolderFailures = 7, + MountAlreadyRunning = 8, } } diff --git a/GVFS/GVFS.Mount/InProcessMount.cs b/GVFS/GVFS.Mount/InProcessMount.cs index c56627703..cdfa76307 100644 --- a/GVFS/GVFS.Mount/InProcessMount.cs +++ b/GVFS/GVFS.Mount/InProcessMount.cs @@ -85,6 +85,28 @@ public void Mount(EventLevel verbosity, Keywords keywords) { this.currentState = MountState.Mounting; + string mountLockPath = Path.Combine(this.enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.MountLock); + using (FileBasedLock mountLock = GVFSPlatform.Instance.CreateFileBasedLock( + new PhysicalFileSystem(), + this.tracer, + mountLockPath)) + { + if (!mountLock.TryAcquireLock(out Exception lockException)) + { + if (lockException is IOException) + { + this.FailMountAndExit(ReturnCode.MountAlreadyRunning, "Mount: Another mount process is already running."); + } + + this.FailMountAndExit("Mount: Failed to acquire mount lock: {0}", lockException.Message); + } + + this.MountWithLockAcquired(verbosity, keywords); + } + } + + private void MountWithLockAcquired(EventLevel verbosity, Keywords keywords) + { // Start auth + config query immediately — these are network-bound and don't // depend on repo metadata or cache paths. Every millisecond of network latency // we can overlap with local I/O is a win. @@ -296,6 +318,11 @@ private NamedPipeServer StartNamedPipe() } private void FailMountAndExit(string error, params object[] args) + { + this.FailMountAndExit(ReturnCode.GenericError, error, args); + } + + private void FailMountAndExit(ReturnCode returnCode, string error, params object[] args) { this.currentState = MountState.MountFailed; @@ -312,7 +339,7 @@ private void FailMountAndExit(string error, params object[] args) this.fileSystemCallbacks = null; } - Environment.Exit((int)ReturnCode.GenericError); + Environment.Exit((int)returnCode); } private T CreateOrReportAndExit(Func factory, string reportMessage) diff --git a/GVFS/GVFS.Platform.Windows/WindowsFileBasedLock.cs b/GVFS/GVFS.Platform.Windows/WindowsFileBasedLock.cs index a965304e4..edf1c43a0 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsFileBasedLock.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsFileBasedLock.cs @@ -36,8 +36,9 @@ public WindowsFileBasedLock( { } - public override bool TryAcquireLock() + public override bool TryAcquireLock(out Exception lockException) { + lockException = null; try { lock (this.deleteOnCloseStreamLock) @@ -63,13 +64,14 @@ public override bool TryAcquireLock() catch (IOException e) { // HResultErrorFileExists is expected when the lock file exists - // HResultErrorSharingViolation is expected when the lock file exists andanother GVFS process has acquired the lock file + // HResultErrorSharingViolation is expected when the lock file exists and another GVFS process has acquired the lock file if (e.HResult != HResultErrorFileExists && e.HResult != HResultErrorSharingViolation) { EventMetadata metadata = this.CreateLockMetadata(e); this.Tracer.RelatedWarning(metadata, $"{nameof(this.TryAcquireLock)}: IOException caught while trying to acquire lock"); } + lockException = e; this.DisposeStream(); return false; } @@ -78,6 +80,7 @@ public override bool TryAcquireLock() EventMetadata metadata = this.CreateLockMetadata(e); this.Tracer.RelatedWarning(metadata, $"{nameof(this.TryAcquireLock)}: UnauthorizedAccessException caught while trying to acquire lock"); + lockException = e; this.DisposeStream(); return false; } @@ -86,6 +89,7 @@ public override bool TryAcquireLock() EventMetadata metadata = this.CreateLockMetadata(e); this.Tracer.RelatedWarning(metadata, $"{nameof(this.TryAcquireLock)}: Win32Exception caught while trying to acquire lock"); + lockException = e; this.DisposeStream(); return false; } diff --git a/GVFS/GVFS.UnitTests/Mock/Common/MockFileBasedLock.cs b/GVFS/GVFS.UnitTests/Mock/Common/MockFileBasedLock.cs index c18c707b4..ba821d1d5 100644 --- a/GVFS/GVFS.UnitTests/Mock/Common/MockFileBasedLock.cs +++ b/GVFS/GVFS.UnitTests/Mock/Common/MockFileBasedLock.cs @@ -1,6 +1,7 @@ using GVFS.Common; using GVFS.Common.FileSystem; using GVFS.Common.Tracing; +using System; namespace GVFS.UnitTests.Mock.Common { @@ -14,8 +15,9 @@ public MockFileBasedLock( { } - public override bool TryAcquireLock() + public override bool TryAcquireLock(out Exception lockException) { + lockException = null; return true; }