summaryrefslogtreecommitdiff
path: root/tests/unit/expire.tcl
blob: 459503adc0b4899adc1774553daf1b3baf14687d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
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 < 10} {incr j} {
            r del x
            r setex x 1 somevalue
            after 900
            set a [r get x]
            after 1100
            set b [r get x]
            if {$a eq {somevalue} && $b eq {}} break
        }
        if {$::verbose} {
            puts "millisecond expire test attempts: $j"
        }
        list $a $b
    } {somevalue {}}

    test {PEXPIRE/PSETEX/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 < 30} {incr j} {
            r del x y z
            r psetex x 100 somevalue
            after 80
            set a [r get x]
            after 120
            set b [r get x]

            r set x somevalue
            r pexpire x 100
            after 80
            set c [r get x]
            after 120
            set d [r get x]

            r set x somevalue
            set now [r time]
            r pexpireat x [expr ([lindex $now 0]*1000)+([lindex $now 1]/1000)+200]
            after 20
            set e [r get x]
            after 220
            set f [r get x]

            if {$a eq {somevalue} && $b eq {} &&
                $c eq {somevalue} && $d eq {} &&
                $e eq {somevalue} && $f eq {}} break
        }
        if {$::verbose} {
            puts "sub-second expire test attempts: $j"
        }
        list $a $b $c $d $e $f
    } {somevalue {} somevalue {} 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 > 900 && $ttl <= 1000}
    }

    test {TTL / PTTL return -1 if key has no expire} {
        r del x
        r set x hello
        list [r ttl x] [r pttl x]
    } {-1 -1}

    test {TTL / PTTL return -2 if key does not exit} {
        r del x
        list [r ttl x] [r pttl x]
    } {-2 -2}

    test {Redis should actively expire keys incrementally} {
        r flushdb
        r psetex key1 500 a
        r psetex key2 500 a
        r psetex key3 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]
        list $size1 $size2
    } {3 0}

    test {Redis should lazy expire keys} {
        r flushdb
        r debug set-active-expire 0
        r psetex key1 500 a
        r psetex key2 500 a
        r psetex key3 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 key2 key3
        set size3 [r dbsize]
        r debug set-active-expire 1
        list $size1 $size2 $size3
    } {3 3 0}

    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}

    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
        lsort [r keys *]
    } {a e foo s t}

    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}

    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}

    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}

    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}

    test {EXPIRE with big integer overflows when converted to milliseconds} {
        r set foo bar
        catch {r EXPIRE foo 10000000000000000} e
        set e
    } {ERR invalid expire time in expire}

    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}

    test {EXPIRE with big negative integer} {
        r set foo bar
        catch {r EXPIRE foo -9999999999999999} e
        assert_match {ERR invalid expire time in expire} $e
        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}

    test {EXPIRE and SET/GETEX EX/PX/EXAT/PXAT option, TTL should not be reset after loadaof} {
        # 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!

        r config set appendonly yes
        r set foo1 bar EX 100
        r set foo2 bar PX 100000
        r set foo3 bar
        r set foo4 bar
        r expire foo3 100
        r pexpire foo4 100000
        r setex foo5 100 bar
        r psetex foo6 100000 bar
        r set foo7 bar EXAT [expr [clock seconds] + 100]
        r set foo8 bar PXAT [expr [clock milliseconds] + 100000]
        r set foo9 bar
        r getex foo9 EX 100
        r set foo10 bar
        r getex foo10 PX 100000
        r set foo11 bar
        r getex foo11 EXAT [expr [clock seconds] + 100]
        r set foo12 bar
        r getex foo12 PXAT [expr [clock milliseconds] + 100000]

        after 2000
        r debug loadaof
        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_range [r ttl foo11] 90 98
        assert_range [r ttl foo12] 90 98
    }

    test {EXPIRE relative and absolute propagation to replicas} {
        # Make sure that relative and absolute expire commands are propagated
        # "as is" to replicas.
        # We want replicas to honor the same high level contract of expires that
        # the master has, that is, we want the time to be counted logically
        # starting from the moment the write was received. This usually provides
        # the most coherent behavior from the point of view of the external
        # users, with TTLs that are similar from the POV of the external observer.
        #
        # This test is here to stop some innocent / eager optimization or cleanup
        # from doing the wrong thing without proper discussion, see:
        # https://github.com/redis/redis/pull/5171#issuecomment-409553266

        set repl [attach_to_replication_stream]
        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]+10000]
        r setex foo1 100 bar
        r psetex foo1 100000 bar
        r set foo2 bar
        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]
        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]+10000]
        assert_replication_stream $repl {
            {select *}
            {set foo1 bar PX 200000}
            {set foo1 bar PX 100000}
            {set foo1 bar PXAT *}
            {set foo1 bar PXAT *}
            {set foo1 bar PX 100000}
            {set foo1 bar PX 100000}
            {set foo2 bar}
            {expire foo2 100}
            {pexpire foo2 100000}
            {set foo3 bar}
            {expireat foo3 *}
            {pexpireat foo3 *}
            {del foo3}
            {set foo4 bar}
            {pexpire foo4 200000}
            {pexpire foo4 200000}
            {pexpireat foo4 *}
            {pexpireat foo4 *}
        }
    }

    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}
    }

    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 set foo bar EX 100
       r getex foo PERSIST
       after 2000
       r debug loadaof
       r ttl foo
    } {-1}

    test {GETEX propagate as to replica as PERSIST, DEL, or nothing} {
       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 PX 100000}
           {persist foo}
           {del foo}
        }
    }
}