Skip to content

Commit

Permalink
Merge pull request ServiceStack#97 from mikkelfish/master
Browse files Browse the repository at this point in the history
Fixed lock
  • Loading branch information
mythz committed Oct 22, 2012
2 parents 740fbdd + f2f273d commit 913d268
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 1 deletion.
30 changes: 29 additions & 1 deletion src/ServiceStack.Redis/RedisLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,35 @@ public RedisLock(RedisClient redisClient, string key, TimeSpan? timeOut)
this.key = key;

ExecExtensions.RetryUntilTrue(
() => redisClient.SetEntryIfNotExists(key, "lock " + DateTime.UtcNow.ToUnixTime()),
() =>
{
//This pattern is taken from the redis command for SETNX http://redis.io/commands/setnx
//Calculate a unix time for when the lock should expire
TimeSpan realSpan = timeOut ?? new TimeSpan(365, 0, 0, 0); //if nothing is passed in the timeout hold for a year
DateTime expireTime = DateTime.UtcNow.Add(realSpan);
string lockString = (expireTime.ToUnixTime() + 1).ToString();
//Try to set the lock, if it does not exist this will succeed and the lock is obtained
var nx = redisClient.SetEntryIfNotExists(key, lockString);
if (nx)
return true;
//If we've gotten here then a key for the lock is present. This could be because the lock is
//correctly acquired or it could be because a client that had acquired the lock crashed (or didn't release it properly).
//Therefore we need to get the value of the lock to see when it should expire
string lockExpireString = redisClient.Get<string>(key);
long lockExpireTime;
if (!long.TryParse(lockExpireString, out lockExpireTime))
return false;
//If the expire time is greater than the current time then we can't let the lock go yet
if (lockExpireTime > DateTime.UtcNow.ToUnixTime())
return false;
//If the expire time is less than the current time then it wasn't released properly and we can attempt to
//acquire the lock. This is done by setting the lock to our timeout string AND checking to make sure
//that what is returned is the old timeout string in order to account for a possible race condition.
return redisClient.GetAndSetEntry(key, lockString) == lockExpireString;
},
timeOut
);
}
Expand Down
14 changes: 14 additions & 0 deletions tests/ServiceStack.Redis.Tests/Examples/SimpleLocks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ public void Acquiring_lock_with_timeout()
}
}

[Test]
public void SimulateLockTimeout()
{
var redisClient = new RedisClient(TestConfig.SingleHost);
var waitFor = TimeSpan.FromMilliseconds(20);

var loc = redisClient.AcquireLock("testlock",waitFor);
Thread.Sleep(40); //should have lock expire
using(var newloc = redisClient.AcquireLock("testlock", waitFor))
{

}
}

}


Expand Down

0 comments on commit 913d268

Please sign in to comment.