VOOZH about

URL: https://www.nuget.org/packages/Excalibur.Dispatch.LeaderElection.Abstractions/

⇱ NuGet Gallery | Excalibur.Dispatch.LeaderElection.Abstractions 3.0.0-alpha.208




Excalibur.Dispatch.LeaderElection.Abstractions 3.0.0-alpha.208

This is a prerelease version of Excalibur.Dispatch.LeaderElection.Abstractions.
dotnet add package Excalibur.Dispatch.LeaderElection.Abstractions --version 3.0.0-alpha.208
 
 
NuGet\Install-Package Excalibur.Dispatch.LeaderElection.Abstractions -Version 3.0.0-alpha.208
 
 
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Excalibur.Dispatch.LeaderElection.Abstractions" Version="3.0.0-alpha.208" />
 
 
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Excalibur.Dispatch.LeaderElection.Abstractions" Version="3.0.0-alpha.208" />
 
Directory.Packages.props
<PackageReference Include="Excalibur.Dispatch.LeaderElection.Abstractions" />
 
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Excalibur.Dispatch.LeaderElection.Abstractions --version 3.0.0-alpha.208
 
 
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Excalibur.Dispatch.LeaderElection.Abstractions, 3.0.0-alpha.208"
 
 
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Excalibur.Dispatch.LeaderElection.Abstractions@3.0.0-alpha.208
 
 
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Excalibur.Dispatch.LeaderElection.Abstractions&version=3.0.0-alpha.208&prerelease
 
Install as a Cake Addin
#tool nuget:?package=Excalibur.Dispatch.LeaderElection.Abstractions&version=3.0.0-alpha.208&prerelease
 
Install as a Cake Tool
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

Excalibur.Dispatch.LeaderElection.Abstractions

Core abstractions for distributed leader election in .NET 9 applications.

Overview

This package provides the foundational interfaces and types for implementing leader election patterns in distributed systems. It contains no implementation - install a provider package (Consul, Kubernetes, or InMemory) for concrete functionality.

Key Features:

  • Provider-agnostic - Define your logic once, swap providers without code changes
  • Event-driven - React to leadership changes via events
  • Health-aware - Optional health-based leadership with automatic step-down
  • AOT Compatible - Full Native AOT support for trimmed, ahead-of-time compiled deployments
  • Zero Dependencies - No external dependencies beyond .NET 9 base libraries

Installation

dotnet add package Excalibur.Dispatch.LeaderElection.Abstractions

This package alone provides no functionality. You must also install a provider:

Choose a Provider

Provider Package Use Case AOT Support
Consul Excalibur.Dispatch.LeaderElection.Consul Production deployments with HashiCorp Consul ❌ No
Kubernetes Excalibur.Dispatch.LeaderElection.Kubernetes Cloud-native apps running in Kubernetes clusters ❌ No
InMemory Excalibur.Dispatch.LeaderElection.InMemory Unit/integration testing, development ✅ Yes

Example: Install Consul provider

dotnet add package Excalibur.Dispatch.LeaderElection.Abstractions
dotnet add package Excalibur.Dispatch.LeaderElection.Consul

Core Interfaces

ILeaderElection

The primary interface for participating in leader election.

public interface ILeaderElection
{
 // Events
 event EventHandler<LeaderElectionEventArgs>? OnBecameLeader;
 event EventHandler<LeaderElectionEventArgs>? OnLostLeadership;
 event EventHandler<LeaderChangedEventArgs>? OnLeaderChanged;

 // Properties
 string CandidateId { get; }
 bool IsLeader { get; }
 string? CurrentLeaderId { get; }

 // Methods
 Task StartAsync(CancellationToken cancellationToken);
 Task StopAsync(CancellationToken cancellationToken);
}

Usage Pattern:

public class MyService : BackgroundService
{
 private readonly ILeaderElection _election;

 public MyService(ILeaderElectionFactory factory)
 {
 _election = factory.CreateElection("my-resource");
 _election.OnBecameLeader += OnBecameLeader;
 _election.OnLostLeadership += OnLostLeadership;
 }

 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 {
 await _election.StartAsync(stoppingToken);

 while (!stoppingToken.IsCancellationRequested)
 {
 if (_election.IsLeader)
 {
 // Perform leader-only work
 await DoLeaderWorkAsync();
 }
 else
 {
 // Perform follower work (or idle)
 await Task.Delay(1000, stoppingToken);
 }
 }

 await _election.StopAsync();
 }

 private void OnBecameLeader(object? sender, LeaderElectionEventArgs e)
 {
 Console.WriteLine($"This instance ({e.CandidateId}) became the leader!");
 // Initialize leader-specific resources
 }

