From 9ccf55c5dcdc024887c15c7141c27d3de1030847 Mon Sep 17 00:00:00 2001 From: teroneko Date: Mon, 22 Jun 2020 04:49:08 +0100 Subject: [PATCH 01/13] Initial commit --- src/GitVersionCore/GitVersionCoreDefaults.cs | 12 + src/GitVersionCore/GitVersionCoreModule.cs | 12 + .../Helpers/Abstractions/IFileLock.cs | 10 + src/GitVersionCore/Helpers/LockFile.cs | 208 ++++++++++++++++++ src/GitVersionCore/Model/FileLock.cs | 17 ++ 5 files changed, 259 insertions(+) create mode 100644 src/GitVersionCore/GitVersionCoreDefaults.cs create mode 100644 src/GitVersionCore/Helpers/Abstractions/IFileLock.cs create mode 100644 src/GitVersionCore/Helpers/LockFile.cs create mode 100644 src/GitVersionCore/Model/FileLock.cs diff --git a/src/GitVersionCore/GitVersionCoreDefaults.cs b/src/GitVersionCore/GitVersionCoreDefaults.cs new file mode 100644 index 0000000000..01d16bc884 --- /dev/null +++ b/src/GitVersionCore/GitVersionCoreDefaults.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GitVersion +{ + public static class GitVersionCoreDefaults + { + public const int LockTimeoutInMilliseconds = 1000 * 15; + public const string LockFileNameWithExtensions = "GitVersion.lock"; + } +} diff --git a/src/GitVersionCore/GitVersionCoreModule.cs b/src/GitVersionCore/GitVersionCoreModule.cs index 93279c23b2..c479f3a4d3 100644 --- a/src/GitVersionCore/GitVersionCoreModule.cs +++ b/src/GitVersionCore/GitVersionCoreModule.cs @@ -1,9 +1,12 @@ using System; +using System.IO; using GitVersion.BuildAgents; using GitVersion.Common; using GitVersion.Configuration; using GitVersion.Configuration.Init; using GitVersion.Extensions; +using GitVersion.Helpers; +using GitVersion.Helpers.Abstractions; using GitVersion.Logging; using GitVersion.VersionCalculation; using GitVersion.VersionCalculation.Cache; @@ -29,6 +32,15 @@ public void RegisterTypes(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); + services.AddSingleton((serviceProvider) => { + var gitVersionCache = serviceProvider.GetRequiredService(); + var cacheDirectory = gitVersionCache.GetCacheDirectory(); + var lockFilePath = Path.Combine(cacheDirectory, GitVersionCoreDefaults.LockFileNameWithExtensions); + var fileStream = LockFile.WaitUntilAcquired(lockFilePath, GitVersionCoreDefaults.LockTimeoutInMilliseconds); + var fileLock = new FileLock(fileStream); + return fileLock; + }); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/GitVersionCore/Helpers/Abstractions/IFileLock.cs b/src/GitVersionCore/Helpers/Abstractions/IFileLock.cs new file mode 100644 index 0000000000..f1a0572da3 --- /dev/null +++ b/src/GitVersionCore/Helpers/Abstractions/IFileLock.cs @@ -0,0 +1,10 @@ +using System; +using System.IO; + +namespace GitVersion.Helpers.Abstractions +{ + public interface IFileLock : IDisposable + { + FileStream FileStream { get; } + } +} diff --git a/src/GitVersionCore/Helpers/LockFile.cs b/src/GitVersionCore/Helpers/LockFile.cs new file mode 100644 index 0000000000..040653e997 --- /dev/null +++ b/src/GitVersionCore/Helpers/LockFile.cs @@ -0,0 +1,208 @@ +/* +MIT License + +Copyright 2020 Teroneko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +using System; +using System.IO; +using System.Threading; + +namespace GitVersion.Helpers +{ + /// + /// This helper class can lock files. + /// + public static class LockFile + { + public const FileMode DefaultFileMode = FileMode.OpenOrCreate; + public const FileAccess DefaultFileAccess = FileAccess.ReadWrite; + public const FileShare DefaultFileShare = FileShare.None; + public const int DefaultTimeoutInMilliseconds = Timeout.Infinite; + + /// + /// Try to acquire lock on file but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The locked file as file stream. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If true the lock acquirement was successful. + public static bool TryAcquire(string filePath, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare) + { + filePath = filePath ?? throw new ArgumentNullException(nameof(filePath)); + + try + { + fileStream = File.Open(filePath, fileMode, fileAccess, fileShare); + return true; + } + catch (Exception error) when (error.GetType() == typeof(IOException)) + { + fileStream = null; + return false; + } + } + + /// + /// Try to acquire lock on file but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If not null the lock acquirement was successful. + public static FileStream? TryAcquire(string filePath, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare) + { + TryAcquire(filePath, out var fileStream, fileMode: fileMode, + fileAccess: fileAccess, fileShare: fileShare); + + return fileStream; + } + + private static bool waitUntilAcquired(string filePath, out FileStream? fileStream, FileMode fileMode, + FileAccess fileAccess, FileShare fileShare, int timeoutInMilliseconds, bool throwOnTimeout) + { + FileStream spinningFileStream = null; + + var spinHasBeenFinished = SpinWait.SpinUntil(() => { + return TryAcquire(filePath, out spinningFileStream, fileMode: fileMode, fileAccess: fileAccess, fileShare: fileShare); + }, timeoutInMilliseconds); + + if (spinHasBeenFinished) + { + fileStream = spinningFileStream ?? throw new ArgumentNullException(nameof(spinningFileStream)); + return true; + } + else + { + if (throwOnTimeout) + { + throw new TimeoutException($"Waiting until file got acquired failed."); + } + + fileStream = null; + return false; + } + } + + private static FileStream? waitUntilAcquired(string filePath, FileMode fileMode, + FileAccess fileAccess, FileShare fileShare, int timeoutInMilliseconds, bool noThrowOnTimeout) + { + waitUntilAcquired(filePath, out var fileStream, fileMode, fileAccess, fileShare, timeoutInMilliseconds, !noThrowOnTimeout); + return fileStream; + } + + /// + /// Wait until file gets acquired lock but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The locked file as file stream. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If true the lock acquirement was successful. + public static bool WaitUntilAcquired(string filePath, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool throwOnTimeout = false) + { + var timeoutInMilliseconds = DefaultTimeoutInMilliseconds; + return waitUntilAcquired(filePath, out fileStream, fileMode, fileAccess, fileShare, timeoutInMilliseconds, throwOnTimeout); + } + + /// + /// Wait until file gets acquired lock but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If not null the lock acquirement was successful. + public static FileStream? WaitUntilAcquired(string filePath, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool noThrowOnTimeout = false) + { + var timeoutInMilliseconds = DefaultTimeoutInMilliseconds; + return waitUntilAcquired(filePath, fileMode, fileAccess, fileShare, timeoutInMilliseconds, noThrowOnTimeout); + } + + /// + /// Wait until file gets acquired lock but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The timeout in milliseconds. + /// The locked file as file stream. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If true the lock acquirement was successful. + public static bool WaitUntilAcquired(string filePath, int timeoutInMilliseconds, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool throwOnTimeout = false) => + waitUntilAcquired(filePath, out fileStream, fileMode, fileAccess, fileShare, timeoutInMilliseconds, throwOnTimeout); + + /// + /// Wait until file gets acquired lock but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The timeout in milliseconds. + /// The locked file as file stream. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If not null the lock acquirement was successful. + public static FileStream? WaitUntilAcquired(string filePath, int timeoutInMilliseconds, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool noThrowOnTimeout = false) => + waitUntilAcquired(filePath, fileMode, fileAccess, fileShare, timeoutInMilliseconds, noThrowOnTimeout); + + /// + /// Wait until file gets acquired lock but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The timeout specified as . + /// The locked file as file stream. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If true the lock acquirement was successful. + public static bool WaitUntilAcquired(string filePath, TimeSpan timeout, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool throwOnTimeout = false) + { + var timeoutInMilliseconds = Convert.ToInt32(timeout.TotalMilliseconds); + return waitUntilAcquired(filePath, out fileStream, fileMode, fileAccess, fileShare, timeoutInMilliseconds, throwOnTimeout); + } + + /// + /// Wait until file gets acquired lock but only as long the file stream is opened. + /// + /// The path to file that get locked. + /// The timeout specified as . + /// The locked file as file stream. + /// The file mode when opening file. + /// The file access when opening file. + /// The file share when opening file + /// If ont null lock acquirement was successful. + public static FileStream? WaitUntilAcquired(string filePath, TimeSpan timeout, FileMode fileMode = DefaultFileMode, + FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool noThrowOnTimeout = false) + { + var timeoutInMilliseconds = Convert.ToInt32(timeout.TotalMilliseconds); + return waitUntilAcquired(filePath, fileMode, fileAccess, fileShare, timeoutInMilliseconds, noThrowOnTimeout); + } + } +} diff --git a/src/GitVersionCore/Model/FileLock.cs b/src/GitVersionCore/Model/FileLock.cs new file mode 100644 index 0000000000..83d1bb800f --- /dev/null +++ b/src/GitVersionCore/Model/FileLock.cs @@ -0,0 +1,17 @@ +using GitVersion.Helpers.Abstractions; +using System; +using System.IO; + +namespace GitVersion.Helpers +{ + public class FileLock : IFileLock + { + public FileStream FileStream { get; } + + public FileLock(FileStream fileStream) => + fileStream = fileStream ?? throw new ArgumentNullException(nameof(fileStream)); + + public void Dispose() => + FileStream.Dispose(); + } +} From 38ee45f4cfc48087e6f14695d873404dc8959e35 Mon Sep 17 00:00:00 2001 From: teroneko Date: Wed, 24 Jun 2020 00:37:04 +0100 Subject: [PATCH 02/13] LockFile API is now mockable; FileLocker implements IFileLocker and can produce FileLockUses; FileLock wraps FileLockUse and implements IDisposable for being cleaned when disposing ServiceProvider --- .../Core/Abstractions/IFileSystem.cs | 1 + src/GitVersionCore/Core/FileSystem.cs | 5 + .../FileLocking/Abstractions/IFileLock.cs | 7 + .../FileLocking/Abstractions/IFileLocker.cs | 29 ++ .../FileLocking/FileLockContext.cs | 76 +++++ .../FileLocking/FileLockContextExtensions.cs | 35 ++ src/GitVersionCore/FileLocking/FileLockUse.cs | 49 +++ src/GitVersionCore/FileLocking/FileLocker.cs | 311 ++++++++++++++++++ .../FileLockerDefaults.cs} | 8 +- .../FileLocking/ILockFileApi.cs | 31 ++ .../LockFileApi.cs} | 56 +++- src/GitVersionCore/GitVersionCoreModule.cs | 23 +- .../Helpers/Abstractions/IFileLock.cs | 10 - src/GitVersionCore/Model/FileLock.cs | 18 +- 14 files changed, 616 insertions(+), 43 deletions(-) create mode 100644 src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs create mode 100644 src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs create mode 100644 src/GitVersionCore/FileLocking/FileLockContext.cs create mode 100644 src/GitVersionCore/FileLocking/FileLockContextExtensions.cs create mode 100644 src/GitVersionCore/FileLocking/FileLockUse.cs create mode 100644 src/GitVersionCore/FileLocking/FileLocker.cs rename src/GitVersionCore/{GitVersionCoreDefaults.cs => FileLocking/FileLockerDefaults.cs} (53%) create mode 100644 src/GitVersionCore/FileLocking/ILockFileApi.cs rename src/GitVersionCore/{Helpers/LockFile.cs => FileLocking/LockFileApi.cs} (78%) delete mode 100644 src/GitVersionCore/Helpers/Abstractions/IFileLock.cs diff --git a/src/GitVersionCore/Core/Abstractions/IFileSystem.cs b/src/GitVersionCore/Core/Abstractions/IFileSystem.cs index bfd32bcbd5..e9357ee027 100644 --- a/src/GitVersionCore/Core/Abstractions/IFileSystem.cs +++ b/src/GitVersionCore/Core/Abstractions/IFileSystem.cs @@ -14,6 +14,7 @@ public interface IFileSystem void WriteAllText(string file, string fileContents); void WriteAllText(string file, string fileContents, Encoding encoding); IEnumerable DirectoryEnumerateFiles(string directory, string searchPattern, SearchOption searchOption); + FileStream Open(string path, FileMode mode, FileAccess access, FileShare share); Stream OpenWrite(string path); Stream OpenRead(string path); void CreateDirectory(string path); diff --git a/src/GitVersionCore/Core/FileSystem.cs b/src/GitVersionCore/Core/FileSystem.cs index cb64fee773..f1b0de945e 100644 --- a/src/GitVersionCore/Core/FileSystem.cs +++ b/src/GitVersionCore/Core/FileSystem.cs @@ -51,6 +51,11 @@ public IEnumerable DirectoryEnumerateFiles(string directory, string sear return Directory.EnumerateFiles(directory, searchPattern, searchOption); } + public FileStream Open(string path, FileMode mode, FileAccess access, FileShare share) + { + return File.Open(path, mode, access, share); + } + public Stream OpenWrite(string path) { return File.OpenWrite(path); diff --git a/src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs b/src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs new file mode 100644 index 0000000000..965fadd338 --- /dev/null +++ b/src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs @@ -0,0 +1,7 @@ +using System; + +namespace GitVersion.FileLocking.Abstractions +{ + public interface IFileLock : IDisposable + { } +} diff --git a/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs b/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs new file mode 100644 index 0000000000..52b3a2e0ab --- /dev/null +++ b/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs @@ -0,0 +1,29 @@ +/* +MIT License + +Copyright 2020 Teroneko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +namespace GitVersion.FileLocking.Abstractions +{ + public interface IFileLocker + { + FileLockUse WaitUntilAcquired(); + } +} diff --git a/src/GitVersionCore/FileLocking/FileLockContext.cs b/src/GitVersionCore/FileLocking/FileLockContext.cs new file mode 100644 index 0000000000..55f213f26d --- /dev/null +++ b/src/GitVersionCore/FileLocking/FileLockContext.cs @@ -0,0 +1,76 @@ +/* +MIT License + +Copyright 2020 Teroneko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; + +namespace GitVersion.FileLocking +{ + internal class FileLockContext + { + public FileStream FileStream { get; set; } + public Exception Error { get; set; } + public ManualResetEvent ErrorUnlockDone { get; set; } + + private readonly FileLocker fileLocker; + private object decreaseLockUseLocker; + + public FileLockContext(FileLocker fileLocker, object decreaseLockUseLocker) + { + this.fileLocker = fileLocker; + this.decreaseLockUseLocker = decreaseLockUseLocker; + this.decreaseLockUseLocker = decreaseLockUseLocker; + } + + public void DecreaseLockUse(bool decreaseToZero, string lockId) + { + var decreaseLockUseLocker = this.decreaseLockUseLocker; + + 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) + { + 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; + } + } + } +} diff --git a/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs b/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs new file mode 100644 index 0000000000..b0ac1a7e1c --- /dev/null +++ b/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs @@ -0,0 +1,35 @@ +/* +MIT License + +Copyright 2020 Teroneko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +namespace GitVersion.FileLocking +{ + internal static class FileLockContextExtensions + { + public static bool IsErroneous(this FileLockContext fileLockContext) + { + if (fileLockContext?.Error is null) + return false; + + return true; + } + } +} diff --git a/src/GitVersionCore/FileLocking/FileLockUse.cs b/src/GitVersionCore/FileLocking/FileLockUse.cs new file mode 100644 index 0000000000..d2bbe013ac --- /dev/null +++ b/src/GitVersionCore/FileLocking/FileLockUse.cs @@ -0,0 +1,49 @@ +/* +MIT License + +Copyright 2020 Teroneko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.ComponentModel; +using System.IO; + +namespace GitVersion.FileLocking +{ + public struct FileLockUse : IDisposable + { + 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; + this.LockId = LockId; + } + + public void Dispose() + { + // When stream not closed, we can decrease lock use. + fileLockContext.DecreaseLockUse(false, LockId); + } + } +} diff --git a/src/GitVersionCore/FileLocking/FileLocker.cs b/src/GitVersionCore/FileLocking/FileLocker.cs new file mode 100644 index 0000000000..9bc362b0ba --- /dev/null +++ b/src/GitVersionCore/FileLocking/FileLocker.cs @@ -0,0 +1,311 @@ +/* +MIT License + +Copyright 2020 Teroneko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using GitVersion.FileLocking.Abstractions; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Threading; + +namespace GitVersion.FileLocking +{ + /// + /// Provides a file locker that is thread-safe and supports nesting. + /// + public sealed class FileLocker : IFileLocker + { +#if TRACE + internal const string TraceCategory = nameof(FileLocker); + private static Random random = new Random(); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static string CurrentThreadWithLockIdPrefix(string lockId) => + $"Thread {Thread.CurrentThread.Name ?? "none"}: Lock {lockId}:"; + + private static string fileStreamHasBeenLockedString(FileStream fileStream) => + "(locked=" + (fileStream != null && (fileStream.CanRead || fileStream.CanWrite)).ToString().ToLower() + ")"; + + private static string unlockSourceString(bool decreaseToZero) => + $"{(decreaseToZero ? "(manual unlock)" : "(dispose unlock)")}"; +#endif + + private static string getLockId() + { +#if TRACE + return random.Next(0, 999).ToString().PadLeft(3, '0'); +#else + return "none"; +#endif + } + + public string FilePath { get; } + + public FileStream FileStream => + fileLockerState?.FileStream; + + /// + /// If true, the lock attempts are going to throw the exception which occured in the lock before. + /// This happens to all locks until the manual unlock within the lock in which the excpetion initially + /// begun has been processed. + /// + public bool EnableConcurrentRethrow { get; set; } + + public int LocksInUse => locksInUse; + + public FileMode FileMode { get; } + public FileAccess FileAccess { get; } + public FileShare FileShare { get; } + public int TimeoutInMilliseconds { get; } + + /// + /// Zero represents the number where no lock is in place. + /// + private int locksInUse = 0; + private FileLockContext fileLockerState; + private object decreaseLockUseLocker; + private readonly ILockFileApi lockFileApi; + + public FileLocker(ILockFileApi lockFileApi, string filePath, FileMode fileMode = LockFileApi.DefaultFileMode, FileAccess fileAccess = LockFileApi.DefaultFileAccess, + FileShare fileShare = LockFileApi.DefaultFileShare) + { + decreaseLockUseLocker = new object(); + this.lockFileApi = lockFileApi; + FilePath = filePath; + FileMode = fileMode; + FileAccess = fileAccess; + FileShare = fileShare; + TimeoutInMilliseconds = LockFileApi.DefaultTimeoutInMilliseconds; + } + + public FileLocker(ILockFileApi lockFileApi, string filePath, int timeoutInMilliseconds, FileMode fileMode = LockFileApi.DefaultFileMode, FileAccess fileAccess = LockFileApi.DefaultFileAccess, + FileShare fileShare = LockFileApi.DefaultFileShare) + : this(lockFileApi, filePath, fileMode, fileAccess, fileShare) + { + TimeoutInMilliseconds = timeoutInMilliseconds; + } + + public FileLocker(ILockFileApi lockFileApi, string filePath, TimeSpan timeout, FileMode fileMode = LockFileApi.DefaultFileMode, FileAccess fileAccess = LockFileApi.DefaultFileAccess, + FileShare fileShare = LockFileApi.DefaultFileShare) + : this(lockFileApi, filePath, fileMode, fileAccess, fileShare) + { + TimeoutInMilliseconds = Convert.ToInt32(timeout.TotalMilliseconds); + } + + /// + /// Locks the file specified at location . + /// + /// The file lock use that can be revoked by disposing it. + public FileLockUse WaitUntilAcquired() + { + var lockId = getLockId(); + Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Begin locking file {FilePath}.", TraceCategory); + SpinWait spinWait = new SpinWait(); + + while (true) + { + var currentLocksInUse = locksInUse; + var desiredLocksInUse = currentLocksInUse + 1; + var currentFileLockerState = fileLockerState; + + if (currentFileLockerState.IsErroneous()) + { + if (EnableConcurrentRethrow) + { + Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Error from previous lock will be rethrown.", TraceCategory); + throw currentFileLockerState.Error; + } + + // Imagine stair steps where each stair step is Lock(): + // Thread #0 Lock #0 -> Incremented to 1 -> Exception occured. + // Thread #1 Lock #1 -> Incremented to 2. Recognozes exception in #0 because #0 not yet entered Unlock(). + // Thread #2 Lock #2 -> Incremented to 3. Recognizes excetion in #1 because #0 not yet entered Unlock(). + // Thread #3 Lock #3 -> Incremented to 1. Lock was successful. + // We want Lock #1 and Lock #2 to retry their Lock(): + // Thread #1 Lock #1 -> Incremented to 2. Lock was successful. + // Thread #2 Lock #2 -> Incremented to 3. Lock was successful. + currentFileLockerState.ErrorUnlockDone.WaitOne(); + Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Retry lock due to previously failed lock.", TraceCategory); + continue; + } + // If it is the initial lock, then we expect file stream being null. + // If it is not the initial lock, we expect the stream being not null. + else if ((currentLocksInUse == 0 && currentFileLockerState != null) || + (currentLocksInUse != 0 && currentFileLockerState == null)) + { + spinWait.SpinOnce(); + continue; + } + else + { + if (currentLocksInUse != Interlocked.CompareExchange(ref locksInUse, desiredLocksInUse, currentLocksInUse)) + { + continue; + } + + // The above conditions met, so if it is the initial lock, then we want + // to acquire the lock. + if (desiredLocksInUse == 1) + { + try + { + var fileStream = lockFileApi.WaitUntilAcquired(FilePath, TimeoutInMilliseconds, fileMode: FileMode, + fileAccess: FileAccess, fileShare: FileShare); + + currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker) + { + FileStream = fileStream + }; + + fileLockerState = currentFileLockerState; + Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} locked by file locker.", TraceCategory); + } + catch (Exception error) + { + currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker) + { + Error = error, + ErrorUnlockDone = new ManualResetEvent(false) + }; + + fileLockerState = currentFileLockerState; + Unlock(lockId); + // After we processed Unlock(), we can surpass these locks + // who could be dependent on state assigment of this Lock(). + currentFileLockerState.ErrorUnlockDone.Set(); + throw; + } + } + else + { + Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} locked {desiredLocksInUse} time(s) concurrently by file locker. {fileStreamHasBeenLockedString(currentFileLockerState.FileStream!)}", TraceCategory); + } + } + + var fileLockContract = new FileLockUse(currentFileLockerState, lockId); + return fileLockContract; + } + } + + /// + /// Decreases the number of locks in use. If becoming zero, file gets unlocked. + /// + internal int DecreaseLockUse(bool decreaseToZero, string? lockId) + { + lockId = lockId ?? "none"; + SpinWait spinWait = new SpinWait(); + int desiredLocksInUse; + + do + { + var currentLocksInUse = locksInUse; + + if (0 >= currentLocksInUse) + { + Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Number of lock remains at 0 because file has been unlocked before. {unlockSourceString(decreaseToZero)}", TraceCategory); + return 0; + } + + if (decreaseToZero) + { + desiredLocksInUse = 0; + } + else + { + desiredLocksInUse = currentLocksInUse - 1; + } + + var actualLocksInUse = Interlocked.CompareExchange(ref locksInUse, desiredLocksInUse, currentLocksInUse); + + if (currentLocksInUse == actualLocksInUse) + { + break; + } + + spinWait.SpinOnce(); + } while (true); + + string decreasedNumberOfLocksInUseMessage() => + $"{CurrentThreadWithLockIdPrefix(lockId)} Number of lock uses is decreased to {desiredLocksInUse}. {unlockSourceString(decreaseToZero)}"; + + // When no locks are registered, we have to .. + if (0 == desiredLocksInUse) + { + // 1. wait for file stream assignment, + FileLockContext nullState = null; + FileLockContext nonNullState = null; + + while (true) + { + nullState = Interlocked.CompareExchange(ref fileLockerState, null, nullState); + + /* When class scoped file stream is null local file stream will be null too. + * => If so, spin once and continue loop. + * + * When class scoped file stream is not null the local file stream will become + * not null too. + * => If so, assigned class scoped file streama to to local non null file stream + * and continue loop. + * + * When class scoped file stream is null and local non null file stream is not null + * => If so, break loop. + */ + if (nullState == null && nonNullState is null) + { + spinWait.SpinOnce(); + } + else if (nullState == null && !(nonNullState is null)) + { + break; + } + else + { + nonNullState = nullState; + } + } + + // 2. invalidate the file stream. + nonNullState.FileStream?.Close(); + nonNullState.FileStream?.Dispose(); + Trace.WriteLine($"{decreasedNumberOfLocksInUseMessage()}{System.Environment.NewLine}{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} unlocked by file locker. {unlockSourceString(decreaseToZero)}", TraceCategory); + } + else + { + Trace.WriteLine($"{decreasedNumberOfLocksInUseMessage()}"); + } + + return desiredLocksInUse; + } + + /// + /// Unlocks the file specified at location . + /// + /// The lock id is for tracing purposes. + internal void Unlock(string lockId) + { + lock (decreaseLockUseLocker) + { + DecreaseLockUse(true, lockId); + } + } + } +} diff --git a/src/GitVersionCore/GitVersionCoreDefaults.cs b/src/GitVersionCore/FileLocking/FileLockerDefaults.cs similarity index 53% rename from src/GitVersionCore/GitVersionCoreDefaults.cs rename to src/GitVersionCore/FileLocking/FileLockerDefaults.cs index 01d16bc884..07de0abf98 100644 --- a/src/GitVersionCore/GitVersionCoreDefaults.cs +++ b/src/GitVersionCore/FileLocking/FileLockerDefaults.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; -namespace GitVersion + +namespace GitVersion.FileLocking { - public static class GitVersionCoreDefaults + public static class FileLockerDefaults { public const int LockTimeoutInMilliseconds = 1000 * 15; public const string LockFileNameWithExtensions = "GitVersion.lock"; diff --git a/src/GitVersionCore/FileLocking/ILockFileApi.cs b/src/GitVersionCore/FileLocking/ILockFileApi.cs new file mode 100644 index 0000000000..5828bf164a --- /dev/null +++ b/src/GitVersionCore/FileLocking/ILockFileApi.cs @@ -0,0 +1,31 @@ +/* +MIT License + +Copyright 2020 Teroneko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System.IO; + +namespace GitVersion.FileLocking +{ + public interface ILockFileApi + { + FileStream WaitUntilAcquired(string filePath, int timeoutInMilliseconds, FileMode fileMode = FileMode.OpenOrCreate, FileAccess fileAccess = FileAccess.ReadWrite, FileShare fileShare = FileShare.None, bool noThrowOnTimeout = false); + } +} diff --git a/src/GitVersionCore/Helpers/LockFile.cs b/src/GitVersionCore/FileLocking/LockFileApi.cs similarity index 78% rename from src/GitVersionCore/Helpers/LockFile.cs rename to src/GitVersionCore/FileLocking/LockFileApi.cs index 040653e997..1565dd4658 100644 --- a/src/GitVersionCore/Helpers/LockFile.cs +++ b/src/GitVersionCore/FileLocking/LockFileApi.cs @@ -19,23 +19,30 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ +*/ using System; using System.IO; using System.Threading; -namespace GitVersion.Helpers +namespace GitVersion.FileLocking { /// /// This helper class can lock files. /// - public static class LockFile + public class LockFileApi : ILockFileApi { public const FileMode DefaultFileMode = FileMode.OpenOrCreate; public const FileAccess DefaultFileAccess = FileAccess.ReadWrite; public const FileShare DefaultFileShare = FileShare.None; public const int DefaultTimeoutInMilliseconds = Timeout.Infinite; + private readonly IFileSystem fileSystem; + + public LockFileApi(IFileSystem fileSystem) + { + this.fileSystem = fileSystem; + } + /// /// Try to acquire lock on file but only as long the file stream is opened. /// @@ -45,16 +52,24 @@ public static class LockFile /// The file access when opening file. /// The file share when opening file /// If true the lock acquirement was successful. - public static bool TryAcquire(string filePath, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + public bool TryAcquire(string filePath, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare) { filePath = filePath ?? throw new ArgumentNullException(nameof(filePath)); try { - fileStream = File.Open(filePath, fileMode, fileAccess, fileShare); + fileStream = fileSystem.Open(filePath, fileMode, fileAccess, fileShare); + // Add UNIX support (reference https://github.com/dotnet/coreclr/pull/8233). + fileStream.Lock(0, 0); return true; } + // The IOException does specify that the file could not been accessed because + // it was partially locked. All other exception have to be handled by consumer. + // + // See references: + // https://docs.microsoft.com/en-US/dotnet/api/system.io.file.open?view=netcore-3.1 (exceptions) + // https://docs.microsoft.com/en-US/dotnet/api/system.io.filestream.lock?view=netcore-3.1#exceptions catch (Exception error) when (error.GetType() == typeof(IOException)) { fileStream = null; @@ -70,7 +85,7 @@ public static bool TryAcquire(string filePath, out FileStream? fileStream, FileM /// The file access when opening file. /// The file share when opening file /// If not null the lock acquirement was successful. - public static FileStream? TryAcquire(string filePath, FileMode fileMode = DefaultFileMode, + public FileStream? TryAcquire(string filePath, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare) { TryAcquire(filePath, out var fileStream, fileMode: fileMode, @@ -79,12 +94,13 @@ public static bool TryAcquire(string filePath, out FileStream? fileStream, FileM return fileStream; } - private static bool waitUntilAcquired(string filePath, out FileStream? fileStream, FileMode fileMode, + private bool waitUntilAcquired(string filePath, out FileStream? fileStream, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int timeoutInMilliseconds, bool throwOnTimeout) { FileStream spinningFileStream = null; - var spinHasBeenFinished = SpinWait.SpinUntil(() => { + var spinHasBeenFinished = SpinWait.SpinUntil(() => + { return TryAcquire(filePath, out spinningFileStream, fileMode: fileMode, fileAccess: fileAccess, fileShare: fileShare); }, timeoutInMilliseconds); @@ -97,7 +113,7 @@ private static bool waitUntilAcquired(string filePath, out FileStream? fileStrea { if (throwOnTimeout) { - throw new TimeoutException($"Waiting until file got acquired failed."); + throw new TimeoutException($"Acquiring file lock failed due to timeout."); } fileStream = null; @@ -105,7 +121,7 @@ private static bool waitUntilAcquired(string filePath, out FileStream? fileStrea } } - private static FileStream? waitUntilAcquired(string filePath, FileMode fileMode, + private FileStream? waitUntilAcquired(string filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int timeoutInMilliseconds, bool noThrowOnTimeout) { waitUntilAcquired(filePath, out var fileStream, fileMode, fileAccess, fileShare, timeoutInMilliseconds, !noThrowOnTimeout); @@ -120,8 +136,9 @@ private static bool waitUntilAcquired(string filePath, out FileStream? fileStrea /// The file mode when opening file. /// The file access when opening file. /// The file share when opening file + /// Enable throw when exception occured due due to timeout. /// If true the lock acquirement was successful. - public static bool WaitUntilAcquired(string filePath, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + public bool WaitUntilAcquired(string filePath, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool throwOnTimeout = false) { var timeoutInMilliseconds = DefaultTimeoutInMilliseconds; @@ -135,8 +152,9 @@ public static bool WaitUntilAcquired(string filePath, out FileStream? fileStream /// The file mode when opening file. /// The file access when opening file. /// The file share when opening file + /// Disable throw when exception occured due due to timeout. /// If not null the lock acquirement was successful. - public static FileStream? WaitUntilAcquired(string filePath, FileMode fileMode = DefaultFileMode, + public FileStream? WaitUntilAcquired(string filePath, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool noThrowOnTimeout = false) { var timeoutInMilliseconds = DefaultTimeoutInMilliseconds; @@ -152,8 +170,9 @@ public static bool WaitUntilAcquired(string filePath, out FileStream? fileStream /// The file mode when opening file. /// The file access when opening file. /// The file share when opening file + /// Enable throw when exception occured due due to timeout. /// If true the lock acquirement was successful. - public static bool WaitUntilAcquired(string filePath, int timeoutInMilliseconds, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + public bool WaitUntilAcquired(string filePath, int timeoutInMilliseconds, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool throwOnTimeout = false) => waitUntilAcquired(filePath, out fileStream, fileMode, fileAccess, fileShare, timeoutInMilliseconds, throwOnTimeout); @@ -162,12 +181,12 @@ public static bool WaitUntilAcquired(string filePath, int timeoutInMilliseconds, /// /// The path to file that get locked. /// The timeout in milliseconds. - /// The locked file as file stream. /// The file mode when opening file. /// The file access when opening file. /// The file share when opening file + /// Disable throw when exception occured due due to timeout. /// If not null the lock acquirement was successful. - public static FileStream? WaitUntilAcquired(string filePath, int timeoutInMilliseconds, FileMode fileMode = DefaultFileMode, + public FileStream? WaitUntilAcquired(string filePath, int timeoutInMilliseconds, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool noThrowOnTimeout = false) => waitUntilAcquired(filePath, fileMode, fileAccess, fileShare, timeoutInMilliseconds, noThrowOnTimeout); @@ -180,8 +199,9 @@ public static bool WaitUntilAcquired(string filePath, int timeoutInMilliseconds, /// The file mode when opening file. /// The file access when opening file. /// The file share when opening file + /// Enable throw when exception occured due due to timeout. /// If true the lock acquirement was successful. - public static bool WaitUntilAcquired(string filePath, TimeSpan timeout, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, + public bool WaitUntilAcquired(string filePath, TimeSpan timeout, out FileStream? fileStream, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool throwOnTimeout = false) { var timeoutInMilliseconds = Convert.ToInt32(timeout.TotalMilliseconds); @@ -193,12 +213,12 @@ public static bool WaitUntilAcquired(string filePath, TimeSpan timeout, out File /// /// The path to file that get locked. /// The timeout specified as . - /// The locked file as file stream. /// The file mode when opening file. /// The file access when opening file. /// The file share when opening file + /// Disable throw when exception occured due due to timeout. /// If ont null lock acquirement was successful. - public static FileStream? WaitUntilAcquired(string filePath, TimeSpan timeout, FileMode fileMode = DefaultFileMode, + public FileStream? WaitUntilAcquired(string filePath, TimeSpan timeout, FileMode fileMode = DefaultFileMode, FileAccess fileAccess = DefaultFileAccess, FileShare fileShare = DefaultFileShare, bool noThrowOnTimeout = false) { var timeoutInMilliseconds = Convert.ToInt32(timeout.TotalMilliseconds); diff --git a/src/GitVersionCore/GitVersionCoreModule.cs b/src/GitVersionCore/GitVersionCoreModule.cs index c479f3a4d3..62abdf105e 100644 --- a/src/GitVersionCore/GitVersionCoreModule.cs +++ b/src/GitVersionCore/GitVersionCoreModule.cs @@ -5,8 +5,9 @@ using GitVersion.Configuration; using GitVersion.Configuration.Init; using GitVersion.Extensions; +using GitVersion.FileLocking; +using GitVersion.FileLocking.Abstractions; using GitVersion.Helpers; -using GitVersion.Helpers.Abstractions; using GitVersion.Logging; using GitVersion.VersionCalculation; using GitVersion.VersionCalculation.Cache; @@ -32,15 +33,27 @@ public void RegisterTypes(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); - services.AddSingleton((serviceProvider) => { + + services.AddSingleton(); + + services.AddSingleton((serviceProvider) => + { + var lockFileApi = serviceProvider.GetRequiredService(); var gitVersionCache = serviceProvider.GetRequiredService(); var cacheDirectory = gitVersionCache.GetCacheDirectory(); - var lockFilePath = Path.Combine(cacheDirectory, GitVersionCoreDefaults.LockFileNameWithExtensions); - var fileStream = LockFile.WaitUntilAcquired(lockFilePath, GitVersionCoreDefaults.LockTimeoutInMilliseconds); - var fileLock = new FileLock(fileStream); + var lockFilePath = Path.Combine(cacheDirectory, FileLockerDefaults.LockFileNameWithExtensions); + return new FileLocker(lockFileApi, lockFilePath); + }); + + services.AddSingleton((serviceProvider) => + { + var fileLocker = serviceProvider.GetRequiredService(); + var fileLockUse = fileLocker.WaitUntilAcquired(); + var fileLock = new FileLock(fileLockUse); return fileLock; }); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/GitVersionCore/Helpers/Abstractions/IFileLock.cs b/src/GitVersionCore/Helpers/Abstractions/IFileLock.cs deleted file mode 100644 index f1a0572da3..0000000000 --- a/src/GitVersionCore/Helpers/Abstractions/IFileLock.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.IO; - -namespace GitVersion.Helpers.Abstractions -{ - public interface IFileLock : IDisposable - { - FileStream FileStream { get; } - } -} diff --git a/src/GitVersionCore/Model/FileLock.cs b/src/GitVersionCore/Model/FileLock.cs index 83d1bb800f..115feb1d47 100644 --- a/src/GitVersionCore/Model/FileLock.cs +++ b/src/GitVersionCore/Model/FileLock.cs @@ -1,4 +1,5 @@ -using GitVersion.Helpers.Abstractions; +using GitVersion.FileLocking; +using GitVersion.FileLocking.Abstractions; using System; using System.IO; @@ -6,12 +7,19 @@ namespace GitVersion.Helpers { public class FileLock : IFileLock { - public FileStream FileStream { get; } + public FileLockUse FileLockUse { get; } - public FileLock(FileStream fileStream) => - fileStream = fileStream ?? throw new ArgumentNullException(nameof(fileStream)); + public FileLock(FileLockUse fileLockUse) + { + if (fileLockUse.Equals(default(FileLockUse))) + { + throw new ArgumentNullException(nameof(fileLockUse)); + } + + FileLockUse = fileLockUse; + } public void Dispose() => - FileStream.Dispose(); + FileLockUse.Dispose(); } } From e6754e2cc36baf4feedd006c8b53c730917b6b71 Mon Sep 17 00:00:00 2001 From: teroneko Date: Wed, 24 Jun 2020 01:13:19 +0100 Subject: [PATCH 03/13] Added default implementation for Open(..) in class TestFileSystem --- src/GitVersionCore.Tests/Helpers/TestFileSystem.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs b/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs index ddfac75184..50373495a5 100644 --- a/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs +++ b/src/GitVersionCore.Tests/Helpers/TestFileSystem.cs @@ -78,6 +78,11 @@ public IEnumerable DirectoryEnumerateFiles(string directory, string sear throw new NotImplementedException(); } + public FileStream Open(string path, FileMode mode, FileAccess access, FileShare share) + { + return new FileStream(path, mode, access, share); + } + public Stream OpenWrite(string path) { return new TestStream(path, this); From d175f3014b0c612f3dcc813cfa873d1cfef226a4 Mon Sep 17 00:00:00 2001 From: teroneko Date: Wed, 24 Jun 2020 08:14:58 +0100 Subject: [PATCH 04/13] Code tidied up and nullable enabled for files of file locking --- .../FileLocking/FileLockContext.cs | 34 +++++++++++++---- .../FileLocking/FileLockContextExtensions.cs | 5 ++- src/GitVersionCore/FileLocking/FileLockUse.cs | 13 ++++++- src/GitVersionCore/FileLocking/FileLocker.cs | 38 +++++++++---------- .../FileLocking/ILockFileApi.cs | 22 ----------- src/GitVersionCore/FileLocking/LockFileApi.cs | 5 ++- 6 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/GitVersionCore/FileLocking/FileLockContext.cs b/src/GitVersionCore/FileLocking/FileLockContext.cs index 55f213f26d..4c2dee76b6 100644 --- a/src/GitVersionCore/FileLocking/FileLockContext.cs +++ b/src/GitVersionCore/FileLocking/FileLockContext.cs @@ -27,24 +27,44 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace GitVersion.FileLocking { + +#nullable enable + internal class FileLockContext { - public FileStream FileStream { get; set; } - public Exception Error { get; set; } - public ManualResetEvent ErrorUnlockDone { get; set; } + public FileStream? FileStream { get; } + public Exception? Error { get; } + public ManualResetEvent? ErrorUnlockDone { get; } private readonly FileLocker fileLocker; - private object decreaseLockUseLocker; + private object? decreaseLockUseLocker; - public FileLockContext(FileLocker fileLocker, object decreaseLockUseLocker) + private FileLockContext(FileLocker fileLocker, object decreaseLockUseLocker) { - this.fileLocker = fileLocker; - this.decreaseLockUseLocker = 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; if (decreaseLockUseLocker == null) diff --git a/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs b/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs index b0ac1a7e1c..359ab6dd44 100644 --- a/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs +++ b/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs @@ -22,9 +22,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ namespace GitVersion.FileLocking { + +#nullable enable + internal static class FileLockContextExtensions { - public static bool IsErroneous(this FileLockContext fileLockContext) + public static bool IsErroneous(this FileLockContext? fileLockContext) { if (fileLockContext?.Error is null) return false; diff --git a/src/GitVersionCore/FileLocking/FileLockUse.cs b/src/GitVersionCore/FileLocking/FileLockUse.cs index d2bbe013ac..f5a75de1f4 100644 --- a/src/GitVersionCore/FileLocking/FileLockUse.cs +++ b/src/GitVersionCore/FileLocking/FileLockUse.cs @@ -26,9 +26,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace GitVersion.FileLocking { + +#nullable enable + public struct FileLockUse : IDisposable { - public FileStream FileStream => fileLockContext.FileStream; + public FileStream FileStream => fileLockContext.FileStream!; private readonly FileLockContext fileLockContext; [EditorBrowsable(EditorBrowsableState.Never)] @@ -36,7 +39,13 @@ public struct FileLockUse : IDisposable internal FileLockUse(FileLockContext fileLockContext, string LockId) { - this.fileLockContext = fileLockContext; + 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; } diff --git a/src/GitVersionCore/FileLocking/FileLocker.cs b/src/GitVersionCore/FileLocking/FileLocker.cs index 9bc362b0ba..81c3ce88ea 100644 --- a/src/GitVersionCore/FileLocking/FileLocker.cs +++ b/src/GitVersionCore/FileLocking/FileLocker.cs @@ -29,6 +29,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace GitVersion.FileLocking { + +#nullable enable + /// /// Provides a file locker that is thread-safe and supports nesting. /// @@ -60,7 +63,7 @@ private static string getLockId() public string FilePath { get; } - public FileStream FileStream => + public FileStream? FileStream => fileLockerState?.FileStream; /// @@ -81,7 +84,7 @@ private static string getLockId() /// Zero represents the number where no lock is in place. /// private int locksInUse = 0; - private FileLockContext fileLockerState; + private FileLockContext? fileLockerState; private object decreaseLockUseLocker; private readonly ILockFileApi lockFileApi; @@ -89,7 +92,7 @@ public FileLocker(ILockFileApi lockFileApi, string filePath, FileMode fileMode = FileShare fileShare = LockFileApi.DefaultFileShare) { decreaseLockUseLocker = new object(); - this.lockFileApi = lockFileApi; + this.lockFileApi = lockFileApi ?? throw new ArgumentNullException(nameof(lockFileApi)); FilePath = filePath; FileMode = fileMode; FileAccess = fileAccess; @@ -132,7 +135,7 @@ public FileLockUse WaitUntilAcquired() if (EnableConcurrentRethrow) { Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Error from previous lock will be rethrown.", TraceCategory); - throw currentFileLockerState.Error; + throw currentFileLockerState!.Error!; } // Imagine stair steps where each stair step is Lock(): @@ -143,7 +146,7 @@ public FileLockUse WaitUntilAcquired() // We want Lock #1 and Lock #2 to retry their Lock(): // Thread #1 Lock #1 -> Incremented to 2. Lock was successful. // Thread #2 Lock #2 -> Incremented to 3. Lock was successful. - currentFileLockerState.ErrorUnlockDone.WaitOne(); + currentFileLockerState!.ErrorUnlockDone!.WaitOne(); Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} Retry lock due to previously failed lock.", TraceCategory); continue; } @@ -169,35 +172,28 @@ public FileLockUse WaitUntilAcquired() try { var fileStream = lockFileApi.WaitUntilAcquired(FilePath, TimeoutInMilliseconds, fileMode: FileMode, - fileAccess: FileAccess, fileShare: FileShare); + fileAccess: FileAccess, fileShare: FileShare)!; - currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker) - { - FileStream = fileStream - }; + currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker, fileStream); fileLockerState = currentFileLockerState; Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} locked by file locker.", TraceCategory); } catch (Exception error) { - currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker) - { - Error = error, - ErrorUnlockDone = new ManualResetEvent(false) - }; - + var errorUnlockDone = new ManualResetEvent(false); + currentFileLockerState = new FileLockContext(this, decreaseLockUseLocker, error, errorUnlockDone); fileLockerState = currentFileLockerState; Unlock(lockId); // After we processed Unlock(), we can surpass these locks // who could be dependent on state assigment of this Lock(). - currentFileLockerState.ErrorUnlockDone.Set(); + currentFileLockerState.ErrorUnlockDone!.Set(); throw; } } else { - Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} locked {desiredLocksInUse} time(s) concurrently by file locker. {fileStreamHasBeenLockedString(currentFileLockerState.FileStream!)}", TraceCategory); + Trace.WriteLine($"{CurrentThreadWithLockIdPrefix(lockId)} File {FilePath} locked {desiredLocksInUse} time(s) concurrently by file locker. {fileStreamHasBeenLockedString(currentFileLockerState!.FileStream!)}", TraceCategory); } } @@ -251,8 +247,8 @@ string decreasedNumberOfLocksInUseMessage() => if (0 == desiredLocksInUse) { // 1. wait for file stream assignment, - FileLockContext nullState = null; - FileLockContext nonNullState = null; + FileLockContext? nullState = null; + FileLockContext nonNullState = null!; while (true) { @@ -279,7 +275,7 @@ string decreasedNumberOfLocksInUseMessage() => } else { - nonNullState = nullState; + nonNullState = nullState!; } } diff --git a/src/GitVersionCore/FileLocking/ILockFileApi.cs b/src/GitVersionCore/FileLocking/ILockFileApi.cs index 5828bf164a..99beb92881 100644 --- a/src/GitVersionCore/FileLocking/ILockFileApi.cs +++ b/src/GitVersionCore/FileLocking/ILockFileApi.cs @@ -1,25 +1,3 @@ -/* -MIT License - -Copyright 2020 Teroneko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ using System.IO; namespace GitVersion.FileLocking diff --git a/src/GitVersionCore/FileLocking/LockFileApi.cs b/src/GitVersionCore/FileLocking/LockFileApi.cs index 1565dd4658..8202e375a7 100644 --- a/src/GitVersionCore/FileLocking/LockFileApi.cs +++ b/src/GitVersionCore/FileLocking/LockFileApi.cs @@ -26,6 +26,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace GitVersion.FileLocking { + +#nullable enable + /// /// This helper class can lock files. /// @@ -97,7 +100,7 @@ public bool TryAcquire(string filePath, out FileStream? fileStream, FileMode fil private bool waitUntilAcquired(string filePath, out FileStream? fileStream, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, int timeoutInMilliseconds, bool throwOnTimeout) { - FileStream spinningFileStream = null; + FileStream? spinningFileStream = null; var spinHasBeenFinished = SpinWait.SpinUntil(() => { From 690087d70e44bede419201391bf6b25122d6ef89 Mon Sep 17 00:00:00 2001 From: teroneko Date: Wed, 24 Jun 2020 08:31:58 +0100 Subject: [PATCH 05/13] Increated timeout to 1 minute and 30 seconds --- src/GitVersionCore/FileLocking/FileLockerDefaults.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GitVersionCore/FileLocking/FileLockerDefaults.cs b/src/GitVersionCore/FileLocking/FileLockerDefaults.cs index 07de0abf98..1cc014a25a 100644 --- a/src/GitVersionCore/FileLocking/FileLockerDefaults.cs +++ b/src/GitVersionCore/FileLocking/FileLockerDefaults.cs @@ -4,7 +4,8 @@ namespace GitVersion.FileLocking { public static class FileLockerDefaults { - public const int LockTimeoutInMilliseconds = 1000 * 15; + // Represents 90 seconds. + public const int LockTimeoutInMilliseconds = 1000 * 90; public const string LockFileNameWithExtensions = "GitVersion.lock"; } } From 081dec9119017377211864bf042f7ee45ac8d78e Mon Sep 17 00:00:00 2001 From: teroneko Date: Fri, 26 Jun 2020 00:01:28 +0100 Subject: [PATCH 06/13] Implemented disposable service provider that get disposed after each executed action in assembly GitVersion.MSBuildTask --- .../IDisposableServiceProvider.cs | 7 ++++ .../GitVersionServiceProvider.cs | 35 +++++++++++++++++++ src/GitVersionTask/GitVersionTasks.cs | 9 +++-- 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs create mode 100644 src/GitVersionTask/GitVersionServiceProvider.cs diff --git a/src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs b/src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs new file mode 100644 index 0000000000..ebec28e2b7 --- /dev/null +++ b/src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs @@ -0,0 +1,7 @@ +using System; + +namespace GitVersion.Core.Abstractions +{ + public interface IDisposableServiceProvider : IServiceProvider, IDisposable + { } +} diff --git a/src/GitVersionTask/GitVersionServiceProvider.cs b/src/GitVersionTask/GitVersionServiceProvider.cs new file mode 100644 index 0000000000..5e1735fb89 --- /dev/null +++ b/src/GitVersionTask/GitVersionServiceProvider.cs @@ -0,0 +1,35 @@ +using GitVersion.Core.Abstractions; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Runtime.CompilerServices; + +namespace GitVersionTask +{ + public readonly struct GitVersionServiceProvider : IDisposableServiceProvider + { + private readonly ServiceProvider serviceProvider; + + public GitVersionServiceProvider(ServiceProvider serviceProvider) => + this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + + private void ensureNonServiceProvider([CallerMemberName] string memberName = "") + { + if (serviceProvider is null) + { + throw new InvalidOperationException($"{memberName} is not available because service provider is null."); + } + } + + public object GetService(Type serviceType) + { + ensureNonServiceProvider(); + return serviceProvider.GetService(serviceType); + } + + public void Dispose() + { + ensureNonServiceProvider(); + serviceProvider.Dispose(); + } + } +} diff --git a/src/GitVersionTask/GitVersionTasks.cs b/src/GitVersionTask/GitVersionTasks.cs index 6c998181da..e28ce61727 100644 --- a/src/GitVersionTask/GitVersionTasks.cs +++ b/src/GitVersionTask/GitVersionTasks.cs @@ -1,8 +1,10 @@ using System; +using GitVersion.Core.Abstractions; using GitVersion.Extensions; using GitVersion.Logging; using GitVersion.Model; using GitVersion.MSBuildTask.Tasks; +using GitVersionTask; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -24,7 +26,7 @@ private static bool ExecuteGitVersionTask(T task, Action(); action(gitVersionTaskExecutor); @@ -58,7 +60,7 @@ private static void Configure(IServiceProvider sp, GitVersionTaskBase task) gitVersionOptions.Settings.NoFetch = gitVersionOptions.Settings.NoFetch || buildAgent != null && buildAgent.PreventFetch(); } - private static IServiceProvider BuildServiceProvider(GitVersionTaskBase task) + private static IDisposableServiceProvider BuildServiceProvider(GitVersionTaskBase task) { var services = new ServiceCollection(); @@ -84,7 +86,8 @@ private static IServiceProvider BuildServiceProvider(GitVersionTaskBase task) var sp = services.BuildServiceProvider(); Configure(sp, task); - return sp; + var gitVersionServiceProder = new GitVersionServiceProvider(sp); + return gitVersionServiceProder; } } } From d5c806d618ff7eefe800a5378554f7520f7c9208 Mon Sep 17 00:00:00 2001 From: Teroneko Date: Sun, 28 Jun 2020 03:51:56 +0200 Subject: [PATCH 07/13] Update src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove because of false impression Co-authored-by: Asbjørn Ulsberg --- src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs b/src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs index 965fadd338..738e6ee9d8 100644 --- a/src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs +++ b/src/GitVersionCore/FileLocking/Abstractions/IFileLock.cs @@ -1,6 +1,6 @@ using System; -namespace GitVersion.FileLocking.Abstractions +namespace GitVersion.FileLocking { public interface IFileLock : IDisposable { } From 3759415da29c19279d7e67fffc28c2bcf7bbd562 Mon Sep 17 00:00:00 2001 From: Teroneko Date: Sun, 28 Jun 2020 03:52:18 +0200 Subject: [PATCH 08/13] Update src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove because of false impresssion Co-authored-by: Asbjørn Ulsberg --- src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs b/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs index 52b3a2e0ab..72e2e726a1 100644 --- a/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs +++ b/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs @@ -20,7 +20,7 @@ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -namespace GitVersion.FileLocking.Abstractions +namespace GitVersion.FileLocking { public interface IFileLocker { From 2432a8ca8cad460ecfa61fcc331e3b229cdd5c80 Mon Sep 17 00:00:00 2001 From: Teroneko Date: Sun, 28 Jun 2020 04:03:57 +0200 Subject: [PATCH 09/13] Update src/GitVersionCore/FileLocking/LockFileApi.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove due to implicit copyright Co-authored-by: Asbjørn Ulsberg --- src/GitVersionCore/FileLocking/LockFileApi.cs | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/GitVersionCore/FileLocking/LockFileApi.cs b/src/GitVersionCore/FileLocking/LockFileApi.cs index 8202e375a7..2e621ca459 100644 --- a/src/GitVersionCore/FileLocking/LockFileApi.cs +++ b/src/GitVersionCore/FileLocking/LockFileApi.cs @@ -1,25 +1,3 @@ -/* -MIT License - -Copyright 2020 Teroneko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ using System; using System.IO; using System.Threading; From 90e57d1d3dfdf2f63e024cce72e7efef26f2df3f Mon Sep 17 00:00:00 2001 From: Teroneko Date: Sun, 28 Jun 2020 04:07:52 +0200 Subject: [PATCH 10/13] Update src/GitVersionCore/Model/FileLock.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Asbjørn Ulsberg --- src/GitVersionCore/Model/FileLock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GitVersionCore/Model/FileLock.cs b/src/GitVersionCore/Model/FileLock.cs index 115feb1d47..f37b39a0ed 100644 --- a/src/GitVersionCore/Model/FileLock.cs +++ b/src/GitVersionCore/Model/FileLock.cs @@ -3,7 +3,7 @@ using System; using System.IO; -namespace GitVersion.Helpers +namespace GitVersion { public class FileLock : IFileLock { From a5edfbad05d624136313efc62fa64c84ff3ee6c6 Mon Sep 17 00:00:00 2001 From: Teroneko Date: Sun, 28 Jun 2020 04:16:12 +0200 Subject: [PATCH 11/13] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove because of false impression Co-authored-by: Asbjørn Ulsberg --- .../FileLocking/FileLockContextExtensions.cs | 22 ------------------- src/GitVersionCore/FileLocking/FileLockUse.cs | 22 ------------------- 2 files changed, 44 deletions(-) diff --git a/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs b/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs index 359ab6dd44..e2c45bba20 100644 --- a/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs +++ b/src/GitVersionCore/FileLocking/FileLockContextExtensions.cs @@ -1,25 +1,3 @@ -/* -MIT License - -Copyright 2020 Teroneko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ namespace GitVersion.FileLocking { diff --git a/src/GitVersionCore/FileLocking/FileLockUse.cs b/src/GitVersionCore/FileLocking/FileLockUse.cs index f5a75de1f4..7005311e93 100644 --- a/src/GitVersionCore/FileLocking/FileLockUse.cs +++ b/src/GitVersionCore/FileLocking/FileLockUse.cs @@ -1,25 +1,3 @@ -/* -MIT License - -Copyright 2020 Teroneko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ using System; using System.ComponentModel; using System.IO; From d9cdcab2ac7f2abd24f02454fee2714a4bad88f7 Mon Sep 17 00:00:00 2001 From: teroneko Date: Sun, 28 Jun 2020 03:33:40 +0100 Subject: [PATCH 12/13] Removed explicit copyright messages due to false impression; Fixed namespaces --- .../FileLocking/Abstractions/IFileLocker.cs | 22 ----------------- .../FileLocking/FileLockContext.cs | 24 ++----------------- src/GitVersionCore/FileLocking/FileLocker.cs | 23 ------------------ src/GitVersionCore/GitVersionCoreModule.cs | 2 -- src/GitVersionCore/Model/FileLock.cs | 1 - 5 files changed, 2 insertions(+), 70 deletions(-) diff --git a/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs b/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs index 72e2e726a1..496db05c19 100644 --- a/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs +++ b/src/GitVersionCore/FileLocking/Abstractions/IFileLocker.cs @@ -1,25 +1,3 @@ -/* -MIT License - -Copyright 2020 Teroneko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ namespace GitVersion.FileLocking { public interface IFileLocker diff --git a/src/GitVersionCore/FileLocking/FileLockContext.cs b/src/GitVersionCore/FileLocking/FileLockContext.cs index 4c2dee76b6..74dd989850 100644 --- a/src/GitVersionCore/FileLocking/FileLockContext.cs +++ b/src/GitVersionCore/FileLocking/FileLockContext.cs @@ -1,25 +1,3 @@ -/* -MIT License - -Copyright 2020 Teroneko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ using System; using System.Diagnostics; using System.IO; @@ -89,7 +67,9 @@ public void DecreaseLockUse(bool decreaseToZero, string lockId) var locksInUse = fileLocker.DecreaseLockUse(decreaseToZero, lockId); if (0 == locksInUse) + { this.decreaseLockUseLocker = null; + } } } } diff --git a/src/GitVersionCore/FileLocking/FileLocker.cs b/src/GitVersionCore/FileLocking/FileLocker.cs index 81c3ce88ea..e566de3a4c 100644 --- a/src/GitVersionCore/FileLocking/FileLocker.cs +++ b/src/GitVersionCore/FileLocking/FileLocker.cs @@ -1,26 +1,3 @@ -/* -MIT License - -Copyright 2020 Teroneko - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -using GitVersion.FileLocking.Abstractions; using System; using System.ComponentModel; using System.Diagnostics; diff --git a/src/GitVersionCore/GitVersionCoreModule.cs b/src/GitVersionCore/GitVersionCoreModule.cs index 62abdf105e..5e7dcdd341 100644 --- a/src/GitVersionCore/GitVersionCoreModule.cs +++ b/src/GitVersionCore/GitVersionCoreModule.cs @@ -6,8 +6,6 @@ using GitVersion.Configuration.Init; using GitVersion.Extensions; using GitVersion.FileLocking; -using GitVersion.FileLocking.Abstractions; -using GitVersion.Helpers; using GitVersion.Logging; using GitVersion.VersionCalculation; using GitVersion.VersionCalculation.Cache; diff --git a/src/GitVersionCore/Model/FileLock.cs b/src/GitVersionCore/Model/FileLock.cs index f37b39a0ed..fca4fda715 100644 --- a/src/GitVersionCore/Model/FileLock.cs +++ b/src/GitVersionCore/Model/FileLock.cs @@ -1,5 +1,4 @@ using GitVersion.FileLocking; -using GitVersion.FileLocking.Abstractions; using System; using System.IO; From 426f3293f7994845f25656a927ead3046ed3a048 Mon Sep 17 00:00:00 2001 From: teroneko Date: Sun, 28 Jun 2020 03:36:35 +0100 Subject: [PATCH 13/13] Removed IDisposableServiceProvider due to recommendations --- .../IDisposableServiceProvider.cs | 7 ---- .../GitVersionServiceProvider.cs | 35 ------------------- src/GitVersionTask/GitVersionTasks.cs | 8 ++--- 3 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs delete mode 100644 src/GitVersionTask/GitVersionServiceProvider.cs diff --git a/src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs b/src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs deleted file mode 100644 index ebec28e2b7..0000000000 --- a/src/GitVersionCore/Core/Abstractions/IDisposableServiceProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace GitVersion.Core.Abstractions -{ - public interface IDisposableServiceProvider : IServiceProvider, IDisposable - { } -} diff --git a/src/GitVersionTask/GitVersionServiceProvider.cs b/src/GitVersionTask/GitVersionServiceProvider.cs deleted file mode 100644 index 5e1735fb89..0000000000 --- a/src/GitVersionTask/GitVersionServiceProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using GitVersion.Core.Abstractions; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Runtime.CompilerServices; - -namespace GitVersionTask -{ - public readonly struct GitVersionServiceProvider : IDisposableServiceProvider - { - private readonly ServiceProvider serviceProvider; - - public GitVersionServiceProvider(ServiceProvider serviceProvider) => - this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - - private void ensureNonServiceProvider([CallerMemberName] string memberName = "") - { - if (serviceProvider is null) - { - throw new InvalidOperationException($"{memberName} is not available because service provider is null."); - } - } - - public object GetService(Type serviceType) - { - ensureNonServiceProvider(); - return serviceProvider.GetService(serviceType); - } - - public void Dispose() - { - ensureNonServiceProvider(); - serviceProvider.Dispose(); - } - } -} diff --git a/src/GitVersionTask/GitVersionTasks.cs b/src/GitVersionTask/GitVersionTasks.cs index e28ce61727..6f30dc9303 100644 --- a/src/GitVersionTask/GitVersionTasks.cs +++ b/src/GitVersionTask/GitVersionTasks.cs @@ -1,10 +1,8 @@ using System; -using GitVersion.Core.Abstractions; using GitVersion.Extensions; using GitVersion.Logging; using GitVersion.Model; using GitVersion.MSBuildTask.Tasks; -using GitVersionTask; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -60,7 +58,7 @@ private static void Configure(IServiceProvider sp, GitVersionTaskBase task) gitVersionOptions.Settings.NoFetch = gitVersionOptions.Settings.NoFetch || buildAgent != null && buildAgent.PreventFetch(); } - private static IDisposableServiceProvider BuildServiceProvider(GitVersionTaskBase task) + private static ServiceProvider BuildServiceProvider(GitVersionTaskBase task) { var services = new ServiceCollection(); @@ -85,9 +83,7 @@ private static IDisposableServiceProvider BuildServiceProvider(GitVersionTaskBas var sp = services.BuildServiceProvider(); Configure(sp, task); - - var gitVersionServiceProder = new GitVersionServiceProvider(sp); - return gitVersionServiceProder; + return sp; } } }