Skip to content

Commit fc15f27

Browse files
committed
Change HttpConnectionPool.GetConnectionAsync to do read-ahead outside of lock (dotnet/corefx#32495)
dotnet/corefx#32495
1 parent b0fa318 commit fc15f27

File tree

1 file changed

+74
-65
lines changed

1 file changed

+74
-65
lines changed

StandardSocketsHttpHandler/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -186,83 +186,92 @@ private static SslClientAuthenticationOptions ConstructSslOptions(HttpConnection
186186
TimeSpan pooledConnectionIdleTimeout = _poolManager.Settings._pooledConnectionIdleTimeout;
187187
DateTimeOffset now = DateTimeOffset.UtcNow;
188188
List<CachedConnection> list = _idleConnections;
189-
lock (SyncObj)
189+
190+
// Try to get a cached connection. If we can and if it's usable, return it. If we can but it's not usable,
191+
// try again. And if we can't because there aren't any valid ones, create a new one and return it.
192+
while (true)
190193
{
191-
// Try to return a cached connection. We need to loop in case the connection
192-
// we get from the list is unusable.
193-
while (list.Count > 0)
194+
CachedConnection cachedConnection;
195+
lock (SyncObj)
194196
{
195-
CachedConnection cachedConnection = list[list.Count - 1];
196-
HttpConnection conn = cachedConnection._connection;
197-
198-
list.RemoveAt(list.Count - 1);
199-
if (cachedConnection.IsUsable(now, pooledConnectionLifetime, pooledConnectionIdleTimeout) &&
200-
!conn.EnsureReadAheadAndPollRead())
197+
if (list.Count > 0)
201198
{
202-
// We found a valid connection. Return it.
203-
if (NetEventSource.IsEnabled) conn.Trace("Found usable connection in pool.");
204-
return Task.FromResult<(HttpConnection, HttpResponseMessage)>((conn, null));
199+
// Pop off the next connection to try. We'll test it outside of the lock
200+
// to avoid doing expensive validation while holding the lock.
201+
cachedConnection = list[list.Count - 1];
202+
list.RemoveAt(list.Count - 1);
205203
}
206-
207-
// We got a connection, but it was already closed by the server or the
208-
// server sent unexpected data or the connection is too old. In any case,
209-
// we can't use the connection, so get rid of it and try again.
210-
if (NetEventSource.IsEnabled) conn.Trace("Found invalid connection in pool.");
211-
conn.Dispose();
212-
}
213-
214-
// No valid cached connections, so we need to create a new one. If
215-
// there's no limit on the number of connections associated with this
216-
// pool, or if we haven't reached such a limit, simply create a new
217-
// connection.
218-
if (_associatedConnectionCount < _maxConnections)
219-
{
220-
if (NetEventSource.IsEnabled) Trace("Creating new connection for pool.");
221-
IncrementConnectionCountNoLock();
222-
return WaitForCreatedConnectionAsync(CreateConnectionAsync(request, cancellationToken));
223-
}
224-
else
225-
{
226-
// There is a limit, and we've reached it, which means we need to
227-
// wait for a connection to be returned to the pool or for a connection
228-
// associated with the pool to be dropped before we can create a
229-
// new one. Create a waiter object and register it with the pool; it'll
230-
// be signaled with the created connection when one is returned or
231-
// space is available and the provided creation func has successfully
232-
// created the connection to be used.
233-
if (NetEventSource.IsEnabled) Trace("Limit reached. Waiting to create new connection.");
234-
var waiter = new ConnectionWaiter(this, request, cancellationToken);
235-
EnqueueWaiter(waiter);
236-
if (cancellationToken.CanBeCanceled)
204+
else
237205
{
238-
// If cancellation could be requested, register a callback for it that'll cancel
239-
// the waiter and remove the waiter from the queue. Note that this registration needs
240-
// to happen under the reentrant lock and after enqueueing the waiter.
241-
waiter._cancellationTokenRegistration = cancellationToken.Register(s =>
206+
// No valid cached connections, so we need to create a new one. If
207+
// there's no limit on the number of connections associated with this
208+
// pool, or if we haven't reached such a limit, simply create a new
209+
// connection.
210+
if (_associatedConnectionCount < _maxConnections)
242211
{
243-
var innerWaiter = (ConnectionWaiter)s;
244-
lock (innerWaiter._pool.SyncObj)
212+
if (NetEventSource.IsEnabled) Trace("Creating new connection for pool.");
213+
IncrementConnectionCountNoLock();
214+
return WaitForCreatedConnectionAsync(CreateConnectionAsync(request, cancellationToken));
215+
}
216+
else
217+
{
218+
// There is a limit, and we've reached it, which means we need to
219+
// wait for a connection to be returned to the pool or for a connection
220+
// associated with the pool to be dropped before we can create a
221+
// new one. Create a waiter object and register it with the pool; it'll
222+
// be signaled with the created connection when one is returned or
223+
// space is available and the provided creation func has successfully
224+
// created the connection to be used.
225+
if (NetEventSource.IsEnabled) Trace("Limit reached. Waiting to create new connection.");
226+
var waiter = new ConnectionWaiter(this, request, cancellationToken);
227+
EnqueueWaiter(waiter);
228+
if (cancellationToken.CanBeCanceled)
245229
{
246-
// If it's in the list, remove it and cancel it.
247-
if (innerWaiter._pool.RemoveWaiterForCancellation(innerWaiter))
230+
// If cancellation could be requested, register a callback for it that'll cancel
231+
// the waiter and remove the waiter from the queue. Note that this registration needs
232+
// to happen under the reentrant lock and after enqueueing the waiter.
233+
waiter._cancellationTokenRegistration = cancellationToken.Register(s =>
248234
{
249-
bool canceled = innerWaiter.TrySetCanceled(innerWaiter._cancellationToken);
250-
Debug.Assert(canceled);
251-
}
235+
var innerWaiter = (ConnectionWaiter)s;
236+
lock (innerWaiter._pool.SyncObj)
237+
{
238+
// If it's in the list, remove it and cancel it.
239+
if (innerWaiter._pool.RemoveWaiterForCancellation(innerWaiter))
240+
{
241+
bool canceled = innerWaiter.TrySetCanceled(innerWaiter._cancellationToken);
242+
Debug.Assert(canceled);
243+
}
244+
}
245+
}, waiter);
252246
}
253-
}, waiter);
247+
return waiter.Task;
248+
}
249+
250+
// Note that we don't check for _disposed. We may end up disposing the
251+
// created connection when it's returned, but we don't want to block use
252+
// of the pool if it's already been disposed, as there's a race condition
253+
// between getting a pool and someone disposing of it, and we don't want
254+
// to complicate the logic about trying to get a different pool when the
255+
// retrieved one has been disposed of. In the future we could alternatively
256+
// try returning such connections to whatever pool is currently considered
257+
// current for that endpoint, if there is one.
254258
}
255-
return waiter.Task;
256259
}
257260

258-
// Note that we don't check for _disposed. We may end up disposing the
259-
// created connection when it's returned, but we don't want to block use
260-
// of the pool if it's already been disposed, as there's a race condition
261-
// between getting a pool and someone disposing of it, and we don't want
262-
// to complicate the logic about trying to get a different pool when the
263-
// retrieved one has been disposed of. In the future we could alternatively
264-
// try returning such connections to whatever pool is currently considered
265-
// current for that endpoint, if there is one.
261+
HttpConnection conn = cachedConnection._connection;
262+
if (cachedConnection.IsUsable(now, pooledConnectionLifetime, pooledConnectionIdleTimeout) &&
263+
!conn.EnsureReadAheadAndPollRead())
264+
{
265+
// We found a valid connection. Return it.
266+
if (NetEventSource.IsEnabled) conn.Trace("Found usable connection in pool.");
267+
return Task.FromResult<(HttpConnection, HttpResponseMessage)>((conn, null));
268+
}
269+
270+
// We got a connection, but it was already closed by the server or the
271+
// server sent unexpected data or the connection is too old. In any case,
272+
// we can't use the connection, so get rid of it and loop around to try again.
273+
if (NetEventSource.IsEnabled) conn.Trace("Found invalid connection in pool.");
274+
conn.Dispose();
266275
}
267276
}
268277

0 commit comments

Comments
 (0)