Unverified Commit 7ac21307 authored by guybe7's avatar guybe7 Committed by GitHub
Browse files

Sort out mess around propagation and MULTI/EXEC (#9890)

The mess:
Some parts use alsoPropagate for late propagation, others using an immediate one (propagate()),
causing edge cases, ugly/hacky code, and the tendency for bugs

The basic idea is that all commands are propagated via alsoPropagate (i.e. added to a list) and the
top-most call() is responsible for going over that list and actually propagating them (and wrapping
them in MULTI/EXEC if there's more than one command). This is done in the new function,
propagatePendingCommands.

Callers to propagatePendingCommands:
1. top-most call() (we want all nested call()s to add to the also_propagate array and just the top-most
   one to propagate them) - via `afterCommand`
2. handleClientsBlockedOnKeys: it is out of call() context and it may propagate stuff - via `afterCommand`. 
3. handleClientsBlockedOnKeys edge case: if the looked-up key is already expired, we will propagate the
   expire but will not unblock any client so `afterCommand` isn'...
parent b7567394
......@@ -306,20 +306,69 @@ start_server {tags {"multi"}} {
r exec
} {11}
test {MULTI / EXEC is propagated correctly (single write command)} {
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 *}
{multi}
{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 {
{select *}
{multi}
{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 {
{select *}
{multi}
{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
......@@ -365,6 +414,25 @@ start_server {tags {"multi"}} {
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
......@@ -585,16 +653,13 @@ start_server {tags {"multi"}} {
test {MULTI propagation of PUBLISH} {
set repl [attach_to_replication_stream]
# make sure that PUBLISH inside MULTI is propagated in a transaction
r multi
r publish bla bla
r exec
assert_replication_stream $repl {
{select *}
{multi}
{publish bla bla}
{exec}
}
close_replication_stream $repl
} {} {needs:repl cluster:skip}
......@@ -611,9 +676,7 @@ start_server {tags {"multi"}} {
assert_replication_stream $repl {
{select *}
{multi}
{set foo bar}
{exec}
}
close_replication_stream $repl
} {} {needs:repl}
......@@ -628,9 +691,7 @@ start_server {tags {"multi"}} {
assert_replication_stream $repl {
{select *}
{multi}
{set bar bar}
{exec}
}
close_replication_stream $repl
} {} {needs:repl}
......@@ -646,32 +707,36 @@ start_server {tags {"multi"}} {
assert_replication_stream $repl {
{select *}
{multi}
{set foo bar}
{exec}
}
close_replication_stream $repl
} {} {need:repl}
tags {"stream"} {
test {MULTI propagation of XREADGROUP} {
# stream is a special case because it calls propagate() directly for 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
......
......@@ -508,7 +508,7 @@ start_server {tags {"scripting"}} {
assert {[r eval {return redis.call('spop', 'myset')} 0] ne {}}
assert {[r eval {return redis.call('spop', 'myset', 1)} 0] ne {}}
assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] ne {}}
#this one below should be replicated by an empty MULTI/EXEC
# this one below should not be replicated
assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] eq {}}
r set trailingkey 1
assert_replication_stream $repl {
......@@ -516,17 +516,9 @@ start_server {tags {"scripting"}} {
{sadd *}
{del *}
{sadd *}
{multi}
{srem myset *}
{exec}
{multi}
{srem myset *}
{exec}
{multi}
{srem myset *}
{exec}
{multi}
{exec}
{set *}
}
close_replication_stream $repl
......@@ -555,9 +547,7 @@ start_server {tags {"scripting"}} {
assert_replication_stream $repl {
{select *}
{set *}
{multi}
{pexpireat expirekey *}
{exec}
}
close_replication_stream $repl
} {} {need:repl}
......
......@@ -1015,7 +1015,8 @@ foreach {pop} {BLPOP BLMPOP_LEFT} {
$rd read
} {k hello} {singledb:skip}
test {SWAPDB awakes blocked client, but the key already expired} {
test {SWAPDB wants to wake blocked client, but the key already expired} {
set repl [attach_to_replication_stream]
r flushall
r debug set-active-expire 0
r select 1
......@@ -1036,6 +1037,55 @@ foreach {pop} {BLPOP BLMPOP_LEFT} {
assert_match "*flags=b*" [r client list id $id]
r client unblock $id
assert_equal {} [$rd read]
assert_replication_stream $repl {
{select *}
{flushall}
{select 1}
{rpush k hello}
{pexpireat k *}
{swapdb 1 9}
{select 9}
{del k}
}
close_replication_stream $repl
# Restore server and client state
r debug set-active-expire 1
r select 9
} {OK} {singledb:skip needs:debug}
test {MULTI + LPUSH + EXPIRE + DEBUG SLEEP on blocked client, key already expired} {
set repl [attach_to_replication_stream]
r flushall
r debug set-active-expire 0
set rd [redis_deferring_client]
$rd client id
set id [$rd read]
$rd brpop k 0
wait_for_blocked_clients_count 1
r multi
r rpush k hello
r pexpire k 100
r debug sleep 0.2
r exec
# The EXEC command tries to awake the blocked client, but it remains
# blocked because the key is expired. Check that the deferred client is
# still blocked. Then unblock it.
assert_match "*flags=b*" [r client list id $id]
r client unblock $id
assert_equal {} [$rd read]
assert_replication_stream $repl {
{select *}
{flushall}
{multi}
{rpush k hello}
{pexpireat k *}
{exec}
{del k}
}
close_replication_stream $repl
# Restore server and client state
r debug set-active-expire 1
r select 9
......
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