[Reliability] Scheduler TaskRunners create a new HttpClient per execution (socket exhaustion — use IHttpClientFactory) #434

Closed
opened 2026-07-06 03:11:07 +00:00 by spikerj · 1 comment
Owner

Problem

SpikerSoft.EventHandlers.Scheduler registers its task runners as singletons, but they new HttpClient() (in a using) on every ExecuteAsync:

  • TaskRunners/HttpCallbackTaskRunner.cs:33 — runs per scheduled HTTP callback (potentially frequent)
  • TaskRunners/GeoIpUpdateTaskRunner.cs:58 — periodic MaxMind download

Creating + disposing an HttpClient per call is the well-known socket-exhaustion anti-pattern: each disposed client leaves its underlying connection in TIME_WAIT, and under sustained callback volume the host runs out of ephemeral ports. The Scheduler host does not register IHttpClientFactory.

Fix

  • Add services.AddHttpClient() to the Scheduler host (registers IHttpClientFactory).
  • Inject IHttpClientFactory into both runners; replace new HttpClient() with _httpClientFactory.CreateClient() (per-call Timeout still set on the returned client). The factory pools and rotates handlers, eliminating the leak.

Acceptance

  • No new HttpClient() in the Scheduler task runners; both use IHttpClientFactory.CreateClient().
  • Behaviour unchanged (same timeouts, same requests).

Refs #411 (resource-leak availability bombs).

## Problem `SpikerSoft.EventHandlers.Scheduler` registers its task runners as **singletons**, but they `new HttpClient()` (in a `using`) on **every** `ExecuteAsync`: - `TaskRunners/HttpCallbackTaskRunner.cs:33` — runs per scheduled HTTP callback (potentially frequent) - `TaskRunners/GeoIpUpdateTaskRunner.cs:58` — periodic MaxMind download Creating + disposing an `HttpClient` per call is the well-known socket-exhaustion anti-pattern: each disposed client leaves its underlying connection in `TIME_WAIT`, and under sustained callback volume the host runs out of ephemeral ports. The Scheduler host does **not** register `IHttpClientFactory`. ## Fix - Add `services.AddHttpClient()` to the Scheduler host (registers `IHttpClientFactory`). - Inject `IHttpClientFactory` into both runners; replace `new HttpClient()` with `_httpClientFactory.CreateClient()` (per-call `Timeout` still set on the returned client). The factory pools and rotates handlers, eliminating the leak. ## Acceptance - No `new HttpClient()` in the Scheduler task runners; both use `IHttpClientFactory.CreateClient()`. - Behaviour unchanged (same timeouts, same requests). Refs #411 (resource-leak availability bombs).
Author
Owner

Resolved in spikersoft-backend PR #113 (merged to master). Registered IHttpClientFactory in the Scheduler host and switched HttpCallbackTaskRunner + GeoIpUpdateTaskRunner to CreateClient() (pooled handlers) instead of new HttpClient() per execution. Guarded by SchedulerTaskRunnerHttpClientFactoryTests + the existing registry tests. Closing.

Resolved in spikersoft-backend PR #113 (merged to `master`). Registered `IHttpClientFactory` in the Scheduler host and switched `HttpCallbackTaskRunner` + `GeoIpUpdateTaskRunner` to `CreateClient()` (pooled handlers) instead of `new HttpClient()` per execution. Guarded by `SchedulerTaskRunnerHttpClientFactoryTests` + the existing registry tests. Closing.
Sign in to join this conversation.