Healthie.NET.LeaderElection
4.1.4
Prefix Reserved
dotnet add package Healthie.NET.LeaderElection --version 4.1.4
NuGet\Install-Package Healthie.NET.LeaderElection -Version 4.1.4
<PackageReference Include="Healthie.NET.LeaderElection" Version="4.1.4" />
<PackageVersion Include="Healthie.NET.LeaderElection" Version="4.1.4" />
<PackageReference Include="Healthie.NET.LeaderElection" />
paket add Healthie.NET.LeaderElection --version 4.1.4
#r "nuget: Healthie.NET.LeaderElection, 4.1.4"
#:package Healthie.NET.LeaderElection@4.1.4
#addin nuget:?package=Healthie.NET.LeaderElection&version=4.1.4
#tool nuget:?package=Healthie.NET.LeaderElection&version=4.1.4

Healthie.NET.LeaderElection
Deprecated as of 4.1.0
Leader election now ships in Healthie.NET, the core package. Call
AddHealthieLeaderElection()there and drop this reference — running the checks on one replica at a time works exactly as it does here.Nothing breaks if you keep it. This package is still published, as an assembly of type forwards, so an application referencing it keeps compiling and running untouched. It carried no third-party dependency, so keeping it separate cost you an install and saved you nothing. It will not gain features.
▶ Live demo — board.healthie-dotnet.dev — a read-only Healthie.NET dashboard watching real status pages (Anthropic, OpenAI, GitHub, Cloudflare, and more), built from these packages. Full documentation at healthie-dotnet.dev.
Runs pulse checks on one replica at a time.
The problem it solves
Without it, every replica runs every check. Three replicas mean:
- a database asked three times whether it is healthy, on every interval
- three sets of results racing to write the same state document, two of them wasted
- one outage paging somebody three times
None of that is visible from a dashboard, which is what makes it worth fixing before it matters.
Installation
dotnet add package Healthie.NET.LeaderElection
Usage
using Healthie.LeaderElection;
builder.Services
.AddHealthie(typeof(Program).Assembly)
.AddHealthieLeaderElection(); // after the scheduler it should wrap
builder.Services.AddSingleton<ILeaseProvider, YourSharedLeaseProvider>();
Call it after the scheduler. It decorates whatever IPulseScheduler is registered at that point, so unlike every other AddHealthie* in this library it is not order-independent. Calling it first throws with an explanation rather than silently wrapping the built-in timer when you meant Quartz.
It works with every scheduler — the built-in timer, Quartz, Hangfire, Coravel, Temporal — because it wraps rather than replaces.
You need a shared lease provider
The default keeps leases in memory, which makes every replica the leader of itself and leaves the problem exactly where it was. It exists so the feature can be switched on and tested without standing anything up.
Implement ILeaseProvider against something your replicas share — a table with a conditional update, a Redis SET NX, a blob lease. It is two methods:
Task<bool> TryAcquireAsync(string leaseName, string holderId, TimeSpan duration, CancellationToken ct);
Task ReleaseAsync(string leaseName, string holderId, CancellationToken ct);
Acquire and renew are one operation on purpose — take it if nobody holds it, if it has expired, or if it is already mine. Separating them invites a renew that succeeds against a lease somebody else now holds.
Failover
A lease expires rather than being handed over, because the failure worth designing for is the replica that stops without saying anything — killed, redeployed, partitioned away. Another replica takes over once the lease lapses, without needing its cooperation.
LeaseDuration (30s default) is therefore how long checks pause when a leader dies abruptly. RenewInterval (10s) is comfortably shorter, so a single slow round trip does not move leadership for no reason.
If the lease store becomes unreachable, the replica stands down. It can no longer prove it leads, and two leaders is the state this exists to prevent.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Healthie.NET.DependencyInjection (>= 4.1.4)
-
net8.0
- Healthie.NET.DependencyInjection (>= 4.1.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
### Fixed
- Timer-scheduled checks now continue after an individual scheduling caller cancels, while host
shutdown still stops them; an older overlapping result can no longer overwrite newer checker state.
- Bulk dashboard state reads and AI diagnoses now avoid redundant state-provider calls.
- Workflow dispatch inputs are passed to shell steps as environment variables, preventing command
injection through a supplied version or branch name.
- Dashboard status text has stronger contrast, and checker details expose their meaning to assistive
technology.
Full changelog: https://github.com/ivanvyd/Healthie.NET/blob/v4.1.4/CHANGELOG.md