@@ -45,7 +45,7 @@ public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancella
45
45
// We internally track an int for that.
46
46
while ( true )
47
47
{
48
- // This isn't great. The issue is that TryRead can get a canceled read result
48
+ // _context.RequestTimedOut The issue is that TryRead can get a canceled read result
49
49
// which is unknown to StartTimingReadAsync.
50
50
if ( _context . RequestTimedOut )
51
51
{
@@ -54,7 +54,8 @@ public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancella
54
54
55
55
try
56
56
{
57
- _readResult = await StartTimingReadAsync ( cancellationToken ) ;
57
+ var readAwaitable = _context . Input . ReadAsync ( cancellationToken ) ;
58
+ _readResult = await StartTimingReadAsync ( readAwaitable , cancellationToken ) ;
58
59
}
59
60
catch ( ConnectionAbortedException ex )
60
61
{
@@ -63,43 +64,39 @@ public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancella
63
64
64
65
if ( _context . RequestTimedOut )
65
66
{
66
- Debug . Assert ( _readResult . IsCanceled ) ;
67
67
BadHttpRequestException . Throw ( RequestRejectionReason . RequestBodyTimeout ) ;
68
68
}
69
69
70
+ // Make sure to handle when this is canceled here.
70
71
if ( _readResult . IsCanceled )
71
72
{
72
- if ( Interlocked . CompareExchange ( ref _userCanceled , 0 , 1 ) == 1 )
73
+ if ( Interlocked . Exchange ( ref _userCanceled , 0 ) == 1 )
73
74
{
74
75
// Ignore the readResult if it wasn't by the user.
75
76
break ;
76
77
}
78
+ else
79
+ {
80
+ // TODO should this reset the timing read?
81
+ StopTimingRead ( 0 ) ;
82
+ continue ;
83
+ }
77
84
}
78
85
79
86
var readableBuffer = _readResult . Buffer ;
80
87
var readableBufferLength = readableBuffer . Length ;
81
88
StopTimingRead ( readableBufferLength ) ;
82
89
83
- if ( _readResult . IsCompleted )
84
- {
85
- // OnInputOrOutputCompleted() is an idempotent method that closes the connection. Sometimes
86
- // input completion is observed here before the Input.OnWriterCompleted() callback is fired,
87
- // so we call OnInputOrOutputCompleted() now to prevent a race in our tests where a 400
88
- // response is written after observing the unexpected end of request content instead of just
89
- // closing the connection without a response as expected.
90
- _context . OnInputOrOutputCompleted ( ) ;
91
-
92
- BadHttpRequestException . Throw ( RequestRejectionReason . UnexpectedEndOfRequestContent ) ;
93
- }
90
+ CheckCompletedReadResult ( _readResult ) ;
94
91
95
92
if ( readableBufferLength > 0 )
96
93
{
94
+ CreateReadResultFromConnectionReadResult ( ) ;
95
+
97
96
break ;
98
97
}
99
98
}
100
99
101
- CreateReadResultFromConnectionReadResult ( ) ;
102
-
103
100
return _readResult ;
104
101
}
105
102
@@ -115,13 +112,27 @@ public override bool TryRead(out ReadResult readResult)
115
112
116
113
TryStart ( ) ;
117
114
118
- var boolResult = _context . Input . TryRead ( out _readResult ) ;
115
+ if ( ! _context . Input . TryRead ( out _readResult ) )
116
+ {
117
+ readResult = default ;
118
+ return false ;
119
+ }
120
+
121
+ if ( _readResult . IsCanceled )
122
+ {
123
+ if ( Interlocked . Exchange ( ref _userCanceled , 0 ) == 0 )
124
+ {
125
+ // Cancellation wasn't by the user, return default ReadResult
126
+ readResult = default ;
127
+ return false ;
128
+ }
129
+ }
119
130
120
131
CreateReadResultFromConnectionReadResult ( ) ;
121
132
122
133
readResult = _readResult ;
123
134
124
- return boolResult ;
135
+ return true ;
125
136
}
126
137
127
138
private void ThrowIfCompleted ( )
@@ -178,33 +189,6 @@ protected override void OnReadStarting()
178
189
}
179
190
}
180
191
181
- private ValueTask < ReadResult > StartTimingReadAsync ( CancellationToken cancellationToken )
182
- {
183
- var readAwaitable = _context . Input . ReadAsync ( cancellationToken ) ;
184
-
185
- if ( ! readAwaitable . IsCompleted && _timingEnabled )
186
- {
187
- TryProduceContinue ( ) ;
188
-
189
- _backpressure = true ;
190
- _context . TimeoutControl . StartTimingRead ( ) ;
191
- }
192
-
193
- return readAwaitable ;
194
- }
195
-
196
- private void StopTimingRead ( long bytesRead )
197
- {
198
- _context . TimeoutControl . BytesRead ( bytesRead - _alreadyTimedBytes ) ;
199
- _alreadyTimedBytes = 0 ;
200
-
201
- if ( _backpressure )
202
- {
203
- _backpressure = false ;
204
- _context . TimeoutControl . StopTimingRead ( ) ;
205
- }
206
- }
207
-
208
192
public override void Complete ( Exception exception )
209
193
{
210
194
_context . ReportApplicationError ( exception ) ;
@@ -227,76 +211,5 @@ protected override Task OnStopAsync()
227
211
Complete ( null ) ;
228
212
return Task . CompletedTask ;
229
213
}
230
-
231
- protected override Task OnConsumeAsync ( )
232
- {
233
- try
234
- {
235
- if ( TryRead ( out var readResult ) )
236
- {
237
- AdvanceTo ( readResult . Buffer . End ) ;
238
-
239
- if ( readResult . IsCompleted )
240
- {
241
- return Task . CompletedTask ;
242
- }
243
- }
244
- }
245
- catch ( BadHttpRequestException ex )
246
- {
247
- // At this point, the response has already been written, so this won't result in a 4XX response;
248
- // however, we still need to stop the request processing loop and log.
249
- _context . SetBadRequestState ( ex ) ;
250
- return Task . CompletedTask ;
251
- }
252
- catch ( InvalidOperationException ex )
253
- {
254
- var connectionAbortedException = new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByApplication , ex ) ;
255
- _context . ReportApplicationError ( connectionAbortedException ) ;
256
-
257
- // Have to abort the connection because we can't finish draining the request
258
- _context . StopProcessingNextRequest ( ) ;
259
- return Task . CompletedTask ;
260
- }
261
-
262
- return OnConsumeAsyncAwaited ( ) ;
263
- }
264
-
265
- private async Task OnConsumeAsyncAwaited ( )
266
- {
267
- Log . RequestBodyNotEntirelyRead ( _context . ConnectionIdFeature , _context . TraceIdentifier ) ;
268
-
269
- _context . TimeoutControl . SetTimeout ( Constants . RequestBodyDrainTimeout . Ticks , TimeoutReason . RequestBodyDrain ) ;
270
-
271
- try
272
- {
273
- ReadResult result ;
274
- do
275
- {
276
- result = await ReadAsync ( ) ;
277
- AdvanceTo ( result . Buffer . End ) ;
278
- } while ( ! result . IsCompleted ) ;
279
- }
280
- catch ( BadHttpRequestException ex )
281
- {
282
- _context . SetBadRequestState ( ex ) ;
283
- }
284
- catch ( ConnectionAbortedException )
285
- {
286
- Log . RequestBodyDrainTimedOut ( _context . ConnectionIdFeature , _context . TraceIdentifier ) ;
287
- }
288
- catch ( InvalidOperationException ex )
289
- {
290
- var connectionAbortedException = new ConnectionAbortedException ( CoreStrings . ConnectionAbortedByApplication , ex ) ;
291
- _context . ReportApplicationError ( connectionAbortedException ) ;
292
-
293
- // Have to abort the connection because we can't finish draining the request
294
- _context . StopProcessingNextRequest ( ) ;
295
- }
296
- finally
297
- {
298
- _context . TimeoutControl . CancelTimeout ( ) ;
299
- }
300
- }
301
214
}
302
215
}
0 commit comments