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() ...@@ -30,7 +30,9 @@ public void TestBasicScripting()
} }
[Test] [Test]
public void CheckLoads() [TestCase(true)]
[TestCase(false)]
public void CheckLoads(bool async)
{ {
using (var conn0 = Create(allowAdmin: true)) using (var conn0 = Create(allowAdmin: true))
using (var conn1 = Create(allowAdmin: true)) using (var conn1 = Create(allowAdmin: true))
...@@ -57,14 +59,25 @@ public void CheckLoads() ...@@ -57,14 +59,25 @@ public void CheckLoads()
server.ScriptFlush(); server.ScriptFlush();
Assert.IsFalse(server.ScriptExists(script)); Assert.IsFalse(server.ScriptExists(script));
db.Ping(); db.Ping();
if (async)
{
// now: fails the first time // now: fails the first time
try try
{ {
Assert.IsTrue((bool)db.ScriptEvaluate(script)); db.Wait(db.ScriptEvaluateAsync(script));
Assert.Fail(); Assert.Fail();
} catch(RedisServerException ex) }
catch(AggregateException ex)
{ {
Assert.IsTrue(ex.Message == "NOSCRIPT No matching script. Please use EVAL."); 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
{
// just works; magic
Assert.IsTrue((bool)db.ScriptEvaluate(script));
} }
// but gets marked as unloaded, so we can use it again... // but gets marked as unloaded, so we can use it again...
......
...@@ -54,5 +54,7 @@ public enum CommandFlags ...@@ -54,5 +54,7 @@ public enum CommandFlags
NoRedirect = 64, NoRedirect = 64,
// 128: used for "internal call"; never user-specified, so not visible on the public API // 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
} }
} }
...@@ -66,7 +66,9 @@ abstract class Message : ICompletable ...@@ -66,7 +66,9 @@ abstract class Message : ICompletable
internal const CommandFlags InternalCallFlag = (CommandFlags)128; internal const CommandFlags InternalCallFlag = (CommandFlags)128;
protected RedisCommand command; 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; const CommandFlags MaskMasterServerPreference = CommandFlags.DemandMaster | CommandFlags.DemandSlave | CommandFlags.PreferMaster | CommandFlags.PreferSlave;
private const CommandFlags UserSelectableFlags private const CommandFlags UserSelectableFlags
...@@ -162,6 +164,15 @@ public bool IsAsking ...@@ -162,6 +164,15 @@ public bool IsAsking
get { return (flags & AskingFlag) != 0; } get { return (flags & AskingFlag) != 0; }
} }
internal bool IsScriptUnavailable
{
get { return (flags & ScriptUnavailableFlag) != 0; }
}
internal void SetScriptUnavailable()
{
flags |= ScriptUnavailableFlag;
}
public bool IsFireAndForget public bool IsFireAndForget
{ {
get { return (flags & CommandFlags.FireAndForget) != 0; } get { return (flags & CommandFlags.FireAndForget) != 0; }
......
...@@ -759,7 +759,15 @@ public Task<long> PublishAsync(RedisChannel channel, RedisValue message, Command ...@@ -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) 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); var msg = new ScriptEvalMessage(Db, flags, RedisCommand.EVAL, script, keys ?? RedisKey.EmptyArray, values ?? RedisValue.EmptyArray);
try
{
return ExecuteSync(msg, ResultProcessor.ScriptResult); 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) 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 ...@@ -985,6 +985,7 @@ public override bool SetResult(PhysicalConnection connection, Message message, R
if (result.Type == ResultType.Error && result.AssertStarts(NOSCRIPT)) if (result.Type == ResultType.Error && result.AssertStarts(NOSCRIPT))
{ // scripts are not flushed individually, so assume the entire script cache is toast ("SCRIPT FLUSH") { // scripts are not flushed individually, so assume the entire script cache is toast ("SCRIPT FLUSH")
connection.Bridge.ServerEndPoint.FlushScripts(); connection.Bridge.ServerEndPoint.FlushScripts();
message.SetScriptUnavailable();
} }
// and apply usual processing for the rest // and apply usual processing for the rest
return base.SetResult(connection, message, result); 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