 private void OnLostLeadership(object? sender, LeaderElectionEventArgs e)
 {
 Console.WriteLine($"This instance ({e.CandidateId}) lost leadership");
 // Clean up leader-specific resources
 }

 private async Task DoLeaderWorkAsync()
 {
 // Example: Process pending jobs, update aggregates, etc.
 await Task.Delay(100);
 }
}

IHealthBasedLeaderElection

Extends leader election with health awareness - leaders can step down when unhealthy.

public interface IHealthBasedLeaderElection : ILeaderElection
{
 Task UpdateHealthAsync(bool isHealthy, IDictionary<string, string>? metadata, CancellationToken cancellationToken);
 Task<IEnumerable<CandidateHealth>> GetCandidateHealthAsync(CancellationToken cancellationToken);
}

Usage Pattern:

public class HealthMonitoredService : BackgroundService
{
 private readonly IHealthBasedLeaderElection _election;

 public HealthMonitoredService(ILeaderElectionFactory factory)
 {
 _election = factory.CreateHealthBasedElection("health-aware-resource");
 }

 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 {
 await _election.StartAsync(stoppingToken);

 while (!stoppingToken.IsCancellationRequested)
 {
 // Continuously monitor health
 bool isHealthy = await CheckApplicationHealthAsync();

 // Update health status with metadata
 await _election.UpdateHealthAsync(isHealthy, new Dictionary<string, string>
 {
 ["cpu_usage"] = "65%",
 ["memory_usage"] = "80%",
 ["disk_space"] = "healthy"
 }, cancellationToken);

 // If unhealthy and leader, will automatically step down
 if (!isHealthy && _election.IsLeader)
 {
 Console.WriteLine("Unhealthy leader - stepping down automatically");
 }

 await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
 }
 }

 private async Task<bool> CheckApplicationHealthAsync()
 {
 // Implement health checks
 // - CPU usage < 90%
 // - Memory usage < 90%
 // - Disk space available
 // - Database connectivity
 return true; // Example
 }
}

ILeaderElectionFactory

Factory for creating leader election instances.

public interface ILeaderElectionFactory
{
 ILeaderElection CreateElection(string resourceName, string? candidateId = null);
 IHealthBasedLeaderElection CreateHealthBasedElection(string resourceName, string? candidateId = null);
}

Dependency Injection (Builder API -- recommended):

services.AddExcalibur(x => x.AddLeaderElection(le => le
 .UseConsul(opts => opts.ConsulAddress = "http://localhost:8500")
 .WithHealthChecks()
 .WithFencingTokens()));

Dependency Injection (Factory pattern):

// Inject factory to create elections for different resources
services.AddSingleton(sp =>
{
 var factory = sp.GetRequiredService<ILeaderElectionFactory>();
 return factory.CreateElection("my-resource-lock");
});

Configuration

LeaderElectionOptions

Base configuration for all leader election implementations.

public class LeaderElectionOptions
{
 // Core timing configuration
 public TimeSpan LeaseDuration { get; set; } = TimeSpan.FromSeconds(15);
 public TimeSpan RenewInterval { get; set; } = TimeSpan.FromSeconds(5);
 public TimeSpan RetryInterval { get; set; } = TimeSpan.FromSeconds(2);
 public TimeSpan GracePeriod { get; set; } = TimeSpan.FromSeconds(5);

 // Identity
 public string InstanceId { get; set; } = Environment.MachineName;
 public IDictionary<string, string> CandidateMetadata { get; }

 // Health-based election settings
 public bool EnableHealthChecks { get; set; } = true;
 public double MinimumHealthScore { get; set; } = 0.8;
 public bool StepDownWhenUnhealthy { get; set; } = true;
}

Configuration Example:

services.AddConsulLeaderElection(options =>
{
 // Lease expires after 30 seconds of no renewal
 options.LeaseDuration = TimeSpan.FromSeconds(30);

 // Renew lease every 10 seconds
 options.RenewInterval = TimeSpan.FromSeconds(10);

 // Retry acquiring leadership every 5 seconds
 options.RetryInterval = TimeSpan.FromSeconds(5);

 // Grace period before declaring leader dead
 options.GracePeriod = TimeSpan.FromSeconds(5);

 // Unique identifier for this instance
 options.InstanceId = $"{Environment.MachineName}-{Guid.NewGuid()}";

 // Enable health-based leadership
 options.EnableHealthChecks = true;
 options.MinimumHealthScore = 0.75; // 75% health minimum
 options.StepDownWhenUnhealthy = true; // Auto step-down when unhealthy

 // Metadata visible to other candidates
 options.CandidateMetadata["version"] = "1.0.0";
 options.CandidateMetadata["region"] = "us-east-1";
});

Event Handling

Leadership Events

