public virtual long Lock(string key, int acquisitionTimeout, int lockTimeout, out long lockExpire, IRedisClient client)
{
lockExpire = 0;
// cannot lock on a null key
if (key == null)
return LOCK_NOT_ACQUIRED;
const int sleepIfLockSet = 200;
acquisitionTimeout *= 1000; //convert to ms
int tryCount = (acquisitionTimeout / sleepIfLockSet) + 1;
var ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
var newLockExpire = CalculateLockExpire(ts, lockTimeout);
var localClient = (RedisClient)client;
long wasSet = localClient.SetNX(key, BitConverter.GetBytes(newLockExpire));
int totalTime = 0;
while (wasSet == LOCK_NOT_ACQUIRED && totalTime < acquisitionTimeout)
{
int count = 0;
while (wasSet == 0 && count < tryCount && totalTime < acquisitionTimeout)
{
TaskUtils.Sleep(sleepIfLockSet);
totalTime += sleepIfLockSet;
ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
newLockExpire = CalculateLockExpire(ts, lockTimeout);
wasSet = localClient.SetNX(key, BitConverter.GetBytes(newLockExpire));
count++;
}
// acquired lock!
if (wasSet != LOCK_NOT_ACQUIRED) break;
// handle possibliity of crashed client still holding the lock
using (var pipe = localClient.CreatePipeline())
{
long lockValue=0;
pipe.QueueCommand(r => ((RedisNativeClient)r).Watch(key));
pipe.QueueCommand(r => ((RedisNativeClient)r).Get(key), x => lockValue = (x != null) ? BitConverter.ToInt64(x,0) : 0);
pipe.Flush();
// if lock value is 0 (key is empty), or expired, then we can try to acquire it
ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
if (lockValue < ts.TotalSeconds)
{
ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0));
newLockExpire = CalculateLockExpire(ts, lockTimeout);
using (var trans = localClient.CreateTransaction())
{
var expire = newLockExpire;
trans.QueueCommand(r => ((RedisNativeClient)r).Set(key, BitConverter.GetBytes(expire)));
if (trans.Commit())
wasSet = LOCK_RECOVERED; //recovered lock!
}
}
else
{
localClient.UnWatch();
}
}
if (wasSet != LOCK_NOT_ACQUIRED) break;
TaskUtils.Sleep(sleepIfLockSet);
totalTime += sleepIfLockSet;
}
if (wasSet != LOCK_NOT_ACQUIRED)
{
lockExpire = newLockExpire;
}
return wasSet;
}