@@ -57,6 +57,7 @@ internal sealed partial class HttpConnection : HttpConnectionBase
57
57
private const int ReadAheadTask_NotStarted = 0 ;
58
58
private const int ReadAheadTask_Started = 1 ;
59
59
private const int ReadAheadTask_CompletionReserved = 2 ;
60
+ private const int ReadAheadTask_Completed = 3 ;
60
61
private int _readAheadTaskStatus ;
61
62
private ValueTask < int > _readAheadTask ;
62
63
private ArrayBuffer _readBuffer ;
@@ -118,8 +119,11 @@ private void Dispose(bool disposing)
118
119
}
119
120
}
120
121
122
+ private bool ReadAheadTaskHasStarted =>
123
+ _readAheadTaskStatus != ReadAheadTask_NotStarted ;
124
+
121
125
/// <summary>Prepare an idle connection to be used for a new request.
122
- /// The caller MUST call SendAsync afterwards if this method returns true.</summary>
126
+ /// The caller MUST call SendAsync afterwards if this method returns true, or dispose the connection if it returns false .</summary>
123
127
/// <param name="async">Indicates whether the coming request will be sync or async.</param>
124
128
/// <returns>True if connection can be used, false if it is invalid due to a timeout or receiving EOF or unexpected data.</returns>
125
129
public bool PrepareForReuse ( bool async )
@@ -133,7 +137,9 @@ public bool PrepareForReuse(bool async)
133
137
// If the read-ahead task is completed, then we've received either EOF or erroneous data the connection, so it's not usable.
134
138
if ( ReadAheadTaskHasStarted )
135
139
{
136
- return TryOwnReadAheadTaskCompletion ( ) ;
140
+ Debug . Assert ( _readAheadTaskStatus is ReadAheadTask_Started or ReadAheadTask_Completed ) ;
141
+
142
+ return Interlocked . Exchange ( ref _readAheadTaskStatus , ReadAheadTask_CompletionReserved ) == ReadAheadTask_Started ;
137
143
}
138
144
139
145
// Check to see if we've received anything on the connection; if we have, that's
@@ -177,6 +183,35 @@ public bool PrepareForReuse(bool async)
177
183
}
178
184
}
179
185
186
+ /// <summary>Takes ownership of the scavenging task completion if it was started.
187
+ /// The caller MUST call either SendAsync or return the completion ownership afterwards if this method returns true, or dispose the connection if it returns false.</summary>
188
+ public bool TryOwnScavengingTaskCompletion ( )
189
+ {
190
+ Debug . Assert ( _readAheadTaskStatus != ReadAheadTask_CompletionReserved ) ;
191
+
192
+ return ! ReadAheadTaskHasStarted
193
+ || Interlocked . Exchange ( ref _readAheadTaskStatus , ReadAheadTask_CompletionReserved ) == ReadAheadTask_Started ;
194
+ }
195
+
196
+ /// <summary>Returns ownership of the scavenging task completion if it was started.
197
+ /// The caller MUST Dispose the connection afterwards if this method returns false.</summary>
198
+ public bool TryReturnScavengingTaskCompletionOwnership ( )
199
+ {
200
+ Debug . Assert ( _readAheadTaskStatus != ReadAheadTask_Started ) ;
201
+
202
+ if ( ! ReadAheadTaskHasStarted ||
203
+ Interlocked . Exchange ( ref _readAheadTaskStatus , ReadAheadTask_Started ) == ReadAheadTask_CompletionReserved )
204
+ {
205
+ return true ;
206
+ }
207
+
208
+ // The read-ahead task has started, and we failed to transition back to CompletionReserved.
209
+ // This means that the read-ahead task has completed, and we can't reuse the connection. The caller must dispose it.
210
+ // We're still responsible for observing potential exceptions thrown by the read-ahead task to avoid leaking unobserved exceptions.
211
+ LogExceptions ( _readAheadTask . AsTask ( ) ) ;
212
+ return false ;
213
+ }
214
+
180
215
/// <summary>Check whether a currently idle connection is still usable, or should be scavenged.</summary>
181
216
/// <returns>True if connection can be used, false if it is invalid due to a timeout or receiving EOF or unexpected data.</returns>
182
217
public override bool CheckUsabilityOnScavenge ( )
@@ -187,21 +222,7 @@ public override bool CheckUsabilityOnScavenge()
187
222
}
188
223
189
224
// We may already have a read-ahead task if we did a previous scavenge and haven't used the connection since.
190
- EnsureReadAheadTaskHasStarted ( ) ;
191
-
192
- // If the read-ahead task is completed, then we've received either EOF or erroneous data the connection, so it's not usable.
193
- return ! _readAheadTask . IsCompleted ;
194
- }
195
-
196
- private bool ReadAheadTaskHasStarted =>
197
- _readAheadTaskStatus != ReadAheadTask_NotStarted ;
198
-
199
- private bool TryOwnReadAheadTaskCompletion ( ) =>
200
- Interlocked . CompareExchange ( ref _readAheadTaskStatus , ReadAheadTask_CompletionReserved , ReadAheadTask_Started ) == ReadAheadTask_Started ;
201
-
202
- private void EnsureReadAheadTaskHasStarted ( )
203
- {
204
- if ( _readAheadTaskStatus == ReadAheadTask_NotStarted )
225
+ if ( ! ReadAheadTaskHasStarted )
205
226
{
206
227
Debug . Assert ( _readAheadTask == default ) ;
207
228
@@ -212,6 +233,9 @@ private void EnsureReadAheadTaskHasStarted()
212
233
#pragma warning restore CA2012
213
234
}
214
235
236
+ // If the read-ahead task is completed, then we've received either EOF or erroneous data the connection, so it's not usable.
237
+ return ! _readAheadTask . IsCompleted ;
238
+
215
239
async ValueTask < int > ReadAheadWithZeroByteReadAsync ( )
216
240
{
217
241
Debug . Assert ( _readAheadTask == default ) ;
@@ -231,19 +255,26 @@ async ValueTask<int> ReadAheadWithZeroByteReadAsync()
231
255
// PrepareForReuse will check TryOwnReadAheadTaskCompletion before calling into SendAsync.
232
256
// If we can own the completion from within the read-ahead task, it means that PrepareForReuse hasn't been called yet.
233
257
// In that case we've received EOF/erroneous data before we sent the request headers, and the connection can't be reused.
234
- if ( TryOwnReadAheadTaskCompletion ( ) )
258
+ if ( TransitionToCompletedAndTryOwnCompletion ( ) )
235
259
{
236
260
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( "Read-ahead task observed data before the request was sent." ) ;
237
261
}
238
262
239
263
return read ;
240
264
}
241
- catch ( Exception error ) when ( TryOwnReadAheadTaskCompletion ( ) )
265
+ catch ( Exception error ) when ( TransitionToCompletedAndTryOwnCompletion ( ) )
242
266
{
243
267
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "Error performing read ahead: { error } ") ;
244
268
245
269
return 0 ;
246
270
}
271
+
272
+ bool TransitionToCompletedAndTryOwnCompletion ( )
273
+ {
274
+ Debug . Assert ( _readAheadTaskStatus is ReadAheadTask_Started or ReadAheadTask_CompletionReserved ) ;
275
+
276
+ return Interlocked . Exchange ( ref _readAheadTaskStatus , ReadAheadTask_Completed ) == ReadAheadTask_Started ;
277
+ }
247
278
}
248
279
}
249
280
@@ -497,7 +528,8 @@ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, boo
497
528
{
498
529
Debug . Assert ( _currentRequest == null , $ "Expected null { nameof ( _currentRequest ) } .") ;
499
530
Debug . Assert ( _readBuffer . ActiveLength == 0 , "Unexpected data in read buffer" ) ;
500
- Debug . Assert ( _readAheadTaskStatus != ReadAheadTask_Started ) ;
531
+ Debug . Assert ( _readAheadTaskStatus != ReadAheadTask_Started ,
532
+ "The caller should have called PrepareForReuse or TryOwnScavengingTaskCompletion if the connection was idle on the pool." ) ;
501
533
502
534
MarkConnectionAsNotIdle ( ) ;
503
535
0 commit comments