start_server {tags {"expire"}} { test {EXPIRE - set timeouts multiple times} { r set x foobar set v1 [r expire x 5] set v2 [r ttl x] set v3 [r expire x 10] set v4 [r ttl x] r expire x 2 list $v1 $v2 $v3 $v4 } {1 [45] 1 10} test {EXPIRE - It should be still possible to read 'x'} { r get x } {foobar} tags {"slow"} { test {EXPIRE - After 2.1 seconds the key should no longer be here} { after 2100 list [r get x] [r exists x] } {{} 0} } test {EXPIRE - write on expire should work} { r del x r lpush x foo r expire x 1000 r lpush x bar r lrange x 0 -1 } {bar foo} test {EXPIREAT - Check for EXPIRE alike behavior} { r del x r set x foo r expireat x [expr [clock seconds]+15] r ttl x } {1[345]} test {SETEX - Set + Expire combo operation. Check for TTL} { r setex x 12 test r ttl x } {1[012]} test {SETEX - Check value} { r get x } {test} test {SETEX - Overwrite old key} { r setex y 1 foo r get y } {foo} tags {"slow"} { test {SETEX - Wait for the key to expire} { after 1100 r get y } {} } test {SETEX - Wrong time parameter} { catch {r setex z -10 foo} e set _ $e } {*invalid expire*} test {PERSIST can undo an EXPIRE} { r set x foo r expire x 50 list [r ttl x] [r persist x] [r ttl x] [r get x] } {50 1 -1 foo} test {PERSIST returns 0 against non existing or non volatile keys} { r set x foo list [r persist foo] [r persist nokeyatall] } {0 0} test {EXPIRE precision is now the millisecond} { # This test is very likely to do a false positive if the # server is under pressure, so if it does not work give it a few more # chances. for {set j 0} {$j < 30} {incr j} { r del x r setex x 1 somevalue after 800 set a [r get x] if {$a ne {somevalue}} continue after 300 set b [r get x] if {$b eq {}} break } if {$::verbose} { puts "millisecond expire test attempts: $j" } assert_equal $a {somevalue} assert_equal $b {} } test "PSETEX can set sub-second expires" { # This test is very likely to do a false positive if the server is # under pressure, so if it does not work give it a few more chances. for {set j 0} {$j < 50} {incr j} { r del x r psetex x 100 somevalue set a [r get x] after 101 set b [r get x] if {$a eq {somevalue} && $b eq {}} break } if {$::verbose} { puts "PSETEX sub-second expire test attempts: $j" } list $a $b } {somevalue {}} test "PEXPIRE can set sub-second expires" { # This test is very likely to do a false positive if the server is # under pressure, so if it does not work give it a few more chances. for {set j 0} {$j < 50} {incr j} { r set x somevalue r pexpire x 100 set c [r get x] after 101 set d [r get x] if {$c eq {somevalue} && $d eq {}} break } if {$::verbose} { puts "PEXPIRE sub-second expire test attempts: $j" } list $c $d } {somevalue {}} test "PEXPIREAT can set sub-second expires" { # This test is very likely to do a false positive if the server is # under pressure, so if it does not work give it a few more chances. for {set j 0} {$j < 50} {incr j} { r set x somevalue set now [r time] r pexpireat x [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+200] set e [r get x] after 201 set f [r get x] if {$e eq {somevalue} && $f eq {}} break } if {$::verbose} { puts "PEXPIREAT sub-second expire test attempts: $j" } list $e $f } {somevalue {}} test {TTL returns time to live in seconds} { r del x r setex x 10 somevalue set ttl [r ttl x] assert {$ttl > 8 && $ttl <= 10} } test {PTTL returns time to live in milliseconds} { r del x r setex x 1 somevalue set ttl [r pttl x] assert {$ttl > 500 && $ttl <= 1000} } test {TTL / PTTL / EXPIRETIME / PEXPIRETIME return -1 if key has no expire} { r del x r set x hello list [r ttl x] [r pttl x] [r expiretime x] [r pexpiretime x] } {-1 -1 -1 -1} test {TTL / PTTL / EXPIRETIME / PEXPIRETIME return -2 if key does not exit} { r del x list [r ttl x] [r pttl x] [r expiretime x] [r pexpiretime x] } {-2 -2 -2 -2} test {EXPIRETIME returns absolute expiration time in seconds} { r del x set abs_expire [expr [clock seconds] + 100] r set x somevalue exat $abs_expire assert_equal [r expiretime x] $abs_expire } test {PEXPIRETIME returns absolute expiration time in milliseconds} { r del x set abs_expire [expr [clock milliseconds] + 100000] r set x somevalue pxat $abs_expire assert_equal [r pexpiretime x] $abs_expire } test {Redis should actively expire keys incrementally} { r flushdb r psetex key1 500 a r psetex key2 500 a r psetex key3 500 a assert_equal 3 [r dbsize] # Redis expires random keys ten times every second so we are # fairly sure that all the three keys should be evicted after # two seconds. wait_for_condition 20 100 { [r dbsize] eq 0 } fail { "Keys did not actively expire." } } test {Redis should lazy expire keys} { r flushdb r debug set-active-expire 0 r psetex key1{t} 500 a r psetex key2{t} 500 a r psetex key3{t} 500 a set size1 [r dbsize] # Redis expires random keys ten times every second so we are # fairly sure that all the three keys should be evicted after # one second. after 1000 set size2 [r dbsize] r mget key1{t} key2{t} key3{t} set size3 [r dbsize] r debug set-active-expire 1 list $size1 $size2 $size3 } {3 3 0} {needs:debug} test {EXPIRE should not resurrect keys (issue #1026)} { r debug set-active-expire 0 r set foo bar r pexpire foo 500 after 1000 r expire foo 10 r debug set-active-expire 1 r exists foo } {0} {needs:debug} test {5 keys in, 5 keys out} { r flushdb r set a c r expire a 5 r set t c r set e c r set s c r set foo b assert_equal [lsort [r keys *]] {a e foo s t} r del a ; # Do not leak volatile keys to other tests } test {EXPIRE with empty string as TTL should report an error} { r set foo bar catch {r expire foo ""} e set e } {*not an integer*} test {SET with EX with big integer should report an error} { catch {r set foo bar EX 10000000000000000} e set e } {ERR invalid expire time in 'set' command} test {SET with EX with smallest integer should report an error} { catch {r SET foo bar EX -9999999999999999} e set e } {ERR invalid expire time in 'set' command} test {GETEX with big integer should report an error} { r set foo bar catch {r GETEX foo EX 10000000000000000} e set e } {ERR invalid expire time in 'getex' command} test {GETEX with smallest integer should report an error} { r set foo bar catch {r GETEX foo EX -9999999999999999} e set e } {ERR invalid expire time in 'getex' command} test {EXPIRE with big integer overflows when converted to milliseconds} { r set foo bar # Hit `when > LLONG_MAX - basetime` assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 9223370399119966} # Hit `when > LLONG_MAX / 1000` assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 9223372036854776} assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 10000000000000000} assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo 18446744073709561} assert_equal {-1} [r ttl foo] } test {PEXPIRE with big integer overflow when basetime is added} { r set foo bar catch {r PEXPIRE foo 9223372036854770000} e set e } {ERR invalid expire time in 'pexpire' command} test {EXPIRE with big negative integer} { r set foo bar # Hit `when < LLONG_MIN / 1000` assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo -9223372036854776} assert_error "ERR invalid expire time in 'expire' command" {r EXPIRE foo -9999999999999999} r ttl foo } {-1} test {PEXPIREAT with big integer works} { r set foo bar r PEXPIREAT foo 9223372036854770000 } {1} test {PEXPIREAT with big negative integer works} { r set foo bar r PEXPIREAT foo -9223372036854770000 r ttl foo } {-2} # Start a new server with empty data and AOF file. start_server {overrides {appendonly {yes} appendfsync always} tags {external:skip}} { test {All time-to-live(TTL) in commands are propagated as absolute timestamp in milliseconds in AOF} { # This test makes sure that expire times are propagated as absolute # times to the AOF file and not as relative time, so that when the AOF # is reloaded the TTLs are not being shifted forward to the future. # We want the time to logically pass when the server is restarted! set aof [get_last_incr_aof_path r] # Apply each TTL-related command to a unique key # SET commands r set foo1 bar ex 100 r set foo2 bar px 100000 r set foo3 bar exat [expr [clock seconds]+100] r set foo4 bar PXAT [expr [clock milliseconds]+100000] r setex foo5 100 bar r psetex foo6 100000 bar # EXPIRE-family commands r set foo7 bar r expire foo7 100 r set foo8 bar r pexpire foo8 100000 r set foo9 bar r expireat foo9 [expr [clock seconds]+100] r set foo10 bar r pexpireat foo10 [expr [clock seconds]*1000+100000] r set foo11 bar r expireat foo11 [expr [clock seconds]-100] # GETEX commands r set foo12 bar r getex foo12 ex 100 r set foo13 bar r getex foo13 px 100000 r set foo14 bar r getex foo14 exat [expr [clock seconds]+100] r set foo15 bar r getex foo15 pxat [expr [clock milliseconds]+100000] # RESTORE commands r set foo16 bar set encoded [r dump foo16] r restore foo17 100000 $encoded r restore foo18 [expr [clock milliseconds]+100000] $encoded absttl # Assert that each TTL-relatd command are persisted with absolute timestamps in AOF assert_aof_content $aof { {select *} {set foo1 bar PXAT *} {set foo2 bar PXAT *} {set foo3 bar PXAT *} {set foo4 bar PXAT *} {set foo5 bar PXAT *} {set foo6 bar PXAT *} {set foo7 bar} {pexpireat foo7 *} {set foo8 bar} {pexpireat foo8 *} {set foo9 bar} {pexpireat foo9 *} {set foo10 bar} {pexpireat foo10 *} {set foo11 bar} {del foo11} {set foo12 bar} {pexpireat foo12 *} {set foo13 bar} {pexpireat foo13 *} {set foo14 bar} {pexpireat foo14 *} {set foo15 bar} {pexpireat foo15 *} {set foo16 bar} {restore foo17 * {*} ABSTTL} {restore foo18 * {*} absttl} } # Remember the absolute TTLs of all the keys set ttl1 [r pexpiretime foo1] set ttl2 [r pexpiretime foo2] set ttl3 [r pexpiretime foo3] set ttl4 [r pexpiretime foo4] set ttl5 [r pexpiretime foo5] set ttl6 [r pexpiretime foo6] set ttl7 [r pexpiretime foo7] set ttl8 [r pexpiretime foo8] set ttl9 [r pexpiretime foo9] set ttl10 [r pexpiretime foo10] assert_equal "-2" [r pexpiretime foo11] ; # foo11 is gone set ttl12 [r pexpiretime foo12] set ttl13 [r pexpiretime foo13] set ttl14 [r pexpiretime foo14] set ttl15 [r pexpiretime foo15] assert_equal "-1" [r pexpiretime foo16] ; # foo16 has no TTL set ttl17 [r pexpiretime foo17] set ttl18 [r pexpiretime foo18] # Let some time pass and reload data from AOF after 2000 r debug loadaof # Assert that relative TTLs are roughly the same assert_range [r ttl foo1] 90 98 assert_range [r ttl foo2] 90 98 assert_range [r ttl foo3] 90 98 assert_range [r ttl foo4] 90 98 assert_range [r ttl foo5] 90 98 assert_range [r ttl foo6] 90 98 assert_range [r ttl foo7] 90 98 assert_range [r ttl foo8] 90 98 assert_range [r ttl foo9] 90 98 assert_range [r ttl foo10] 90 98 assert_equal [r ttl foo11] "-2" ; # foo11 is gone assert_range [r ttl foo12] 90 98 assert_range [r ttl foo13] 90 98 assert_range [r ttl foo14] 90 98 assert_range [r ttl foo15] 90 98 assert_equal [r ttl foo16] "-1" ; # foo16 has no TTL assert_range [r ttl foo17] 90 98 assert_range [r ttl foo18] 90 98 # Assert that all keys have restored the same absolute TTLs from AOF assert_equal [r pexpiretime foo1] $ttl1 assert_equal [r pexpiretime foo2] $ttl2 assert_equal [r pexpiretime foo3] $ttl3 assert_equal [r pexpiretime foo4] $ttl4 assert_equal [r pexpiretime foo5] $ttl5 assert_equal [r pexpiretime foo6] $ttl6 assert_equal [r pexpiretime foo7] $ttl7 assert_equal [r pexpiretime foo8] $ttl8 assert_equal [r pexpiretime foo9] $ttl9 assert_equal [r pexpiretime foo10] $ttl10 assert_equal [r pexpiretime foo11] "-2" ; # foo11 is gone assert_equal [r pexpiretime foo12] $ttl12 assert_equal [r pexpiretime foo13] $ttl13 assert_equal [r pexpiretime foo14] $ttl14 assert_equal [r pexpiretime foo15] $ttl15 assert_equal [r pexpiretime foo16] "-1" ; # foo16 has no TTL assert_equal [r pexpiretime foo17] $ttl17 assert_equal [r pexpiretime foo18] $ttl18 } {} {needs:debug} } test {All TTL in commands are propagated as absolute timestamp in replication stream} { # Make sure that both relative and absolute expire commands are propagated # as absolute to replicas for two reasons: # 1) We want to avoid replicas retaining data much longer than primary due # to replication lag. # 2) We want to unify the way TTLs are replicated in both RDB and replication # stream, which is as absolute timestamps. # See: https://github.com/redis/redis/issues/8433 r flushall ; # Clean up keyspace to avoid interference by keys from other tests set repl [attach_to_replication_stream] # SET commands r set foo1 bar ex 200 r set foo1 bar px 100000 r set foo1 bar exat [expr [clock seconds]+100] r set foo1 bar pxat [expr [clock milliseconds]+100000] r setex foo1 100 bar r psetex foo1 100000 bar r set foo2 bar # EXPIRE-family commands r expire foo2 100 r pexpire foo2 100000 r set foo3 bar r expireat foo3 [expr [clock seconds]+100] r pexpireat foo3 [expr [clock seconds]*1000+100000] r expireat foo3 [expr [clock seconds]-100] # GETEX-family commands r set foo4 bar r getex foo4 ex 200 r getex foo4 px 200000 r getex foo4 exat [expr [clock seconds]+100] r getex foo4 pxat [expr [clock milliseconds]+100000] # RESTORE commands r set foo5 bar set encoded [r dump foo5] r restore foo6 100000 $encoded r restore foo7 [expr [clock milliseconds]+100000] $encoded absttl assert_replication_stream $repl { {select *} {set foo1 bar PXAT *} {set foo1 bar PXAT *} {set foo1 bar PXAT *} {set foo1 bar pxat *} {set foo1 bar PXAT *} {set foo1 bar PXAT *} {set foo2 bar} {pexpireat foo2 *} {pexpireat foo2 *} {set foo3 bar} {pexpireat foo3 *} {pexpireat foo3 *} {del foo3} {set foo4 bar} {pexpireat foo4 *} {pexpireat foo4 *} {pexpireat foo4 *} {pexpireat foo4 *} {set foo5 bar} {restore foo6 * {*} ABSTTL} {restore foo7 * {*} absttl} } close_replication_stream $repl } {} {needs:repl} # Start another server to test replication of TTLs start_server {tags {needs:repl external:skip}} { # Set the outer layer server as primary set primary [srv -1 client] set primary_host [srv -1 host] set primary_port [srv -1 port] # Set this inner layer server as replica set replica [srv 0 client] test {First server should have role slave after REPLICAOF} { $replica replicaof $primary_host $primary_port wait_for_condition 50 100 { [s 0 role] eq {slave} } else { fail "Replication not started." } } test {For all replicated TTL-related commands, absolute expire times are identical on primary and replica} { # Apply each TTL-related command to a unique key on primary # SET commands $primary set foo1 bar ex 100 $primary set foo2 bar px 100000 $primary set foo3 bar exat [expr [clock seconds]+100] $primary set foo4 bar pxat [expr [clock milliseconds]+100000] $primary setex foo5 100 bar $primary psetex foo6 100000 bar # EXPIRE-family commands $primary set foo7 bar $primary expire foo7 100 $primary set foo8 bar $primary pexpire foo8 100000 $primary set foo9 bar $primary expireat foo9 [expr [clock seconds]+100] $primary set foo10 bar $primary pexpireat foo10 [expr [clock milliseconds]+100000] # GETEX commands $primary set foo11 bar $primary getex foo11 ex 100 $primary set foo12 bar $primary getex foo12 px 100000 $primary set foo13 bar $primary getex foo13 exat [expr [clock seconds]+100] $primary set foo14 bar $primary getex foo14 pxat [expr [clock milliseconds]+100000] # RESTORE commands $primary set foo15 bar set encoded [$primary dump foo15] $primary restore foo16 100000 $encoded $primary restore foo17 [expr [clock milliseconds]+100000] $encoded absttl # Wait for replica to get the keys and TTLs assert {[$primary wait 1 0] == 1} # Verify absolute TTLs are identical on primary and replica for all keys # This is because TTLs are always replicated as absolute values foreach key [$primary keys *] { assert_equal [$primary pexpiretime $key] [$replica pexpiretime $key] } } test {expired key which is created in writeable replicas should be deleted by active expiry} { $primary flushall $replica config set replica-read-only no foreach {yes_or_no} {yes no} { $replica config set appendonly $yes_or_no waitForBgrewriteaof $replica set prev_expired [s expired_keys] $replica set foo bar PX 1 wait_for_condition 100 10 { [s expired_keys] eq $prev_expired + 1 } else { fail "key not expired" } assert_equal {} [$replica get foo] } } } test {SET command will remove expire} { r set foo bar EX 100 r set foo bar r ttl foo } {-1} test {SET - use KEEPTTL option, TTL should not be removed} { r set foo bar EX 100 r set foo bar KEEPTTL set ttl [r ttl foo] assert {$ttl <= 100 && $ttl > 90} } test {SET - use KEEPTTL option, TTL should not be removed after loadaof} { r config set appendonly yes r set foo bar EX 100 r set foo bar2 KEEPTTL after 2000 r debug loadaof set ttl [r ttl foo] assert {$ttl <= 98 && $ttl > 90} } {} {needs:debug} test {GETEX use of PERSIST option should remove TTL} { r set foo bar EX 100 r getex foo PERSIST r ttl foo } {-1} test {GETEX use of PERSIST option should remove TTL after loadaof} { r config set appendonly yes r set foo bar EX 100 r getex foo PERSIST r debug loadaof r ttl foo } {-1} {needs:debug} test {GETEX propagate as to replica as PERSIST, DEL, or nothing} { # In the above tests, many keys with random expiration times are set, flush # the DBs to avoid active expiry kicking in and messing the replication streams. r flushall set repl [attach_to_replication_stream] r set foo bar EX 100 r getex foo PERSIST r getex foo r getex foo exat [expr [clock seconds]-100] assert_replication_stream $repl { {select *} {set foo bar PXAT *} {persist foo} {del foo} } close_replication_stream $repl } {} {needs:repl} test {EXPIRE with NX option on a key with ttl} { r SET foo bar EX 100 assert_equal [r EXPIRE foo 200 NX] 0 assert_range [r TTL foo] 50 100 } {} test {EXPIRE with NX option on a key without ttl} { r SET foo bar assert_equal [r EXPIRE foo 200 NX] 1 assert_range [r TTL foo] 100 200 } {} test {EXPIRE with XX option on a key with ttl} { r SET foo bar EX 100 assert_equal [r EXPIRE foo 200 XX] 1 assert_range [r TTL foo] 100 200 } {} test {EXPIRE with XX option on a key without ttl} { r SET foo bar assert_equal [r EXPIRE foo 200 XX] 0 assert_equal [r TTL foo] -1 } {} test {EXPIRE with GT option on a key with lower ttl} { r SET foo bar EX 100 assert_equal [r EXPIRE foo 200 GT] 1 assert_range [r TTL foo] 100 200 } {} test {EXPIRE with GT option on a key with higher ttl} { r SET foo bar EX 200 assert_equal [r EXPIRE foo 100 GT] 0 assert_range [r TTL foo] 100 200 } {} test {EXPIRE with GT option on a key without ttl} { r SET foo bar assert_equal [r EXPIRE foo 200 GT] 0 assert_equal [r TTL foo] -1 } {} test {EXPIRE with LT option on a key with higher ttl} { r SET foo bar EX 100 assert_equal [r EXPIRE foo 200 LT] 0 assert_range [r TTL foo] 50 100 } {} test {EXPIRE with LT option on a key with lower ttl} { r SET foo bar EX 200 assert_equal [r EXPIRE foo 100 LT] 1 assert_range [r TTL foo] 50 100 } {} test {EXPIRE with LT option on a key without ttl} { r SET foo bar assert_equal [r EXPIRE foo 100 LT] 1 assert_range [r TTL foo] 50 100 } {} test {EXPIRE with LT and XX option on a key with ttl} { r SET foo bar EX 200 assert_equal [r EXPIRE foo 100 LT XX] 1 assert_range [r TTL foo] 50 100 } {} test {EXPIRE with LT and XX option on a key without ttl} { r SET foo bar assert_equal [r EXPIRE foo 200 LT XX] 0 assert_equal [r TTL foo] -1 } {} test {EXPIRE with conflicting options: LT GT} { catch {r EXPIRE foo 200 LT GT} e set e } {ERR GT and LT options at the same time are not compatible} test {EXPIRE with conflicting options: NX GT} { catch {r EXPIRE foo 200 NX GT} e set e } {ERR NX and XX, GT or LT options at the same time are not compatible} test {EXPIRE with conflicting options: NX LT} { catch {r EXPIRE foo 200 NX LT} e set e } {ERR NX and XX, GT or LT options at the same time are not compatible} test {EXPIRE with conflicting options: NX XX} { catch {r EXPIRE foo 200 NX XX} e set e } {ERR NX and XX, GT or LT options at the same time are not compatible} test {EXPIRE with unsupported options} { catch {r EXPIRE foo 200 AB} e set e } {ERR Unsupported option AB} test {EXPIRE with unsupported options} { catch {r EXPIRE foo 200 XX AB} e set e } {ERR Unsupported option AB} test {EXPIRE with negative expiry} { r SET foo bar EX 100 assert_equal [r EXPIRE foo -10 LT] 1 assert_equal [r TTL foo] -2 } {} test {EXPIRE with negative expiry on a non-valitale key} { r SET foo bar assert_equal [r EXPIRE foo -10 LT] 1 assert_equal [r TTL foo] -2 } {} test {EXPIRE with non-existed key} { assert_equal [r EXPIRE none 100 NX] 0 assert_equal [r EXPIRE none 100 XX] 0 assert_equal [r EXPIRE none 100 GT] 0 assert_equal [r EXPIRE none 100 LT] 0 } {} test {Redis should not propagate the read command on lazy expire} { r debug set-active-expire 0 r flushall ; # Clean up keyspace to avoid interference by keys from other tests r set foo bar PX 1 set repl [attach_to_replication_stream] wait_for_condition 50 100 { [r get foo] eq {} } else { fail "Replication not started." } # dummy command to verify nothing else gets into the replication stream. r set x 1 assert_replication_stream $repl { {select *} {del foo} {set x 1} } close_replication_stream $repl assert_equal [r debug set-active-expire 1] {OK} } {} {needs:debug} test {SCAN: Lazy-expire should not be wrapped in MULTI/EXEC} { r debug set-active-expire 0 r flushall r set foo1 bar PX 1 r set foo2 bar PX 1 after 2 set repl [attach_to_replication_stream] r scan 0 assert_replication_stream $repl { {select *} {del foo*} {del foo*} } close_replication_stream $repl assert_equal [r debug set-active-expire 1] {OK} } {} {needs:debug} test {RANDOMKEY: Lazy-expire should not be wrapped in MULTI/EXEC} { r debug set-active-expire 0 r flushall r set foo1 bar PX 1 r set foo2 bar PX 1 after 2 set repl [attach_to_replication_stream] r randomkey assert_replication_stream $repl { {select *} {del foo*} {del foo*} } close_replication_stream $repl assert_equal [r debug set-active-expire 1] {OK} } {} {needs:debug} }