DistributedLock

repository·master·Indexed 25 days ago

https://github.com/madelson/distributedlock

A .NET library providing distributed mutexes, reader-writer locks, and semaphores. It supports multiple backends including SQL Server, PostgreSQL, MySQL, Oracle, Redis, MongoDB, Apache ZooKeeper, Azure blobs, the file system, and Windows global WaitHandles. The library offers synchronous and asynchronous acquisition methods, timeout and cancellation support, and an IDistributedLockProvider for dependency injection.

Tokens
12.5K
Snippets
25
Records
82
Agent score
82%

What's inside DistributedLock

  1. Use DistributedLock.WaitHandles for local process coordination

    master

    The DistributedLock.WaitHandles package provides distributed locks and semaphores based on global Windows WaitHandles.

    Important Constraints:

    • Windows Only: This library only works on Windows operating systems.
    • Local Coordination: Because it relies on global Windows WaitHandles, it is designed to coordinate between different processes running on the same machine, not across different machines in a network.
  2. How RedLock and Semaphores work in Redis

    master

    RedLock Algorithm

    RedisDistributedLock and RedisDistributedReaderWriterLock use the RedLock algorithm. You can increase robustness by providing a set of databases instead of a single one. A lock is considered acquired only if it is successfully acquired on more than half of the provided databases.

    Semaphores

    RedisDistributedSemaphore is based on a counting semaphore algorithm but does not support multiple databases because the RedLock majority-rule logic cannot guarantee semaphore invariants across multiple nodes. If you use a RedisDistributedSynchronizationProvider initialized with multiple databases to call CreateSemaphore(), it will default to using only the first database in the list.

    Auto-extension

    Both RedLock and the semaphore algorithm claim locks for a specific period. The library automatically extends the lock hold in the background to ensure the lock is not released until the returned handle is disposed.

  3. How distributed synchronization primitives work

    master

    DistributedLock provides three main types of synchronization primitives to coordinate access across multiple applications or machines:

    1. Locks: Provide exclusive access to a region of code (only one holder at a time).
    2. Reader-writer locks: A lock with multiple levels of access. It can be held concurrently by any number of "readers" or by a single "writer".
    3. Semaphores: Similar to a lock, but can be held by up to $N$ users concurrently instead of just one.

    Note: While all implementations support locks, reader-writer locks and semaphores are only supported by certain implementations. Check the specific implementation documentation for details.

  4. Use distributed semaphores to throttle resource access

    master

    A distributed semaphore allows a fixed number of processes or threads to access a resource simultaneously. This is useful for throttling access to resources like databases or email servers to prevent overloading. Unlike a mutex, which allows only one holder, a semaphore caps the level of concurrency to a specified maxCount.

    // uses the Redis implementation; others are available
    var semaphore = new RedisDistributedSemaphore("ComputeDatabase", maxCount: 5, database: database);
    using (semaphore.Acquire())
    {
        // only 5 callers can be inside this block concurrently
        UseComputeDatabase();
    }
  5. Configure connection methods for Postgres locks

    master

    When constructing a lock, you can specify how to connect to the database using one of the following:

    • connectionString: Preferred method. Allows the library to efficiently multiplex connections.
    • DbDataSource: Provides a source for connections.
    • IDbConnection: Uses an existing connection. Warning: Since IDbConnection objects are not thread-safe, lock objects constructed with them can only be used by one thread at a time.
  6. Isolate Redis lock keys using prefixes

    master
    As of version 1.0.1, Redis-based primitives support IDatabase.WithKeyPrefix(keyPrefix). When using a prefixed database, all underlying keys created by the lock will implicitly include that prefix. This allows multiple locks with the same name to exist on the same Redis instance without colliding, provided they use different prefixes.
  7. Connection management for Oracle locks

    master

    You can construct locks using either a connectionString or an IDbConnection:

    • Using connectionString (Recommended): Allows the library to efficiently multiplex connections and avoids disrupting the locking process.
    • Using IDbConnection: Since IDbConnection objects are not thread-safe, lock objects constructed with them can only be used by one thread at a time.
  8. Constructing SQL-based locks with connections or transactions

    master

    SQL-based locks can be initialized using a connectionString, an IDbConnection, or an IDbTransaction:

    • Connection String (Recommended): Using a connectionString allows the library to efficiently multiplex connections and avoids risks associated with external connection management.
    • IDbConnection: The lock is scoped to the provided connection.
    • IDbTransaction: The lock is scoped to the provided transaction.

    CRITICAL: Because IDbConnection and IDbTransaction objects are not thread-safe, any lock objects constructed with them can only be used by one thread at a time.

  9. How reader-writer locks work in DistributedLock

    master

    A reader-writer lock allows for either multiple readers OR one writer to hold the lock at any given time. This is ideal for protecting resources that are safe for concurrent access (like reading from a cache) but require exclusive access when modifications are being made.

    Key behaviors:

    • Concurrency: Multiple readers can hold the lock simultaneously.
    • Exclusivity: Only one writer can hold the lock, and a writer blocks all readers.
    • Writer Precedence: Writers are given precedence over readers to prevent a continuous stream of readers from starving a queued writer.
  10. Configure PostgresAdvisoryLockKey

    master

    PostgreSQL advisory locks are based on either a single 64-bit integer or a pair of 32-bit integers. The PostgresAdvisoryLockKey object handles the mapping from various input formats to these underlying types:

    • Single long: A single 64-bit integer.
    • Pair of int values: Two 32-bit integers.
    • 16-character hex string: Parsed as a long (e.g., "00000003ffffffff").
    • Comma-separated hex strings: A pair of 8-character hex strings parsed as a pair of ints (e.g., "00000003,ffffffff").
    • ASCII string (0-9): Mapped to a long via a custom scheme.
    • Arbitrary string: If allowHashing is set to true, the string is hashed to a long. Hashing is used as a fallback if other interpretation methods fail.
  11. Handle case-sensitivity and exact lock names in MySQL

    master

    MySQL's GET_LOCK is natively case-insensitive, but the DistributedLock library is case-sensitive. To handle this:

    1. The library automatically transforms or hashes lock names containing uppercase characters, empty names, or names that are too long.
    2. If you need to coordinate with external code using GET_LOCK directly, use a lowercase name.
    3. Pass exactName: true when constructing the lock instance. In exactName mode, an invalid name will throw an exception instead of being silently transformed.