7

I'm looking for recommendations for a locking mechanism that will work across multiple machines. In my case I basically just want to be able to start a service on 2 machines and have one block until the other finishes as a simple way to insure redundancy in case a service machine goes down.

Sort of along the same lines as Distributed Lock Service but specifically looking for projects that people have successfully integrated with .NET.

Community
  • 1
  • 1
hackerhasid
  • 11,699
  • 10
  • 42
  • 60
  • Has anyone found one of these yet? I am also looking for one. The Paxos and perhaps algos like the Bully Algorithm might work I guess? Would like to see a .NET implementation of something like this. – freddy smith Jan 16 '11 at 00:11
  • Have a look at http://blog.kristandyson.com/2011/01/distributed-lock.html – krisdyson Jan 25 '11 at 17:38

3 Answers3

15

We use SqlServer's application lock functionality for distributed locking. This is especially convenient if SqlServer is already a part of your stack.

To make this easier to work with from .NET, I created a NuGet package which makes it easy to use this functionality. The library also supports other backends such as Postgres, Azure blob storage, and Redis.

With this library, the code looks like:

var @lock = new SqlDistributedLock("my_lock_name", connectionString);
using (@lock.Acquire())
{
   // critical region
}

Because the underlying SqlServer functionality is very flexible, there are also overloads supporting TryAcquire semantics, timeouts, and async locking.

ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152
  • @PeterKnaggs I'm not familiar with that setup. If the servers are truly separate and you're hitting one at random, then that won't work. If this is some built-in SQLServer functionality that can synchronize things like lock holding between the servers then it might work? Since acting as a distributed lock provider doesn't take many resources, you can always create a separate database that isn't load balanced to act as your locking database. – ChaseMedallion Nov 19 '16 at 00:19
  • for kicks could you add the main code here to... as to how you would do it with out the nuget package. – Seabizkit Jan 08 '20 at 06:37
  • is the idea that the resource which your locking is the name itself, so 2 servers... runnning jobs... ensure only one does it.... use same random resource name. Even tho "lockname" does not actually exist as a thing on sql. I thought it would work on a table resource or object resources but getting the impression that its just the name itself, is that correct? – Seabizkit Jan 08 '20 at 06:42
  • @seabizkit that is correct. The sql server application lock functionality allows you to operate on arbitrary named locks (which are created on demand behind the scenes). This can often be cleaner than locking on tables/rows. – ChaseMedallion Jan 10 '20 at 09:48
  • real sample using 2 programs? Program A execute `using (@lock.Acquire())` and Program B execute `using (@lock.Acquire())` at the same time, both. – Kiquenet Jul 22 '20 at 13:01
  • Warning : this approach exhausts connection pool – Alex from Jitbit Feb 27 '21 at 23:04
  • @Alex it's true that sp_getapplock does hold open a connection while it runs. You can reduce the impact of this by using TryAcquire instead of Acquire (if possible). Furthermore, more recent versions of the library multiplex SqlConnections under the hood so that often multiple locks end up being held by one connection. Finally, another approach is to layer an in-memory lock on top of the distributed one if you have multiple competing acquirers on the same machine. – ChaseMedallion Mar 01 '21 at 13:25
  • @ChaseMedallion I used this DistributedLock you created to execute a task by only one node in a pool of nodes and works fine. But I came up with a situation that after acquiring the lock by one node if the SQL connection dropped (for that node only) for a few seconds another node could go and acquire the lock (as the other nodes are also waiting to acquire the lock). Is this can be handled? what is the best practice of using this in a leader election kind of a mechanism to select a leader from a pool of nodes? – Nlr Dec 09 '21 at 02:37
  • 1
    @Nlr with SQL Server there isn't a great way to do this perfectly. You can observe the loss of connectivity with the lock handle's HandleLostToken and recover, but there will still be a blip. If you want to handle connection loss, I'd recommend using the DistributedLock library with a different underlying provider (e. g. Redis or ZooKeeper) in place of SqlServer. Some of these use a "lease" mechanism that stops other threads from picking up an abandoned lock until a (configurable) timeout elapses, which may be helpful in your case. – ChaseMedallion Dec 10 '21 at 21:53
  • @ChaseMedallion thank you very much for the information. sadly we have some difficulty (with our client) using other providers. So prefer to go with SQL server. as you said can't we lease the lock for a configurable amount of time in SQL server version? so that we can give enough time to restore the connections or we can be certain that the service is not working – Nlr Dec 12 '21 at 17:38
  • 1
    @Nlr the underlying SQL API doesn't give you this flexibility. If you're stuck with SQL, I'd first suggest looking at configuration options for how quickly SQL itself gives up on connections when things go wrong. Another option is to build a leasing mechanism on top of it though; e. g. once you claim a lock check another table to see whether someone has an active lease (the table could be keyed on lock name and have one other column for the expiry date; upon taking a lock you'd insert yourself into the table with some expiry and upon releasing the lock (finally block!) you'd remove the row. – ChaseMedallion Dec 14 '21 at 01:01
  • @ChaseMedallion thanks. I also tried the table with the expiry column option and it seems fine for now. – Nlr Dec 14 '21 at 10:14
3

If you are using AppFabric for Windows Server, you can use this DataCache extension. You can also use redis locks with ServiceStack's redis client.

Both are .NET implementations but require a server component. I've been tinkering with a PeerChannel implementation of a distributed lock that uses peer to peer communication and doesn't require any server infrastructure. Let me know if this is something you would be interested in.

Guy Godin
  • 448
  • 5
  • 16
0

You could use Pessimistic Locking for this specific use case using NCache. Optimistic locking is beneficial for scenarios when your dealing with read intensive applications

NCache helps you achieve it. http://blogs.alachisoft.com/ncache/distributed-locking/

// Instance of the object used to lock and unlock cache items in NCache
LockHandle lockHandle = new LockHandle();

// Specify time span of 10 sec for which the item remains locked
// NCache will auto release the lock after 10 seconds.
TimeSpan lockSpan = new TimeSpan(0, 0, 10); 

try
{
    // If item fetch is successful, lockHandle object will be populated
    // The lockHandle object will be used to unlock the cache item
    // acquireLock should be true if you want to acquire to the lock.
    // If item does not exists, account will be null
    BankAccount account = cache.Get(key, lockSpan, 
    ref lockHandle, acquireLock) as BankAccount;
    // Lock acquired otherwise it will throw LockingException exception

    if(account != null &&; account.IsActive)
    {
        // Withdraw money or Deposit
        account.Balance += withdrawAmount;
        // account.Balance -= depositAmount;

        // Insert the data in the cache and release the lock simultaneously 
        // LockHandle initially used to lock the item must be provided
        // releaseLock should be true to release the lock, otherwise false
        cache.Insert("Key", account, lockHandle, releaseLock); 
    }
    else
    {
        // Either does not exist or unable to cast
        // Explicitly release the lock in case of errors
        cache.Unlock("Key", lockHandle);
    } 
}
catch(LockingException lockException)
{
    // Lock couldn't be acquired
    // Wait and try again
}
Basit Anwer
  • 6,742
  • 7
  • 45
  • 88