Skip to content

Commit 9c27a09

Browse files
committed
More comments
1 parent 139336b commit 9c27a09

File tree

1 file changed

+35
-7
lines changed

1 file changed

+35
-7
lines changed

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs

+35-7
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ internal sealed class HttpConnectionPool : IDisposable
7171

7272
/// <summary>Queue of currently available HTTP/1.1 connections stored in the pool.</summary>
7373
private readonly ConcurrentQueue<HttpConnection> _http11Connections = new();
74-
/// <summary>Controls whether we can use a fast path when returning connections to the pool.</summary>
74+
/// <summary>Controls whether we can use a fast path when returning connections to the pool and skip calling into <see cref="ProcessHttp11RequestQueue(HttpConnection?)"/>.</summary>
7575
private bool _http11RequestQueueIsEmptyAndNotDisposed;
7676
/// <summary>The maximum number of HTTP/1.1 connections allowed to be associated with the pool.</summary>
7777
private readonly int _maxHttp11Connections;
@@ -556,19 +556,34 @@ private void CheckForHttp11ConnectionInjection()
556556
}
557557
}
558558

559+
/// <summary>
560+
/// This method is called:
561+
/// <br/>- When returning a connection and observing that the request queue is not empty (<see cref="_http11RequestQueueIsEmptyAndNotDisposed"/> is <see langword="false"/>).
562+
/// <br/>- After adding a request to the queue if we fail to obtain a connection from the queue.
563+
/// <br/>- After scavenging or disposing the pool to ensure that any pending requests are handled or connections disposed.
564+
/// <para>The method will attempt to match one request from the <see cref="_http11RequestQueue"/> to an available connection.
565+
/// The <paramref name="connection"/> can either be provided as an argument (when returning a connection to the pool), or one will be rented from <see cref="_http11Connections"/>.
566+
/// As we'll only process a single request, we are expecting the method to be called every time a request is enqueued, and every time a connection is returned while the request queue is not empty.</para>
567+
/// <para>If the <see cref="_http11RequestQueue"/> becomes empty, this method will reset the <see cref="_http11RequestQueueIsEmptyAndNotDisposed"/> flag back to <see langword="true"/>,
568+
/// such that returning connections will use the fast path again and skip calling into this method.</para>
569+
/// <para>Notably, this method will not be called on the fast path as long as we have enough connections to handle all new requests.</para>
570+
/// </summary>
571+
/// <param name="connection">The connection to use for a pending request, or return to the pool.</param>
559572
private void ProcessHttp11RequestQueue(HttpConnection? connection)
560573
{
561-
// Try to signal a single request that is waiting for a connection.
562-
// We're expecting this method to be called enough times to handle all requests.
574+
// Loop in case the request we try to signal was already cancelled or handled by a different connection.
563575
while (true)
564576
{
565577
HttpConnectionWaiter<HttpConnection>? waiter = null;
566578

567579
lock (SyncObj)
568580
{
569-
// This assert message may be misleading due to a race condition (we're reading Count twice).
570-
Debug.Assert(_associatedHttp11ConnectionCount >= _http11Connections.Count + _pendingHttp11ConnectionCount,
571-
$"Expected {_associatedHttp11ConnectionCount} >= {_http11Connections.Count} + {_pendingHttp11ConnectionCount}");
581+
#if DEBUG
582+
// Other threads may still interact with the connections queue. Read the count once to keep the assert message accurate.
583+
int connectionCount = _http11Connections.Count;
584+
Debug.Assert(_associatedHttp11ConnectionCount >= connectionCount + _pendingHttp11ConnectionCount,
585+
$"Expected {_associatedHttp11ConnectionCount} >= {connectionCount} + {_pendingHttp11ConnectionCount}");
586+
#endif
572587
Debug.Assert(_associatedHttp11ConnectionCount <= _maxHttp11Connections,
573588
$"Expected {_associatedHttp11ConnectionCount} <= {_maxHttp11Connections}");
574589
Debug.Assert(_associatedHttp11ConnectionCount >= _pendingHttp11ConnectionCount,
@@ -578,12 +593,15 @@ private void ProcessHttp11RequestQueue(HttpConnection? connection)
578593
{
579594
if (connection is not null || _http11Connections.TryDequeue(out connection))
580595
{
596+
// TryDequeueWaiter will prune completed requests from the head of the queue,
597+
// so it's possible for it to return false even though we checked that Count != 0.
581598
bool success = _http11RequestQueue.TryDequeueWaiter(this, out waiter);
582599
Debug.Assert(success == waiter is not null);
583600
}
584601
}
585602

586603
// Update the empty queue flag now.
604+
// If the request queue is now empty, returning connections will use the fast path and skip calling into this method.
587605
_http11RequestQueueIsEmptyAndNotDisposed = _http11RequestQueue.Count == 0 && !_disposed;
588606

589607
if (waiter is null)
@@ -601,7 +619,10 @@ private void ProcessHttp11RequestQueue(HttpConnection? connection)
601619
// - thread A returns the connection to the pool
602620
// We'd have both a connection and a request waiting in the pool, but nothing to pair the two.
603621

604-
// This should be a rare case, so the added contention should be minimal.
622+
// The main scenario where we'll reach this branch is when we enqueue a request to the queue
623+
// and set the _http11RequestQueueIsEmptyAndNotDisposed flag to false, followed by multiple
624+
// returning connections observing the flag and calling into this method before we clear the flag.
625+
// This should be a relatively rare case, so the added contention should be minimal.
605626
_http11Connections.Enqueue(connection);
606627
}
607628

@@ -2045,6 +2066,11 @@ private void ReturnHttp11Connection(HttpConnection connection)
20452066
}
20462067
else
20472068
{
2069+
// ProcessHttp11RequestQueue is responsible for handing the connection to a pending request,
2070+
// or to return it back to the pool if there aren't any.
2071+
2072+
// We hand over the connection directly instead of enqueuing it first to ensure
2073+
// that pending requests are processed in a fair (FIFO) order.
20482074
ProcessHttp11RequestQueue(connection);
20492075
}
20502076
}
@@ -2371,6 +2397,8 @@ public bool CleanCacheAndDisposeIfUnused()
23712397

23722398
static void ScavengeConnectionQueue(ConcurrentQueue<HttpConnection> connections, ref List<HttpConnectionBase>? toDispose, long nowTicks, TimeSpan pooledConnectionLifetime, TimeSpan pooledConnectionIdleTimeout)
23732399
{
2400+
// We can't simply enumerate the connections queue as other threads may still be adding and removing entries.
2401+
// If we want to check the state of a connection, we must dequeue it first to ensure we own it.
23742402
int initialCount = connections.Count;
23752403

23762404
while (--initialCount >= 0 && connections.TryDequeue(out HttpConnection? connection))

0 commit comments

Comments
 (0)