-
Notifications
You must be signed in to change notification settings - Fork 650
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
A lock file implementation applied to project GitVersionCore for resolving #1031 #2333
Changes from all commits
9ccf55c
38ee45f
e6754e2
d175f30
690087d
081dec9
d5c806d
3759415
2432a8c
90e57d1
a5edfba
d9cdcab
426f329
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using System; | ||
|
||
namespace GitVersion.FileLocking | ||
{ | ||
public interface IFileLock : IDisposable | ||
{ } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace GitVersion.FileLocking | ||
{ | ||
public interface IFileLocker | ||
{ | ||
FileLockUse WaitUntilAcquired(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,76 @@ | ||||||
using System; | ||||||
using System.Diagnostics; | ||||||
using System.IO; | ||||||
using System.Threading; | ||||||
|
||||||
namespace GitVersion.FileLocking | ||||||
{ | ||||||
|
||||||
#nullable enable | ||||||
|
||||||
internal class FileLockContext | ||||||
{ | ||||||
public FileStream? FileStream { get; } | ||||||
public Exception? Error { get; } | ||||||
public ManualResetEvent? ErrorUnlockDone { get; } | ||||||
|
||||||
private readonly FileLocker fileLocker; | ||||||
private object? decreaseLockUseLocker; | ||||||
|
||||||
private FileLockContext(FileLocker fileLocker, object decreaseLockUseLocker) | ||||||
{ | ||||||
this.fileLocker = fileLocker ?? throw new ArgumentNullException(nameof(fileLocker)); | ||||||
this.decreaseLockUseLocker = decreaseLockUseLocker; | ||||||
} | ||||||
|
||||||
public FileLockContext(FileLocker fileLocker, object decreaseLockUseLocker, FileStream fileStream) | ||||||
: this(fileLocker, decreaseLockUseLocker) | ||||||
{ | ||||||
fileStream = fileStream ?? throw new ArgumentNullException(nameof(fileStream)); | ||||||
FileStream = fileStream; | ||||||
} | ||||||
|
||||||
public FileLockContext(FileLocker fileLocker, object decreaseLockUseLocker, Exception error, ManualResetEvent errorUnlockDone) | ||||||
: this(fileLocker, decreaseLockUseLocker) | ||||||
{ | ||||||
Error = error ?? throw new ArgumentNullException(nameof(error)); | ||||||
ErrorUnlockDone = errorUnlockDone ?? throw new ArgumentNullException(nameof(errorUnlockDone)); | ||||||
} | ||||||
|
||||||
public void DecreaseLockUse(bool decreaseToZero, string lockId) | ||||||
{ | ||||||
if (FileStream == null) { | ||||||
throw new InvalidOperationException("You cannot decrease lock use when no file stream has been assgined."); | ||||||
} | ||||||
|
||||||
var decreaseLockUseLocker = this.decreaseLockUseLocker; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This assignment seems unnecessary and makes me think twice about what the below There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does lock the Unlock function. You will see that there is another lock in FileLocker in Unlock. These calls can interfer each other and can lead to race conditions like the comment does tell you. |
||||||
|
||||||
if (decreaseLockUseLocker == null) | ||||||
return; | ||||||
|
||||||
// Why surround by lock? | ||||||
// There is a race condition, when number of file lock uses | ||||||
// is decrased to 0. It may not have invalidated the file | ||||||
// stream yet. Now it can happen that the number of file lock | ||||||
// uses is increased to 1 due to file lock, but right after another | ||||||
// file unlock is about to decrease the number again to 0. | ||||||
// There is the possiblity that the actual file lock gets released | ||||||
// two times accidentally. | ||||||
lock (decreaseLockUseLocker) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
if (!(FileStream.CanRead || FileStream.CanWrite)) | ||||||
{ | ||||||
Trace.WriteLine($"{FileLocker.CurrentThreadWithLockIdPrefix(lockId)} Lock use has been invalidated before. Skip decreasing lock use.", FileLocker.TraceCategory); | ||||||
return; | ||||||
} | ||||||
|
||||||
var locksInUse = fileLocker.DecreaseLockUse(decreaseToZero, lockId); | ||||||
|
||||||
if (0 == locksInUse) | ||||||
{ | ||||||
this.decreaseLockUseLocker = null; | ||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
namespace GitVersion.FileLocking | ||
{ | ||
|
||
#nullable enable | ||
|
||
internal static class FileLockContextExtensions | ||
{ | ||
public static bool IsErroneous(this FileLockContext? fileLockContext) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why isn't this just an instance method on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the parameter "fileLockContext" can be null. With this "trick" you can shift the null check to this method. That's simply a shortcut. |
||
{ | ||
if (fileLockContext?.Error is null) | ||
return false; | ||
|
||
return true; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.IO; | ||
|
||
namespace GitVersion.FileLocking | ||
{ | ||
|
||
#nullable enable | ||
|
||
public struct FileLockUse : IDisposable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't quite understand what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It describes the use of a file lock. As one lock is shared by many I thought the term "use" of one file lock would be somewhat self-explanatory. |
||
{ | ||
public FileStream FileStream => fileLockContext.FileStream!; | ||
|
||
private readonly FileLockContext fileLockContext; | ||
[EditorBrowsable(EditorBrowsableState.Never)] | ||
public readonly string LockId; | ||
|
||
internal FileLockUse(FileLockContext fileLockContext, string LockId) | ||
{ | ||
this.fileLockContext = fileLockContext ?? throw new ArgumentNullException(nameof(fileLockContext)); | ||
|
||
if (fileLockContext.FileStream is null) | ||
{ | ||
throw new ArgumentException("File stream context has invalid file stream."); | ||
} | ||
|
||
this.LockId = LockId; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
// When stream not closed, we can decrease lock use. | ||
fileLockContext.DecreaseLockUse(false, LockId); | ||
} | ||
} | ||
} |
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.
TestStream is deriving from Stream, but need Lock implementation of FileStream for LockFileApi. So when needed, another Stream wrapper have to be written for FileStream.