@@ -135,54 +135,84 @@ private void Dispose(bool disposing)
135
135
}
136
136
}
137
137
138
- /// <summary>Do a non-blocking poll to see whether the connection has data available or has been closed.</summary>
139
- /// <remarks>If we don't have direct access to the underlying socket, we instead use a read-ahead task.</remarks>
140
- public bool PollRead ( )
138
+ /// <summary>Prepare an idle connection to be used for a new request.</summary>
139
+ /// <param name="async">Indicates whether the coming request will be sync or async.</param>
140
+ /// <returns>True if connection can be used, false if it is invalid due to receiving EOF or unexpected data.</returns>
141
+ public bool PrepareForReuse ( bool async )
141
142
{
142
- if ( _socket != null ) // may be null if we don't have direct access to the socket
143
+ // We may already have a read-ahead task if we did a previous scavenge and haven't used the connection since.
144
+ // If the read-ahead task is completed, then we've received either EOF or erroneous data the connection, so it's not usable.
145
+ if ( _readAheadTask is not null )
143
146
{
147
+ return ! _readAheadTask . Value . IsCompleted ;
148
+ }
149
+
150
+ // Check to see if we've received anything on the connection; if we have, that's
151
+ // either erroneous data (we shouldn't have received anything yet) or the connection
152
+ // has been closed; either way, we can't use it.
153
+ if ( ! async & & _socket is not null )
154
+ {
155
+ // Directly poll the socket rather than doing an async read, so that we can
156
+ // issue an appropriate sync read when we actually need it.
144
157
try
145
158
{
146
- return _socket . Poll ( 0 , SelectMode . SelectRead ) ;
159
+ return ! _socket . Poll ( 0 , SelectMode . SelectRead ) ;
147
160
}
148
161
catch ( Exception e ) when ( e is SocketException || e is ObjectDisposedException )
149
162
{
150
163
// Poll can throw when used on a closed socket.
151
- return true ;
164
+ return false ;
152
165
}
153
166
}
154
167
else
155
168
{
156
- return EnsureReadAheadAndPollRead ( ) ;
169
+ // Perform an async read on the stream, since we're going to need to read from it
170
+ // anyway, and in doing so we can avoid the extra syscall.
171
+ try
172
+ {
173
+ #pragma warning disable CA2012 // we're very careful to ensure the ValueTask is only consumed once, even though it's stored into a field
174
+ _readAheadTask = _stream . ReadAsync ( new Memory < byte > ( _readBuffer ) ) ;
175
+ #pragma warning restore CA2012
176
+ return ! _readAheadTask . Value . IsCompleted ;
177
+ }
178
+ catch ( Exception error )
179
+ {
180
+ // If reading throws, eat the error and don't reuse the connection.
181
+ if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "Error performing read ahead: { error } ") ;
182
+ return false ;
183
+ }
157
184
}
158
185
}
159
186
160
- /// <summary>
161
- /// Issues a read-ahead on the connection, which will serve both as the first read on the
162
- /// response as well as a polling indication of whether the connection is usable.
163
- /// </summary>
164
- /// <returns>true if there's data available on the connection or it's been closed; otherwise, false.</returns>
165
- public bool EnsureReadAheadAndPollRead ( )
187
+ /// <summary>Check whether a currently idle connection is still usable, or should be scavenged.</summary>
188
+ /// <returns>True if connection can be used, false if it is invalid due to receiving EOF or unexpected data.</returns>
189
+ public bool CheckUsabilityOnScavenge ( )
166
190
{
167
- try
191
+ // We may already have a read-ahead task if we did a previous scavenge and haven't used the connection since.
192
+ if ( _readAheadTask is null )
168
193
{
169
- Debug . Assert ( _readAheadTask == null || _socket == null , "Should only already have a read-ahead task if we don't have a socket to poll" ) ;
170
- if ( _readAheadTask == null )
171
- {
172
194
#pragma warning disable CA2012 // we're very careful to ensure the ValueTask is only consumed once, even though it's stored into a field
173
- _readAheadTask = _stream . ReadAsync ( new Memory < byte > ( _readBuffer ) ) ;
195
+ _readAheadTask = ReadAheadWithZeroByteReadAsync ( ) ;
174
196
#pragma warning restore CA2012
175
- }
176
197
}
177
- catch ( Exception error )
198
+
199
+ // If the read-ahead task is completed, then we've received either EOF or erroneous data the connection, so it's not usable.
200
+ return ! _readAheadTask . Value . IsCompleted ;
201
+
202
+ async ValueTask < int > ReadAheadWithZeroByteReadAsync ( )
178
203
{
179
- // If reading throws, eat the error and don't pool the connection.
180
- if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "Error performing read ahead: { error } ") ;
181
- Dispose ( ) ;
182
- _readAheadTask = new ValueTask < int > ( 0 ) ;
183
- }
204
+ Debug . Assert ( _readAheadTask is null ) ;
205
+ Debug . Assert ( RemainingBuffer . Length == 0 ) ;
206
+
207
+ // Issue a zero-byte read.
208
+ // If the underlying stream supports it, this will not complete until the stream has data available,
209
+ // which will avoid pinning the connection's read buffer (and possibly allow us to release it to the buffer pool in the future, if desired).
210
+ // If not, it will complete immediately.
211
+ await _stream . ReadAsync ( Memory < byte > . Empty ) . ConfigureAwait ( false ) ;
184
212
185
- return _readAheadTask . Value . IsCompleted ; // equivalent to polling
213
+ // We don't know for sure that the stream actually has data available, so we need to issue a real read now.
214
+ return await _stream . ReadAsync ( new Memory < byte > ( _readBuffer ) ) . ConfigureAwait ( false ) ;
215
+ }
186
216
}
187
217
188
218
private ValueTask < int > ? ConsumeReadAheadTask ( )
0 commit comments