summaryrefslogtreecommitdiff
path: root/tests/unit
diff options
context:
space:
mode:
authorBinbin <binloveplay1314@qq.com>2022-10-09 13:18:34 +0800
committerGitHub <noreply@github.com>2022-10-09 08:18:34 +0300
commit35b3fbd90c2ad2c503c9e3d28bfbffff13099925 (patch)
tree8ca44f6572095f44f29fcddcefb8bdac3490b806 /tests/unit
parentd2ad01ab3e24ca537efdd1fc6ff1ae2c657f4a51 (diff)
downloadredis-35b3fbd90c2ad2c503c9e3d28bfbffff13099925.tar.gz
Freeze time sampling during command execution, and scripts (#10300)
Freeze time during execution of scripts and all other commands. This means that a key is either expired or not, and doesn't change state during a script execution. resolves #10182 This PR try to add a new `commandTimeSnapshot` function. The function logic is extracted from `keyIsExpired`, but the related calls to `fixed_time_expire` and `mstime()` are removed, see below. In commands, we will avoid calling `mstime()` multiple times and just use the one that sampled in call. The background is, e.g. using `PEXPIRE 1` with valgrind sometimes result in the key being deleted rather than expired. The reason is that both `PEXPIRE` command and `checkAlreadyExpired` call `mstime()` separately. There are other more important changes in this PR: 1. Eliminate `fixed_time_expire`, it is no longer needed. When we want to sample time we should always use a time snapshot. We will use `in_nested_call` instead to update the cached time in `call`. 2. Move the call for `updateCachedTime` from `serverCron` to `afterSleep`. Now `commandTimeSnapshot` will always return the sample time, the `lookupKeyReadWithFlags` call in `getNodeByQuery` will get a outdated cached time (because `processCommand` is out of the `call` context). We put the call to `updateCachedTime` in `aftersleep`. 3. Cache the time each time the module lock Redis. Call `updateCachedTime` in `moduleGILAfterLock`, affecting `RM_ThreadSafeContextLock` and `RM_ThreadSafeContextTryLock` Currently the commandTimeSnapshot change affects the following TTL commands: - SET EX / SET PX - EXPIRE / PEXPIRE - SETEX / PSETEX - GETEX EX / GETEX PX - TTL / PTTL - EXPIRETIME / PEXPIRETIME - RESTORE key TTL And other commands just use the cached mstime (including TIME). This is considered to be a breaking change since it can break a script that uses a loop to wait for a key to expire.
Diffstat (limited to 'tests/unit')
-rw-r--r--tests/unit/scripting.tcl112
1 files changed, 112 insertions, 0 deletions
diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl
index 424017832..ca9422787 100644
--- a/tests/unit/scripting.tcl
+++ b/tests/unit/scripting.tcl
@@ -1376,6 +1376,118 @@ start_server {tags {"scripting needs:debug"}} {
r DEBUG set-active-expire 1
}
+ test "TIME command using cached time" {
+ set res [run_script {
+ local result1 = {redis.call("TIME")}
+ redis.call("DEBUG", "SLEEP", 0.01)
+ local result2 = {redis.call("TIME")}
+ return {result1, result2}
+ } 0]
+ assert_equal [lindex $res 0] [lindex $res 1]
+ }
+
+ test "Script block the time in some expiration related commands" {
+ # The test uses different commands to set the "same" expiration time for different keys,
+ # and interspersed with "DEBUG SLEEP", to verify that time is frozen in script.
+ # The commands involved are [P]TTL / SET EX[PX] / [P]EXPIRE / GETEX / [P]SETEX / [P]EXPIRETIME
+ set res [run_script {
+ redis.call("SET", "key1{t}", "value", "EX", 1)
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ redis.call("SET", "key2{t}", "value", "PX", 1000)
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ redis.call("SET", "key3{t}", "value")
+ redis.call("EXPIRE", "key3{t}", 1)
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ redis.call("SET", "key4{t}", "value")
+ redis.call("PEXPIRE", "key4{t}", 1000)
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ redis.call("SETEX", "key5{t}", 1, "value")
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ redis.call("PSETEX", "key6{t}", 1000, "value")
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ redis.call("SET", "key7{t}", "value")
+ redis.call("GETEX", "key7{t}", "EX", 1)
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ redis.call("SET", "key8{t}", "value")
+ redis.call("GETEX", "key8{t}", "PX", 1000)
+ redis.call("DEBUG", "SLEEP", 0.01)
+
+ local ttl_results = {redis.call("TTL", "key1{t}"),
+ redis.call("TTL", "key2{t}"),
+ redis.call("TTL", "key3{t}"),
+ redis.call("TTL", "key4{t}"),
+ redis.call("TTL", "key5{t}"),
+ redis.call("TTL", "key6{t}"),
+ redis.call("TTL", "key7{t}"),
+ redis.call("TTL", "key8{t}")}
+
+ local pttl_results = {redis.call("PTTL", "key1{t}"),
+ redis.call("PTTL", "key2{t}"),
+ redis.call("PTTL", "key3{t}"),
+ redis.call("PTTL", "key4{t}"),
+ redis.call("PTTL", "key5{t}"),
+ redis.call("PTTL", "key6{t}"),
+ redis.call("PTTL", "key7{t}"),
+ redis.call("PTTL", "key8{t}")}
+
+ local expiretime_results = {redis.call("EXPIRETIME", "key1{t}"),
+ redis.call("EXPIRETIME", "key2{t}"),
+ redis.call("EXPIRETIME", "key3{t}"),
+ redis.call("EXPIRETIME", "key4{t}"),
+ redis.call("EXPIRETIME", "key5{t}"),
+ redis.call("EXPIRETIME", "key6{t}"),
+ redis.call("EXPIRETIME", "key7{t}"),
+ redis.call("EXPIRETIME", "key8{t}")}
+
+ local pexpiretime_results = {redis.call("PEXPIRETIME", "key1{t}"),
+ redis.call("PEXPIRETIME", "key2{t}"),
+ redis.call("PEXPIRETIME", "key3{t}"),
+ redis.call("PEXPIRETIME", "key4{t}"),
+ redis.call("PEXPIRETIME", "key5{t}"),
+ redis.call("PEXPIRETIME", "key6{t}"),
+ redis.call("PEXPIRETIME", "key7{t}"),
+ redis.call("PEXPIRETIME", "key8{t}")}
+
+ return {ttl_results, pttl_results, expiretime_results, pexpiretime_results}
+ } 8 key1{t} key2{t} key3{t} key4{t} key5{t} key6{t} key7{t} key8{t}]
+
+ # The elements in each list are equal.
+ assert_equal 1 [llength [lsort -unique [lindex $res 0]]]
+ assert_equal 1 [llength [lsort -unique [lindex $res 1]]]
+ assert_equal 1 [llength [lsort -unique [lindex $res 2]]]
+ assert_equal 1 [llength [lsort -unique [lindex $res 3]]]
+
+ # Then we check that the expiration time is set successfully.
+ assert_morethan [lindex $res 0] 0
+ assert_morethan [lindex $res 1] 0
+ assert_morethan [lindex $res 2] 0
+ assert_morethan [lindex $res 3] 0
+ }
+
+ test "RESTORE expired keys with expiration time" {
+ set res [run_script {
+ redis.call("SET", "key1{t}", "value")
+ local encoded = redis.call("DUMP", "key1{t}")
+
+ redis.call("RESTORE", "key2{t}", 1, encoded, "REPLACE")
+ redis.call("DEBUG", "SLEEP", 0.01)
+ redis.call("RESTORE", "key3{t}", 1, encoded, "REPLACE")
+
+ return {redis.call("PEXPIRETIME", "key2{t}"), redis.call("PEXPIRETIME", "key3{t}")}
+ } 3 key1{t} key2{t} key3{t}]
+
+ # Can get the expiration time and they are all equal.
+ assert_morethan [lindex $res 0] 0
+ assert_equal [lindex $res 0] [lindex $res 1]
+ }
+
r debug set-disable-deny-scripts 0
}
} ;# foreach is_eval