Files
valkey/tests/unit/moduleapi/block_keyspace_notification.tcl
Rain ValentineandGitHub c21689ab4d [DEFLAKE] Fix flaky block_keyspace_notification pipelining test (#3294)
The test "Blocking keyspace notification with pipelining hset after
hget" was recently failing intermittently with two different errors:

1. `Expected [expr {114 * 10 < 1114}]` - timing assertion failed under Valgrind
2. `Timeout waiting for blocked clients` - race condition on normal runs

The test used wall-clock timing to verify that hget (non-blocking)
completed faster than hset (blocking). This is unreliable because:
- Valgrind slows execution 10-50x, making timing ratios meaningless
- Fast systems may complete both operations in <10ms, causing ratio failures

This fix replaces timing assertions with blocked client count checks,
which directly verify the blocking mechanism rather than inferring it
from timing. The test now confirms hget's response is available before
hset blocks, then waits for the blocked client count to transition
through the expected states.

Signed-off-by: Rain Valentine <[email protected]>
2026-03-03 15:56:28 +08:00

127 lines
4.9 KiB
Tcl

set testmodule [file normalize tests/modules/block_keyspace_notification.so]
start_server {tags {"modules"}} {
r module load $testmodule
test {Blocking keyspace notification one} {
wait_for_blocked_clients_count 0
r b_keyspace.clear
assert_equal "1" [r hset a b c]
assert_equal "{event hset key a}" [r b_keyspace.events]
}
test {Blocking keyspace notification two} {
wait_for_blocked_clients_count 0
r b_keyspace.clear
set rd1 [valkey_deferring_client]
$rd1 hset b c d
after 500
set rd2 [valkey_deferring_client]
$rd2 hset c d e
wait_for_blocked_clients_count 2
assert_equal "" [r b_keyspace.events]
wait_for_blocked_clients_count 1
assert_equal "{event hset key b}" [r b_keyspace.events]
wait_for_blocked_clients_count 0
assert_equal "{event hset key b} {event hset key c}" [r b_keyspace.events]
}
test {Blocking keyspace notification with pipelining hset after hget} {
wait_for_blocked_clients_count 0
r b_keyspace.clear
set rd1 [valkey_deferring_client]
$rd1 hset key_10 field_10 value_10
wait_for_blocked_clients_count 0
assert_equal "1" [$rd1 read]
pause_process [srv 0 pid]
# Queue up three commands: hget (non-blocking), hset (blocking), hget (non-blocking)
$rd1 hget key_10 field_10
$rd1 hset key_10 value_10 ss
$rd1 hget key_10 field_10
resume_process [srv 0 pid]
# First hget should return immediately (non-blocking)
assert_equal "value_10" [$rd1 read]
# hset triggers blocking keyspace notification - wait for it to block
wait_for_blocked_clients_count 1
# Now wait for hset to complete
wait_for_blocked_clients_count 0
assert_equal "1" [$rd1 read]
# Second hget returns after hset completes
assert_equal "value_10" [$rd1 read]
$rd1 close
}
test {Blocking keyspace notification with pipelining hget after hset} {
wait_for_blocked_clients_count 0
r b_keyspace.clear
set rd1 [valkey_deferring_client]
pause_process [srv 0 pid]
$rd1 hset key_2 field_1 value_1
$rd1 hget key_2 field_1
resume_process [srv 0 pid]
wait_for_blocked_clients_count 1
assert_equal "" [r b_keyspace.events]
wait_for_blocked_clients_count 0
assert_equal "{event hset key key_2}" [r b_keyspace.events]
assert_equal "1" [$rd1 read]
assert_equal "value_1" [$rd1 read]
}
test {Blocking keyspace notif during multi} {
wait_for_blocked_clients_count 0
r b_keyspace.clear
r multi
r hset d e f
r hset e f g
assert_equal "1 1" [r exec]
# Nothing should be blocked inside the transaction
assert_equal [s 0 blocked_clients] 0
assert_equal [r b_keyspace.events] ""
# In the background, the work should still occur
wait_for_condition 1000 50 {
[string match "{event hset key *} {event hset key *}" [set latest [r b_keyspace.events]]]
} else {
fail "Keyspace event not propagated within 5 seconds"
}
}
test {Event that fires twice} {
wait_for_blocked_clients_count 0
r b_keyspace.clear
r hset f g h
wait_for_blocked_clients_count 0
assert_equal "OK" [r b_keyspace.clear]
set rd1 [valkey_deferring_client]
$rd1 RENAME f g
# Only one blocked client
assert_equal [s 0 blocked_clients] 1
assert_equal "" [r b_keyspace.events]
# We should still see async processing for both events, which could be processed in either order
wait_for_condition 1000 50 {
[string match "{event rename_* key *} {event rename_* key *}" [set latest [r b_keyspace.events]]]
} else {
fail "Rename event not propagated within 5 seconds: latest value ${latest}"
}
}
test {Server-created keyspace notification} {
wait_for_blocked_clients_count 0
r b_keyspace.clear
assert_equal "1" [r hset h i j]
assert_equal "1" [r expire h 1]
assert_equal [s 0 blocked_clients] 0
# Expire will proceed processing in the background as we haven't
# enabled blocking for the command yet.
wait_for_condition 1000 50 {
[r b_keyspace.events] eq "{event hset key h} {event expire key h}"
} else {
fail "Expire event not propagated within 5 seconds"
}
assert_equal "OK" [r b_keyspace.clear]
# Expiration should happen in the background
wait_for_condition 1000 50 {
[r b_keyspace.events] eq "{event expired key h}"
} else {
fail "Expired event not propagated within 5 seconds"
}
}
test "Unload the module - testblockingkeyspacenotif" {
assert_equal {OK} [r module unload testblockingkeyspacenotif]
}
}