@@ -257,6 +257,7 @@ private void Shutdown()
257
257
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "{ nameof ( _shutdown ) } ={ _shutdown } , { nameof ( _abortException ) } ={ _abortException } ") ;
258
258
259
259
Debug . Assert ( Monitor . IsEntered ( SyncObject ) ) ;
260
+ Debug . Assert ( ! _pool . HasSyncObjLock ) ;
260
261
261
262
if ( ! _shutdown )
262
263
{
@@ -276,6 +277,8 @@ private void Shutdown()
276
277
277
278
public bool TryReserveStream ( )
278
279
{
280
+ Debug . Assert ( ! _pool . HasSyncObjLock ) ;
281
+
279
282
lock ( SyncObject )
280
283
{
281
284
if ( _shutdown )
@@ -302,6 +305,8 @@ public bool TryReserveStream()
302
305
// Otherwise, will be called when the request is complete and stream is closed.
303
306
public void ReleaseStream ( )
304
307
{
308
+ Debug . Assert ( ! _pool . HasSyncObjLock ) ;
309
+
305
310
lock ( SyncObject )
306
311
{
307
312
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "{ nameof ( _streamsInUse ) } ={ _streamsInUse } ") ;
@@ -333,6 +338,8 @@ public void ReleaseStream()
333
338
// Returns false to indicate that the connection is shutting down and cannot be used anymore
334
339
public Task < bool > WaitForAvailableStreamsAsync ( )
335
340
{
341
+ Debug . Assert ( ! _pool . HasSyncObjLock ) ;
342
+
336
343
lock ( SyncObject )
337
344
{
338
345
Debug . Assert ( _availableStreamsWaiter is null , "As used currently, shouldn't already have a waiter" ) ;
@@ -730,6 +737,7 @@ private void ProcessAltSvcFrame(FrameHeader frameHeader)
730
737
{
731
738
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "{ frameHeader } ") ;
732
739
Debug . Assert ( frameHeader . Type == FrameType . AltSvc ) ;
740
+ Debug . Assert ( ! Monitor . IsEntered ( SyncObject ) ) ;
733
741
734
742
ReadOnlySpan < byte > span = _incomingBuffer . ActiveSpan . Slice ( 0 , frameHeader . PayloadLength ) ;
735
743
@@ -1308,6 +1316,8 @@ private Task SendRstStreamAsync(int streamId, Http2ProtocolErrorCode errorCode)
1308
1316
1309
1317
internal void HeartBeat ( )
1310
1318
{
1319
+ Debug . Assert ( ! _pool . HasSyncObjLock ) ;
1320
+
1311
1321
if ( _shutdown )
1312
1322
return ;
1313
1323
@@ -1800,10 +1810,14 @@ private void ExtendWindow(int amount)
1800
1810
1801
1811
public override long GetIdleTicks ( long nowTicks )
1802
1812
{
1803
- lock ( SyncObject )
1804
- {
1805
- return _streamsInUse == 0 ? base . GetIdleTicks ( nowTicks ) : 0 ;
1806
- }
1813
+ // The pool is holding the lock as part of its scavenging logic.
1814
+ // We must not lock on Http2Connection.SyncObj here as that could lead to lock ordering problems.
1815
+ Debug . Assert ( _pool . HasSyncObjLock ) ;
1816
+
1817
+ // There is a race condition here where the connection pool may see this connection as idle right before
1818
+ // we start processing a new request and start its disposal. This is okay as we will either
1819
+ // return false from TryReserveStream, or process pending requests before tearing down the transport.
1820
+ return _streamsInUse == 0 ? base . GetIdleTicks ( nowTicks ) : 0 ;
1807
1821
}
1808
1822
1809
1823
/// <summary>Abort all streams and cause further processing to fail.</summary>
@@ -1992,6 +2006,7 @@ private static TaskCompletionSourceWithCancellation<bool> CreateSuccessfullyComp
1992
2006
public async Task < HttpResponseMessage > SendAsync ( HttpRequestMessage request , bool async , CancellationToken cancellationToken )
1993
2007
{
1994
2008
Debug . Assert ( async ) ;
2009
+ Debug . Assert ( ! _pool . HasSyncObjLock ) ;
1995
2010
if ( NetEventSource . Log . IsEnabled ( ) ) Trace ( $ "Sending request: { request } ") ;
1996
2011
1997
2012
try
0 commit comments