Skip to content

Commit a2ecbab

Browse files
Fix thread-pool starvation that wedged attach E2E test
CanAttachScriptWithPathMappings intermittently hung Windows CI for hours instead of failing fast. Its ReadScriptLogLineAsync tailed the script log with `while (...) await ReadLineAsync()`, but at EOF ReadLineAsync completes synchronously with null, so the loop never released its thread-pool thread. On constrained CI runners that starved the pool, which both wedged the DAP client's background I/O and prevented the xUnit (15s) and harness (30s) timeout continuations from ever running -- so a transient stall rode the job timeout for hours. Await a short delay between reads so the tail loop yields, and add a matching sleep to the child process's Debug-Runspace readiness poll so it cannot peg a core during the attach handshake. Combined with the 30-minute CI job cap, a genuine stall now fails fast via the test's own timeout instead of hanging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 016ee4a commit a2ecbab

1 file changed

Lines changed: 20 additions & 5 deletions

File tree

test/PowerShellEditorServices.Test.E2E/DebugAdapterProtocolMessageTests.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,24 @@ private async Task<string> ReadScriptLogLineAsync()
238238
}
239239
}
240240

241-
// return valid lines only
242-
string nextLine = string.Empty;
243-
while (nextLine is null || nextLine.Length == 0)
241+
// Tail the log until a non-empty line is available. The awaited
242+
// delay between reads is essential: at EOF ReadLineAsync completes
243+
// synchronously with null, so without it this becomes a tight loop
244+
// that never releases its thread-pool thread. On constrained CI
245+
// runners that starves the pool, wedging the DAP client's
246+
// background I/O (and the whole test) and defeating both the xUnit
247+
// and harness timeouts so a stall hangs for hours instead of
248+
// failing fast.
249+
while (true)
244250
{
245-
nextLine = await scriptLogReader.ReadLineAsync(); //Might return null if at EOF because we created it above but the script hasn't written to it yet
251+
string nextLine = await scriptLogReader.ReadLineAsync();
252+
if (!string.IsNullOrEmpty(nextLine))
253+
{
254+
return nextLine;
255+
}
256+
257+
await Task.Delay(100);
246258
}
247-
return nextLine;
248259
}
249260

250261
[Fact]
@@ -762,6 +773,10 @@ WinPS will always need this.
762773
if (((Get-Date) - $start).TotalSeconds -gt 10) {
763774
throw 'Timeout waiting for Debug-Runspace to be subscribed.'
764775
}
776+
777+
# Yield a slice so this poll doesn't peg a core while the
778+
# runner is also servicing the attach handshake.
779+
Start-Sleep -Milliseconds 100
765780
}
766781
767782
$ps.Invoke()

0 commit comments

Comments
 (0)