mirror of
https://github.com/FalloutCollaborationProject/FCP-Mod-Updater.git
synced 2026-07-27 17:04:05 -07:00
Improve idle timeout
This commit is contained in:
@@ -182,6 +182,22 @@ public class GitServiceIntegrationTests : IDisposable
|
|||||||
Assert.Equal(0, ahead);
|
Assert.Equal(0, ahead);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CloneAsync_LocalRepository_CreatesGitRepo()
|
||||||
|
{
|
||||||
|
var ct = TestContext.Current.CancellationToken;
|
||||||
|
var remoteDir = Path.Combine(_tempDir, "remote");
|
||||||
|
Directory.CreateDirectory(remoteDir);
|
||||||
|
await InitRepoWithCommit(remoteDir);
|
||||||
|
|
||||||
|
var targetDir = Path.Combine(_tempDir, "clone-target");
|
||||||
|
(bool Success, string? Error) result = await _service.CloneAsync(remoteDir, targetDir, ct: ct);
|
||||||
|
|
||||||
|
Assert.True(result.Success, result.Error);
|
||||||
|
Assert.Null(result.Error);
|
||||||
|
Assert.True(await _service.IsGitRepositoryAsync(targetDir, ct));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetRemoteBranchesAsync_ReturnsBranches()
|
public async Task GetRemoteBranchesAsync_ReturnsBranches()
|
||||||
{
|
{
|
||||||
|
|||||||
+28
-8
@@ -7,6 +7,9 @@ namespace FCPModUpdater.Services;
|
|||||||
|
|
||||||
public partial class GitService : IGitService
|
public partial class GitService : IGitService
|
||||||
{
|
{
|
||||||
|
internal const int StandardCommandTimeoutMs = 30000;
|
||||||
|
internal const int NetworkCommandIdleTimeoutMs = 300000;
|
||||||
|
|
||||||
// Matches git progress output like: "Receiving objects: 45% (555/1234), 12.34 MiB | 1.23 MiB/s"
|
// Matches git progress output like: "Receiving objects: 45% (555/1234), 12.34 MiB | 1.23 MiB/s"
|
||||||
[GeneratedRegex(@"(\d+)%\s*\((\d+)/(\d+)\)", RegexOptions.Compiled)]
|
[GeneratedRegex(@"(\d+)%\s*\((\d+)/(\d+)\)", RegexOptions.Compiled)]
|
||||||
private static partial Regex GitProgressRegex();
|
private static partial Regex GitProgressRegex();
|
||||||
@@ -109,7 +112,12 @@ public partial class GitService : IGitService
|
|||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
progress?.Report("Fetching from remote...");
|
progress?.Report("Fetching from remote...");
|
||||||
var (exitCode, _, error) = await RunGitCommandAsync(path, "fetch --all --prune --recurse-submodules", ct);
|
var (exitCode, error) = await RunGitCommandWithProgressAsync(
|
||||||
|
path,
|
||||||
|
"fetch --all --prune --recurse-submodules",
|
||||||
|
percentProgress: null,
|
||||||
|
ct,
|
||||||
|
idleTimeoutMs: NetworkCommandIdleTimeoutMs);
|
||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
progress?.Report($"Fetch failed: {error}");
|
progress?.Report($"Fetch failed: {error}");
|
||||||
@@ -121,7 +129,12 @@ public partial class GitService : IGitService
|
|||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
progress?.Report("Pulling changes...");
|
progress?.Report("Pulling changes...");
|
||||||
var (exitCode, _, error) = await RunGitCommandAsync(path, "pull --ff-only --recurse-submodules", ct);
|
var (exitCode, error) = await RunGitCommandWithProgressAsync(
|
||||||
|
path,
|
||||||
|
"pull --ff-only --recurse-submodules",
|
||||||
|
percentProgress: null,
|
||||||
|
ct,
|
||||||
|
idleTimeoutMs: NetworkCommandIdleTimeoutMs);
|
||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
progress?.Report($"Pull failed: {error}");
|
progress?.Report($"Pull failed: {error}");
|
||||||
@@ -142,7 +155,7 @@ public partial class GitService : IGitService
|
|||||||
$"clone --progress --recurse-submodules \"{url}\" \"{folderName}\"",
|
$"clone --progress --recurse-submodules \"{url}\" \"{folderName}\"",
|
||||||
percentProgress,
|
percentProgress,
|
||||||
ct,
|
ct,
|
||||||
timeoutMs: 300000); // 5 minute timeout for clone
|
idleTimeoutMs: NetworkCommandIdleTimeoutMs);
|
||||||
|
|
||||||
// Exit code 141 = SIGPIPE (128+13), harmless when git's progress pipe closes early
|
// Exit code 141 = SIGPIPE (128+13), harmless when git's progress pipe closes early
|
||||||
if (exitCode != 0 && exitCode != 141)
|
if (exitCode != 0 && exitCode != 141)
|
||||||
@@ -205,10 +218,10 @@ public partial class GitService : IGitService
|
|||||||
|
|
||||||
private static async Task<(int ExitCode, string Error)> RunGitCommandWithProgressAsync(
|
private static async Task<(int ExitCode, string Error)> RunGitCommandWithProgressAsync(
|
||||||
string workingDirectory, string arguments, IProgress<double>? percentProgress,
|
string workingDirectory, string arguments, IProgress<double>? percentProgress,
|
||||||
CancellationToken ct, int timeoutMs = 30000)
|
CancellationToken ct, int idleTimeoutMs = StandardCommandTimeoutMs)
|
||||||
{
|
{
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
cts.CancelAfter(timeoutMs);
|
cts.CancelAfter(idleTimeoutMs);
|
||||||
|
|
||||||
var errorLines = new List<string>();
|
var errorLines = new List<string>();
|
||||||
|
|
||||||
@@ -223,6 +236,7 @@ public partial class GitService : IGitService
|
|||||||
.WithStandardOutputPipe(PipeTarget.Null)
|
.WithStandardOutputPipe(PipeTarget.Null)
|
||||||
.WithStandardErrorPipe(PipeTarget.ToDelegate(line =>
|
.WithStandardErrorPipe(PipeTarget.ToDelegate(line =>
|
||||||
{
|
{
|
||||||
|
cts.CancelAfter(idleTimeoutMs);
|
||||||
errorLines.Add(line);
|
errorLines.Add(line);
|
||||||
|
|
||||||
if (percentProgress == null)
|
if (percentProgress == null)
|
||||||
@@ -240,7 +254,10 @@ public partial class GitService : IGitService
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
return (ExitCode: -1, Error: "Operation timed out or was cancelled");
|
return ct.IsCancellationRequested
|
||||||
|
? (ExitCode: -1, Error: "Operation was cancelled")
|
||||||
|
: (ExitCode: -1,
|
||||||
|
Error: $"Git command made no progress for {TimeSpan.FromMilliseconds(idleTimeoutMs).TotalMinutes:0} minute(s)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +285,7 @@ public partial class GitService : IGitService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<(int ExitCode, string Output, string Error)> RunGitCommandAsync(
|
private static async Task<(int ExitCode, string Output, string Error)> RunGitCommandAsync(
|
||||||
string workingDirectory, string arguments, CancellationToken ct, int timeoutMs = 30000)
|
string workingDirectory, string arguments, CancellationToken ct, int timeoutMs = StandardCommandTimeoutMs)
|
||||||
{
|
{
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
cts.CancelAfter(timeoutMs);
|
cts.CancelAfter(timeoutMs);
|
||||||
@@ -287,7 +304,10 @@ public partial class GitService : IGitService
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
return (ExitCode: -1, Output: "", Error: "Operation timed out or was cancelled");
|
return ct.IsCancellationRequested
|
||||||
|
? (ExitCode: -1, Output: "", Error: "Operation was cancelled")
|
||||||
|
: (ExitCode: -1, Output: "",
|
||||||
|
Error: $"Git command timed out after {TimeSpan.FromMilliseconds(timeoutMs).TotalSeconds:0} second(s)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user