microsoft / microsoft/aspire

Unobserved task exception ("The request was aborted.") from DCP KubernetesClient watch stream on DistributedApplication dispose

Open
#18,388 2 comments 2 reactions 0 assignees View on GitHub
area-app-model blocked triage:bot-seen
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.