public class LeaderAwareService
{
 private readonly ILeaderElection _election;

 public LeaderAwareService(ILeaderElectionFactory factory)
 {
 _election = factory.CreateElection("my-resource");

 // Subscribe to all events
 _election.OnBecameLeader += OnBecameLeader;
 _election.OnLostLeadership += OnLostLeadership;
 _election.OnLeaderChanged += OnLeaderChanged;
 }

 private void OnBecameLeader(object? sender, LeaderElectionEventArgs e)
 {
 // This instance acquired leadership
 Console.WriteLine($"✓ Became leader: {e.CandidateId} at {e.Timestamp}");

 // Initialize leader-only resources
 // - Start scheduled jobs
 // - Acquire exclusive locks
 // - Begin processing work queues
 }

 private void OnLostLeadership(object? sender, LeaderElectionEventArgs e)
 {
 // This instance lost leadership (lease expired, stepped down, etc.)
 Console.WriteLine($"✗ Lost leadership: {e.CandidateId} at {e.Timestamp}");

 // Clean up leader-only resources
 // - Stop scheduled jobs
 // - Release exclusive locks
 // - Drain work queues gracefully
 }

 private void OnLeaderChanged(object? sender, LeaderChangedEventArgs e)
 {
 // Any candidate detected a leadership change
 Console.WriteLine($"Leader changed: {e.OldLeaderId ?? "none"} → {e.NewLeaderId ?? "none"}");

 // Update routing, caches, or external monitoring
 }
}

Common Patterns

Pattern 1: Single Leader for Singleton Work

Ensure only one instance performs a task (e.g., scheduled job, cleanup, aggregation).

public class SingletonJobService : BackgroundService
{
 private readonly ILeaderElection _election;

 public SingletonJobService(ILeaderElectionFactory factory)
 {
 _election = factory.CreateElection("singleton-job");
 }

 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 {
 await _election.StartAsync(stoppingToken);

 while (!stoppingToken.IsCancellationRequested)
 {
 if (_election.IsLeader)
 {
 // Only the leader processes this job
 await ProcessJobAsync();
 }

 await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
 }

 await _election.StopAsync();
 }

 private async Task ProcessJobAsync()
 {
 Console.WriteLine("Leader processing job...");
 // Job logic here
 }
}

Pattern 2: Active-Passive Failover

Primary instance handles traffic; standby takes over on failure.

public class ActivePassiveService : BackgroundService
{
 private readonly ILeaderElection _election;
 private CancellationTokenSource? _activeWorkCts;

 public ActivePassiveService(ILeaderElectionFactory factory)
 {
 _election = factory.CreateElection("active-passive");
 _election.OnBecameLeader += async (s, e) => await ActivateAsync();
 _election.OnLostLeadership += async (s, e) => await DeactivateAsync();
 }

 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 {
 await _election.StartAsync(stoppingToken);

 // Keep election alive
 while (!stoppingToken.IsCancellationRequested)
 {
 await Task.Delay(1000, stoppingToken);
 }

 await _election.StopAsync();
 }

 private async Task ActivateAsync()
 {
 Console.WriteLine("Activating as leader");

 _activeWorkCts = new CancellationTokenSource();

 // Start leader work
 _ = Task.Run(async () =>
 {
 while (!_activeWorkCts.Token.IsCancellationRequested)
 {
 // Active processing
 await Task.Delay(100, _activeWorkCts.Token);
 }
 });
 }

 private async Task DeactivateAsync()
 {
 Console.WriteLine("Deactivating (no longer leader)");

 // Stop leader work gracefully
 _activeWorkCts?.Cancel();
 await Task.Delay(100); // Allow work to drain

 _activeWorkCts?.Dispose();
 _activeWorkCts = null;
 }
}

Pattern 3: Health-Based Leadership with Step-Down

Leader monitors its own health and steps down if unhealthy.

public class HealthAwareLeaderService : BackgroundService
{
 private readonly IHealthBasedLeaderElection _election;

 public HealthAwareLeaderService(ILeaderElectionFactory factory)
 {
 _election = factory.CreateHealthBasedElection("health-leader");
 }

 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
 {
 await _election.StartAsync(stoppingToken);

 while (!stoppingToken.IsCancellationRequested)
 {
 // Check system health
 var health = await MeasureHealthAsync();

 // Update election with health status
 await _election.UpdateHealthAsync(
 isHealthy: health.Score >= 0.75,
 metadata: health.Metadata,
 cancellationToken
 );

 if (_election.IsLeader)
 {
 // Do leader work
 await DoLeaderWorkAsync();
 }

 await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
 }

 await _election.StopAsync();
 }

