Skip to content

Commit f84d632

Browse files
fix(installer): read child stdout/stderr concurrently to avoid pipe deadlock
1 parent 9f075cc commit f84d632

1 file changed

Lines changed: 23 additions & 4 deletions

File tree

package/AgentWindowsManaged/Actions/CustomActions.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,13 +376,23 @@ ActionResult Fail(string msg)
376376
};
377377

378378
using Process process = Process.Start(startInfo);
379+
380+
// Start draining stdout/stderr BEFORE WaitForExit. The default
381+
// OS pipe buffer is small (4 KiB-ish on Windows), so if the
382+
// child writes more than the buffer holds while we're still
383+
// sitting on WaitForExit, the child blocks on a full pipe and
384+
// we kill it for "timing out" — classic .NET deadlock.
385+
// Running both reads concurrently (as tasks) means the buffer
386+
// is drained as the child fills it.
387+
System.Threading.Tasks.Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync();
388+
System.Threading.Tasks.Task<string> stderrTask = process.StandardError.ReadToEndAsync();
379389
if (!process.WaitForExit(60_000))
380390
{
381391
try { process.Kill(); } catch { /* already gone */ }
382392
return Fail("Agent tunnel enrollment timed out after 60 seconds.");
383393
}
384-
string stdout = process.StandardOutput.ReadToEnd();
385-
string stderr = process.StandardError.ReadToEnd();
394+
string stdout = stdoutTask.GetAwaiter().GetResult();
395+
string stderr = stderrTask.GetAwaiter().GetResult();
386396

387397
if (!string.IsNullOrEmpty(stdout)) session.Log($"enrollment stdout: {Redact(stdout)}");
388398
if (!string.IsNullOrEmpty(stderr)) session.Log($"enrollment stderr: {Redact(stderr)}");
@@ -519,6 +529,15 @@ ActionResult Fail(string title, string detail, string nextStep)
519529
};
520530

521531
using Process process = Process.Start(startInfo);
532+
533+
// Drain stdout/stderr concurrently BEFORE WaitForExit so a
534+
// chatty child (verify-tunnel can emit a large diagnostic
535+
// payload on the failure path) cannot fill its pipe buffer
536+
// and deadlock waiting for us to read. See the matching
537+
// pattern in `EnrollAgentTunnel`.
538+
System.Threading.Tasks.Task<string> stdoutTask = process.StandardOutput.ReadToEndAsync();
539+
System.Threading.Tasks.Task<string> stderrTask = process.StandardError.ReadToEndAsync();
540+
522541
// Hard wall-clock cap a few seconds beyond the agent's own --timeout so a
523542
// misbehaving process can't hang the installer.
524543
if (!process.WaitForExit((VerifyTimeoutSeconds + 5) * 1000))
@@ -530,8 +549,8 @@ ActionResult Fail(string title, string detail, string nextStep)
530549
"Re-run the installer. If the failure repeats, network path likely drops UDP mid-flow; check Windows Firewall, NAT, and EDR network inspection.");
531550
}
532551

533-
string stdout = process.StandardOutput.ReadToEnd();
534-
string stderr = process.StandardError.ReadToEnd();
552+
string stdout = stdoutTask.GetAwaiter().GetResult();
553+
string stderr = stderrTask.GetAwaiter().GetResult();
535554

536555
if (!string.IsNullOrEmpty(stdout)) session.Log($"verify-tunnel stdout: {stdout}");
537556
if (!string.IsNullOrEmpty(stderr)) session.Log($"verify-tunnel stderr: {stderr}");

0 commit comments

Comments
 (0)