mirror of https://github.com/redis/redis.git
				
				
				
			
		
			
				
	
	
		
			916 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Tcl
		
	
	
	
			
		
		
	
	
			916 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Tcl
		
	
	
	
proc wait_for_dbsize {size} {
 | 
						|
    set r2 [redis_client]
 | 
						|
    wait_for_condition 50 100 {
 | 
						|
        [$r2 dbsize] == $size
 | 
						|
    } else {
 | 
						|
        fail "Target dbsize not reached"
 | 
						|
    }
 | 
						|
    $r2 close
 | 
						|
}
 | 
						|
 | 
						|
start_server {tags {"multi"}} {
 | 
						|
    test {MULTI / EXEC basics} {
 | 
						|
        r del mylist
 | 
						|
        r rpush mylist a
 | 
						|
        r rpush mylist b
 | 
						|
        r rpush mylist c
 | 
						|
        r multi
 | 
						|
        set v1 [r lrange mylist 0 -1]
 | 
						|
        set v2 [r ping]
 | 
						|
        set v3 [r exec]
 | 
						|
        list $v1 $v2 $v3
 | 
						|
    } {QUEUED QUEUED {{a b c} PONG}}
 | 
						|
 | 
						|
    test {DISCARD} {
 | 
						|
        r del mylist
 | 
						|
        r rpush mylist a
 | 
						|
        r rpush mylist b
 | 
						|
        r rpush mylist c
 | 
						|
        r multi
 | 
						|
        set v1 [r del mylist]
 | 
						|
        set v2 [r discard]
 | 
						|
        set v3 [r lrange mylist 0 -1]
 | 
						|
        list $v1 $v2 $v3
 | 
						|
    } {QUEUED OK {a b c}}
 | 
						|
 | 
						|
    test {Nested MULTI are not allowed} {
 | 
						|
        set err {}
 | 
						|
        r multi
 | 
						|
        catch {[r multi]} err
 | 
						|
        r exec
 | 
						|
        set _ $err
 | 
						|
    } {*ERR MULTI*}
 | 
						|
 | 
						|
    test {MULTI where commands alter argc/argv} {
 | 
						|
        r sadd myset a
 | 
						|
        r multi
 | 
						|
        r spop myset
 | 
						|
        list [r exec] [r exists myset]
 | 
						|
    } {a 0}
 | 
						|
 | 
						|
    test {WATCH inside MULTI is not allowed} {
 | 
						|
        set err {}
 | 
						|
        r multi
 | 
						|
        catch {[r watch x]} err
 | 
						|
        r exec
 | 
						|
        set _ $err
 | 
						|
    } {*ERR WATCH*}
 | 
						|
 | 
						|
    test {EXEC fails if there are errors while queueing commands #1} {
 | 
						|
        r del foo1{t} foo2{t}
 | 
						|
        r multi
 | 
						|
        r set foo1{t} bar1
 | 
						|
        catch {r non-existing-command}
 | 
						|
        r set foo2{t} bar2
 | 
						|
        catch {r exec} e
 | 
						|
        assert_match {EXECABORT*} $e
 | 
						|
        list [r exists foo1{t}] [r exists foo2{t}]
 | 
						|
    } {0 0}
 | 
						|
 | 
						|
    test {EXEC fails if there are errors while queueing commands #2} {
 | 
						|
        set rd [redis_deferring_client]
 | 
						|
        r del foo1{t} foo2{t}
 | 
						|
        r multi
 | 
						|
        r set foo1{t} bar1
 | 
						|
        $rd config set maxmemory 1
 | 
						|
        assert  {[$rd read] eq {OK}}
 | 
						|
        catch {r lpush mylist{t} myvalue}
 | 
						|
        $rd config set maxmemory 0
 | 
						|
        assert  {[$rd read] eq {OK}}
 | 
						|
        r set foo2{t} bar2
 | 
						|
        catch {r exec} e
 | 
						|
        assert_match {EXECABORT*} $e
 | 
						|
        $rd close
 | 
						|
        list [r exists foo1{t}] [r exists foo2{t}]
 | 
						|
    } {0 0} {needs:config-maxmemory}
 | 
						|
 | 
						|
    test {If EXEC aborts, the client MULTI state is cleared} {
 | 
						|
        r del foo1{t} foo2{t}
 | 
						|
        r multi
 | 
						|
        r set foo1{t} bar1
 | 
						|
        catch {r non-existing-command}
 | 
						|
        r set foo2{t} bar2
 | 
						|
        catch {r exec} e
 | 
						|
        assert_match {EXECABORT*} $e
 | 
						|
        r ping
 | 
						|
    } {PONG}
 | 
						|
 | 
						|
    test {EXEC works on WATCHed key not modified} {
 | 
						|
        r watch x{t} y{t} z{t}
 | 
						|
        r watch k{t}
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {PONG}
 | 
						|
 | 
						|
    test {EXEC fail on WATCHed key modified (1 key of 1 watched)} {
 | 
						|
        r set x 30
 | 
						|
        r watch x
 | 
						|
        r set x 40
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {}
 | 
						|
 | 
						|
    test {EXEC fail on WATCHed key modified (1 key of 5 watched)} {
 | 
						|
        r set x{t} 30
 | 
						|
        r watch a{t} b{t} x{t} k{t} z{t}
 | 
						|
        r set x{t} 40
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {}
 | 
						|
 | 
						|
    test {EXEC fail on WATCHed key modified by SORT with STORE even if the result is empty} {
 | 
						|
        r flushdb
 | 
						|
        r lpush foo bar
 | 
						|
        r watch foo
 | 
						|
        r sort emptylist store foo
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {} {cluster:skip}
 | 
						|
 | 
						|
    test {EXEC fail on lazy expired WATCHed key} {
 | 
						|
        r del key
 | 
						|
        r debug set-active-expire 0
 | 
						|
 | 
						|
        for {set j 0} {$j < 10} {incr j} {
 | 
						|
            r set key 1 px 100
 | 
						|
            r watch key
 | 
						|
            after 101
 | 
						|
            r multi
 | 
						|
            r incr key
 | 
						|
 | 
						|
            set res [r exec]
 | 
						|
            if {$res eq {}} break
 | 
						|
        }
 | 
						|
        if {$::verbose} { puts "EXEC fail on lazy expired WATCHed key attempts: $j" }
 | 
						|
 | 
						|
        r debug set-active-expire 1
 | 
						|
        set _ $res
 | 
						|
    } {} {needs:debug}
 | 
						|
 | 
						|
    test {WATCH stale keys should not fail EXEC} {
 | 
						|
        r del x
 | 
						|
        r debug set-active-expire 0
 | 
						|
        r set x foo px 1
 | 
						|
        after 2
 | 
						|
        r watch x
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        assert_equal {PONG} [r exec]
 | 
						|
        r debug set-active-expire 1
 | 
						|
    } {OK} {needs:debug}
 | 
						|
 | 
						|
    test {Delete WATCHed stale keys should not fail EXEC} {
 | 
						|
        r del x
 | 
						|
        r debug set-active-expire 0
 | 
						|
        r set x foo px 1
 | 
						|
        after 2
 | 
						|
        r watch x
 | 
						|
        # EXISTS triggers lazy expiry/deletion
 | 
						|
        assert_equal 0 [r exists x]
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        assert_equal {PONG} [r exec]
 | 
						|
        r debug set-active-expire 1
 | 
						|
    } {OK} {needs:debug}
 | 
						|
 | 
						|
    test {FLUSHDB while watching stale keys should not fail EXEC} {
 | 
						|
        r del x
 | 
						|
        r debug set-active-expire 0
 | 
						|
        r set x foo px 1
 | 
						|
        after 2
 | 
						|
        r watch x
 | 
						|
        r flushdb
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        assert_equal {PONG} [r exec]
 | 
						|
        r debug set-active-expire 1
 | 
						|
    } {OK} {needs:debug}
 | 
						|
 | 
						|
    test {After successful EXEC key is no longer watched} {
 | 
						|
        r set x 30
 | 
						|
        r watch x
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
        r set x 40
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {PONG}
 | 
						|
 | 
						|
    test {After failed EXEC key is no longer watched} {
 | 
						|
        r set x 30
 | 
						|
        r watch x
 | 
						|
        r set x 40
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
        r set x 40
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {PONG}
 | 
						|
 | 
						|
    test {It is possible to UNWATCH} {
 | 
						|
        r set x 30
 | 
						|
        r watch x
 | 
						|
        r set x 40
 | 
						|
        r unwatch
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {PONG}
 | 
						|
 | 
						|
    test {UNWATCH when there is nothing watched works as expected} {
 | 
						|
        r unwatch
 | 
						|
    } {OK}
 | 
						|
 | 
						|
    test {FLUSHALL is able to touch the watched keys} {
 | 
						|
        r set x 30
 | 
						|
        r watch x
 | 
						|
        r flushall
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {}
 | 
						|
 | 
						|
    test {FLUSHALL does not touch non affected keys} {
 | 
						|
        r del x
 | 
						|
        r watch x
 | 
						|
        r flushall
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {PONG}
 | 
						|
 | 
						|
    test {FLUSHDB is able to touch the watched keys} {
 | 
						|
        r set x 30
 | 
						|
        r watch x
 | 
						|
        r flushdb
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {}
 | 
						|
 | 
						|
    test {FLUSHDB does not touch non affected keys} {
 | 
						|
        r del x
 | 
						|
        r watch x
 | 
						|
        r flushdb
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {PONG}
 | 
						|
 | 
						|
    test {SWAPDB is able to touch the watched keys that exist} {
 | 
						|
        r flushall
 | 
						|
        r select 0
 | 
						|
        r set x 30
 | 
						|
        r watch x ;# make sure x (set to 30) doesn't change (SWAPDB will "delete" it)
 | 
						|
        r swapdb 0 1
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {} {singledb:skip}
 | 
						|
 | 
						|
    test {SWAPDB is able to touch the watched keys that do not exist} {
 | 
						|
        r flushall
 | 
						|
        r select 1
 | 
						|
        r set x 30
 | 
						|
        r select 0
 | 
						|
        r watch x ;# make sure the key x (currently missing) doesn't change (SWAPDB will create it)
 | 
						|
        r swapdb 0 1
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {} {singledb:skip}
 | 
						|
 | 
						|
    test {SWAPDB does not touch watched stale keys} {
 | 
						|
        r flushall
 | 
						|
        r select 1
 | 
						|
        r debug set-active-expire 0
 | 
						|
        r set x foo px 1
 | 
						|
        after 2
 | 
						|
        r watch x
 | 
						|
        r swapdb 0 1 ; # expired key replaced with no key => no change
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        assert_equal {PONG} [r exec]
 | 
						|
        r debug set-active-expire 1
 | 
						|
    } {OK} {singledb:skip needs:debug}
 | 
						|
 | 
						|
    test {SWAPDB does not touch non-existing key replaced with stale key} {
 | 
						|
        r flushall
 | 
						|
        r select 0
 | 
						|
        r debug set-active-expire 0
 | 
						|
        r set x foo px 1
 | 
						|
        after 2
 | 
						|
        r select 1
 | 
						|
        r watch x
 | 
						|
        r swapdb 0 1 ; # no key replaced with expired key => no change
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        assert_equal {PONG} [r exec]
 | 
						|
        r debug set-active-expire 1
 | 
						|
    } {OK} {singledb:skip needs:debug}
 | 
						|
 | 
						|
    test {SWAPDB does not touch stale key replaced with another stale key} {
 | 
						|
        r flushall
 | 
						|
        r debug set-active-expire 0
 | 
						|
        r select 1
 | 
						|
        r set x foo px 1
 | 
						|
        r select 0
 | 
						|
        r set x bar px 1
 | 
						|
        after 2
 | 
						|
        r select 1
 | 
						|
        r watch x
 | 
						|
        r swapdb 0 1 ; # no key replaced with expired key => no change
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        assert_equal {PONG} [r exec]
 | 
						|
        r debug set-active-expire 1
 | 
						|
    } {OK} {singledb:skip needs:debug}
 | 
						|
 | 
						|
    test {WATCH is able to remember the DB a key belongs to} {
 | 
						|
        r select 5
 | 
						|
        r set x 30
 | 
						|
        r watch x
 | 
						|
        r select 1
 | 
						|
        r set x 10
 | 
						|
        r select 5
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        set res [r exec]
 | 
						|
        # Restore original DB
 | 
						|
        r select 9
 | 
						|
        set res
 | 
						|
    } {PONG} {singledb:skip}
 | 
						|
 | 
						|
    test {WATCH will consider touched keys target of EXPIRE} {
 | 
						|
        r del x
 | 
						|
        r set x foo
 | 
						|
        r watch x
 | 
						|
        r expire x 10
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {}
 | 
						|
 | 
						|
    test {WATCH will consider touched expired keys} {
 | 
						|
        r flushall
 | 
						|
        r del x
 | 
						|
        r set x foo
 | 
						|
        r expire x 1
 | 
						|
        r watch x
 | 
						|
 | 
						|
        # Wait for the keys to expire.
 | 
						|
        wait_for_dbsize 0
 | 
						|
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        r exec
 | 
						|
    } {}
 | 
						|
 | 
						|
    test {DISCARD should clear the WATCH dirty flag on the client} {
 | 
						|
        r watch x
 | 
						|
        r set x 10
 | 
						|
        r multi
 | 
						|
        r discard
 | 
						|
        r multi
 | 
						|
        r incr x
 | 
						|
        r exec
 | 
						|
    } {11}
 | 
						|
 | 
						|
    test {DISCARD should UNWATCH all the keys} {
 | 
						|
        r watch x
 | 
						|
        r set x 10
 | 
						|
        r multi
 | 
						|
        r discard
 | 
						|
        r set x 10
 | 
						|
        r multi
 | 
						|
        r incr x
 | 
						|
        r exec
 | 
						|
    } {11}
 | 
						|
 | 
						|
    test {MULTI / EXEC is not propagated (single write command)} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
        r multi
 | 
						|
        r set foo bar
 | 
						|
        r exec
 | 
						|
        r set foo2 bar
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {set foo bar}
 | 
						|
            {set foo2 bar}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    test {MULTI / EXEC is propagated correctly (multiple commands)} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
        r multi
 | 
						|
        r set foo{t} bar
 | 
						|
        r get foo{t}
 | 
						|
        r set foo2{t} bar2
 | 
						|
        r get foo2{t}
 | 
						|
        r set foo3{t} bar3
 | 
						|
        r get foo3{t}
 | 
						|
        r exec
 | 
						|
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {multi}
 | 
						|
            {select *}
 | 
						|
            {set foo{t} bar}
 | 
						|
            {set foo2{t} bar2}
 | 
						|
            {set foo3{t} bar3}
 | 
						|
            {exec}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    test {MULTI / EXEC is propagated correctly (multiple commands with SELECT)} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
        r multi
 | 
						|
        r select 1
 | 
						|
        r set foo{t} bar
 | 
						|
        r get foo{t}
 | 
						|
        r select 2
 | 
						|
        r set foo2{t} bar2
 | 
						|
        r get foo2{t}
 | 
						|
        r select 3
 | 
						|
        r set foo3{t} bar3
 | 
						|
        r get foo3{t}
 | 
						|
        r exec
 | 
						|
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {multi}
 | 
						|
            {select *}
 | 
						|
            {set foo{t} bar}
 | 
						|
            {select *}
 | 
						|
            {set foo2{t} bar2}
 | 
						|
            {select *}
 | 
						|
            {set foo3{t} bar3}
 | 
						|
            {exec}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl singledb:skip}
 | 
						|
 | 
						|
    test {MULTI / EXEC is propagated correctly (empty transaction)} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
        r multi
 | 
						|
        r exec
 | 
						|
        r set foo bar
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {set foo bar}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    test {MULTI / EXEC is propagated correctly (read-only commands)} {
 | 
						|
        r set foo value1
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
        r multi
 | 
						|
        r get foo
 | 
						|
        r exec
 | 
						|
        r set foo value2
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {set foo value2}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    test {MULTI / EXEC is propagated correctly (write command, no effect)} {
 | 
						|
        r del bar
 | 
						|
        r del foo
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
        r multi
 | 
						|
        r del foo
 | 
						|
        r exec
 | 
						|
 | 
						|
        # add another command so that when we see it we know multi-exec wasn't
 | 
						|
        # propagated
 | 
						|
        r incr foo
 | 
						|
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {incr foo}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    test {MULTI / EXEC with REPLICAOF} {
 | 
						|
        # This test verifies that if we demote a master to replica inside a transaction, the
 | 
						|
        # entire transaction is not propagated to the already-connected replica
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
        r set foo bar
 | 
						|
        r multi
 | 
						|
        r set foo2 bar
 | 
						|
        r replicaof localhost 9999
 | 
						|
        r set foo3 bar
 | 
						|
        r exec
 | 
						|
        catch {r set foo4 bar} e
 | 
						|
        assert_match {READONLY*} $e
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {set foo bar}
 | 
						|
        }
 | 
						|
        r replicaof no one
 | 
						|
    } {OK} {needs:repl cluster:skip}
 | 
						|
 | 
						|
    test {DISCARD should not fail during OOM} {
 | 
						|
        set rd [redis_deferring_client]
 | 
						|
        $rd config set maxmemory 1
 | 
						|
        assert  {[$rd read] eq {OK}}
 | 
						|
        r multi
 | 
						|
        catch {r set x 1} e
 | 
						|
        assert_match {OOM*} $e
 | 
						|
        r discard
 | 
						|
        $rd config set maxmemory 0
 | 
						|
        assert  {[$rd read] eq {OK}}
 | 
						|
        $rd close
 | 
						|
        r ping
 | 
						|
    } {PONG} {needs:config-maxmemory}
 | 
						|
 | 
						|
    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 r2 [redis_client]
 | 
						|
        r config set lua-time-limit 10
 | 
						|
        r set xx 1
 | 
						|
        $rd1 eval {while true do end} 0
 | 
						|
        after 200
 | 
						|
        catch { $r2 multi; } e
 | 
						|
        catch { $r2 incr xx; } e
 | 
						|
        r script kill
 | 
						|
        after 200 ; # Give some time to Lua to call the hook again...
 | 
						|
        catch { $r2 incr xx; } e
 | 
						|
        catch { $r2 exec; } e
 | 
						|
        assert_match {EXECABORT*previous errors*} $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
 | 
						|
        set pong [$r2 ping asdf]
 | 
						|
        assert_equal $pong "asdf"
 | 
						|
        $rd1 close; $r2 close
 | 
						|
    }
 | 
						|
 | 
						|
    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 r2 [redis_client]
 | 
						|
        r config set lua-time-limit 10
 | 
						|
        r set xx 1
 | 
						|
        catch { $r2 multi; } e
 | 
						|
        catch { $r2 incr xx; } e
 | 
						|
        $rd1 eval {while true do end} 0
 | 
						|
        after 200
 | 
						|
        catch { $r2 incr xx; } e
 | 
						|
        catch { $r2 exec; } e
 | 
						|
        assert_match {EXECABORT*BUSY*} $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
 | 
						|
        set pong [$r2 ping asdf]
 | 
						|
        assert_equal $pong "asdf"
 | 
						|
        $rd1 close; $r2 close
 | 
						|
    }
 | 
						|
 | 
						|
    test {MULTI-EXEC body and script timeout} {
 | 
						|
        # check that we don't run an incomplete transaction due to some commands
 | 
						|
        # arriving during busy script
 | 
						|
        set rd1 [redis_deferring_client]
 | 
						|
        set r2 [redis_client]
 | 
						|
        r config set lua-time-limit 10
 | 
						|
        r set xx 1
 | 
						|
        catch { $r2 multi; } e
 | 
						|
        catch { $r2 incr xx; } e
 | 
						|
        $rd1 eval {while true do end} 0
 | 
						|
        after 200
 | 
						|
        catch { $r2 incr xx; } e
 | 
						|
        r script kill
 | 
						|
        after 200 ; # Give some time to Lua to call the hook again...
 | 
						|
        catch { $r2 exec; } e
 | 
						|
        assert_match {EXECABORT*previous errors*} $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
 | 
						|
        set pong [$r2 ping asdf]
 | 
						|
        assert_equal $pong "asdf"
 | 
						|
        $rd1 close; $r2 close
 | 
						|
    }
 | 
						|
 | 
						|
    test {just EXEC and script timeout} {
 | 
						|
        # check that if EXEC arrives during timeout, we don't end up executing
 | 
						|
        # actual commands during busy script, and also that we exit the multi state
 | 
						|
        set rd1 [redis_deferring_client]
 | 
						|
        set r2 [redis_client]
 | 
						|
        r config set lua-time-limit 10
 | 
						|
        r set xx 1
 | 
						|
        catch { $r2 multi; } e
 | 
						|
        catch { $r2 incr xx; } e
 | 
						|
        $rd1 eval {while true do end} 0
 | 
						|
        after 200
 | 
						|
        catch { $r2 exec; } e
 | 
						|
        assert_match {EXECABORT*BUSY*} $e
 | 
						|
        r script kill
 | 
						|
        after 200 ; # Give some time to Lua to call the hook again...
 | 
						|
        set xx [r get xx]
 | 
						|
        # make we didn't execute the transaction
 | 
						|
        assert { $xx == 1}
 | 
						|
        # check that the connection is no longer in multi state
 | 
						|
        set pong [$r2 ping asdf]
 | 
						|
        assert_equal $pong "asdf"
 | 
						|
        $rd1 close; $r2 close
 | 
						|
    }
 | 
						|
 | 
						|
    test {exec with write commands and state change} {
 | 
						|
        # check that exec that contains write commands fails if server state changed since they were queued
 | 
						|
        set r1 [redis_client]
 | 
						|
        r set xx 1
 | 
						|
        r multi
 | 
						|
        r incr xx
 | 
						|
        $r1 config set min-replicas-to-write 2
 | 
						|
        catch {r exec} e
 | 
						|
        assert_match {*EXECABORT*NOREPLICAS*} $e
 | 
						|
        set xx [r get xx]
 | 
						|
        # make sure that the INCR wasn't executed
 | 
						|
        assert { $xx == 1}
 | 
						|
        $r1 config set min-replicas-to-write 0
 | 
						|
        $r1 close
 | 
						|
    } {0} {needs:repl}
 | 
						|
 | 
						|
    test {exec with read commands and stale replica state change} {
 | 
						|
        # check that exec that contains read commands fails if server state changed since they were queued
 | 
						|
        r config set replica-serve-stale-data no
 | 
						|
        set r1 [redis_client]
 | 
						|
        r set xx 1
 | 
						|
 | 
						|
        # check that GET and PING are disallowed on stale replica, even if the replica becomes stale only after queuing.
 | 
						|
        r multi
 | 
						|
        r get xx
 | 
						|
        $r1 replicaof localhsot 0
 | 
						|
        catch {r exec} e
 | 
						|
        assert_match {*EXECABORT*MASTERDOWN*} $e
 | 
						|
 | 
						|
        # reset
 | 
						|
        $r1 replicaof no one
 | 
						|
 | 
						|
        r multi
 | 
						|
        r ping
 | 
						|
        $r1 replicaof localhsot 0
 | 
						|
        catch {r exec} e
 | 
						|
        assert_match {*EXECABORT*MASTERDOWN*} $e
 | 
						|
 | 
						|
        # check that when replica is not stale, GET is allowed
 | 
						|
        # while we're at it, let's check that multi is allowed on stale replica too
 | 
						|
        r multi
 | 
						|
        $r1 replicaof no one
 | 
						|
        r get xx
 | 
						|
        set xx [r exec]
 | 
						|
        # make sure that the INCR was executed
 | 
						|
        assert { $xx == 1 }
 | 
						|
        $r1 close
 | 
						|
    } {0} {needs:repl cluster:skip}
 | 
						|
 | 
						|
    test {EXEC with only read commands should not be rejected when OOM} {
 | 
						|
        set r2 [redis_client]
 | 
						|
 | 
						|
        r set x value
 | 
						|
        r multi
 | 
						|
        r get x
 | 
						|
        r ping
 | 
						|
 | 
						|
        # enforcing OOM
 | 
						|
        $r2 config set maxmemory 1
 | 
						|
 | 
						|
        # finish the multi transaction with exec
 | 
						|
        assert { [r exec] == {value PONG} }
 | 
						|
 | 
						|
        # releasing OOM
 | 
						|
        $r2 config set maxmemory 0
 | 
						|
        $r2 close
 | 
						|
    } {0} {needs:config-maxmemory}
 | 
						|
 | 
						|
    test {EXEC with at least one use-memory command should fail} {
 | 
						|
        set r2 [redis_client]
 | 
						|
 | 
						|
        r multi
 | 
						|
        r set x 1
 | 
						|
        r get x
 | 
						|
 | 
						|
        # enforcing OOM
 | 
						|
        $r2 config set maxmemory 1
 | 
						|
 | 
						|
        # finish the multi transaction with exec
 | 
						|
        catch {r exec} e
 | 
						|
        assert_match {EXECABORT*OOM*} $e
 | 
						|
 | 
						|
        # releasing OOM
 | 
						|
        $r2 config set maxmemory 0
 | 
						|
        $r2 close
 | 
						|
    } {0} {needs:config-maxmemory}
 | 
						|
 | 
						|
    test {Blocking commands ignores the timeout} {
 | 
						|
        r xgroup create s{t} g $ MKSTREAM
 | 
						|
 | 
						|
        set m [r multi]
 | 
						|
        r blpop empty_list{t} 0
 | 
						|
        r brpop empty_list{t} 0
 | 
						|
        r brpoplpush empty_list1{t} empty_list2{t} 0
 | 
						|
        r blmove empty_list1{t} empty_list2{t} LEFT LEFT 0
 | 
						|
        r bzpopmin empty_zset{t} 0
 | 
						|
        r bzpopmax empty_zset{t} 0
 | 
						|
        r xread BLOCK 0 STREAMS s{t} $
 | 
						|
        r xreadgroup group g c BLOCK 0 STREAMS s{t} >
 | 
						|
        set res [r exec]
 | 
						|
 | 
						|
        list $m $res
 | 
						|
    } {OK {{} {} {} {} {} {} {} {}}}
 | 
						|
 | 
						|
    test {MULTI propagation of PUBLISH} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
 | 
						|
        r multi
 | 
						|
        r publish bla bla
 | 
						|
        r exec
 | 
						|
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {publish bla bla}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl cluster:skip}
 | 
						|
 | 
						|
    test {MULTI propagation of SCRIPT LOAD} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
 | 
						|
        # make sure that SCRIPT LOAD inside MULTI isn't propagated
 | 
						|
        r multi
 | 
						|
        r script load {redis.call('set', KEYS[1], 'foo')}
 | 
						|
        r set foo bar
 | 
						|
        set res [r exec]
 | 
						|
        set sha [lindex $res 0]
 | 
						|
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {set foo bar}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    test {MULTI propagation of EVAL} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
 | 
						|
        # make sure that EVAL inside MULTI is propagated in a transaction in effects
 | 
						|
        r multi
 | 
						|
        r eval {redis.call('set', KEYS[1], 'bar')} 1 bar
 | 
						|
        r exec
 | 
						|
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {set bar bar}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    test {MULTI propagation of SCRIPT FLUSH} {
 | 
						|
        set repl [attach_to_replication_stream]
 | 
						|
 | 
						|
        # make sure that SCRIPT FLUSH isn't propagated
 | 
						|
        r multi
 | 
						|
        r script flush
 | 
						|
        r set foo bar
 | 
						|
        r exec
 | 
						|
 | 
						|
        assert_replication_stream $repl {
 | 
						|
            {select *}
 | 
						|
            {set foo bar}
 | 
						|
        }
 | 
						|
        close_replication_stream $repl
 | 
						|
    } {} {needs:repl}
 | 
						|
 | 
						|
    tags {"stream"} {
 | 
						|
        test {MULTI propagation of XREADGROUP} {
 | 
						|
            set repl [attach_to_replication_stream]
 | 
						|
 | 
						|
            r XADD mystream * foo bar
 | 
						|
            r XADD mystream * foo2 bar2
 | 
						|
            r XADD mystream * foo3 bar3
 | 
						|
            r XGROUP CREATE mystream mygroup 0
 | 
						|
 | 
						|
            # make sure the XCALIM (propagated by XREADGROUP) is indeed inside MULTI/EXEC
 | 
						|
            r multi
 | 
						|
            r XREADGROUP GROUP mygroup consumer1 COUNT 2 STREAMS mystream ">"
 | 
						|
            r XREADGROUP GROUP mygroup consumer1 STREAMS mystream ">"
 | 
						|
            r exec
 | 
						|
 | 
						|
            assert_replication_stream $repl {
 | 
						|
                {select *}
 | 
						|
                {xadd *}
 | 
						|
                {xadd *}
 | 
						|
                {xadd *}
 | 
						|
                {xgroup CREATE *}
 | 
						|
                {multi}
 | 
						|
                {xclaim *}
 | 
						|
                {xclaim *}
 | 
						|
                {xclaim *}
 | 
						|
                {exec}
 | 
						|
            }
 | 
						|
            close_replication_stream $repl
 | 
						|
        } {} {needs:repl}
 | 
						|
    }
 | 
						|
 | 
						|
    foreach {cmd} {SAVE SHUTDOWN} {
 | 
						|
        test "MULTI with $cmd" {
 | 
						|
            r del foo
 | 
						|
            r multi
 | 
						|
            r set foo bar
 | 
						|
            catch {r $cmd} e1
 | 
						|
            catch {r exec} e2
 | 
						|
            assert_match {*Command not allowed inside a transaction*} $e1
 | 
						|
            assert_match {EXECABORT*} $e2
 | 
						|
            r get foo
 | 
						|
        } {}
 | 
						|
    }
 | 
						|
 | 
						|
    test "MULTI with BGREWRITEAOF" {
 | 
						|
        set forks [s total_forks]
 | 
						|
        r multi
 | 
						|
        r set foo bar
 | 
						|
        r BGREWRITEAOF
 | 
						|
        set res [r exec]
 | 
						|
        assert_match "*rewriting scheduled*" [lindex $res 1]
 | 
						|
        wait_for_condition 50 100 {
 | 
						|
            [s total_forks] > $forks
 | 
						|
        } else {
 | 
						|
            fail "aofrw didn't start"
 | 
						|
        }
 | 
						|
        waitForBgrewriteaof r
 | 
						|
    } {} {external:skip}
 | 
						|
 | 
						|
    test "MULTI with config set appendonly" {
 | 
						|
        set lines [count_log_lines 0]
 | 
						|
        set forks [s total_forks]
 | 
						|
        r multi
 | 
						|
        r set foo bar
 | 
						|
        r config set appendonly yes
 | 
						|
        r exec
 | 
						|
        verify_log_message 0 "*AOF background was scheduled*" $lines
 | 
						|
        wait_for_condition 50 100 {
 | 
						|
            [s total_forks] > $forks
 | 
						|
        } else {
 | 
						|
            fail "aofrw didn't start"
 | 
						|
        }
 | 
						|
        waitForBgrewriteaof r
 | 
						|
    } {} {external:skip}
 | 
						|
 | 
						|
    test "MULTI with config error" {
 | 
						|
        r multi
 | 
						|
        r set foo bar
 | 
						|
        r config set maxmemory bla
 | 
						|
 | 
						|
        # letting the redis parser read it, it'll throw an exception instead of
 | 
						|
        # reply with an array that contains an error, so we switch to reading
 | 
						|
        # raw RESP instead
 | 
						|
        r readraw 1
 | 
						|
 | 
						|
        set res [r exec]
 | 
						|
        assert_equal $res "*2"
 | 
						|
        set res [r read]
 | 
						|
        assert_equal $res "+OK"
 | 
						|
        set res [r read]
 | 
						|
        r readraw 1
 | 
						|
        set _ $res
 | 
						|
    } {*CONFIG SET failed*}
 | 
						|
}
 | 
						|
 | 
						|
start_server {overrides {appendonly {yes} appendfilename {appendonly.aof} appendfsync always} tags {external:skip}} {
 | 
						|
    test {MULTI with FLUSHALL and AOF} {
 | 
						|
        set aof [get_last_incr_aof_path r]
 | 
						|
        r multi
 | 
						|
        r set foo bar
 | 
						|
        r flushall
 | 
						|
        r exec
 | 
						|
        assert_aof_content $aof {
 | 
						|
            {multi}
 | 
						|
            {select *}
 | 
						|
            {set *}
 | 
						|
            {flushall}
 | 
						|
            {exec}
 | 
						|
        }
 | 
						|
        r get foo
 | 
						|
    } {}
 | 
						|
}
 |