Problem
In Flagsmith .NET SDK 9.0.0, enabling local evaluation starts a PollingManager backed by System.Threading.Timer.
The only cleanup currently occurs in the FlagsmithClient finalizer:
~FlagsmithClient() => _pollingManager?.StopPoll();
This does not provide deterministic cleanup when an application host or dependency-injection container stops. Additionally, the timer callback retains the polling manager, whose callback retains the client. This may keep the client reachable and prevent its finalizer from running.
Relevant source:
Impact
In long-running processes that create and stop application hosts:
- Environment-document requests can continue after the host stops.
- Recreated hosts and tests can accumulate polling timers.
- References to clients, loggers, and related services can remain alive.
- Consumers cannot bind polling cleanup to their application lifecycle.
Process termination masks the issue because the operating system removes the timer.
Reproduction
- Configure
FlagsmithClient with local evaluation and a short refresh interval.
- Point it at an HTTP handler that counts environment-document requests.
- Create the client and observe polling.
- Stop or dispose the owning host or service provider without terminating the process.
- Wait beyond the refresh interval.
- Environment-document requests continue because there is no public shutdown or disposal API.
Expected behaviour
FlagsmithClient should expose deterministic cleanup, for example through IDisposable, IAsyncDisposable, or an explicit shutdown method.
Cleanup should:
- Stop and dispose the polling timer immediately.
- Prevent future environment-document requests.
- Cancel an in-flight refresh where possible.
- Be idempotent.
- Call
GC.SuppressFinalize if a finalizer remains as a fallback.
This would allow integrations such as OpenFeature providers to stop polling from their normal host shutdown lifecycle.
Problem
In Flagsmith .NET SDK 9.0.0, enabling local evaluation starts a
PollingManagerbacked bySystem.Threading.Timer.The only cleanup currently occurs in the
FlagsmithClientfinalizer:~FlagsmithClient() => _pollingManager?.StopPoll();This does not provide deterministic cleanup when an application host or dependency-injection container stops. Additionally, the timer callback retains the polling manager, whose callback retains the client. This may keep the client reachable and prevent its finalizer from running.
Relevant source:
Impact
In long-running processes that create and stop application hosts:
Process termination masks the issue because the operating system removes the timer.
Reproduction
FlagsmithClientwith local evaluation and a short refresh interval.Expected behaviour
FlagsmithClientshould expose deterministic cleanup, for example throughIDisposable,IAsyncDisposable, or an explicit shutdown method.Cleanup should:
GC.SuppressFinalizeif a finalizer remains as a fallback.This would allow integrations such as OpenFeature providers to stop polling from their normal host shutdown lifecycle.