public async Task Factory_CleanupCycle_DisposesLiveHandler() { // Arrange var disposeHandler = new DisposeTrackingHandler(); Options.Get("github").HttpMessageHandlerBuilderActions.Add(b => { b.AdditionalHandlers.Add(disposeHandler); }); var factory = new TestHttpClientFactory(Services, LoggerFactory, Options, EmptyFilters) { EnableExpiryTimer = true, EnableCleanupTimer = true, }; // Create a handler and move it to the expired state var client1 = factory.CreateClient("github"); var kvp = Assert.Single(factory.ActiveEntryState); kvp.Value.Item1.SetResult(kvp.Key); await kvp.Value.Item2; // Our handler is now in the cleanup state. var cleanupEntry = Assert.Single(factory._expiredHandlers); Assert.True(factory.CleanupTimerStarted.IsSet, "Cleanup timer started"); // Nulling out the refernces to the internal state of the factory since they wouldn't exist in the non-test // scenario. We're holding on the client to prevent disposal - like a real use case. kvp = default; // Act - 1 factory.CleanupTimer_Tick(null); // Assert Assert.Same(cleanupEntry, Assert.Single(factory._expiredHandlers)); Assert.Equal(0, disposeHandler.DisposeCount); Assert.True(factory.CleanupTimerStarted.IsSet, "Cleanup timer started"); // We need to make sure that the outer handler actually gets GCed, so drop our references to it. // This is important because the factory relies on this possibility for correctness. We need to ensure that // the factory isn't keeping any references. client1 = null; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Assert.True(cleanupEntry.CanDispose, "Cleanup entry disposable"); // Act - 2 factory.CleanupTimer_Tick(null); // Assert Assert.Empty(factory._expiredHandlers); Assert.Equal(1, disposeHandler.DisposeCount); Assert.False(factory.CleanupTimerStarted.IsSet, "Cleanup timer not started"); }
private async Task <ExpiredHandlerTrackingEntry> SimulateClientUse_Factory_CleanupCycle_DisposesLiveHandler( TestHttpClientFactory factory, DisposeTrackingHandler disposeHandler) { // Create a handler and move it to the expired state var client1 = factory.CreateClient("github"); var kvp = Assert.Single(factory.ActiveEntryState); kvp.Value.Item1.SetResult(kvp.Key); await kvp.Value.Item2; // Our handler is now in the cleanup state. var cleanupEntry = Assert.Single(factory._expiredHandlers); Assert.True(factory.CleanupTimerStarted.IsSet, "Cleanup timer started"); // Nulling out the references to the internal state of the factory since they wouldn't exist in the non-test // scenario. We're holding on the client to prevent disposal - like a real use case. lock (this) { // Prevent reordering kvp = default; } // Let's verify the the ActiveHandlerTrackingEntry is gone. This would be prevent // the handler from being disposed if it was still rooted. Assert.Empty(factory.ActiveEntryState); // Act - 1 - Run a cleanup cycle, this will not dispose the handler, because the client is still live. factory.CleanupTimer_Tick(); // Assert Assert.Same(cleanupEntry, Assert.Single(factory._expiredHandlers)); Assert.Equal(0, disposeHandler.DisposeCount); Assert.True(factory.CleanupTimerStarted.IsSet, "Cleanup timer started"); // We need to make sure that the outer handler actually gets GCed, so drop our references to it. // This is important because the factory relies on this possibility for correctness. We need to ensure that // the factory isn't keeping any references. lock (this) { // Prevent reordering GC.KeepAlive(client1); client1 = null; } return(cleanupEntry); }
public async Task Factory_CleanupCycle_DisposesLiveHandler() { // Arrange var disposeHandler = new DisposeTrackingHandler(); Options.Get("github").HttpMessageHandlerBuilderActions.Add(b => { b.AdditionalHandlers.Add(disposeHandler); }); var factory = new TestHttpClientFactory(Services, ScopeFactory, LoggerFactory, Options, EmptyFilters) { EnableExpiryTimer = true, EnableCleanupTimer = true, }; var cleanupEntry = await SimulateClientUse(factory, disposeHandler); // Being pretty conservative here because we want this test to be reliable, // and it depends on the GC and timing. for (var i = 0; i < 3; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); if (cleanupEntry.CanDispose) { break; } await Task.Delay(TimeSpan.FromSeconds(1)); } Assert.True(cleanupEntry.CanDispose, "Cleanup entry disposable"); // Act - 2 factory.CleanupTimer_Tick(null); // Assert Assert.Empty(factory._expiredHandlers); Assert.Equal(1, disposeHandler.DisposeCount); Assert.False(factory.CleanupTimerStarted.IsSet, "Cleanup timer not started"); }