Skip to content

#2578: Retry some file reads and writes to avoid 'file in use' on gitversion.json #2581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
35 changes: 25 additions & 10 deletions src/GitVersion.Core/Core/GitPreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using GitVersion.Common;
using GitVersion.Extensions;
using GitVersion.Helpers;
using GitVersion.Logging;
using GitVersion.Model.Configuration;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -148,7 +149,10 @@ private void CloneRepository(string repositoryUrl, string gitDirectory, Authenti
{
using (log.IndentLog($"Cloning repository from url '{repositoryUrl}'"))
{
repository.Clone(repositoryUrl, gitDirectory, auth);
new OperationWithExponentialBackoff<LockedFileException>(new ThreadSleep(), log, () =>
{
repository.Clone(repositoryUrl, gitDirectory, auth);
}).ExecuteAsync().Wait();
}
}

Expand Down Expand Up @@ -178,7 +182,9 @@ private void NormalizeGitDirectory(bool noFetch, string currentBranchName, bool
{
var refSpecs = string.Join(", ", remote.FetchRefSpecs.Select(r => r.Specification));
log.Info($"Fetching from remote '{remote.Name}' using the following refspecs: {refSpecs}.");
repository.Fetch(remote.Name, Enumerable.Empty<string>(), authentication, null);
new OperationWithExponentialBackoff<LockedFileException>(new ThreadSleep(), log,
() => repository.Fetch(remote.Name, Enumerable.Empty<string>(), authentication, null))
.ExecuteAsync().Wait();
}

EnsureLocalBranchExistsForCurrentBranch(remote, currentBranchName);
Expand Down Expand Up @@ -220,7 +226,7 @@ private void NormalizeGitDirectory(bool noFetch, string currentBranchName, bool
if (matchingCurrentBranch != null)
{
log.Info($"Checking out local branch '{currentBranchName}'.");
repository.Checkout(matchingCurrentBranch.Name.Canonical);
Checkout(matchingCurrentBranch.Name.Canonical);
}
else if (localBranchesWhereCommitShaIsHead.Count > 1)
{
Expand All @@ -233,7 +239,7 @@ private void NormalizeGitDirectory(bool noFetch, string currentBranchName, bool
if (main != null)
{
log.Warning("Because one of the branches is 'main', will build main." + moveBranchMsg);
repository.Checkout(Config.MainBranchKey);
Checkout(Config.MainBranchKey);
}
else
{
Expand All @@ -242,7 +248,7 @@ private void NormalizeGitDirectory(bool noFetch, string currentBranchName, bool
{
var branchWithoutSeparator = branchesWithoutSeparators[0];
log.Warning($"Choosing {branchWithoutSeparator.Name.Canonical} as it is the only branch without / or - in it. " + moveBranchMsg);
repository.Checkout(branchWithoutSeparator.Name.Canonical);
Checkout(branchWithoutSeparator.Name.Canonical);
}
else
{
Expand All @@ -253,12 +259,15 @@ private void NormalizeGitDirectory(bool noFetch, string currentBranchName, bool
else if (localBranchesWhereCommitShaIsHead.Count == 0)
{
log.Info($"No local branch pointing at the commit '{headSha}'. Fake branch needs to be created.");
repository.CreateBranchForPullRequestBranch(authentication);
new OperationWithExponentialBackoff<LockedFileException>(new ThreadSleep(), log, () =>
{
repository.CreateBranchForPullRequestBranch(authentication);
}).ExecuteAsync().Wait();
}
else
{
log.Info($"Checking out local branch 'refs/heads/{localBranchesWhereCommitShaIsHead[0]}'.");
repository.Checkout(localBranchesWhereCommitShaIsHead[0].Name.Friendly);
Checkout(localBranchesWhereCommitShaIsHead[0].Name.Friendly);
}
}
finally
Expand Down Expand Up @@ -329,7 +338,7 @@ private void CreateOrUpdateLocalBranchesFromRemoteTrackingOnes(string remoteName
}
var remoteRefTipId = remoteTrackingReference.ReferenceTargetId;
log.Info($"Updating local ref '{localRef.Name.Canonical}' to point at {remoteRefTipId}.");
repository.Refs.UpdateTarget(localRef, remoteRefTipId);
new OperationWithExponentialBackoff<LockedFileException>(new ThreadSleep(), log, () => repository.Refs.UpdateTarget(localRef, remoteRefTipId)).ExecuteAsync().Wait();
continue;
}

Expand Down Expand Up @@ -382,10 +391,16 @@ public void EnsureLocalBranchExistsForCurrentBranch(IRemote remote, string curre
log.Info(isBranch ? $"Updating local branch {referenceName} to point at {repoTip}"
: $"Updating local branch {referenceName} to match ref {currentBranch}");
var localRef = repository.Refs[localCanonicalName];
repository.Refs.UpdateTarget(localRef, repoTipId);
new OperationWithExponentialBackoff<LockedFileException>(new ThreadSleep(), log, () => repository.Refs.UpdateTarget(localRef, repoTipId)).ExecuteAsync().Wait();
}

repository.Checkout(localCanonicalName);
Checkout(localCanonicalName);
}

private void Checkout(string commitOrBranchSpec)
{
new OperationWithExponentialBackoff<LockedFileException>(new ThreadSleep(), log, () =>
repository.Checkout(commitOrBranchSpec)).ExecuteAsync().Wait();
}
}
}
2 changes: 1 addition & 1 deletion src/GitVersion.Core/Core/ThreadSleep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace GitVersion
{
internal class ThreadSleep : IThreadSleep
public class ThreadSleep : IThreadSleep
{
public async Task SleepAsync(int milliseconds)
{
Expand Down
1 change: 1 addition & 0 deletions src/GitVersion.Core/Git/IReferenceCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public interface IReferenceCollection : IEnumerable<IReference>
void UpdateTarget(IReference directRef, IObjectId targetId);
IEnumerable<IReference> FromGlob(string prefix);
}

}
26 changes: 19 additions & 7 deletions src/GitVersion.Core/Helpers/OperationWithExponentialBackoff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,37 @@

namespace GitVersion.Helpers
{
internal class OperationWithExponentialBackoff<T> where T : Exception
public class OperationWithExponentialBackoff<T> : OperationWithExponentialBackoff<T, bool> where T : Exception
{
public OperationWithExponentialBackoff(IThreadSleep threadSleep, ILog log, Action operation, int maxRetries = 5)
: base(threadSleep, log, () => { operation(); return false; }, maxRetries)
{
}

public new Task ExecuteAsync()
{
return base.ExecuteAsync();
}
}
public class OperationWithExponentialBackoff<T, Result> where T : Exception
{
private readonly IThreadSleep threadSleep;
private readonly ILog log;
private readonly Action operation;
private readonly Func<Result> operation;
private readonly int maxRetries;

public OperationWithExponentialBackoff(IThreadSleep threadSleep, ILog log, Action operation, int maxRetries = 5)
public OperationWithExponentialBackoff(IThreadSleep threadSleep, ILog log, Func<Result> operation, int maxRetries = 5)
{
if (maxRetries < 0)
throw new ArgumentOutOfRangeException(nameof(maxRetries));

this.threadSleep = threadSleep ?? throw new ArgumentNullException(nameof(threadSleep));
this.log = log;
this.log = log ?? throw new ArgumentNullException(nameof(log));
this.operation = operation;
this.maxRetries = maxRetries;
}

public async Task ExecuteAsync()
public async Task<Result> ExecuteAsync()
{
var exceptions = new List<Exception>();

Expand All @@ -36,8 +48,7 @@ public async Task ExecuteAsync()

try
{
operation();
break;
return operation();
}
catch (T e)
{
Expand All @@ -53,6 +64,7 @@ public async Task ExecuteAsync()

sleepMSec *= 2;
}
return default;
}
}
}
11 changes: 11 additions & 0 deletions src/GitVersion.Core/Model/Exceptions/LockedFileException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace GitVersion
{
public class LockedFileException : Exception
{
public LockedFileException(Exception inner) : base(inner.Message, inner)
{
}
}
}
26 changes: 25 additions & 1 deletion src/GitVersion.Core/Model/VersionVariables.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using GitVersion.Helpers;
using GitVersion.Logging;
using System;
using System.Collections;
using System.Collections.Generic;
Expand Down Expand Up @@ -164,7 +166,29 @@ public static VersionVariables FromJson(string json)
return FromDictionary(variablePairs);
}

public static VersionVariables FromFile(string filePath, IFileSystem fileSystem)
public static VersionVariables FromFile(string filePath, IFileSystem fileSystem, ILog log)
{
try
{
if (log == null)
{
return FromFileInternal(filePath, fileSystem);
}
var retryOperation = new OperationWithExponentialBackoff<IOException, VersionVariables>(new ThreadSleep(), log, () => FromFileInternal(filePath, fileSystem));
var versionVariables = retryOperation.ExecuteAsync().Result;
return versionVariables;
}
catch (AggregateException ex)
{
var lastException = ex.InnerExceptions?.LastOrDefault() ?? ex.InnerException;
if (lastException != null)
{
throw lastException;
}
throw;
}
}
private static VersionVariables FromFileInternal(string filePath, IFileSystem fileSystem)
{
using var stream = fileSystem.OpenRead(filePath);
using var reader = new StreamReader(stream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public VersionVariables LoadVersionVariablesFromDiskCache(GitVersionCacheKey key
{
try
{
var loadedVariables = VersionVariables.FromFile(cacheFileName, fileSystem);
var loadedVariables = VersionVariables.FromFile(cacheFileName, fileSystem, log);
return loadedVariables;
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.IO;
using GitVersion.Helpers;
using GitVersion.Logging;
using GitVersion.Model;
using GitVersion.OutputVariables;
Expand All @@ -13,13 +15,15 @@ public interface IOutputGenerator : IVersionConverter<OutputContext>
public class OutputGenerator : IOutputGenerator
{
private readonly IConsole console;
private readonly ILog log;
private readonly IFileSystem fileSystem;
private readonly IOptions<GitVersionOptions> options;
private readonly ICurrentBuildAgent buildAgent;

public OutputGenerator(ICurrentBuildAgent buildAgent, IConsole console, IFileSystem fileSystem, IOptions<GitVersionOptions> options)
public OutputGenerator(ICurrentBuildAgent buildAgent, IConsole console, ILog log, IFileSystem fileSystem, IOptions<GitVersionOptions> options)
{
this.console = console ?? throw new ArgumentNullException(nameof(console));
this.log = log ?? throw new ArgumentNullException(nameof(log));
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
this.options = options ?? throw new ArgumentNullException(nameof(options));
this.buildAgent = buildAgent;
Expand All @@ -34,7 +38,8 @@ public void Execute(VersionVariables variables, OutputContext context)
}
if (gitVersionOptions.Output.Contains(OutputType.File))
{
fileSystem.WriteAllText(context.OutputFile, variables.ToString());
var retryOperation = new OperationWithExponentialBackoff<IOException>(new ThreadSleep(), log, () => fileSystem.WriteAllText(context.OutputFile, variables.ToString()));
retryOperation.ExecuteAsync().Wait();
}
if (gitVersionOptions.Output.Contains(OutputType.Json))
{
Expand Down
Loading