mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 16:18:28 -05:00
c81813148b
Add a new module event `RedisModule_Event_Key`, this event is fired when a key is removed from the keyspace. The event includes an open key that can be used for reading the key before it is removed. Modules can also extract the key-name, and use RM_Open or RM_Call to access key from within that event, but shouldn't modify anything from within this event. The following sub events are available: - `REDISMODULE_SUBEVENT_KEY_DELETED` - `REDISMODULE_SUBEVENT_KEY_EXPIRED` - `REDISMODULE_SUBEVENT_KEY_EVICTED` - `REDISMODULE_SUBEVENT_KEY_OVERWRITE` The data pointer can be casted to a RedisModuleKeyInfo structure with the following fields: ``` RedisModuleKey *key; // Opened Key ``` ### internals * We also add two dict functions: `dictTwoPhaseUnlinkFind` finds an element from the table, also get the plink of the entry. The entry is returned if the element is found. The user should later call `dictTwoPhaseUnlinkFree` with it in order to unlink and release it. Otherwise if the key is not found, NULL is returned. These two functions should be used in pair. `dictTwoPhaseUnlinkFind` pauses rehash and `dictTwoPhaseUnlinkFree` resumes rehash. * We change `dbOverwrite` to `dbReplaceValue` which just replaces the value of the key and doesn't fire any events. The "overwrite" part (which emits events) is just when called from `setKey`, the other places that called dbOverwrite were ones that just update the value in-place (INCR*, SPOP, and dbUnshareStringValue). This should not have any real impact since `moduleNotifyKeyUnlink` and `signalDeletedKeyAsReady` wouldn't have mattered in these cases anyway (i.e. module keys and stream keys didn't have direct calls to dbOverwrite) * since we allow doing RM_OpenKey from withing these callbacks, we temporarily disable lazy expiry. * We also temporarily disable lazy expiry when we are in unlink/unlink2 callback and keyspace notification callback. * Move special definitions to the top of redismodule.h This is needed to resolve compilation errors with RedisModuleKeyInfoV1 that carries a RedisModuleKey member. Co-authored-by: Oran Agra <oran@redislabs.com>
314 lines
12 KiB
Tcl
314 lines
12 KiB
Tcl
set testmodule [file normalize tests/modules/hooks.so]
|
|
|
|
tags "modules" {
|
|
start_server [list overrides [list loadmodule "$testmodule" appendonly yes]] {
|
|
test {Test module aof save on server start from empty} {
|
|
assert {[r hooks.event_count persistence-syncaof-start] == 1}
|
|
}
|
|
|
|
test {Test clients connection / disconnection hooks} {
|
|
for {set j 0} {$j < 2} {incr j} {
|
|
set rd1 [redis_deferring_client]
|
|
$rd1 close
|
|
}
|
|
assert {[r hooks.event_count client-connected] > 1}
|
|
assert {[r hooks.event_count client-disconnected] > 1}
|
|
}
|
|
|
|
test {Test module client change event for blocked client} {
|
|
set rd [redis_deferring_client]
|
|
# select db other than 0
|
|
$rd select 1
|
|
# block on key
|
|
$rd brpop foo 0
|
|
# kill blocked client
|
|
r client kill skipme yes
|
|
# assert server is still up
|
|
assert_equal [r ping] PONG
|
|
$rd close
|
|
}
|
|
|
|
test {Test module cron hook} {
|
|
after 100
|
|
assert {[r hooks.event_count cron-loop] > 0}
|
|
set hz [r hooks.event_last cron-loop]
|
|
assert_equal $hz 10
|
|
}
|
|
|
|
test {Test module loaded / unloaded hooks} {
|
|
set othermodule [file normalize tests/modules/infotest.so]
|
|
r module load $othermodule
|
|
r module unload infotest
|
|
assert_equal [r hooks.event_last module-loaded] "infotest"
|
|
assert_equal [r hooks.event_last module-unloaded] "infotest"
|
|
}
|
|
|
|
test {Test module aofrw hook} {
|
|
r debug populate 1000 foo 10000 ;# 10mb worth of data
|
|
r config set rdbcompression no ;# rdb progress is only checked once in 2mb
|
|
r BGREWRITEAOF
|
|
waitForBgrewriteaof r
|
|
assert_equal [string match {*module-event-persistence-aof-start*} [exec tail -20 < [srv 0 stdout]]] 1
|
|
assert_equal [string match {*module-event-persistence-end*} [exec tail -20 < [srv 0 stdout]]] 1
|
|
}
|
|
|
|
test {Test module aof load and rdb/aof progress hooks} {
|
|
# create some aof tail (progress is checked only once in 1000 commands)
|
|
for {set j 0} {$j < 4000} {incr j} {
|
|
r set "bar$j" x
|
|
}
|
|
# set some configs that will cause many loading progress events during aof loading
|
|
r config set key-load-delay 500
|
|
r config set dynamic-hz no
|
|
r config set hz 500
|
|
r DEBUG LOADAOF
|
|
assert_equal [r hooks.event_last loading-aof-start] 0
|
|
assert_equal [r hooks.event_last loading-end] 0
|
|
assert {[r hooks.event_count loading-rdb-start] == 0}
|
|
assert_lessthan 2 [r hooks.event_count loading-progress-rdb] ;# comes from the preamble section
|
|
assert_lessthan 2 [r hooks.event_count loading-progress-aof]
|
|
if {$::verbose} {
|
|
puts "rdb progress events [r hooks.event_count loading-progress-rdb]"
|
|
puts "aof progress events [r hooks.event_count loading-progress-aof]"
|
|
}
|
|
}
|
|
# undo configs before next test
|
|
r config set dynamic-hz yes
|
|
r config set key-load-delay 0
|
|
|
|
test {Test module rdb save hook} {
|
|
# debug reload does: save, flush, load:
|
|
assert {[r hooks.event_count persistence-syncrdb-start] == 0}
|
|
assert {[r hooks.event_count loading-rdb-start] == 0}
|
|
r debug reload
|
|
assert {[r hooks.event_count persistence-syncrdb-start] == 1}
|
|
assert {[r hooks.event_count loading-rdb-start] == 1}
|
|
}
|
|
|
|
test {Test key unlink hook} {
|
|
r set testkey1 hello
|
|
r del testkey1
|
|
assert {[r hooks.event_count key-info-testkey1] == 1}
|
|
assert_equal [r hooks.event_last key-info-testkey1] testkey1
|
|
r lpush testkey1 hello
|
|
r lpop testkey1
|
|
assert {[r hooks.event_count key-info-testkey1] == 2}
|
|
assert_equal [r hooks.event_last key-info-testkey1] testkey1
|
|
r set testkey2 world
|
|
r unlink testkey2
|
|
assert {[r hooks.event_count key-info-testkey2] == 1}
|
|
assert_equal [r hooks.event_last key-info-testkey2] testkey2
|
|
}
|
|
|
|
test {Test removed key event} {
|
|
r set str abcd
|
|
r set str abcde
|
|
# For String Type value is returned
|
|
assert_equal {abcd overwritten} [r hooks.is_key_removed str]
|
|
assert_equal -1 [r hooks.pexpireat str]
|
|
|
|
r del str
|
|
assert_equal {abcde deleted} [r hooks.is_key_removed str]
|
|
assert_equal -1 [r hooks.pexpireat str]
|
|
|
|
# test int encoded string
|
|
r set intstr 12345678
|
|
# incr doesn't fire event
|
|
r incr intstr
|
|
catch {[r hooks.is_key_removed intstr]} output
|
|
assert_match {ERR * removed} $output
|
|
r del intstr
|
|
assert_equal {12345679 deleted} [r hooks.is_key_removed intstr]
|
|
|
|
catch {[r hooks.is_key_removed not-exists]} output
|
|
assert_match {ERR * removed} $output
|
|
|
|
r hset hash f v
|
|
r hdel hash f
|
|
assert_equal {0 deleted} [r hooks.is_key_removed hash]
|
|
|
|
r hset hash f v a b
|
|
r del hash
|
|
assert_equal {2 deleted} [r hooks.is_key_removed hash]
|
|
|
|
r lpush list 1
|
|
r lpop list
|
|
assert_equal {0 deleted} [r hooks.is_key_removed list]
|
|
|
|
r lpush list 1 2 3
|
|
r del list
|
|
assert_equal {3 deleted} [r hooks.is_key_removed list]
|
|
|
|
r sadd set 1
|
|
r spop set
|
|
assert_equal {0 deleted} [r hooks.is_key_removed set]
|
|
|
|
r sadd set 1 2 3 4
|
|
r del set
|
|
assert_equal {4 deleted} [r hooks.is_key_removed set]
|
|
|
|
r zadd zset 1 f
|
|
r zpopmin zset
|
|
assert_equal {0 deleted} [r hooks.is_key_removed zset]
|
|
|
|
r zadd zset 1 f 2 d
|
|
r del zset
|
|
assert_equal {2 deleted} [r hooks.is_key_removed zset]
|
|
|
|
r xadd stream 1-1 f v
|
|
r xdel stream 1-1
|
|
# Stream does not delete object when del entry
|
|
catch {[r hooks.is_key_removed stream]} output
|
|
assert_match {ERR * removed} $output
|
|
r del stream
|
|
assert_equal {0 deleted} [r hooks.is_key_removed stream]
|
|
|
|
r xadd stream 2-1 f v
|
|
r del stream
|
|
assert_equal {1 deleted} [r hooks.is_key_removed stream]
|
|
|
|
# delete key because of active expire
|
|
set size [r dbsize]
|
|
r set active-expire abcd px 1
|
|
#ensure active expire
|
|
wait_for_condition 50 100 {
|
|
[r dbsize] == $size
|
|
} else {
|
|
fail "Active expire not trigger"
|
|
}
|
|
assert_equal {abcd expired} [r hooks.is_key_removed active-expire]
|
|
# current time is greater than pexpireat
|
|
set now [r time]
|
|
set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)]
|
|
assert {$mill >= [r hooks.pexpireat active-expire]}
|
|
|
|
# delete key because of lazy expire
|
|
r debug set-active-expire 0
|
|
r set lazy-expire abcd px 1
|
|
after 10
|
|
r get lazy-expire
|
|
assert_equal {abcd expired} [r hooks.is_key_removed lazy-expire]
|
|
set now [r time]
|
|
set mill [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)]
|
|
assert {$mill >= [r hooks.pexpireat lazy-expire]}
|
|
r debug set-active-expire 1
|
|
|
|
# delete key not yet expired
|
|
set now [r time]
|
|
set expireat [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+1000000]
|
|
r set not-expire abcd pxat $expireat
|
|
r del not-expire
|
|
assert_equal {abcd deleted} [r hooks.is_key_removed not-expire]
|
|
assert_equal $expireat [r hooks.pexpireat not-expire]
|
|
|
|
# Test key evict
|
|
set used [expr {[s used_memory] - [s mem_not_counted_for_evict]}]
|
|
set limit [expr {$used+100*1024}]
|
|
set old_policy [lindex [r config get maxmemory-policy] 1]
|
|
r config set maxmemory $limit
|
|
# We set policy volatile-random, so only keys with ttl will be evicted
|
|
r config set maxmemory-policy volatile-random
|
|
r setex volatile-key 10000 x
|
|
# We use SETBIT here, so we can set a big key and get the used_memory
|
|
# bigger than maxmemory. Next command will evict volatile keys. We
|
|
# can't use SET, as SET uses big input buffer, so it will fail.
|
|
r setbit big-key 1600000 0 ;# this will consume 200kb
|
|
r getbit big-key 0
|
|
assert_equal {x evicted} [r hooks.is_key_removed volatile-key]
|
|
r config set maxmemory-policy $old_policy
|
|
r config set maxmemory 0
|
|
} {OK} {needs:debug}
|
|
|
|
test {Test flushdb hooks} {
|
|
r flushdb
|
|
assert_equal [r hooks.event_last flush-start] 9
|
|
assert_equal [r hooks.event_last flush-end] 9
|
|
r flushall
|
|
assert_equal [r hooks.event_last flush-start] -1
|
|
assert_equal [r hooks.event_last flush-end] -1
|
|
}
|
|
|
|
# replication related tests
|
|
set master [srv 0 client]
|
|
set master_host [srv 0 host]
|
|
set master_port [srv 0 port]
|
|
start_server {} {
|
|
r module load $testmodule
|
|
set replica [srv 0 client]
|
|
set replica_host [srv 0 host]
|
|
set replica_port [srv 0 port]
|
|
$replica replicaof $master_host $master_port
|
|
|
|
wait_replica_online $master
|
|
|
|
test {Test master link up hook} {
|
|
assert_equal [r hooks.event_count masterlink-up] 1
|
|
assert_equal [r hooks.event_count masterlink-down] 0
|
|
}
|
|
|
|
test {Test role-replica hook} {
|
|
assert_equal [r hooks.event_count role-replica] 1
|
|
assert_equal [r hooks.event_count role-master] 0
|
|
assert_equal [r hooks.event_last role-replica] [s 0 master_host]
|
|
}
|
|
|
|
test {Test replica-online hook} {
|
|
assert_equal [r -1 hooks.event_count replica-online] 1
|
|
assert_equal [r -1 hooks.event_count replica-offline] 0
|
|
}
|
|
|
|
test {Test master link down hook} {
|
|
r client kill type master
|
|
assert_equal [r hooks.event_count masterlink-down] 1
|
|
|
|
wait_for_condition 50 100 {
|
|
[string match {*master_link_status:up*} [r info replication]]
|
|
} else {
|
|
fail "Replica didn't reconnect"
|
|
}
|
|
|
|
assert_equal [r hooks.event_count masterlink-down] 1
|
|
assert_equal [r hooks.event_count masterlink-up] 2
|
|
}
|
|
|
|
wait_for_condition 50 10 {
|
|
[string match {*master_link_status:up*} [r info replication]]
|
|
} else {
|
|
fail "Can't turn the instance into a replica"
|
|
}
|
|
|
|
$replica replicaof no one
|
|
|
|
test {Test role-master hook} {
|
|
assert_equal [r hooks.event_count role-replica] 1
|
|
assert_equal [r hooks.event_count role-master] 1
|
|
assert_equal [r hooks.event_last role-master] {}
|
|
}
|
|
|
|
test {Test replica-offline hook} {
|
|
assert_equal [r -1 hooks.event_count replica-online] 2
|
|
assert_equal [r -1 hooks.event_count replica-offline] 2
|
|
}
|
|
# get the replica stdout, to be used by the next test
|
|
set replica_stdout [srv 0 stdout]
|
|
}
|
|
|
|
test {Test swapdb hooks} {
|
|
r swapdb 0 10
|
|
assert_equal [r hooks.event_last swapdb-first] 0
|
|
assert_equal [r hooks.event_last swapdb-second] 10
|
|
}
|
|
|
|
test {Test configchange hooks} {
|
|
r config set rdbcompression no
|
|
assert_equal [r hooks.event_last config-change-count] 1
|
|
assert_equal [r hooks.event_last config-change-first] rdbcompression
|
|
}
|
|
|
|
# look into the log file of the server that just exited
|
|
test {Test shutdown hook} {
|
|
assert_equal [string match {*module-event-shutdown*} [exec tail -5 < $replica_stdout]] 1
|
|
}
|
|
}
|
|
}
|