This page shows how to use the [ServiceStack's C# Redis Client](~/redis-client/redis-client) to take advantage of Redis's fast atomic server operations to enable high-performance *distributed locks* that can span across multiple app servers.

# Achieving High Performance, Distributed Locking with Redis 

When you have a high-performance, scalable network data structure server like Redis accessible to your back end systems, a whole range of technical possibilities open up that we're previously difficult to achieve. Something like multi-server-wide application-level locks were previously only achievable using dedicated, centralized infrastructure and the crafting of some carefully custom concurrent programming logic. 

With Redis this becomes a trivial task as you get simplified access to rich atomic server operations that complete within a fraction of a millisecond. So the same normally CPU-intensive load generated by distributed locking when using a remote file system or RDBMS is barely noticeable on a Redis server.

[ServiceStack's C# Redis Client](~/redis-client/redis-client) takes advantage of the convenience and safety offered by .NET's IDisposable interface and Redis's 
[SetNX operation](http://code.google.com/p/redis/wiki/SetnxCommand) to provide a simple API to implement your own *custom distributed locks*, ensuring at all times that only 
1 client at a time can execute the protected logic. While one of the Redis clients obtains the lock, the other clients enter into an 'exponential retry back-off multiplier state' 
continually retrying to obtain the lock at random intervals until they are finally successful.

## Simple API Usage 
The locking functionality is available on the [IRedisClient](IRedisClient) and [IRedisTypedClient](IRedisTypedClient) interfaces, the relevant portion of which is displayed below.

	public interface IRedisClient
	{
	    ...
	    IDisposable AcquireLock(string key);
	    IDisposable AcquireLock(string key, TimeSpan timeOut);
	}


_NOTE: The above implementation does not include the ability to auto-recover from a crashed client, power or network failure, so under rare conditions it is possible for all clients to be deadlocked indefinitely waiting on a lock that is never released. In these cases it's wise to supply a `TimeOut` or manually recover from 'zombie locks' by clearing them all on server restarts, etc._

Below are a couple examples showing how to use the API in some typical usage scenarios. The full runnable source code of the following examples are 
[available here](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/tests/ServiceStack.Redis.Tests/Examples/SimpleLocks.cs).

## Example: Multiple clients acquiring the same lock 

The example below shows the behavior of running 5 concurrent clients trying to acquire the same lock at the same time.
An artificial delay is added inside the lock to simulate a cpu-intensive workload.


	//The number of concurrent clients to run
	const int noOfClients = 5;
	var asyncResults = new List<IAsyncResult>(noOfClients);
	for (var i = 1; i <= noOfClients; i++)
	{
		var clientNo = i;
		var actionFn = (Action)delegate
		{
			var redisClient = new RedisClient(TestConfig.SingleHost);
			using (redisClient.AcquireLock("testlock"))
			{
				Console.WriteLine("client {0} acquired lock", clientNo);
				var counter = redisClient.Get<int>("atomic-counter");

				//Add an artificial delay to demonstrate locking behaviour
				Thread.Sleep(100);

				redisClient.Set("atomic-counter", counter + 1);
				Console.WriteLine("client {0} released lock", clientNo);
			}
		};

		//Asynchronously invoke the above delegate in a background thread
		asyncResults.Add(actionFn.BeginInvoke(null, null));
	}

	//Wait at most 1 second for all the threads to complete
	asyncResults.WaitAll(TimeSpan.FromSeconds(1));

	//Print out the 'atomic-counter' result
	using (var redisClient = new RedisClient(TestConfig.SingleHost))
	{
		var counter = redisClient.Get<int>("atomic-counter");
		Console.WriteLine("atomic-counter after 1sec: {0}", counter);
	}

	/*Output:
	client 1 acquired lock
	client 1 released lock
	client 3 acquired lock
	client 3 released lock
	client 4 acquired lock
	client 4 released lock
	client 5 acquired lock
	client 5 released lock
	client 2 acquired lock
	client 2 released lock
	atomic-counter after 1sec: 5
	*/


When you acquire a lock without specifying a `TimeOut` as seen in the above example, each waiting client goes into an indefinite loop retrying to acquire the lock until its successful. 
If by some chance you had some rogue code not following convention and implementing logic within the disposable scope or worse short circuiting execution using `Thread.Abort`, 
you could potentially run into a deadlock situation. This is why it is generally a good idea to specify a `TimeOut` whenever you make a blocking call. So like all good blocking API's 
the client lets you specify an optional `TimeOut` parameter as seen in the following example:

## Example: Acquiring a lock with Time Out 

	var redisClient = new RedisClient(TestConfig.SingleHost);

	//Initialize and set counter to '1'
	redisClient.Increment("atomic-counter"); 

	//Acquire lock and never release it
	redisClient.AcquireLock("testlock");

	var waitFor = TimeSpan.FromSeconds(2);
	var now = DateTime.Now;

	try
	{
		using (var newClient = new RedisClient(TestConfig.SingleHost))
		{
			//Attempt to acquire a lock with a 2 second timeout
			using (newClient.AcquireLock("testlock", waitFor))
			{
				//If lock was acquired this would be incremented to '2'
				redisClient.Increment("atomic-counter"); 
			}
		}
	}
	catch (TimeoutException tex)
	{
		var timeTaken = DateTime.Now - now;
		Console.WriteLine("After '{0}', Received TimeoutException: '{1}'", timeTaken, tex.Message);

		var counter = redisClient.Get<int>("atomic-counter");
		Console.WriteLine("atomic-counter remains at '{0}'", counter);
	}
	/*Output:
	After '00:00:02.3321334', Received TimeoutException: 'Exceeded timeout of 00:00:02'
	atomic-counter remains at '1'
	*/
