Unobserved task exception ("The request was aborted.") from DCP KubernetesClient watch stream on DistributedApplication dispose
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
### Is there an existing issue for this?
- Searched; the closest are #4665 (closed, different symptom), #8028 (`ObjectDisposedException`, not this), and the dispose-area issues #10280 / #7139. None covers this exact unobserved-exception leak.
### Describe the bug
When a `DistributedApplication` built via `Aspire.Hosting.Testing` / `DistributedApplicationTestingBuilder` is disposed at the end of a test run, the DCP resource-watch HTTP/2 stream is aborted mid-read and the watch task's exception is never observed. The GC finalizer then surfaces it as an `UnobservedTaskException` (one per active watch stream):
```
Unobserved task exception: System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. (The request was aborted.)
---> System.IO.IOException: The request was aborted.
at System.Net.Http.Http2Connection.ThrowRequestAborted(Exception innerException)
at System.Net.Http.Http2Connection.Http2Stream.ReadDataAsync(Memory`1 buffer, HttpResponseMessage responseMessage, CancellationToken cancellationToken)
at k8s.LineSeparatedHttpContent.CancelableStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
at System.IO.StreamReader.ReadBufferAsync(CancellationToken cancellationToken)
at System.IO.StreamReader.ReadLineAsyncInternal(CancellationToken cancellationToken)
--- End of inner exception stack trace ---
```
It is benign (it fires on the finalizer thread after the run completes and does not fail any test), but it is noisy — emitted once per watch stream — and it dilutes signal for genuine unobserved exceptions originating in app code. This looks adjacent to the testing-dispose work in #10280 and #7139.
### Expected Behavior
During `DisposeAsync`, the DCP resource-watch task's abort `IOException` (`The request was aborted.`) should be observed/swallowed so it never reaches `TaskScheduler.UnobservedTaskException` / the finalizer.
### Steps To Reproduce
Run any test that boots an AppHost via `Aspire.Hosting.Testing` (e.g. `DistributedApplicationTestingBuilder`/`AspireFixture`) to completion and inspect the process output after the run — the unobserved-exception lines appear on shutdown. Observed consistently on Linux CI runners with a multi-resource AppHost (Postgres/Redis/containers). Happy to put together a minimal repro project if that helps.
### .NET Version info
.NET 10 (SDK 10.0.100)
### Aspire Version
Aspire.Hosting 13.4.6 (KubernetesClient 19.0.2 transitive)
### Anything else?
Surfaced while running an Aspire-backed Playwright E2E smoke suite under TUnit in GitHub Actions.
Contributor guide
Research direction
Start at the DisposeAsync lifecycle used by DistributedApplicationTestingBuilder and the DCP resource-watch task. Reproduce with an Aspire.Hosting.Testing AppHost containing multiple resources, then trace shutdown handling for the KubernetesClient watch stream. Done means the expected request-abort IOException is observed during disposal and no UnobservedTaskException is emitted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, kubernetes
- Domain
- devops, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100