mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
0ffb9d2ea9
Redis 7.2 (#9406) introduced a new modules event, `RedisModuleEvent_Key`. This new event allows the module to read the key data just before it is removed from the database (either deleted, expired, evicted, or overwritten). When the key is removed from the database, either by active expire or eviction. The new event was not called as part of an execution unit. This can cause an issue if the module registers a post notification job inside the event. This job will not be executed atomically with the expiration/eviction operation and will not replicated inside a Multi/Exec. Moreover, the post notification job will be executed right after the event where it is still not safe to perform any write operation, this will violate the promise that post notification job will be called atomically with the operation that triggered it and **only when it is safe to write**. This PR fixes the issue by wrapping each expiration/eviction of a key with an execution unit. This makes sure the entire operation will run atomically and all the post notification jobs will be executed at the end where it is safe to write. Tests were modified to verify the fix.
438 lines
14 KiB
Tcl
438 lines
14 KiB
Tcl
set testmodule [file normalize tests/modules/blockedclient.so]
|
|
set testmodule2 [file normalize tests/modules/postnotifications.so]
|
|
set testmodule3 [file normalize tests/modules/blockonkeys.so]
|
|
|
|
start_server {tags {"modules"}} {
|
|
r module load $testmodule
|
|
|
|
test {Locked GIL acquisition from async RM_Call} {
|
|
assert_equal {OK} [r do_rm_call_async acquire_gil]
|
|
}
|
|
|
|
test "Blpop on async RM_Call fire and forget" {
|
|
assert_equal {Blocked} [r do_rm_call_fire_and_forget blpop l 0]
|
|
r lpush l a
|
|
assert_equal {0} [r llen l]
|
|
}
|
|
|
|
test "Blpop on threaded async RM_Call" {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd do_rm_call_async_on_thread blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {l a}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
foreach cmd {do_rm_call_async do_rm_call_async_script_mode } {
|
|
|
|
test "Blpop on async RM_Call using $cmd" {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd $cmd blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {l a}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test "Brpop on async RM_Call using $cmd" {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd $cmd brpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {l a}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test "Brpoplpush on async RM_Call using $cmd" {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd $cmd brpoplpush l1 l2 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l1 a
|
|
assert_equal [$rd read] {a}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
r lpop l2
|
|
} {a}
|
|
|
|
test "Blmove on async RM_Call using $cmd" {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd $cmd blmove l1 l2 LEFT LEFT 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l1 a
|
|
assert_equal [$rd read] {a}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
r lpop l2
|
|
} {a}
|
|
|
|
test "Bzpopmin on async RM_Call using $cmd" {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd $cmd bzpopmin s 0
|
|
wait_for_blocked_clients_count 1
|
|
r zadd s 10 foo
|
|
assert_equal [$rd read] {s foo 10}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test "Bzpopmax on async RM_Call using $cmd" {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd $cmd bzpopmax s 0
|
|
wait_for_blocked_clients_count 1
|
|
r zadd s 10 foo
|
|
assert_equal [$rd read] {s foo 10}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
}
|
|
|
|
test {Nested async RM_Call} {
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {l a}
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test {Test multiple async RM_Call waiting on the same event} {
|
|
set rd1 [redis_deferring_client]
|
|
set rd2 [redis_deferring_client]
|
|
|
|
$rd1 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0
|
|
$rd2 do_rm_call_async do_rm_call_async do_rm_call_async do_rm_call_async blpop l 0
|
|
wait_for_blocked_clients_count 2
|
|
r lpush l element element
|
|
assert_equal [$rd1 read] {l element}
|
|
assert_equal [$rd2 read] {l element}
|
|
wait_for_blocked_clients_count 0
|
|
$rd1 close
|
|
$rd2 close
|
|
}
|
|
|
|
test {async RM_Call calls RM_Call} {
|
|
assert_equal {PONG} [r do_rm_call_async do_rm_call ping]
|
|
}
|
|
|
|
test {async RM_Call calls background RM_Call calls RM_Call} {
|
|
assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call ping]
|
|
}
|
|
|
|
test {async RM_Call calls background RM_Call calls RM_Call calls async RM_Call} {
|
|
assert_equal {PONG} [r do_rm_call_async do_bg_rm_call do_rm_call do_rm_call_async ping]
|
|
}
|
|
|
|
test {async RM_Call inside async RM_Call callback} {
|
|
set rd [redis_deferring_client]
|
|
$rd wait_and_do_rm_call blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
|
|
start_server {} {
|
|
test "Connect a replica to the master instance" {
|
|
r slaveof [srv -1 host] [srv -1 port]
|
|
wait_for_condition 50 100 {
|
|
[s role] eq {slave} &&
|
|
[string match {*master_link_status:up*} [r info replication]]
|
|
} else {
|
|
fail "Can't turn the instance into a replica"
|
|
}
|
|
}
|
|
|
|
assert_equal {1} [r -1 lpush l a]
|
|
assert_equal [$rd read] {l a}
|
|
}
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test {Become replica while having async RM_Call running} {
|
|
r flushall
|
|
set rd [redis_deferring_client]
|
|
$rd do_rm_call_async blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
|
|
#become a replica of a not existing redis
|
|
r replicaof localhost 30000
|
|
|
|
catch {[$rd read]} e
|
|
assert_match {UNBLOCKED force unblock from blocking operation*} $e
|
|
wait_for_blocked_clients_count 0
|
|
|
|
r replicaof no one
|
|
|
|
r lpush l 1
|
|
# make sure the async rm_call was aborted
|
|
assert_equal [r llen l] {1}
|
|
$rd close
|
|
}
|
|
|
|
test {Pipeline with blocking RM_Call} {
|
|
r flushall
|
|
set rd [redis_deferring_client]
|
|
set buf ""
|
|
append buf "do_rm_call_async blpop l 0\r\n"
|
|
append buf "ping\r\n"
|
|
$rd write $buf
|
|
$rd flush
|
|
wait_for_blocked_clients_count 1
|
|
|
|
# release the blocked client
|
|
r lpush l 1
|
|
|
|
assert_equal [$rd read] {l 1}
|
|
assert_equal [$rd read] {PONG}
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test {blocking RM_Call abort} {
|
|
r flushall
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd client id
|
|
set client_id [$rd read]
|
|
|
|
$rd do_rm_call_async blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
|
|
r client kill ID $client_id
|
|
assert_error {*error reading reply*} {$rd read}
|
|
|
|
wait_for_blocked_clients_count 0
|
|
|
|
r lpush l 1
|
|
# make sure the async rm_call was aborted
|
|
assert_equal [r llen l] {1}
|
|
$rd close
|
|
}
|
|
}
|
|
|
|
start_server {tags {"modules"}} {
|
|
r module load $testmodule
|
|
|
|
test {Test basic replication stream on unblock handler} {
|
|
r flushall
|
|
set repl [attach_to_replication_stream]
|
|
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd do_rm_call_async blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {l a}
|
|
|
|
assert_replication_stream $repl {
|
|
{select *}
|
|
{lpush l a}
|
|
{lpop l}
|
|
}
|
|
close_replication_stream $repl
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test {Test unblock handler are executed as a unit} {
|
|
r flushall
|
|
set repl [attach_to_replication_stream]
|
|
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd blpop_and_set_multiple_keys l x 1 y 2
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {OK}
|
|
|
|
assert_replication_stream $repl {
|
|
{select *}
|
|
{lpush l a}
|
|
{multi}
|
|
{lpop l}
|
|
{set x 1}
|
|
{set y 2}
|
|
{exec}
|
|
}
|
|
close_replication_stream $repl
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test {Test no propagation of blocking command} {
|
|
r flushall
|
|
set repl [attach_to_replication_stream]
|
|
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd do_rm_call_async_no_replicate blpop l 0
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {l a}
|
|
|
|
# make sure the lpop are not replicated
|
|
r set x 1
|
|
|
|
assert_replication_stream $repl {
|
|
{select *}
|
|
{lpush l a}
|
|
{set x 1}
|
|
}
|
|
close_replication_stream $repl
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
}
|
|
|
|
start_server {tags {"modules"}} {
|
|
r module load $testmodule
|
|
r module load $testmodule2
|
|
|
|
test {Test unblock handler are executed as a unit with key space notifications} {
|
|
r flushall
|
|
set repl [attach_to_replication_stream]
|
|
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {OK}
|
|
|
|
# Explanation of the first multi exec block:
|
|
# {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys'
|
|
# {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys'
|
|
# {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys'
|
|
# {incr string_changed{string_foo}} - post notification job that was registered when 'string_foo' changed
|
|
# {incr string_changed{string_bar}} - post notification job that was registered when 'string_bar' changed
|
|
# {incr string_total} - post notification job that was registered when string_changed{string_foo} changed
|
|
# {incr string_total} - post notification job that was registered when string_changed{string_bar} changed
|
|
assert_replication_stream $repl {
|
|
{select *}
|
|
{lpush l a}
|
|
{multi}
|
|
{lpop l}
|
|
{set string_foo 1}
|
|
{set string_bar 2}
|
|
{incr string_changed{string_foo}}
|
|
{incr string_changed{string_bar}}
|
|
{incr string_total}
|
|
{incr string_total}
|
|
{exec}
|
|
}
|
|
close_replication_stream $repl
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
|
|
test {Test unblock handler are executed as a unit with lazy expire} {
|
|
r flushall
|
|
r DEBUG SET-ACTIVE-EXPIRE 0
|
|
set repl [attach_to_replication_stream]
|
|
|
|
set rd [redis_deferring_client]
|
|
|
|
$rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {OK}
|
|
|
|
# set expiration on string_foo
|
|
r pexpire string_foo 1
|
|
after 10
|
|
|
|
# now the key should have been expired
|
|
$rd blpop_and_set_multiple_keys l string_foo 1 string_bar 2
|
|
wait_for_blocked_clients_count 1
|
|
r lpush l a
|
|
assert_equal [$rd read] {OK}
|
|
|
|
# Explanation of the first multi exec block:
|
|
# {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys'
|
|
# {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys'
|
|
# {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys'
|
|
# {incr string_changed{string_foo}} - post notification job that was registered when 'string_foo' changed
|
|
# {incr string_changed{string_bar}} - post notification job that was registered when 'string_bar' changed
|
|
# {incr string_total} - post notification job that was registered when string_changed{string_foo} changed
|
|
# {incr string_total} - post notification job that was registered when string_changed{string_bar} changed
|
|
#
|
|
# Explanation of the second multi exec block:
|
|
# {lpop l} - pop the value by our blocking command 'blpop_and_set_multiple_keys'
|
|
# {del string_foo} - lazy expiration of string_foo when 'blpop_and_set_multiple_keys' tries to write to it.
|
|
# {set string_foo 1} - the action of our blocking command 'blpop_and_set_multiple_keys'
|
|
# {set string_bar 2} - the action of our blocking command 'blpop_and_set_multiple_keys'
|
|
# {incr expired} - the post notification job, registered after string_foo got expired
|
|
# {incr string_changed{string_foo}} - post notification job triggered when we set string_foo
|
|
# {incr string_changed{string_bar}} - post notification job triggered when we set string_bar
|
|
# {incr string_total} - post notification job triggered when we incr 'string_changed{string_foo}'
|
|
# {incr string_total} - post notification job triggered when we incr 'string_changed{string_bar}'
|
|
assert_replication_stream $repl {
|
|
{select *}
|
|
{lpush l a}
|
|
{multi}
|
|
{lpop l}
|
|
{set string_foo 1}
|
|
{set string_bar 2}
|
|
{incr string_changed{string_foo}}
|
|
{incr string_changed{string_bar}}
|
|
{incr string_total}
|
|
{incr string_total}
|
|
{exec}
|
|
{pexpireat string_foo *}
|
|
{lpush l a}
|
|
{multi}
|
|
{lpop l}
|
|
{del string_foo}
|
|
{set string_foo 1}
|
|
{set string_bar 2}
|
|
{incr expired}
|
|
{incr string_changed{string_foo}}
|
|
{incr string_changed{string_bar}}
|
|
{incr string_total}
|
|
{incr string_total}
|
|
{exec}
|
|
}
|
|
close_replication_stream $repl
|
|
r DEBUG SET-ACTIVE-EXPIRE 1
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
}
|
|
|
|
start_server {tags {"modules"}} {
|
|
r module load $testmodule
|
|
r module load $testmodule3
|
|
|
|
test {Test unblock handler on module blocked on keys} {
|
|
set rd [redis_deferring_client]
|
|
|
|
r fsl.push l 1
|
|
$rd do_rm_call_async FSL.BPOPGT l 3 0
|
|
wait_for_blocked_clients_count 1
|
|
r fsl.push l 2
|
|
r fsl.push l 3
|
|
r fsl.push l 4
|
|
assert_equal [$rd read] {4}
|
|
|
|
wait_for_blocked_clients_count 0
|
|
$rd close
|
|
}
|
|
}
|