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