Skip to content

TaskScheduler.UnobservedTaskException catches quic exceptions with kestrel combined with grpc + http3 #114128

@xeropresence

Description

@xeropresence

Description

I've got a grpc service that has been running for sometime using just http2. I recently enabled http3 to see if there would be any performance benefit or improvement in connectivity some users were facing. I noticed almost immediately some exceptions were being caught where previously there weren't any.

Three different exceptions, all slightly different seem to be thrown

2025-04-01T15:54:54.8403559-07:00	CRIT	[LogicBase]	[0]	System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Connection aborted by peer (256).)
 ---> System.Net.Quic.QuicException: Connection aborted by peer (256).
   at System.Net.Quic.QuicConnection.HandleEventShutdownInitiatedByPeer(_SHUTDOWN_INITIATED_BY_PEER_e__Struct& data)
   at System.Net.Quic.QuicConnection.HandleConnectionEvent(QUIC_CONNECTION_EVENT& connectionEvent)
   at System.Net.Quic.QuicConnection.NativeCallback(QUIC_HANDLE* connection, Void* context, QUIC_CONNECTION_EVENT* connectionEvent)
--- End of stack trace from previous location ---
   at System.Net.Quic.QuicConnection.AcceptInboundStreamAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicConnectionContext.AcceptAsync(CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   
  
   2025-04-01T15:54:54.8406298-07:00	CRIT	[LogicBase]	[0] System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Connection aborted by peer (256).)
 ---> System.Net.Quic.QuicException: Connection aborted by peer (256).
   at System.Net.Quic.ResettableValueTaskSource.TryComplete(Exception exception, Boolean final)
   at System.Net.Quic.QuicStream.HandleEventShutdownComplete(_SHUTDOWN_COMPLETE_e__Struct& data)
   at System.Net.Quic.QuicStream.HandleStreamEvent(QUIC_STREAM_EVENT& streamEvent)
   at System.Net.Quic.QuicStream.NativeCallback(QUIC_HANDLE* connection, Void* context, QUIC_STREAM_EVENT* streamEvent)
--- End of stack trace from previous location ---
   at System.Net.Quic.ResettableValueTaskSource.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Quic.QuicStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicStreamContext.DoReceiveAsync()
   --- End of inner exception stack trace ---
   
   
   2025-04-01T15:54:54.8410699-07:00	CRIT	[LogicBase]	[0]	System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Connection aborted by peer (256).)
 ---> Microsoft.AspNetCore.Connections.ConnectionResetException: Connection aborted by peer (256).
 ---> System.Net.Quic.QuicException: Connection aborted by peer (256).
   at System.Net.Quic.ResettableValueTaskSource.TryComplete(Exception exception, Boolean final)
   at System.Net.Quic.QuicStream.HandleEventShutdownComplete(_SHUTDOWN_COMPLETE_e__Struct& data)
   at System.Net.Quic.QuicStream.HandleStreamEvent(QUIC_STREAM_EVENT& streamEvent)
   at System.Net.Quic.QuicStream.NativeCallback(QUIC_HANDLE* connection, Void* context, QUIC_STREAM_EVENT* streamEvent)
--- End of stack trace from previous location ---
   at System.Net.Quic.ResettableValueTaskSource.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
   at System.Net.Quic.QuicStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicStreamContext.DoReceiveAsync()
   --- End of inner exception stack trace ---
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.GetReadAsyncResult()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3ControlStream.HandleControlStream()
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3ControlStream.ProcessRequestAsync[TContext](IHttpApplication`1 application)
   --- End of inner exception stack trace ---
   
   

There maybe more exceptions, but when it happens so many are generated its hard for me to tell.

Reproduction Steps

I'm not exactly sure what's happening on the clientside to produce the issue, but the serverside setup is almost exactly a basic copy of

https://protobuf-net.github.io/protobuf-net.Grpc/gettingstarted

with http3 enabled.

Expected behavior

Exceptions are handled in the layer they are being generated

Actual behavior

Exceptions are going uncaught

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

I'm currently using .net8, but I also tried upgrading to .net9 but the issue persisted.
Possibly related to #80111 ?

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions