Commit 54d69d2f authored by Marc Gravell's avatar Marc Gravell

SCRIPT eval (when failing due to NOSCRIPT) can retry when using the sync API,...

SCRIPT eval (when failing due to NOSCRIPT) can retry when using the sync API, since there can be no competing commands (but not via the async API)
parent 7710017f
......@@ -30,7 +30,9 @@ public void TestBasicScripting()
}
[Test]
public void CheckLoads()
[TestCase(true)]
[TestCase(false)]
public void CheckLoads(bool async)
{
using (var conn0 = Create(allowAdmin: true))
using (var conn1 = Create(allowAdmin: true))
......@@ -57,14 +59,25 @@ public void CheckLoads()
server.ScriptFlush();
Assert.IsFalse(server.ScriptExists(script));
db.Ping();
// now: fails the first time
try
if (async)
{
Assert.IsTrue((bool)db.ScriptEvaluate(script));
Assert.Fail();
} catch(RedisServerException ex)
// now: fails the first time
try
{
db.Wait(db.ScriptEvaluateAsync(script));
Assert.Fail();
}
catch(AggregateException ex)
{
Assert.AreEqual(1, ex.InnerExceptions.Count);
Assert.IsInstanceOf<RedisServerException>(ex.InnerExceptions[0]);
Assert.AreEqual("NOSCRIPT No matching script. Please use EVAL.", ex.InnerExceptions[0].Message);
}
} else
{
Assert.IsTrue(ex.Message == "NOSCRIPT No matching script. Please use EVAL.");
// just works; magic
Assert.IsTrue((bool)db.ScriptEvaluate(script));
}
// but gets marked as unloaded, so we can use it again...
......
......@@ -54,5 +54,7 @@ public enum CommandFlags
NoRedirect = 64,
// 128: used for "internal call"; never user-specified, so not visible on the public API
// 256: used for "retry"; never user-specified, so not visible on the public API
}
}
......@@ -56,7 +56,7 @@ public sealed class RedisServerException : RedisException
internal RedisServerException(string message) : base(message) { }
}
abstract class Message : ICompletable
{
......@@ -66,11 +66,13 @@ abstract class Message : ICompletable
internal const CommandFlags InternalCallFlag = (CommandFlags)128;
protected RedisCommand command;
private const CommandFlags AskingFlag = (CommandFlags)32;
private const CommandFlags AskingFlag = (CommandFlags)32,
ScriptUnavailableFlag = (CommandFlags)256;
const CommandFlags MaskMasterServerPreference = CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave;
private const CommandFlags UserSelectableFlags
= CommandFlags.None | CommandFlags.DemandMaster | CommandFlags.DemandSlave
= CommandFlags.None | CommandFlags.DemandMaster | CommandFlags.DemandSlave
| CommandFlags.PreferMaster | CommandFlags.PreferSlave
| CommandFlags.HighPriority | CommandFlags.FireAndForget | CommandFlags.NoRedirect;
......@@ -162,6 +164,15 @@ public bool IsAsking
get { return (flags & AskingFlag) != 0; }
}
internal bool IsScriptUnavailable
{
get { return (flags & ScriptUnavailableFlag) != 0; }
}
internal void SetScriptUnavailable()
{
flags |= ScriptUnavailableFlag;
}
public bool IsFireAndForget
{
get { return (flags & CommandFlags.FireAndForget) != 0; }
......
......@@ -759,7 +759,15 @@ public Task<long> PublishAsync(RedisChannel channel, RedisValue message, Command
public RedisResult ScriptEvaluate(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None)
{
var msg = new ScriptEvalMessage(Db, flags, RedisCommand.EVAL, script, keys ?? RedisKey.EmptyArray, values ?? RedisValue.EmptyArray);
return ExecuteSync(msg, ResultProcessor.ScriptResult);
try
{
return ExecuteSync(msg, ResultProcessor.ScriptResult);
} catch(RedisServerException)
{
// could be a NOSCRIPT; for a sync call, we can re-issue that without problem
if(msg.IsScriptUnavailable) return ExecuteSync(msg, ResultProcessor.ScriptResult);
throw;
}
}
public Task<RedisResult> ScriptEvaluateAsync(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None)
......
......@@ -985,6 +985,7 @@ public override bool SetResult(PhysicalConnection connection, Message message, R
if (result.Type == ResultType.Error && result.AssertStarts(NOSCRIPT))
{ // scripts are not flushed individually, so assume the entire script cache is toast ("SCRIPT FLUSH")
connection.Bridge.ServerEndPoint.FlushScripts();
message.SetScriptUnavailable();
}
// and apply usual processing for the rest
return base.SetResult(connection, message, result);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment