mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #20936 from michaelnebel/csharp/nocrashdotnetinfo
C#: Retry logic for `dotnet --info` when it fails with exit code 143.
This commit is contained in:
@@ -48,7 +48,7 @@ namespace Semmle.Autobuild.CSharp
|
||||
{
|
||||
// When a custom .NET CLI has been installed, `dotnet --info` has already been executed
|
||||
// to verify the installation.
|
||||
var ret = dotNetPath is null ? GetInfoCommand(builder.Actions, dotNetPath, environment) : BuildScript.Success;
|
||||
var ret = dotNetPath is null ? DotNet.InfoScript(builder.Actions, DotNetCommand(builder.Actions, dotNetPath), environment, builder.Logger) : BuildScript.Success;
|
||||
foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild)
|
||||
{
|
||||
var cleanCommand = GetCleanCommand(builder.Actions, dotNetPath, environment);
|
||||
@@ -111,14 +111,6 @@ namespace Semmle.Autobuild.CSharp
|
||||
private static string DotNetCommand(IBuildActions actions, string? dotNetPath) =>
|
||||
dotNetPath is not null ? actions.PathCombine(dotNetPath, "dotnet") : "dotnet";
|
||||
|
||||
private static BuildScript GetInfoCommand(IBuildActions actions, string? dotNetPath, IDictionary<string, string>? environment)
|
||||
{
|
||||
var info = new CommandBuilder(actions, null, environment).
|
||||
RunCommand(DotNetCommand(actions, dotNetPath)).
|
||||
Argument("--info");
|
||||
return info.Script;
|
||||
}
|
||||
|
||||
private static CommandBuilder GetCleanCommand(IBuildActions actions, string? dotNetPath, IDictionary<string, string>? environment)
|
||||
{
|
||||
var clean = new CommandBuilder(actions, null, environment).
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using Semmle.Util;
|
||||
@@ -36,12 +37,29 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
|
||||
public static IDotNet Make(ILogger logger, string? dotNetPath, TemporaryDirectory tempWorkingDirectory, DependabotProxy? dependabotProxy) => new DotNet(logger, dotNetPath, tempWorkingDirectory, dependabotProxy);
|
||||
|
||||
private static void HandleRetryExitCode143(string dotnet, int attempt, ILogger logger)
|
||||
{
|
||||
logger.LogWarning($"Running '{dotnet} --info' failed with exit code 143. Retrying...");
|
||||
var sleep = Math.Pow(2, attempt) * 1000;
|
||||
Thread.Sleep((int)sleep);
|
||||
}
|
||||
|
||||
private void Info()
|
||||
{
|
||||
var res = dotnetCliInvoker.RunCommand("--info", silent: false);
|
||||
if (!res)
|
||||
// Allow up to four attempts (with up to three retries) to run `dotnet --info`, to mitigate transient issues
|
||||
for (int attempt = 0; attempt < 4; attempt++)
|
||||
{
|
||||
throw new Exception($"{dotnetCliInvoker.Exec} --info failed.");
|
||||
var exitCode = dotnetCliInvoker.RunCommandExitCode("--info", silent: false);
|
||||
switch (exitCode)
|
||||
{
|
||||
case 0:
|
||||
return;
|
||||
case 143 when attempt < 3:
|
||||
HandleRetryExitCode143(dotnetCliInvoker.Exec, attempt, logger);
|
||||
continue;
|
||||
default:
|
||||
throw new Exception($"{dotnetCliInvoker.Exec} --info failed with exit code {exitCode}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +211,35 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
return BuildScript.Failure;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a script for running `dotnet --info`, with retries on exit code 143.
|
||||
/// </summary>
|
||||
public static BuildScript InfoScript(IBuildActions actions, string dotnet, IDictionary<string, string>? environment, ILogger logger)
|
||||
{
|
||||
var info = new CommandBuilder(actions, null, environment).
|
||||
RunCommand(dotnet).
|
||||
Argument("--info");
|
||||
var script = info.Script;
|
||||
for (var attempt = 0; attempt < 4; attempt++)
|
||||
{
|
||||
var attemptCopy = attempt; // Capture in local variable
|
||||
script = BuildScript.Bind(script, ret =>
|
||||
{
|
||||
switch (ret)
|
||||
{
|
||||
case 0:
|
||||
return BuildScript.Success;
|
||||
case 143 when attemptCopy < 3:
|
||||
HandleRetryExitCode143(dotnet, attemptCopy, logger);
|
||||
return info.Script;
|
||||
default:
|
||||
return BuildScript.Failure;
|
||||
}
|
||||
});
|
||||
}
|
||||
return script;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a script for downloading specific .NET SDK versions, if the
|
||||
/// versions are not already installed.
|
||||
@@ -292,9 +339,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
};
|
||||
}
|
||||
|
||||
var dotnetInfo = new CommandBuilder(actions, environment: MinimalEnvironment).
|
||||
RunCommand(actions.PathCombine(path, "dotnet")).
|
||||
Argument("--info").Script;
|
||||
var dotnetInfo = InfoScript(actions, actions.PathCombine(path, "dotnet"), MinimalEnvironment.ToDictionary(), logger);
|
||||
|
||||
Func<string, BuildScript> getInstallAndVerify = version =>
|
||||
// run `dotnet --info` after install, to check that it executes successfully
|
||||
|
||||
@@ -57,15 +57,21 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
return startInfo;
|
||||
}
|
||||
|
||||
private bool RunCommandAux(string args, string? workingDirectory, out IList<string> output, bool silent)
|
||||
private int RunCommandExitCodeAux(string args, string? workingDirectory, out IList<string> output, out string dirLog, bool silent)
|
||||
{
|
||||
var dirLog = string.IsNullOrWhiteSpace(workingDirectory) ? "" : $" in {workingDirectory}";
|
||||
dirLog = string.IsNullOrWhiteSpace(workingDirectory) ? "" : $" in {workingDirectory}";
|
||||
var pi = MakeDotnetStartInfo(args, workingDirectory);
|
||||
var threadId = Environment.CurrentManagedThreadId;
|
||||
void onOut(string s) => logger.Log(silent ? Severity.Debug : Severity.Info, s, threadId);
|
||||
void onError(string s) => logger.LogError(s, threadId);
|
||||
logger.LogInfo($"Running '{Exec} {args}'{dirLog}");
|
||||
var exitCode = pi.ReadOutput(out output, onOut, onError);
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
private bool RunCommandAux(string args, string? workingDirectory, out IList<string> output, bool silent)
|
||||
{
|
||||
var exitCode = RunCommandExitCodeAux(args, workingDirectory, out output, out var dirLog, silent);
|
||||
if (exitCode != 0)
|
||||
{
|
||||
logger.LogError($"Command '{Exec} {args}'{dirLog} failed with exit code {exitCode}");
|
||||
@@ -77,6 +83,9 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
public bool RunCommand(string args, bool silent = true) =>
|
||||
RunCommandAux(args, null, out _, silent);
|
||||
|
||||
public int RunCommandExitCode(string args, bool silent = true) =>
|
||||
RunCommandExitCodeAux(args, null, out _, out _, silent);
|
||||
|
||||
public bool RunCommand(string args, out IList<string> output, bool silent = true) =>
|
||||
RunCommandAux(args, null, out output, silent);
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
|
||||
/// </summary>
|
||||
bool RunCommand(string args, bool silent = true);
|
||||
|
||||
/// <summary>
|
||||
/// Execute `dotnet <paramref name="args"/>` and return the exit code.
|
||||
/// If `silent` is true the output of the command is logged as `debug` otherwise as `info`.
|
||||
/// </summary>
|
||||
int RunCommandExitCode(string args, bool silent = true);
|
||||
|
||||
/// <summary>
|
||||
/// Execute `dotnet <paramref name="args"/>` and return true if the command succeeded, otherwise false.
|
||||
/// The output of the command is returned in `output`.
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Semmle.Extraction.Tests
|
||||
private string lastArgs = "";
|
||||
public string WorkingDirectory { get; private set; } = "";
|
||||
public bool Success { get; set; } = true;
|
||||
public int ExitCode { get; set; } = 0;
|
||||
|
||||
public DotNetCliInvokerStub(IList<string> output)
|
||||
{
|
||||
@@ -26,6 +27,12 @@ namespace Semmle.Extraction.Tests
|
||||
return Success;
|
||||
}
|
||||
|
||||
public int RunCommandExitCode(string args, bool silent)
|
||||
{
|
||||
lastArgs = args;
|
||||
return ExitCode;
|
||||
}
|
||||
|
||||
public bool RunCommand(string args, out IList<string> output, bool silent)
|
||||
{
|
||||
lastArgs = args;
|
||||
@@ -83,7 +90,7 @@ namespace Semmle.Extraction.Tests
|
||||
public void TestDotnetInfoFailure()
|
||||
{
|
||||
// Setup
|
||||
var dotnetCliInvoker = new DotNetCliInvokerStub(new List<string>()) { Success = false };
|
||||
var dotnetCliInvoker = new DotNetCliInvokerStub(new List<string>()) { ExitCode = 1 };
|
||||
|
||||
// Execute
|
||||
try
|
||||
@@ -94,7 +101,7 @@ namespace Semmle.Extraction.Tests
|
||||
// Verify
|
||||
catch (Exception e)
|
||||
{
|
||||
Assert.Equal("dotnet --info failed.", e.Message);
|
||||
Assert.Equal("dotnet --info failed with exit code 1.", e.Message);
|
||||
return;
|
||||
}
|
||||
Assert.Fail("Expected exception");
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import pytest
|
||||
|
||||
@pytest.mark.skip(reason=".NET 10 info command crashes")
|
||||
def test1(codeql, csharp):
|
||||
codeql.database.create()
|
||||
|
||||
@pytest.mark.skip(reason=".NET 10 info command crashes")
|
||||
def test2(codeql, csharp):
|
||||
codeql.database.create(build_mode="none")
|
||||
|
||||
Reference in New Issue
Block a user