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:
Michael Nebel
2025-12-04 09:13:12 +01:00
committed by GitHub
6 changed files with 78 additions and 21 deletions

View File

@@ -48,7 +48,7 @@ namespace Semmle.Autobuild.CSharp
{ {
// When a custom .NET CLI has been installed, `dotnet --info` has already been executed // When a custom .NET CLI has been installed, `dotnet --info` has already been executed
// to verify the installation. // 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) foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild)
{ {
var cleanCommand = GetCleanCommand(builder.Actions, dotNetPath, environment); var cleanCommand = GetCleanCommand(builder.Actions, dotNetPath, environment);
@@ -111,14 +111,6 @@ namespace Semmle.Autobuild.CSharp
private static string DotNetCommand(IBuildActions actions, string? dotNetPath) => private static string DotNetCommand(IBuildActions actions, string? dotNetPath) =>
dotNetPath is not null ? actions.PathCombine(dotNetPath, "dotnet") : "dotnet"; 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) private static CommandBuilder GetCleanCommand(IBuildActions actions, string? dotNetPath, IDictionary<string, string>? environment)
{ {
var clean = new CommandBuilder(actions, null, environment). var clean = new CommandBuilder(actions, null, environment).

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Semmle.Util; 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); 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() private void Info()
{ {
var res = dotnetCliInvoker.RunCommand("--info", silent: false); // Allow up to four attempts (with up to three retries) to run `dotnet --info`, to mitigate transient issues
if (!res) 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; 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> /// <summary>
/// Returns a script for downloading specific .NET SDK versions, if the /// Returns a script for downloading specific .NET SDK versions, if the
/// versions are not already installed. /// versions are not already installed.
@@ -292,9 +339,7 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
}; };
} }
var dotnetInfo = new CommandBuilder(actions, environment: MinimalEnvironment). var dotnetInfo = InfoScript(actions, actions.PathCombine(path, "dotnet"), MinimalEnvironment.ToDictionary(), logger);
RunCommand(actions.PathCombine(path, "dotnet")).
Argument("--info").Script;
Func<string, BuildScript> getInstallAndVerify = version => Func<string, BuildScript> getInstallAndVerify = version =>
// run `dotnet --info` after install, to check that it executes successfully // run `dotnet --info` after install, to check that it executes successfully

View File

@@ -57,15 +57,21 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
return startInfo; 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 pi = MakeDotnetStartInfo(args, workingDirectory);
var threadId = Environment.CurrentManagedThreadId; var threadId = Environment.CurrentManagedThreadId;
void onOut(string s) => logger.Log(silent ? Severity.Debug : Severity.Info, s, threadId); void onOut(string s) => logger.Log(silent ? Severity.Debug : Severity.Info, s, threadId);
void onError(string s) => logger.LogError(s, threadId); void onError(string s) => logger.LogError(s, threadId);
logger.LogInfo($"Running '{Exec} {args}'{dirLog}"); logger.LogInfo($"Running '{Exec} {args}'{dirLog}");
var exitCode = pi.ReadOutput(out output, onOut, onError); 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) if (exitCode != 0)
{ {
logger.LogError($"Command '{Exec} {args}'{dirLog} failed with exit code {exitCode}"); 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) => public bool RunCommand(string args, bool silent = true) =>
RunCommandAux(args, null, out _, silent); 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) => public bool RunCommand(string args, out IList<string> output, bool silent = true) =>
RunCommandAux(args, null, out output, silent); RunCommandAux(args, null, out output, silent);

View File

@@ -30,6 +30,12 @@ namespace Semmle.Extraction.CSharp.DependencyFetching
/// </summary> /// </summary>
bool RunCommand(string args, bool silent = true); 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> /// <summary>
/// Execute `dotnet <paramref name="args"/>` and return true if the command succeeded, otherwise false. /// Execute `dotnet <paramref name="args"/>` and return true if the command succeeded, otherwise false.
/// The output of the command is returned in `output`. /// The output of the command is returned in `output`.

View File

@@ -12,6 +12,7 @@ namespace Semmle.Extraction.Tests
private string lastArgs = ""; private string lastArgs = "";
public string WorkingDirectory { get; private set; } = ""; public string WorkingDirectory { get; private set; } = "";
public bool Success { get; set; } = true; public bool Success { get; set; } = true;
public int ExitCode { get; set; } = 0;
public DotNetCliInvokerStub(IList<string> output) public DotNetCliInvokerStub(IList<string> output)
{ {
@@ -26,6 +27,12 @@ namespace Semmle.Extraction.Tests
return Success; return Success;
} }
public int RunCommandExitCode(string args, bool silent)
{
lastArgs = args;
return ExitCode;
}
public bool RunCommand(string args, out IList<string> output, bool silent) public bool RunCommand(string args, out IList<string> output, bool silent)
{ {
lastArgs = args; lastArgs = args;
@@ -83,7 +90,7 @@ namespace Semmle.Extraction.Tests
public void TestDotnetInfoFailure() public void TestDotnetInfoFailure()
{ {
// Setup // Setup
var dotnetCliInvoker = new DotNetCliInvokerStub(new List<string>()) { Success = false }; var dotnetCliInvoker = new DotNetCliInvokerStub(new List<string>()) { ExitCode = 1 };
// Execute // Execute
try try
@@ -94,7 +101,7 @@ namespace Semmle.Extraction.Tests
// Verify // Verify
catch (Exception e) catch (Exception e)
{ {
Assert.Equal("dotnet --info failed.", e.Message); Assert.Equal("dotnet --info failed with exit code 1.", e.Message);
return; return;
} }
Assert.Fail("Expected exception"); Assert.Fail("Expected exception");

View File

@@ -1,9 +1,7 @@
import pytest import pytest
@pytest.mark.skip(reason=".NET 10 info command crashes")
def test1(codeql, csharp): def test1(codeql, csharp):
codeql.database.create() codeql.database.create()
@pytest.mark.skip(reason=".NET 10 info command crashes")
def test2(codeql, csharp): def test2(codeql, csharp):
codeql.database.create(build_mode="none") codeql.database.create(build_mode="none")