-
Notifications
You must be signed in to change notification settings - Fork 461
Add Mac specific FileBasedLock implementation #213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
wilbaker
merged 8 commits into
microsoft:master
from
wilbaker:mac_func_test_reliability
Aug 24, 2018
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2ab1b2b
Cleanup FileBasedLock and make it Windows specific
wilbaker ddc5f85
Rename FileBasedLock.cs to WindowsFileBasedLock.cs and move to Window…
wilbaker 7b17d52
More cleanup, switch from interface to abstract class
wilbaker 3027507
Initial MacFileBasedLock implementation
wilbaker eca1bd2
Fix StyleCop errors
wilbaker 540aaf5
Cleanup and changes for PR feedback
wilbaker 394b337
Fix StyleCop errors
wilbaker 467c653
Remove code for writing signature
wilbaker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,300 +1,27 @@ | ||
| using GVFS.Common.FileSystem; | ||
| using GVFS.Common.Tracing; | ||
| using System; | ||
| using System.ComponentModel; | ||
| using System.IO; | ||
| using System.Text; | ||
|
|
||
| namespace GVFS.Common | ||
| { | ||
| public class FileBasedLock : IDisposable | ||
| public abstract class FileBasedLock : IDisposable | ||
| { | ||
| private const int HResultErrorSharingViolation = -2147024864; // -2147024864 = 0x80070020 = ERROR_SHARING_VIOLATION | ||
| private const int HResultErrorFileExists = -2147024816; // -2147024816 = 0x80070050 = ERROR_FILE_EXISTS | ||
| private const int DefaultStreamWriterBufferSize = 1024; // Copied from: http://referencesource.microsoft.com/#mscorlib/system/io/streamwriter.cs,5516ce201dc06b5f | ||
| private const long InvalidFileLength = -1; | ||
| private const string EtwArea = nameof(FileBasedLock); | ||
| private static readonly Encoding UTF8NoBOM = new UTF8Encoding(false, true); // Default encoding used by StreamWriter | ||
|
|
||
| private readonly object deleteOnCloseStreamLock = new object(); | ||
| private readonly PhysicalFileSystem fileSystem; | ||
| private readonly string lockPath; | ||
| private ITracer tracer; | ||
| private Stream deleteOnCloseStream; | ||
| private bool overwriteExistingLock; | ||
|
|
||
| /// <summary> | ||
| /// FileBasedLock constructor | ||
| /// </summary> | ||
| /// <param name="lockPath">Path to lock file</param> | ||
| /// <param name="signature">Text to write in lock file</param> | ||
| /// <param name="overwriteExistingLock"> | ||
| /// If true, FileBasedLock will attempt to overwrite an existing lock file (if one exists on disk) when | ||
| /// acquiring the lock file. | ||
| /// </param> | ||
| /// <remarks> | ||
| /// GVFS keeps an exclusive write handle open to lock files that it creates with FileBasedLock. This means that | ||
| /// FileBasedLock still ensures exclusivity when "overwriteExistingLock" is true if the lock file is only used for | ||
| /// coordination between multiple GVFS processes. | ||
| /// </remarks> | ||
| public FileBasedLock( | ||
| PhysicalFileSystem fileSystem, | ||
| ITracer tracer, | ||
| string lockPath, | ||
| string signature, | ||
| bool overwriteExistingLock) | ||
| { | ||
| this.fileSystem = fileSystem; | ||
| this.tracer = tracer; | ||
| this.lockPath = lockPath; | ||
| this.Signature = signature; | ||
| this.overwriteExistingLock = overwriteExistingLock; | ||
| } | ||
|
|
||
| public string Signature { get; private set; } | ||
|
|
||
| public bool TryAcquireLockAndDeleteOnClose() | ||
| { | ||
| try | ||
| { | ||
| lock (this.deleteOnCloseStreamLock) | ||
| { | ||
| if (this.IsOpen()) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| this.fileSystem.CreateDirectory(Path.GetDirectoryName(this.lockPath)); | ||
|
|
||
| this.deleteOnCloseStream = this.fileSystem.OpenFileStream( | ||
| this.lockPath, | ||
| this.overwriteExistingLock ? FileMode.Create : FileMode.CreateNew, | ||
| FileAccess.ReadWrite, | ||
| FileShare.Read, | ||
| FileOptions.DeleteOnClose, | ||
| callFlushFileBuffers: false); | ||
|
|
||
| // Pass in true for leaveOpen to ensure that lockStream stays open | ||
| using (StreamWriter writer = new StreamWriter( | ||
| this.deleteOnCloseStream, | ||
| UTF8NoBOM, | ||
| DefaultStreamWriterBufferSize, | ||
| leaveOpen: true)) | ||
| { | ||
| this.WriteSignatureAndMessage(writer, message: null); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
| catch (IOException e) | ||
| { | ||
| // HResultErrorFileExists is expected when the lock file exists | ||
| // HResultErrorSharingViolation is expected when the lock file exists and we're in this.overwriteExistingLock mode, as | ||
| // another GVFS process has likely acquired the lock file | ||
| if (e.HResult != HResultErrorFileExists && | ||
| !(this.overwriteExistingLock && e.HResult == HResultErrorSharingViolation)) | ||
| { | ||
| EventMetadata metadata = this.CreateLockMetadata(e); | ||
| this.tracer.RelatedWarning(metadata, "TryAcquireLockAndDeleteOnClose: IOException caught while trying to acquire lock"); | ||
| } | ||
|
|
||
| this.DisposeStream(); | ||
| return false; | ||
| } | ||
| catch (UnauthorizedAccessException e) | ||
| { | ||
| EventMetadata metadata = this.CreateLockMetadata(e); | ||
| this.tracer.RelatedWarning(metadata, "TryAcquireLockAndDeleteOnClose: UnauthorizedAccessException caught while trying to acquire lock"); | ||
|
|
||
| this.DisposeStream(); | ||
| return false; | ||
| } | ||
| catch (Win32Exception e) | ||
| { | ||
| EventMetadata metadata = this.CreateLockMetadata(e); | ||
| this.tracer.RelatedWarning(metadata, "TryAcquireLockAndDeleteOnClose: Win32Exception caught while trying to acquire lock"); | ||
|
|
||
| this.DisposeStream(); | ||
| return false; | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| EventMetadata metadata = this.CreateLockMetadata(e); | ||
| this.tracer.RelatedError(metadata, "TryAcquireLockAndDeleteOnClose: Unhandled exception caught while trying to acquire lock"); | ||
|
|
||
| this.DisposeStream(); | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| public bool TryReleaseLock() | ||
| { | ||
| if (this.DisposeStream()) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| LockData lockData = this.GetLockDataFromDisk(); | ||
| if (lockData == null || lockData.Signature != this.Signature) | ||
| { | ||
| if (lockData == null) | ||
| { | ||
| throw new LockFileDoesNotExistException(this.lockPath); | ||
| } | ||
|
|
||
| throw new LockSignatureDoesNotMatchException(this.lockPath, this.Signature, lockData.Signature); | ||
| } | ||
|
|
||
| try | ||
| { | ||
| this.fileSystem.DeleteFile(this.lockPath); | ||
| } | ||
| catch (IOException e) | ||
| { | ||
| EventMetadata metadata = this.CreateLockMetadata(e); | ||
| this.tracer.RelatedWarning(metadata, "TryReleaseLock: IOException caught while trying to release lock"); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| public bool IsOpen() | ||
| { | ||
| return this.deleteOnCloseStream != null; | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| this.Dispose(true); | ||
| GC.SuppressFinalize(this); | ||
| } | ||
|
|
||
| protected void Dispose(bool disposing) | ||
| { | ||
| if (disposing) | ||
| { | ||
| this.DisposeStream(); | ||
| } | ||
| } | ||
|
|
||
| private LockData GetLockDataFromDisk() | ||
| string lockPath) | ||
| { | ||
| if (this.LockFileExists()) | ||
| { | ||
| string existingSignature; | ||
| string existingMessage; | ||
| this.ReadLockFile(out existingSignature, out existingMessage); | ||
| return new LockData(existingSignature, existingMessage); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private void ReadLockFile(out string existingSignature, out string lockerMessage) | ||
| { | ||
| using (Stream fs = this.fileSystem.OpenFileStream(this.lockPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, callFlushFileBuffers: false)) | ||
| using (StreamReader reader = new StreamReader(fs, UTF8NoBOM)) | ||
| { | ||
| existingSignature = reader.ReadLine(); | ||
| lockerMessage = reader.ReadLine(); | ||
| } | ||
|
|
||
| existingSignature = existingSignature ?? string.Empty; | ||
| lockerMessage = lockerMessage ?? string.Empty; | ||
| } | ||
|
|
||
| private bool LockFileExists() | ||
| { | ||
| return this.fileSystem.FileExists(this.lockPath); | ||
| } | ||
|
|
||
| private void WriteSignatureAndMessage(StreamWriter writer, string message) | ||
| { | ||
| writer.WriteLine(this.Signature); | ||
| if (message != null) | ||
| { | ||
| writer.Write(message); | ||
| } | ||
| } | ||
|
|
||
| private EventMetadata CreateLockMetadata() | ||
| { | ||
| EventMetadata metadata = new EventMetadata(); | ||
| metadata.Add("Area", EtwArea); | ||
| metadata.Add("LockPath", this.lockPath); | ||
| metadata.Add("Signature", this.Signature); | ||
|
|
||
| return metadata; | ||
| } | ||
|
|
||
| private EventMetadata CreateLockMetadata(Exception exception = null) | ||
| { | ||
| EventMetadata metadata = this.CreateLockMetadata(); | ||
| if (exception != null) | ||
| { | ||
| metadata.Add("Exception", exception.ToString()); | ||
| } | ||
|
|
||
| return metadata; | ||
| this.FileSystem = fileSystem; | ||
| this.Tracer = tracer; | ||
| this.LockPath = lockPath; | ||
| } | ||
|
|
||
| private bool DisposeStream() | ||
| { | ||
| lock (this.deleteOnCloseStreamLock) | ||
| { | ||
| if (this.deleteOnCloseStream != null) | ||
| { | ||
| this.deleteOnCloseStream.Dispose(); | ||
| this.deleteOnCloseStream = null; | ||
| return true; | ||
| } | ||
| } | ||
| protected PhysicalFileSystem FileSystem { get; } | ||
| protected string LockPath { get; } | ||
| protected ITracer Tracer { get; } | ||
|
|
||
| return false; | ||
| } | ||
| public abstract bool TryAcquireLock(); | ||
|
|
||
| public class LockException : Exception | ||
| { | ||
| public LockException(string messageFormat, params string[] args) | ||
| : base(string.Format(messageFormat, args)) | ||
| { | ||
| } | ||
| } | ||
|
|
||
| public class LockFileDoesNotExistException : LockException | ||
| { | ||
| public LockFileDoesNotExistException(string lockPath) | ||
| : base("Lock file {0} does not exist", lockPath) | ||
| { | ||
| } | ||
| } | ||
|
|
||
| public class LockSignatureDoesNotMatchException : LockException | ||
| { | ||
| public LockSignatureDoesNotMatchException(string lockPath, string expectedSignature, string actualSignature) | ||
| : base( | ||
| "Lock file {0} does not contain expected signature '{1}' (existing signature: '{2}')", | ||
| lockPath, | ||
| expectedSignature, | ||
| actualSignature) | ||
| { | ||
| } | ||
| } | ||
|
|
||
| public class LockData | ||
| { | ||
| public LockData(string signature, string message) | ||
| { | ||
| this.Signature = signature; | ||
| this.Message = message; | ||
| } | ||
|
|
||
| public string Signature { get; } | ||
|
|
||
| public string Message { get; } | ||
| } | ||
| public abstract void Dispose(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need this method definition here. The interface should already be enough to require subclasses to implement this right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this might be leftover from when I was moving things around, let me try removing it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It ends up the abstract
Disposeis required, without it we get the error:FileBasedLock.cs(7,43): error CS0535: 'FileBasedLock' does not implement interface member 'IDisposable.Dispose()' [/Users/wilbaker/Repos/VFSForGit/src/GVFS/GVFS.Common/GVFS.Common.csproj]There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea you're right. I must be spending too much time in C++ these days :-)