Skip to content
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

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/GitVersionCore/GitVersionCoreDefaults.cs
Original file line number Diff line number Diff line change
@@ -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";
teneko marked this conversation as resolved.
Show resolved Hide resolved
}
}
12 changes: 12 additions & 0 deletions src/GitVersionCore/GitVersionCoreModule.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -29,6 +32,15 @@ public void RegisterTypes(IServiceCollection services)
services.AddSingleton<IConsole, ConsoleAdapter>();
services.AddSingleton<IGitVersionCache, GitVersionCache>();

services.AddSingleton<IFileLock>((serviceProvider) => {
var gitVersionCache = serviceProvider.GetRequiredService<IGitVersionCache>();
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<IGitVersionCacheKeyFactory, GitVersionCacheKeyFactory>();
services.AddSingleton<IGitVersionContextFactory, GitVersionContextFactory>();
services.AddSingleton<IConfigFileLocatorFactory, ConfigFileLocatorFactory>();
Expand Down
10 changes: 10 additions & 0 deletions src/GitVersionCore/Helpers/Abstractions/IFileLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.IO;

namespace GitVersion.Helpers.Abstractions
{
public interface IFileLock : IDisposable
{
FileStream FileStream { get; }
}
}
208 changes: 208 additions & 0 deletions src/GitVersionCore/Helpers/LockFile.cs
Original file line number Diff line number Diff line change
@@ -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;
teneko marked this conversation as resolved.
Show resolved Hide resolved
using System.Threading;

namespace GitVersion.Helpers
{
/// <summary>
/// This helper class can lock files.
/// </summary>
public static class LockFile
teneko marked this conversation as resolved.
Show resolved Hide resolved
{
public const FileMode DefaultFileMode = FileMode.OpenOrCreate;
public const FileAccess DefaultFileAccess = FileAccess.ReadWrite;
public const FileShare DefaultFileShare = FileShare.None;
teneko marked this conversation as resolved.
Show resolved Hide resolved
public const int DefaultTimeoutInMilliseconds = Timeout.Infinite;

/// <summary>
/// Try to acquire lock on file but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="fileStream">The locked file as file stream.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If true the lock acquirement was successful.</returns>
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))
teneko marked this conversation as resolved.
Show resolved Hide resolved
{
fileStream = null;
return false;
}
}

/// <summary>
/// Try to acquire lock on file but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If not null the lock acquirement was successful.</returns>
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;
}

/// <summary>
/// Wait until file gets acquired lock but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="fileStream">The locked file as file stream.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If true the lock acquirement was successful.</returns>
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);
}

/// <summary>
/// Wait until file gets acquired lock but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If not null the lock acquirement was successful.</returns>
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);
}

/// <summary>
/// Wait until file gets acquired lock but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="timeoutInMilliseconds">The timeout in milliseconds.</param>
/// <param name="fileStream">The locked file as file stream.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If true the lock acquirement was successful.</returns>
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);

/// <summary>
/// Wait until file gets acquired lock but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="timeoutInMilliseconds">The timeout in milliseconds.</param>
/// <param name="fileStream">The locked file as file stream.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If not null the lock acquirement was successful.</returns>
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);

/// <summary>
/// Wait until file gets acquired lock but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="timeout">The timeout specified as <see cref="TimeSpan"/>.</param>
/// <param name="fileStream">The locked file as file stream.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If true the lock acquirement was successful.</returns>
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);
}

/// <summary>
/// Wait until file gets acquired lock but only as long the file stream is opened.
/// </summary>
/// <param name="filePath">The path to file that get locked.</param>
/// <param name="timeout">The timeout specified as <see cref="TimeSpan"/>.</param>
/// <param name="fileStream">The locked file as file stream.</param>
/// <param name="fileMode">The file mode when opening file.</param>
/// <param name="fileAccess">The file access when opening file.</param>
/// <param name="fileShare">The file share when opening file</param>
/// <returns>If ont null lock acquirement was successful.</returns>
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);
}
}
}
17 changes: 17 additions & 0 deletions src/GitVersionCore/Model/FileLock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using GitVersion.Helpers.Abstractions;
using System;
using System.IO;

namespace GitVersion.Helpers
teneko marked this conversation as resolved.
Show resolved Hide resolved
{
public class FileLock : IFileLock
teneko marked this conversation as resolved.
Show resolved Hide resolved
{
public FileStream FileStream { get; }

public FileLock(FileStream fileStream) =>
fileStream = fileStream ?? throw new ArgumentNullException(nameof(fileStream));

public void Dispose() =>
FileStream.Dispose();
}
}