Repository navigation
Drain the version probe's pipes while waiting so a chatty --version cannot deadlock startup - #869
Conversation
…859) A --version that writes past the OS pipe buffer blocks on the full pipe while the probe sits in WaitForExit unread, so the probe hangs until its per-attempt timeout kills the child and retries — the multi-minute silent daemon startup. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PR Summary by QodoDrain version probe output to prevent startup deadlocks
AI Description
Diagram
High-Level Assessment
Files changed (2)
|
Code Review by Qodo
1.
|
| if (!OperatingSystem.IsWindows()) | ||
| File.SetUnixFileMode(cli, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); | ||
|
|
||
| await Assert.That(DaemonRunner.ProbeCliVersionForLaunch(cli)).IsEqualTo("9.9.9"); |
There was a problem hiding this comment.
2. Probe tests bypass subprocess limits 📘 Rule violation ▣ Testability
DaemonRunnerVersionProbeTests adds a test that launches a real POSIX shell process through ProbeCliVersionForLaunch without placing the class under ParallelLimiter<SubprocessLimit>. Under the default concurrent test schedule, this probe competes outside the suite-wide subprocess budget with every other process-heavy test class.
Agent Prompt
## Issue description
`DaemonRunnerVersionProbeTests` now runs a real shell subprocess but does not participate in the suite-wide subprocess concurrency limit.
## Fix Focus Areas
- test/Capacitor.Cli.Daemon.Tests.Unit/DaemonRunnerVersionProbeTests.cs[13-13]
- test/Capacitor.Cli.Daemon.Tests.Unit/DaemonRunnerVersionProbeTests.cs[61-70]
## Recommended Fix
Annotate `DaemonRunnerVersionProbeTests` with `[ParallelLimiter<SubprocessLimit>]` so this real-process test draws from the shared subprocess budget.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| [Test] | ||
| public async Task A_version_whose_output_exceeds_the_pipe_buffer_is_drained_not_deadlocked() { | ||
| Skip.Unless(!OperatingSystem.IsWindows(), "The stub binary is a POSIX shell script."); | ||
| using var tmp = new TempDir(); |
There was a problem hiding this comment.
3. Test temp storage bypasses injection 📘 Rule violation ▣ Testability
A_version_whose_output_exceeds_the_pipe_buffer_is_drained_not_deadlocked constructs and disposes a local TempDir instead of using a framework-injected [TempDir] property. Whenever this test runs, its temporary state follows a manually managed lifecycle that differs from the required lifecycle used by other TUnit fixtures.
Agent Prompt
## Issue description
The added test manually creates `TempDir`, while test classes must receive temporary storage through one public required property annotated with `[TempDir]`.
## Fix Focus Areas
- test/Capacitor.Cli.Daemon.Tests.Unit/DaemonRunnerVersionProbeTests.cs[13-13]
- test/Capacitor.Cli.Daemon.Tests.Unit/DaemonRunnerVersionProbeTests.cs[61-66]
## Recommended Fix
Add `[TempDir] public required TempDir Tmp { get; init; }` to the test class, remove the local `new TempDir()`, and create the stub through `Tmp.CreateFile(...)`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
#859) Draining kept every byte in a string, removing pipe backpressure: a CLI that floods --version could allocate without limit for the whole probe budget. Keep a bounded prefix (the version is at the front) and read the rest to EOF unretained.
|
Ran a Codex review of this branch (
No other findings. |
Refs #859 — AI-2684
What & why
The daemon's startup vendor
--versionprobe deadlocks on any CLI that prints more than the OS pipe buffer.ProbeCliVersionOnceredirected both stdout and stderr but calledWaitForExit(timeout)before reading either, so a child that writes ~16-64 KB+ (some vendor CLIs emit a banner or an "update available" notice) blocks on the full pipe while the parent blocks in the wait — neither side moves until the per-attempt timeout kills the child, then it retries three times, across several probe sites. That is the multi-minute silent daemon startup: observed live asagy --versionalive 75+ seconds as a daemon child though it returns in ~1 s standalone. The probe now drains both streams while the child runs, so it exits promptly and the version is read.This is the pipe-deadlock cause only. The other AI-2684 items — moving the probes off the startup critical path so a genuinely hung CLI cannot delay the socket bind, a startup phase trace for the pre-host window, and
kcap daemon statusdistinguishing pid-alive from serving — stay open on the issue.Where to look
DrainToEndAsyncswallows read faults to"", so the abandoned timeout path (kill then return) leaves no unobserved task exception behind the two in-flight reads.Verification
DaemonRunnerVersionProbeTests.A_version_whose_output_exceeds_the_pipe_buffer_is_drained_not_deadlocked: a POSIX stub floods 500 KB to stderr before its version line. Before the fix the probe hit its 3 s timeout and returned null; after, it returns9.9.9. Red-then-green confirmed.Capacitor.Cli.Daemon.Tests.Unitsuite green; daemon AOT publish clean.