[TUnit.Aspire] Fail fast (and attributed) when a resource crashes during fixture startup
Labels: enhancement, TUnit.Aspire, dx
Problem
When a resource dies during boot, AspireFixture<TAppHost> currently waits out the entire app-start timeout and then throws a generic error — e.g. "Timed out after 480s waiting for the Aspire application to start." That message:
- costs the full timeout (here: 8 minutes of wall-clock per attempt), and
- names nothing — it doesn't say which resource failed, its exit code, or why.
The author is left to reverse-engineer a diagnostics dump. In a real case, the true cause was chat exiting with 0xE0434352 (a .NET unhandled exception) ~40s in; the fixture nonetheless blocked until 480s and reported only the generic timeout.
Proposal
While waiting for readiness, watch resource states. If any awaited resource enters FailedToStart or Finished with a non-zero exit code, abort immediately and throw a message that names the resource, decodes the exit code, and includes its last N log lines.
Example:
Aspire startup aborted: resource 'chat' exited with code -532462766 (0xE0434352 — .NET unhandled exception)
after 41.3s (state: Finished). It was awaited by: media, web.
Last 20 log lines from 'chat':
[chat] Unhandled exception. System.InvalidOperationException: ...
[chat] at ...
Exit-code decoding
Map common codes to human-readable causes:
| Exit code |
Meaning |
-532462766 / 0xE0434352 |
.NET unhandled managed exception |
-1073741819 / 0xC0000005 |
Access violation (native crash) |
137 |
SIGKILL (often OOM) |
139 |
SIGSEGV |
134 |
SIGABRT |
0 |
Clean exit (a service exiting 0 during startup is usually still a misconfiguration) |
Attribution
Because the fixture knows the WaitFor graph, it can report who was waiting on the crashed resource (in the repro: media.WaitFor(chat) → web.WaitFor(media)), which immediately explains the cascade instead of leaving the author to chase the wrong resource (they'll otherwise blame media/web, which are only Waiting).
Acceptance criteria
- A resource reaching
FailedToStart / Finished(non-zero) during the readiness wait aborts the wait promptly (no full-timeout stall).
- Failure message includes: resource name, decoded exit code, time-to-exit, final state, and the awaiters (
WaitFor dependents).
- Last N log lines of the crashed resource are included (pairs with the resource-log-forwarding issue).
- A clean-exit-0 during startup is still surfaced (a web host exiting 0 mid-boot is a failure).
Rationale / impact
This single change would have converted ~40 minutes of manual archaeology (diagnostics-file parsing + a throwaway log-tap harness + exit-code lookup) into a one-line, immediately actionable failure. It's the highest-leverage DX improvement of the set.
Related
[TUnit.Aspire] Fail fast (and attributed) when a resource crashes during fixture startup
Labels:
enhancement,TUnit.Aspire,dxProblem
When a resource dies during boot,
AspireFixture<TAppHost>currently waits out the entire app-start timeout and then throws a generic error — e.g. "Timed out after 480s waiting for the Aspire application to start." That message:The author is left to reverse-engineer a diagnostics dump. In a real case, the true cause was
chatexiting with0xE0434352(a .NET unhandled exception) ~40s in; the fixture nonetheless blocked until 480s and reported only the generic timeout.Proposal
While waiting for readiness, watch resource states. If any awaited resource enters
FailedToStartorFinishedwith a non-zero exit code, abort immediately and throw a message that names the resource, decodes the exit code, and includes its last N log lines.Example:
Exit-code decoding
Map common codes to human-readable causes:
-532462766/0xE0434352-1073741819/0xC00000051371391340Attribution
Because the fixture knows the
WaitForgraph, it can report who was waiting on the crashed resource (in the repro:media.WaitFor(chat)→web.WaitFor(media)), which immediately explains the cascade instead of leaving the author to chase the wrong resource (they'll otherwise blamemedia/web, which are only Waiting).Acceptance criteria
FailedToStart/Finished(non-zero)during the readiness wait aborts the wait promptly (no full-timeout stall).WaitFordependents).Rationale / impact
This single change would have converted ~40 minutes of manual archaeology (diagnostics-file parsing + a throwaway log-tap harness + exit-code lookup) into a one-line, immediately actionable failure. It's the highest-leverage DX improvement of the set.
Related