You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add distributed counting semaphore with multi-provider support
Implement a distributed counting semaphore abstraction (ISemaphoreService /
ISemaphoreProvider) mirroring the existing leader-election design, with
providers for PostgreSQL, SQL Server, Redis, Azure Blob Storage, Consul,
ZooKeeper, FileSystem, and InMemory.
Key design decisions and fixes included in this commit:
- AutoStart=false defers acquisition to the caller; StartAsync drives
acquisition when true; ExecuteAsync is a no-op stub (BackgroundService
requirement)
- GetCurrentCountAsync filters by heartbeat age (slotTimeout) so expired
holders are excluded from the live count across all providers
- PostgreSQL uses pg_advisory_xact_lock(hashtext(semaphoreName)) under
ReadCommitted instead of SERIALIZABLE + FOR UPDATE, which deadlocked on
empty tables
- SQL Server uses sp_getapplock(@resource, 'Exclusive', 'Transaction') under
ReadCommitted instead of SERIALIZABLE + range locks, which caused deadlocks
when the table was empty
- Consul and ZooKeeper apply a post-acquisition count recheck to close the
TOCTOU window between the count read and the slot creation
- FileSystem provider validates canonical paths to prevent directory traversal
via semaphoreName or holderId
- Schema and table names are validated against ^[a-zA-Z_][a-zA-Z0-9_]*$
(max 128 chars) before interpolation into SQL to prevent injection
- InMemory provider replaced ConcurrentDictionary with plain Dictionary
guarded by a single lock, removing redundant thread-safety layering
- SemaphoreAcquisition.Dispose() delegates to DisposeAsync() to avoid
sync-context deadlocks from direct GetAwaiter().GetResult() on the task
- SemaphoreService.DisposeAsync() awaits broadcastTask before completing
- UpdateStatus() is a plain void method; the previous async overload only
awaited Task.CompletedTask and accepted an unused CancellationToken
- HeartbeatTimeout (not the now-removed SlotTimeout) is passed as the
expiry window to all provider calls
- IAsyncDisposable removed from SemaphoreService class declaration
(already satisfied via ISemaphoreService)
- CurrentCount decrements are guarded with Math.Max(0, ...) to prevent
negative counts on spurious releases
- Integration tests: each test instance uses a unique table name and
semaphore name (Guid-per-instance); DisposeAsync drops the table;
ConcurrentAcquire asserts successCount == maxCount and cross-checks
via GetCurrentCountAsync
Fixes#100
A comprehensive .NET framework for implementing the Leader Election pattern with support for multiple providers including Azure Blob Storage, SQL Server, Redis, File System, In-Memory, Consul, and ZooKeeper.
14
+
A comprehensive .NET framework for implementing **Leader Election** and **Distributed Semaphores** patterns with support for multiple providers including Azure Blob Storage, SQL Server, PostgreSQL, Redis, File System, In-Memory, Consul, and ZooKeeper.
15
15
16
16
## Requirements
17
17
@@ -37,32 +37,25 @@ A comprehensive .NET framework for implementing the Leader Election pattern with
-**Heartbeat Monitoring**: Automatic leader health monitoring and failover
78
+
-**Leader Election**: First-to-acquire becomes leader strategy with automatic failover
79
+
-**Distributed Semaphores**: Control concurrent access with configurable slot limits
80
+
-**Heartbeat Monitoring**: Automatic health monitoring and failover
87
81
-**Resilient Design**: Handles transient and persistent failures gracefully
88
82
-**Thread-Safe**: All operations are thread-safe
89
83
-**Configurable**: Extensive configuration options for timeouts, retry policies, etc.
@@ -602,6 +596,181 @@ Use `GetLeadershipChangesAsync()` to subscribe to these events and optionally fi
602
596
-**LockTimeout**: Maximum time to hold a lock during election
603
597
-**AutoStart**: Whether to automatically start the election process
604
598
599
+
## Distributed Semaphores
600
+
601
+
Distributed semaphores allow you to control concurrent access to a shared resource across multiple instances. Unlike leader election (which allows only one holder), semaphores allow a configurable number of concurrent holders.
602
+
603
+
### Use Cases
604
+
605
+
-**Rate Limiting**: Limit concurrent API calls to an external service
606
+
-**Resource Pooling**: Control access to a limited pool of resources (database connections, licenses, etc.)
607
+
-**Throttling**: Limit concurrent processing of expensive operations
608
+
-**Capacity Management**: Ensure only N instances process work simultaneously
609
+
610
+
### Quick Start
611
+
612
+
```csharp
613
+
usingMultiLock;
614
+
usingMultiLock.InMemory;
615
+
616
+
varbuilder=WebApplication.CreateBuilder(args);
617
+
618
+
// Add semaphore with In-Memory provider (max 5 concurrent holders)
0 commit comments