@@ -133,6 +133,11 @@ public void InitializeWithExistingContext(IDuplexPipe transport)
133
133
}
134
134
135
135
public void Abort ( ConnectionAbortedException abortReason , Http3ErrorCode errorCode )
136
+ {
137
+ AbortCore ( abortReason , errorCode ) ;
138
+ }
139
+
140
+ private void AbortCore ( Exception exception , Http3ErrorCode errorCode )
136
141
{
137
142
lock ( _completionLock )
138
143
{
@@ -148,26 +153,27 @@ public void Abort(ConnectionAbortedException abortReason, Http3ErrorCode errorCo
148
153
return ;
149
154
}
150
155
151
- Log . Http3StreamAbort ( TraceIdentifier , errorCode , abortReason ) ;
156
+ if ( ! ( exception is ConnectionAbortedException abortReason ) )
157
+ {
158
+ abortReason = new ConnectionAbortedException ( exception . Message , exception ) ;
159
+ }
152
160
153
- _errorCodeFeature . Error = ( long ) errorCode ;
154
- _frameWriter . Abort ( abortReason ) ;
161
+ Log . Http3StreamAbort ( TraceIdentifier , errorCode , abortReason ) ;
155
162
156
- AbortCore ( abortReason ) ;
157
- }
158
- }
163
+ // Call _http3Output.Stop() prior to poisoning the request body stream or pipe to
164
+ // ensure that an app that completes early due to the abort doesn't result in header frames being sent.
165
+ _http3Output . Stop ( ) ;
159
166
160
- private void AbortCore ( Exception exception )
161
- {
162
- // Call _http3Output.Stop() prior to poisoning the request body stream or pipe to
163
- // ensure that an app that completes early due to the abort doesn't result in header frames being sent.
164
- _http3Output . Stop ( ) ;
167
+ CancelRequestAbortedToken ( ) ;
165
168
166
- CancelRequestAbortedToken ( ) ;
169
+ // Unblock the request body.
170
+ PoisonBody ( exception ) ;
171
+ RequestBodyPipe . Writer . Complete ( exception ) ;
167
172
168
- // Unblock the request body.
169
- PoisonBody ( exception ) ;
170
- RequestBodyPipe . Writer . Complete ( exception ) ;
173
+ // Abort framewriter and underlying transport after stopping output.
174
+ _errorCodeFeature . Error = ( long ) errorCode ;
175
+ _frameWriter . Abort ( abortReason ) ;
176
+ }
171
177
}
172
178
173
179
protected override void OnErrorAfterResponseStarted ( )
@@ -470,7 +476,8 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
470
476
catch ( ConnectionResetException ex )
471
477
{
472
478
error = ex ;
473
- AbortCore ( new IOException ( CoreStrings . HttpStreamResetByClient , ex ) ) ;
479
+ var resolvedErrorCode = _errorCodeFeature . Error >= 0 ? _errorCodeFeature . Error : 0 ;
480
+ AbortCore ( new IOException ( CoreStrings . HttpStreamResetByClient , ex ) , ( Http3ErrorCode ) resolvedErrorCode ) ;
474
481
}
475
482
catch ( Exception ex )
476
483
{
@@ -484,10 +491,44 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
484
491
485
492
await Input . CompleteAsync ( ) ;
486
493
487
- // Make sure application func is completed before completing writer.
488
- if ( _appCompleted != null )
494
+ var appCompleted = _appCompleted ? . Task ?? Task . CompletedTask ;
495
+ if ( ! appCompleted . IsCompletedSuccessfully )
489
496
{
490
- await _appCompleted . Task ;
497
+ // At this point in the stream's read-side is complete. However, with HTTP/3
498
+ // the write-side of the stream can still be aborted by the client on request
499
+ // aborted.
500
+ //
501
+ // To get notification of request aborted we register to connection closed
502
+ // token. It will notify this type that the client has aborted the request
503
+ // and Kestrel will complete pipes and cancel the RequestAborted token.
504
+ //
505
+ // Only subscribe to this event after the stream's read-side is complete to
506
+ // avoid interactions between reading that is in-progress and an abort.
507
+ // This means while reading, read-side abort will handle getting abort notifications.
508
+ //
509
+ // We don't need to hang on to the CancellationTokenRegistration from register.
510
+ // The CTS is cleaned up in StreamContext.DisposeAsync.
511
+ //
512
+ // TODO: Consider a better way to provide this notification. For perf we want to
513
+ // make the ConnectionClosed CTS pay-for-play, and change this event to use
514
+ // something that is more lightweight than a CTS.
515
+ _context . StreamContext . ConnectionClosed . Register ( static s =>
516
+ {
517
+ var stream = ( Http3Stream ) s ! ;
518
+
519
+ if ( ! stream . IsCompleted )
520
+ {
521
+ // An error code value other than -1 indicates a value was set and the request didn't gracefully complete.
522
+ var errorCode = stream . _errorCodeFeature . Error ;
523
+ if ( errorCode >= 0 )
524
+ {
525
+ stream . AbortCore ( new IOException ( CoreStrings . HttpStreamResetByClient ) , ( Http3ErrorCode ) errorCode ) ;
526
+ }
527
+ }
528
+ } , this ) ;
529
+
530
+ // Make sure application func is completed before completing writer.
531
+ await appCompleted ;
491
532
}
492
533
493
534
try
0 commit comments