4
4
using System ;
5
5
using System . Collections . Generic ;
6
6
using System . Net . Http ;
7
+ using System . Runtime . CompilerServices ;
7
8
using System . Threading ;
8
9
using System . Threading . Tasks ;
9
10
using Microsoft . Extensions . DependencyInjection ;
@@ -328,6 +329,40 @@ public async Task Factory_CleanupCycle_DisposesEligibleHandler()
328
329
EnableCleanupTimer = true ,
329
330
} ;
330
331
332
+ var cleanupEntry = await SimulateClientUse_Factory_CleanupCycle_DisposesEligibleHandler ( factory ) ;
333
+
334
+ // Being pretty conservative here because we want this test to be reliable,
335
+ // and it depends on the GC and timing.
336
+ for ( var i = 0 ; i < 3 ; i ++ )
337
+ {
338
+ GC . Collect ( ) ;
339
+ GC . WaitForPendingFinalizers ( ) ;
340
+ GC . Collect ( ) ;
341
+
342
+ if ( cleanupEntry . CanDispose )
343
+ {
344
+ break ;
345
+ }
346
+
347
+ await Task . Delay ( TimeSpan . FromSeconds ( 1 ) ) ;
348
+ }
349
+
350
+ Assert . True ( cleanupEntry . CanDispose , "Cleanup entry disposable" ) ;
351
+
352
+ // Act
353
+ factory . CleanupTimer_Tick ( state : null ) ;
354
+
355
+ // Assert
356
+ Assert . Empty ( factory . _expiredHandlers ) ;
357
+ Assert . Equal ( 1 , disposeHandler . DisposeCount ) ;
358
+ Assert . False ( factory . CleanupTimerStarted . IsSet , "Cleanup timer not started" ) ;
359
+ }
360
+
361
+ // Seprate to avoid the HttpClient getting its lifetime extended by
362
+ // the state machine of the test.
363
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
364
+ private async Task < ExpiredHandlerTrackingEntry > SimulateClientUse_Factory_CleanupCycle_DisposesEligibleHandler ( TestHttpClientFactory factory )
365
+ {
331
366
// Create a handler and move it to the expired state
332
367
var client1 = factory . CreateClient ( "github" ) ;
333
368
@@ -344,19 +379,8 @@ public async Task Factory_CleanupCycle_DisposesEligibleHandler()
344
379
// the factory isn't keeping any references.
345
380
kvp = default ;
346
381
client1 = null ;
347
- GC . Collect ( ) ;
348
- GC . WaitForPendingFinalizers ( ) ;
349
- GC . Collect ( ) ;
350
-
351
- Assert . True ( cleanupEntry . CanDispose , "Cleanup entry disposable" ) ;
352
-
353
- // Act
354
- factory . CleanupTimer_Tick ( null ) ;
355
382
356
- // Assert
357
- Assert . Empty ( factory . _expiredHandlers ) ;
358
- Assert . Equal ( 1 , disposeHandler . DisposeCount ) ;
359
- Assert . False ( factory . CleanupTimerStarted . IsSet , "Cleanup timer not started" ) ;
383
+ return cleanupEntry ;
360
384
}
361
385
362
386
[ Fact ]
@@ -375,6 +399,42 @@ public async Task Factory_CleanupCycle_DisposesLiveHandler()
375
399
EnableCleanupTimer = true ,
376
400
} ;
377
401
402
+ var cleanupEntry = await SimulateClientUse_Factory_CleanupCycle_DisposesLiveHandler ( factory , disposeHandler ) ;
403
+
404
+ // Being pretty conservative here because we want this test to be reliable,
405
+ // and it depends on the GC and timing.
406
+ for ( var i = 0 ; i < 3 ; i ++ )
407
+ {
408
+ GC . Collect ( ) ;
409
+ GC . WaitForPendingFinalizers ( ) ;
410
+ GC . Collect ( ) ;
411
+
412
+ if ( cleanupEntry . CanDispose )
413
+ {
414
+ break ;
415
+ }
416
+
417
+ await Task . Delay ( TimeSpan . FromSeconds ( 1 ) ) ;
418
+ }
419
+
420
+ Assert . True ( cleanupEntry . CanDispose , "Cleanup entry disposable" ) ;
421
+
422
+ // Act - 2
423
+ factory . CleanupTimer_Tick ( state : null ) ;
424
+
425
+ // Assert
426
+ Assert . Empty ( factory . _expiredHandlers ) ;
427
+ Assert . Equal ( 1 , disposeHandler . DisposeCount ) ;
428
+ Assert . False ( factory . CleanupTimerStarted . IsSet , "Cleanup timer not started" ) ;
429
+ }
430
+
431
+ // Seprate to avoid the HttpClient getting its lifetime extended by
432
+ // the state machine of the test.
433
+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
434
+ private async Task < ExpiredHandlerTrackingEntry > SimulateClientUse_Factory_CleanupCycle_DisposesLiveHandler (
435
+ TestHttpClientFactory factory ,
436
+ DisposeTrackingHandler disposeHandler )
437
+ {
378
438
// Create a handler and move it to the expired state
379
439
var client1 = factory . CreateClient ( "github" ) ;
380
440
@@ -386,12 +446,16 @@ public async Task Factory_CleanupCycle_DisposesLiveHandler()
386
446
var cleanupEntry = Assert . Single ( factory . _expiredHandlers ) ;
387
447
Assert . True ( factory . CleanupTimerStarted . IsSet , "Cleanup timer started" ) ;
388
448
389
- // Nulling out the refernces to the internal state of the factory since they wouldn't exist in the non-test
449
+ // Nulling out the references to the internal state of the factory since they wouldn't exist in the non-test
390
450
// scenario. We're holding on the client to prevent disposal - like a real use case.
391
- kvp = default ;
451
+ lock ( this )
452
+ {
453
+ // Prevent reordering
454
+ kvp = default ;
455
+ }
392
456
393
457
// Act - 1
394
- factory . CleanupTimer_Tick ( null ) ;
458
+ factory . CleanupTimer_Tick ( state : null ) ;
395
459
396
460
// Assert
397
461
Assert . Same ( cleanupEntry , Assert . Single ( factory . _expiredHandlers ) ) ;
@@ -401,20 +465,13 @@ public async Task Factory_CleanupCycle_DisposesLiveHandler()
401
465
// We need to make sure that the outer handler actually gets GCed, so drop our references to it.
402
466
// This is important because the factory relies on this possibility for correctness. We need to ensure that
403
467
// the factory isn't keeping any references.
404
- client1 = null ;
405
- GC . Collect ( ) ;
406
- GC . WaitForPendingFinalizers ( ) ;
407
- GC . Collect ( ) ;
408
-
409
- Assert . True ( cleanupEntry . CanDispose , "Cleanup entry disposable" ) ;
410
-
411
- // Act - 2
412
- factory . CleanupTimer_Tick ( null ) ;
468
+ lock ( this )
469
+ {
470
+ // Prevent reordering
471
+ client1 = null ;
472
+ }
413
473
414
- // Assert
415
- Assert . Empty ( factory . _expiredHandlers ) ;
416
- Assert . Equal ( 1 , disposeHandler . DisposeCount ) ;
417
- Assert . False ( factory . CleanupTimerStarted . IsSet , "Cleanup timer not started" ) ;
474
+ return cleanupEntry ;
418
475
}
419
476
420
477
private class TestHttpClientFactory : DefaultHttpClientFactory
0 commit comments