summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOran Agra <oran@redislabs.com>2020-03-23 20:13:52 +0200
committerOran Agra <oran@redislabs.com>2020-03-23 20:45:32 +0200
commitec007559ff703d27916f54ad0a41d154a88d9ac4 (patch)
tree8e3b891b592322fcdd568b79bb0b56138ab88229
parentc80d81c80aa4421f48d9e3fd005ca82546d82239 (diff)
downloadredis-ec007559ff703d27916f54ad0a41d154a88d9ac4.tar.gz
MULTI/EXEC during LUA script timeout are messed up
Redis refusing to run MULTI or EXEC during script timeout may cause partial transactions to run. 1) if the client sends MULTI+commands+EXEC in pipeline without waiting for response, but these arrive to the shards partially while there's a busy script, and partially after it eventually finishes: we'll end up running only part of the transaction (since multi was ignored, and exec would fail). 2) similar to the above if EXEC arrives during busy script, it'll be ignored and the client state remains in a transaction. the 3rd test which i added for a case where MULTI and EXEC are ok, and only the body arrives during busy script was already handled correctly since processCommand calls flagTransaction
-rw-r--r--src/server.c1
-rw-r--r--tests/unit/multi.tcl72
2 files changed, 73 insertions, 0 deletions
diff --git a/src/server.c b/src/server.c
index ddc90b3dd..5a54a3abb 100644
--- a/src/server.c
+++ b/src/server.c
@@ -3553,6 +3553,7 @@ int processCommand(client *c) {
c->cmd->proc != authCommand &&
c->cmd->proc != helloCommand &&
c->cmd->proc != replconfCommand &&
+ c->cmd->proc != multiCommand && c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
!(c->cmd->proc == shutdownCommand &&
c->argc == 2 &&
tolower(((char*)c->argv[1]->ptr)[0]) == 'n') &&
diff --git a/tests/unit/multi.tcl b/tests/unit/multi.tcl
index 9fcef71d6..55f18bec8 100644
--- a/tests/unit/multi.tcl
+++ b/tests/unit/multi.tcl
@@ -320,4 +320,76 @@ start_server {tags {"multi"}} {
$rd close
r ping
} {PONG}
+
+ test {MULTI and script timeout} {
+ # check that if MULTI arrives during timeout, it is either refused, or
+ # allowed to pass, and we don't end up executing half of the transaction
+ set rd1 [redis_deferring_client]
+ set rd2 [redis_deferring_client]
+ r config set lua-time-limit 10
+ r set xx 1
+ $rd1 eval {while true do end} 0
+ after 200
+ catch { $rd2 multi; $rd2 read } e
+ catch { $rd2 incr xx; $rd2 read } e
+ r script kill
+ after 200 ; # Give some time to Lua to call the hook again...
+ catch { $rd2 incr xx; $rd2 read } e
+ catch { $rd2 exec; $rd2 read } e
+ set xx [r get xx]
+ # make sure that either the whole transcation passed or none of it (we actually expect none)
+ assert { $xx == 1 || $xx == 3}
+ # check that the connection is no longer in multi state
+ $rd2 ping asdf
+ set pong [$rd2 read]
+ assert_equal $pong "asdf"
+ }
+
+ test {EXEC and script timeout} {
+ # check that if EXEC arrives during timeout, we don't end up executing
+ # half of the transaction, and also that we exit the multi state
+ set rd1 [redis_deferring_client]
+ set rd2 [redis_deferring_client]
+ r config set lua-time-limit 10
+ r set xx 1
+ catch { $rd2 multi; $rd2 read } e
+ catch { $rd2 incr xx; $rd2 read } e
+ $rd1 eval {while true do end} 0
+ after 200
+ catch { $rd2 incr xx; $rd2 read } e
+ catch { $rd2 exec; $rd2 read } e
+ r script kill
+ after 200 ; # Give some time to Lua to call the hook again...
+ set xx [r get xx]
+ # make sure that either the whole transcation passed or none of it (we actually expect none)
+ assert { $xx == 1 || $xx == 3}
+ # check that the connection is no longer in multi state
+ $rd2 ping asdf
+ set pong [$rd2 read]
+ assert_equal $pong "asdf"
+ }
+
+ test {MULTI-EXEC body and script timeout} {
+ # check that we don't run an imcomplete transaction due to some commands
+ # arriving during busy script
+ set rd1 [redis_deferring_client]
+ set rd2 [redis_deferring_client]
+ r config set lua-time-limit 10
+ r set xx 1
+ catch { $rd2 multi; $rd2 read } e
+ catch { $rd2 incr xx; $rd2 read } e
+ $rd1 eval {while true do end} 0
+ after 200
+ catch { $rd2 incr xx; $rd2 read } e
+ r script kill
+ after 200 ; # Give some time to Lua to call the hook again...
+ catch { $rd2 exec; $rd2 read } e
+ set xx [r get xx]
+ # make sure that either the whole transcation passed or none of it (we actually expect none)
+ assert { $xx == 1 || $xx == 3}
+ # check that the connection is no longer in multi state
+ $rd2 ping asdf
+ set pong [$rd2 read]
+ assert_equal $pong "asdf"
+ }
}