 private async Task<(double Score, Dictionary<string, string> Metadata)> MeasureHealthAsync()
 {
 // Example health calculation
 var cpuUsage = 0.6; // 60%
 var memoryUsage = 0.7; // 70%
 var diskHealthy = true;

 var score = ((1.0 - cpuUsage) + (1.0 - memoryUsage) + (diskHealthy ? 1.0 : 0.0)) / 3.0;

 var metadata = new Dictionary<string, string>
 {
 ["cpu"] = $"{cpuUsage * 100:F0}%",
 ["memory"] = $"{memoryUsage * 100:F0}%",
 ["disk"] = diskHealthy ? "healthy" : "unhealthy"
 };

 return (score, metadata);
 }

 private async Task DoLeaderWorkAsync()
 {
 // Leader-only processing
 await Task.Delay(100);
 }
}

Thread Safety

All implementations of ILeaderElection and IHealthBasedLeaderElection must be thread-safe for concurrent access to properties (IsLeader, CurrentLeaderId) and event subscriptions.

Safe Usage:

// Safe: Multiple threads can check leadership
if (_election.IsLeader)
{
 // This is safe - leadership state is consistent
}

// Safe: Event subscriptions are thread-safe
_election.OnBecameLeader += Handler;

Testing

For unit and integration testing, use the InMemory provider which supports AOT and has zero external dependencies.

dotnet add package Excalibur.Dispatch.LeaderElection.InMemory

Test Example:

[Fact]
public async Task LeaderElection_ShouldElectSingleLeader()
{
 // Arrange
 var services = new ServiceCollection();
 services.AddInMemoryLeaderElection();
 var provider = services.BuildServiceProvider();

 var factory = provider.GetRequiredService<ILeaderElectionFactory>();
 var election1 = factory.CreateElection("test-resource");
 var election2 = factory.CreateElection("test-resource");

 // Act
 await election1.StartAsync();
 await election2.StartAsync();
 await Task.Delay(100); // Allow election to settle

 // Assert
 var leaders = new[] { election1.IsLeader, election2.IsLeader };
 Assert.Single(leaders.Where(x => x)); // Exactly one leader
}

See Also

  • - Production Consul-based leader election
  • - Cloud-native Kubernetes Lease coordination
  • - Testing and development

License

This package is part of the Excalibur.Dispatch framework and is licensed under multiple licenses. See the project root for license details.


Support

Product Versions Compatible and additional computed target framework versions.
.NET net10.0 net10.0 is compatible.  net10.0-android net10.0-android was computed.  net10.0-browser net10.0-browser was computed.  net10.0-ios net10.0-ios was computed.  net10.0-maccatalyst net10.0-maccatalyst was computed.  net10.0-macos net10.0-macos was computed.  net10.0-tvos net10.0-tvos was computed.  net10.0-windows net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (10)

Showing the top 5 NuGet packages that depend on Excalibur.Dispatch.LeaderElection.Abstractions:

Package Downloads
Excalibur.LeaderElection

Core leader election utilities for the Excalibur framework. Provides distributed coordination capabilities with pluggable providers (SqlServer, Redis, Consul, Kubernetes, InMemory).

Excalibur.Data.MongoDB

MongoDB database provider implementation for Excalibur data access layer.

Excalibur.LeaderElection.SqlServer

SQL Server implementation of leader election for the Excalibur framework. Uses sp_getapplock for distributed coordination with automatic failover and session-based locking.

Excalibur.LeaderElection.Redis

Redis implementation of leader election for the Excalibur framework. Uses SET NX with TTL for distributed coordination with automatic lease renewal.

Excalibur.LeaderElection.Consul

Consul-based leader election implementation for the Excalibur framework using session-based distributed locking.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0-alpha.208 131 6/11/2026
3.0.0-alpha.207 141 6/11/2026
3.0.0-alpha.205 139 6/10/2026
3.0.0-alpha.204 147 6/8/2026
3.0.0-alpha.203 137 6/8/2026
3.0.0-alpha.202 136 6/8/2026
3.0.0-alpha.201 140 6/8/2026
3.0.0-alpha.199 138 6/8/2026
3.0.0-alpha.198 150 5/28/2026
3.0.0-alpha.197 148 5/28/2026
3.0.0-alpha.194 148 5/20/2026
3.0.0-alpha.193 147 5/13/2026
3.0.0-alpha.192 141 5/13/2026
3.0.0-alpha.191 134 5/13/2026
3.0.0-alpha.189 136 5/12/2026
3.0.0-alpha.187 147 5/8/2026
3.0.0-alpha.185 141 5/7/2026
3.0.0-alpha.183 146 5/7/2026
3.0.0-alpha.182 153 5/6/2026
3.0.0-alpha.181 152 5/6/2026
Loading failed