Skip to content

Fix UnobservedTaskException from orphaned ReadLineAsync task during watch cancellation#1814

Draft
Copilot wants to merge 3 commits into
masterfrom
copilot/fix-unobserved-task-exception
Draft

Fix UnobservedTaskException from orphaned ReadLineAsync task during watch cancellation#1814
Copilot wants to merge 3 commits into
masterfrom
copilot/fix-unobserved-task-exception

Conversation

Copilot AI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Cancelling a watch's CancellationToken while ReadLineAsync() is in flight left the original read task orphaned. When the transport tore the connection down, that task faulted with an unobserved IOException, surfacing as a TaskScheduler.UnobservedTaskException at finalization (e.g. during app shutdown).

Root cause: AttachCancellationToken in CreateWatchEventEnumerator wraps the read via task.ContinueWith(..., cancellationToken). On cancellation the continuation is cancelled immediately while the original ReadLineAsync() keeps running and later faults — with no one observing it.

Changes — src/KubernetesClient/Watcher.cs

  • net7+: cancel the read directly. Pass the token to ReadLineAsync(cancellationToken), which cancels the underlying read so no task is orphaned. The AttachCancellationToken wrapper is no longer used for reads here.

    #if NET7_0_OR_GREATER
                var line = await streamReader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
    #else
                var line = await AttachCancellationToken(streamReader.ReadLineAsync()).ConfigureAwait(false);
    #endif
  • Older TFMs (netstandard2.0/net48 via KubernetesClient.Classic): observe the orphan. AttachCancellationToken now attaches an OnlyOnFaulted continuation that consumes the original task's exception. This also covers the streamReaderCreator() task path on all frameworks.

    _ = task.ContinueWith(
        static t => { _ = t.Exception; },
        CancellationToken.None,
        TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default);

Tests — tests/KubernetesClient.Tests/WatchTests.cs

  • Adds CancellationDoesNotLeaveUnobservedTaskException: cancels mid-read, then faults the underlying task and forces finalization, asserting no UnobservedTaskException fires. Fails against the unpatched code, passes with the fix.

Notes

  • The pre-existing AsyncEnumerableWatchWithCancellation test times out in this sandbox against the baseline as well; unrelated to this change (mock Kestrel server environment limitation).

Copilot AI requested review from Copilot and removed request for Copilot June 22, 2026 07:14
@kubernetes-prow kubernetes-prow Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 22, 2026
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 22, 2026

Copy link
Copy Markdown

CLA Not Signed

@kubernetes-prow kubernetes-prow Bot added cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Jun 22, 2026
Copilot AI requested review from Copilot and removed request for Copilot June 22, 2026 07:18
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Copilot
Once this PR has been reviewed and has the lgtm label, please ask for approval from tg123. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow kubernetes-prow Bot added size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Jun 22, 2026
Copilot AI requested review from Copilot and removed request for Copilot June 22, 2026 07:55
@kubernetes-prow kubernetes-prow Bot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Jun 22, 2026
Copilot AI changed the title [WIP] Fix unobserved TaskException for orphaned ReadLineAsync task Fix UnobservedTaskException from orphaned ReadLineAsync task during watch cancellation Jun 22, 2026
Copilot AI requested a review from tg123 June 22, 2026 07:58
Copilot finished work on behalf of tg123 June 22, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants