Skip to content

Commit 0ae14cd

Browse files
authored
Merge pull request #1915 from tyrielv/tyrielv/concurrent-mount
Add mount lock to prevent concurrent mount race condition
2 parents 0000e6e + d4546ac commit 0ae14cd

6 files changed

Lines changed: 56 additions & 5 deletions

File tree

GVFS/GVFS.Common/FileBasedLock.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,23 @@ public FileBasedLock(
2020
protected string LockPath { get; }
2121
protected ITracer Tracer { get; }
2222

23-
public abstract bool TryAcquireLock();
23+
public bool TryAcquireLock()
24+
{
25+
return this.TryAcquireLock(out _);
26+
}
27+
28+
/// <summary>
29+
/// Attempts to acquire the lock, providing the exception that prevented acquisition.
30+
/// </summary>
31+
/// <param name="lockException">
32+
/// When the method returns false, contains the exception that prevented lock acquisition.
33+
/// Callers can pattern-match on the exception type to distinguish lock contention
34+
/// (e.g. <see cref="System.IO.IOException"/> with a sharing violation HResult) from
35+
/// permission errors (<see cref="UnauthorizedAccessException"/>) or other failures.
36+
/// Null when the method returns true.
37+
/// </param>
38+
/// <returns>True if the lock was acquired, false otherwise.</returns>
39+
public abstract bool TryAcquireLock(out Exception lockException);
2440

2541
public abstract void Dispose();
2642
}

GVFS/GVFS.Common/GVFSConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public static class DotGVFS
117117
{
118118
public const string CorruptObjectsName = "CorruptObjects";
119119
public const string LogName = "logs";
120+
public const string MountLock = "mount.lock";
120121

121122
public static class Databases
122123
{

GVFS/GVFS.Common/ReturnCode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ public enum ReturnCode
1010
NullRequestData = 5,
1111
UnableToRegisterForOfflineIO = 6,
1212
DehydrateFolderFailures = 7,
13+
MountAlreadyRunning = 8,
1314
}
1415
}

GVFS/GVFS.Mount/InProcessMount.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,28 @@ public void Mount(EventLevel verbosity, Keywords keywords)
9292
{
9393
this.currentState = MountState.Mounting;
9494

95+
string mountLockPath = Path.Combine(this.enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.MountLock);
96+
using (FileBasedLock mountLock = GVFSPlatform.Instance.CreateFileBasedLock(
97+
new PhysicalFileSystem(),
98+
this.tracer,
99+
mountLockPath))
100+
{
101+
if (!mountLock.TryAcquireLock(out Exception lockException))
102+
{
103+
if (lockException is IOException)
104+
{
105+
this.FailMountAndExit(ReturnCode.MountAlreadyRunning, "Mount: Another mount process is already running.");
106+
}
107+
108+
this.FailMountAndExit("Mount: Failed to acquire mount lock: {0}", lockException.Message);
109+
}
110+
111+
this.MountWithLockAcquired(verbosity, keywords);
112+
}
113+
}
114+
115+
private void MountWithLockAcquired(EventLevel verbosity, Keywords keywords)
116+
{
95117
// Start auth + config query immediately — these are network-bound and don't
96118
// depend on repo metadata or cache paths. Every millisecond of network latency
97119
// we can overlap with local I/O is a win.
@@ -303,6 +325,11 @@ private NamedPipeServer StartNamedPipe()
303325
}
304326

305327
private void FailMountAndExit(string error, params object[] args)
328+
{
329+
this.FailMountAndExit(ReturnCode.GenericError, error, args);
330+
}
331+
332+
private void FailMountAndExit(ReturnCode returnCode, string error, params object[] args)
306333
{
307334
this.currentState = MountState.MountFailed;
308335

@@ -319,7 +346,7 @@ private void FailMountAndExit(string error, params object[] args)
319346
this.fileSystemCallbacks = null;
320347
}
321348

322-
Environment.Exit((int)ReturnCode.GenericError);
349+
Environment.Exit((int)returnCode);
323350
}
324351

325352
private T CreateOrReportAndExit<T>(Func<T> factory, string reportMessage)

GVFS/GVFS.Platform.Windows/WindowsFileBasedLock.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ public WindowsFileBasedLock(
3636
{
3737
}
3838

39-
public override bool TryAcquireLock()
39+
public override bool TryAcquireLock(out Exception lockException)
4040
{
41+
lockException = null;
4142
try
4243
{
4344
lock (this.deleteOnCloseStreamLock)
@@ -63,13 +64,14 @@ public override bool TryAcquireLock()
6364
catch (IOException e)
6465
{
6566
// HResultErrorFileExists is expected when the lock file exists
66-
// HResultErrorSharingViolation is expected when the lock file exists andanother GVFS process has acquired the lock file
67+
// HResultErrorSharingViolation is expected when the lock file exists and another GVFS process has acquired the lock file
6768
if (e.HResult != HResultErrorFileExists && e.HResult != HResultErrorSharingViolation)
6869
{
6970
EventMetadata metadata = this.CreateLockMetadata(e);
7071
this.Tracer.RelatedWarning(metadata, $"{nameof(this.TryAcquireLock)}: IOException caught while trying to acquire lock");
7172
}
7273

74+
lockException = e;
7375
this.DisposeStream();
7476
return false;
7577
}
@@ -78,6 +80,7 @@ public override bool TryAcquireLock()
7880
EventMetadata metadata = this.CreateLockMetadata(e);
7981
this.Tracer.RelatedWarning(metadata, $"{nameof(this.TryAcquireLock)}: UnauthorizedAccessException caught while trying to acquire lock");
8082

83+
lockException = e;
8184
this.DisposeStream();
8285
return false;
8386
}
@@ -86,6 +89,7 @@ public override bool TryAcquireLock()
8689
EventMetadata metadata = this.CreateLockMetadata(e);
8790
this.Tracer.RelatedWarning(metadata, $"{nameof(this.TryAcquireLock)}: Win32Exception caught while trying to acquire lock");
8891

92+
lockException = e;
8993
this.DisposeStream();
9094
return false;
9195
}

GVFS/GVFS.UnitTests/Mock/Common/MockFileBasedLock.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using GVFS.Common;
22
using GVFS.Common.FileSystem;
33
using GVFS.Common.Tracing;
4+
using System;
45

56
namespace GVFS.UnitTests.Mock.Common
67
{
@@ -14,8 +15,9 @@ public MockFileBasedLock(
1415
{
1516
}
1617

17-
public override bool TryAcquireLock()
18+
public override bool TryAcquireLock(out Exception lockException)
1819
{
20+
lockException = null;
1921
return true;
2022
}
2123

0 commit comments

Comments
 (0)