Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ jobs:
matrix:
os: [ windows-latest, macos-latest, ubuntu-latest ]
runs-on: ${{ matrix.os }}
# A normal run finishes in well under 15 minutes (Windows, the slowest, is
# ~12-14 minutes); this caps a hung test at 15 minutes instead of letting it
# ride GitHub's 6-hour default.
timeout-minutes: 15
Comment thread
andyleejordan marked this conversation as resolved.
Outdated
env:
DOTNET_NOLOGO: true
DOTNET_GENERATE_ASPNET_CERTIFICATE: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,34 +217,58 @@ private string GenerateLoggingScript(params string[] logStatements)
/// <summary>
/// Reads the next output line from the test script log file. Useful in assertions to verify script progress against breakpointing.
/// </summary>
private async Task<string> ReadScriptLogLineAsync()
private async Task<string> ReadScriptLogLineAsync(CancellationToken cancellationToken = default)
{
while (scriptLogReader is null)
// Reading a log line should be near-instant, but the script we read
// from is driven by the debugger and can fail to produce output (for
// example if an attach or breakpoint never lands). Cap the wait so
// the test fails fast with a clear message instead of spinning
// forever -- a busy-spin here previously pegged the CPU and starved
// xUnit's cooperative test timeout, hanging CI for the full six hours.
using CancellationTokenSource timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(TimeSpan.FromSeconds(15));
Comment thread
andyleejordan marked this conversation as resolved.
Outdated
CancellationToken token = timeoutCts.Token;

try
{
try
while (scriptLogReader is null)
{
scriptLogReader = new StreamReader(
new FileStream(
testScriptLogPath,
FileMode.OpenOrCreate,
FileAccess.Read, // Because we use append, its OK to create the file ahead of the script
FileShare.ReadWrite
)
);
try
{
scriptLogReader = new StreamReader(
new FileStream(
testScriptLogPath,
FileMode.OpenOrCreate,
FileAccess.Read, // Because we use append, its OK to create the file ahead of the script
FileShare.ReadWrite
)
);
}
catch (IOException) //Sadly there does not appear to be a xplat way to wait for file availability, but luckily this does not appear to fire often.
{
await Task.Delay(500, token);
}
}
catch (IOException) //Sadly there does not appear to be a xplat way to wait for file availability, but luckily this does not appear to fire often.

// return valid lines only
string nextLine = string.Empty;
while (string.IsNullOrEmpty(nextLine))
{
await Task.Delay(500);
nextLine = await scriptLogReader.ReadLineAsync(token); //Might return null if at EOF because we created it above but the script hasn't written to it yet
if (string.IsNullOrEmpty(nextLine))
{
// At EOF waiting for the script to write more: yield and
// back off so we don't busy-spin the CPU while polling.
await Task.Delay(100, token);
}
}
return nextLine;
}

// return valid lines only
string nextLine = string.Empty;
while (nextLine is null || nextLine.Length == 0)
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
{
nextLine = await scriptLogReader.ReadLineAsync(); //Might return null if at EOF because we created it above but the script hasn't written to it yet
throw new TimeoutException(
$"Timed out waiting for the test script to write a log line to '{testScriptLogPath}'.");
}
return nextLine;
}

[Fact]
Expand Down
Loading