void IMultiMessage.Execute(RedisConnectionBase connection, ref int currentDb)
{
// note: composite command in a tight time-frame! most user-facing code won't look this bad; this is just ugly
// because it is infrastructure code; tough!
var existsResult = new MessageResultBoolean();
var existsMessage = RedisMessage.Create(Db, RedisLiteral.EXISTS, key); // watch the key; we want changes to cause abort
existsMessage.SetMessageResult(existsResult);
connection.WriteMessage(ref currentDb, existsMessage, null);
connection.Flush(true); // make sure it goes to the server! if we wait for it, and it is stuck
// in the buffer, we've deadlocked ourself
// now, we need to issue the rest of the composite command immediately to avoid multiplex issues,
// so we must wait on the EXISTS, and act accordingly
bool exists = connection.Wait(existsResult.Task);
if (exists)
{
// obviously locked; just unwatch and return false
connection.WriteMessage(ref currentDb, RedisMessage.Create(Db, RedisLiteral.UNWATCH), null);
base.Complete(RedisResult.Integer(0));
}
else
{
// isn't obviously locked; try a multi/setnx/expire/exec; if someone else has touched the key, this will fail and
// we'll return false; otherwise, we get a lock with an expiry set
connection.WriteMessage(ref currentDb, RedisMessage.Create(-1, RedisLiteral.MULTI), null);
var pending = new List<QueuedMessage>();
connection.WriteMessage(ref currentDb, RedisMessage.Create(Db, RedisLiteral.SETNX, key, value), pending);
connection.WriteMessage(ref currentDb, RedisMessage.Create(Db, RedisLiteral.EXPIRE, key, timeout), pending);
var execResult = new MessageLockResult();
var exec = RedisMessage.Create(-1, RedisLiteral.EXEC).Critical();
exec.SetMessageResult(execResult);
execResult.Task.ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
base.Complete(RedisResult.Integer(task.Result ? 1 : 0));
}
else
{
base.Complete(RedisResult.Error(GetErrorMessage(task.Exception)));
}
});
connection.WriteMessage(ref currentDb, exec, null);
}
}