1/* Implementation of EXPIRE (keys with fixed time to live).
2 *
3 * ----------------------------------------------------------------------------
4 *
5 * Copyright (c) 2009-2016, Salvatore Sanfilippo <antirez at gmail dot com>
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
10 *
11 * * Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of Redis nor the names of its contributors may be used
17 * to endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include "server.h"
34
35/*-----------------------------------------------------------------------------
36 * Incremental collection of expired keys.
37 *
38 * When keys are accessed they are expired on-access. However we need a
39 * mechanism in order to ensure keys are eventually removed when expired even
40 * if no access is performed on them.
41 *----------------------------------------------------------------------------*/
42
43/* Helper function for the activeExpireCycle() function.
44 * This function will try to expire the key that is stored in the hash table
45 * entry 'de' of the 'expires' hash table of a Redis database.
46 *
47 * If the key is found to be expired, it is removed from the database and
48 * 1 is returned. Otherwise no operation is performed and 0 is returned.
49 *
50 * When a key is expired, server.stat_expiredkeys is incremented.
51 *
52 * The parameter 'now' is the current time in milliseconds as is passed
53 * to the function to avoid too many gettimeofday() syscalls. */
54int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
55 long long t = dictGetSignedIntegerVal(de);
56 if (now > t) {
57 sds key = dictGetKey(de);
58 robj *keyobj = createStringObject(key,sdslen(key));
59 deleteExpiredKeyAndPropagate(db,keyobj);
60 decrRefCount(keyobj);
61 return 1;
62 } else {
63 return 0;
64 }
65}
66
67/* Try to expire a few timed out keys. The algorithm used is adaptive and
68 * will use few CPU cycles if there are few expiring keys, otherwise
69 * it will get more aggressive to avoid that too much memory is used by
70 * keys that can be removed from the keyspace.
71 *
72 * Every expire cycle tests multiple databases: the next call will start
73 * again from the next db. No more than CRON_DBS_PER_CALL databases are
74 * tested at every iteration.
75 *
76 * The function can perform more or less work, depending on the "type"
77 * argument. It can execute a "fast cycle" or a "slow cycle". The slow
78 * cycle is the main way we collect expired cycles: this happens with
79 * the "server.hz" frequency (usually 10 hertz).
80 *
81 * However the slow cycle can exit for timeout, since it used too much time.
82 * For this reason the function is also invoked to perform a fast cycle
83 * at every event loop cycle, in the beforeSleep() function. The fast cycle
84 * will try to perform less work, but will do it much more often.
85 *
86 * The following are the details of the two expire cycles and their stop
87 * conditions:
88 *
89 * If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
90 * "fast" expire cycle that takes no longer than ACTIVE_EXPIRE_CYCLE_FAST_DURATION
91 * microseconds, and is not repeated again before the same amount of time.
92 * The cycle will also refuse to run at all if the latest slow cycle did not
93 * terminate because of a time limit condition.
94 *
95 * If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
96 * executed, where the time limit is a percentage of the REDIS_HZ period
97 * as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. In the
98 * fast cycle, the check of every database is interrupted once the number
99 * of already expired keys in the database is estimated to be lower than
100 * a given percentage, in order to avoid doing too much work to gain too
101 * little memory.
102 *
103 * The configured expire "effort" will modify the baseline parameters in
104 * order to do more work in both the fast and slow expire cycles.
105 */
106
107#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
108#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
109#define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
110#define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
111 we do extra efforts. */
112
113void activeExpireCycle(int type) {
114 /* Adjust the running parameters according to the configured expire
115 * effort. The default effort is 1, and the maximum configurable effort
116 * is 10. */
117 unsigned long
118 effort = server.active_expire_effort-1, /* Rescale from 0 to 9. */
119 config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
120 ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
121 config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
122 ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
123 config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
124 2*effort,
125 config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
126 effort;
127
128 /* This function has some global state in order to continue the work
129 * incrementally across calls. */
130 static unsigned int current_db = 0; /* Next DB to test. */
131 static int timelimit_exit = 0; /* Time limit hit in previous call? */
132 static long long last_fast_cycle = 0; /* When last fast cycle ran. */
133
134 int j, iteration = 0;
135 int dbs_per_call = CRON_DBS_PER_CALL;
136 long long start = ustime(), timelimit, elapsed;
137
138 /* When clients are paused the dataset should be static not just from the
139 * POV of clients not being able to write, but also from the POV of
140 * expires and evictions of keys not being performed. */
141 if (checkClientPauseTimeoutAndReturnIfPaused()) return;
142
143 if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
144 /* Don't start a fast cycle if the previous cycle did not exit
145 * for time limit, unless the percentage of estimated stale keys is
146 * too high. Also never repeat a fast cycle for the same period
147 * as the fast cycle total duration itself. */
148 if (!timelimit_exit &&
149 server.stat_expired_stale_perc < config_cycle_acceptable_stale)
150 return;
151
152 if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
153 return;
154
155 last_fast_cycle = start;
156 }
157
158 /* We usually should test CRON_DBS_PER_CALL per iteration, with
159 * two exceptions:
160 *
161 * 1) Don't test more DBs than we have.
162 * 2) If last time we hit the time limit, we want to scan all DBs
163 * in this iteration, as there is work to do in some DB and we don't want
164 * expired keys to use memory for too much time. */
165 if (dbs_per_call > server.dbnum || timelimit_exit)
166 dbs_per_call = server.dbnum;
167
168 /* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
169 * time per iteration. Since this function gets called with a frequency of
170 * server.hz times per second, the following is the max amount of
171 * microseconds we can spend in this function. */
172 timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
173 timelimit_exit = 0;
174 if (timelimit <= 0) timelimit = 1;
175
176 if (type == ACTIVE_EXPIRE_CYCLE_FAST)
177 timelimit = config_cycle_fast_duration; /* in microseconds. */
178
179 /* Accumulate some global stats as we expire keys, to have some idea
180 * about the number of keys that are already logically expired, but still
181 * existing inside the database. */
182 long total_sampled = 0;
183 long total_expired = 0;
184
185 /* Sanity: There can't be any pending commands to propagate when
186 * we're in cron */
187 serverAssert(server.also_propagate.numops == 0);
188 server.core_propagates = 1;
189 server.propagate_no_multi = 1;
190
191 for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
192 /* Expired and checked in a single loop. */
193 unsigned long expired, sampled;
194
195 redisDb *db = server.db+(current_db % server.dbnum);
196
197 /* Increment the DB now so we are sure if we run out of time
198 * in the current DB we'll restart from the next. This allows to
199 * distribute the time evenly across DBs. */
200 current_db++;
201
202 /* Continue to expire if at the end of the cycle there are still
203 * a big percentage of keys to expire, compared to the number of keys
204 * we scanned. The percentage, stored in config_cycle_acceptable_stale
205 * is not fixed, but depends on the Redis configured "expire effort". */
206 do {
207 unsigned long num, slots;
208 long long now, ttl_sum;
209 int ttl_samples;
210 iteration++;
211
212 /* If there is nothing to expire try next DB ASAP. */
213 if ((num = dictSize(db->expires)) == 0) {
214 db->avg_ttl = 0;
215 break;
216 }
217 slots = dictSlots(db->expires);
218 now = mstime();
219
220 /* When there are less than 1% filled slots, sampling the key
221 * space is expensive, so stop here waiting for better times...
222 * The dictionary will be resized asap. */
223 if (slots > DICT_HT_INITIAL_SIZE &&
224 (num*100/slots < 1)) break;
225
226 /* The main collection cycle. Sample random keys among keys
227 * with an expire set, checking for expired ones. */
228 expired = 0;
229 sampled = 0;
230 ttl_sum = 0;
231 ttl_samples = 0;
232
233 if (num > config_keys_per_loop)
234 num = config_keys_per_loop;
235
236 /* Here we access the low level representation of the hash table
237 * for speed concerns: this makes this code coupled with dict.c,
238 * but it hardly changed in ten years.
239 *
240 * Note that certain places of the hash table may be empty,
241 * so we want also a stop condition about the number of
242 * buckets that we scanned. However scanning for free buckets
243 * is very fast: we are in the cache line scanning a sequential
244 * array of NULL pointers, so we can scan a lot more buckets
245 * than keys in the same time. */
246 long max_buckets = num*20;
247 long checked_buckets = 0;
248
249 while (sampled < num && checked_buckets < max_buckets) {
250 for (int table = 0; table < 2; table++) {
251 if (table == 1 && !dictIsRehashing(db->expires)) break;
252
253 unsigned long idx = db->expires_cursor;
254 idx &= DICTHT_SIZE_MASK(db->expires->ht_size_exp[table]);
255 dictEntry *de = db->expires->ht_table[table][idx];
256 long long ttl;
257
258 /* Scan the current bucket of the current table. */
259 checked_buckets++;
260 while(de) {
261 /* Get the next entry now since this entry may get
262 * deleted. */
263 dictEntry *e = de;
264 de = de->next;
265
266 ttl = dictGetSignedIntegerVal(e)-now;
267 if (activeExpireCycleTryExpire(db,e,now)) expired++;
268 if (ttl > 0) {
269 /* We want the average TTL of keys yet
270 * not expired. */
271 ttl_sum += ttl;
272 ttl_samples++;
273 }
274 sampled++;
275 }
276 }
277 db->expires_cursor++;
278 }
279 total_expired += expired;
280 total_sampled += sampled;
281
282 /* Update the average TTL stats for this database. */
283 if (ttl_samples) {
284 long long avg_ttl = ttl_sum/ttl_samples;
285
286 /* Do a simple running average with a few samples.
287 * We just use the current estimate with a weight of 2%
288 * and the previous estimate with a weight of 98%. */
289 if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
290 db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
291 }
292
293 /* We can't block forever here even if there are many keys to
294 * expire. So after a given amount of milliseconds return to the
295 * caller waiting for the other active expire cycle. */
296 if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
297 elapsed = ustime()-start;
298 if (elapsed > timelimit) {
299 timelimit_exit = 1;
300 server.stat_expired_time_cap_reached_count++;
301 break;
302 }
303 }
304 /* We don't repeat the cycle for the current database if there are
305 * an acceptable amount of stale keys (logically expired but yet
306 * not reclaimed). */
307 } while (sampled == 0 ||
308 (expired*100/sampled) > config_cycle_acceptable_stale);
309 }
310
311 serverAssert(server.core_propagates); /* This function should not be re-entrant */
312
313 /* Propagate all DELs */
314 propagatePendingCommands();
315
316 server.core_propagates = 0;
317 server.propagate_no_multi = 0;
318
319 elapsed = ustime()-start;
320 server.stat_expire_cycle_time_used += elapsed;
321 latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
322
323 /* Update our estimate of keys existing but yet to be expired.
324 * Running average with this sample accounting for 5%. */
325 double current_perc;
326 if (total_sampled) {
327 current_perc = (double)total_expired/total_sampled;
328 } else
329 current_perc = 0;
330 server.stat_expired_stale_perc = (current_perc*0.05)+
331 (server.stat_expired_stale_perc*0.95);
332}
333
334/*-----------------------------------------------------------------------------
335 * Expires of keys created in writable slaves
336 *
337 * Normally slaves do not process expires: they wait the masters to synthesize
338 * DEL operations in order to retain consistency. However writable slaves are
339 * an exception: if a key is created in the slave and an expire is assigned
340 * to it, we need a way to expire such a key, since the master does not know
341 * anything about such a key.
342 *
343 * In order to do so, we track keys created in the slave side with an expire
344 * set, and call the expireSlaveKeys() function from time to time in order to
345 * reclaim the keys if they already expired.
346 *
347 * Note that the use case we are trying to cover here, is a popular one where
348 * slaves are put in writable mode in order to compute slow operations in
349 * the slave side that are mostly useful to actually read data in a more
350 * processed way. Think at sets intersections in a tmp key, with an expire so
351 * that it is also used as a cache to avoid intersecting every time.
352 *
353 * This implementation is currently not perfect but a lot better than leaking
354 * the keys as implemented in 3.2.
355 *----------------------------------------------------------------------------*/
356
357/* The dictionary where we remember key names and database ID of keys we may
358 * want to expire from the slave. Since this function is not often used we
359 * don't even care to initialize the database at startup. We'll do it once
360 * the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
361 * is called.
362 *
363 * The dictionary has an SDS string representing the key as the hash table
364 * key, while the value is a 64 bit unsigned integer with the bits corresponding
365 * to the DB where the keys may exist set to 1. Currently the keys created
366 * with a DB id > 63 are not expired, but a trivial fix is to set the bitmap
367 * to the max 64 bit unsigned value when we know there is a key with a DB
368 * ID greater than 63, and check all the configured DBs in such a case. */
369dict *slaveKeysWithExpire = NULL;
370
371/* Check the set of keys created by the master with an expire set in order to
372 * check if they should be evicted. */
373void expireSlaveKeys(void) {
374 if (slaveKeysWithExpire == NULL ||
375 dictSize(slaveKeysWithExpire) == 0) return;
376
377 int cycles = 0, noexpire = 0;
378 mstime_t start = mstime();
379 while(1) {
380 dictEntry *de = dictGetRandomKey(slaveKeysWithExpire);
381 sds keyname = dictGetKey(de);
382 uint64_t dbids = dictGetUnsignedIntegerVal(de);
383 uint64_t new_dbids = 0;
384
385 /* Check the key against every database corresponding to the
386 * bits set in the value bitmap. */
387 int dbid = 0;
388 while(dbids && dbid < server.dbnum) {
389 if ((dbids & 1) != 0) {
390 redisDb *db = server.db+dbid;
391 dictEntry *expire = dictFind(db->expires,keyname);
392 int expired = 0;
393
394 if (expire &&
395 activeExpireCycleTryExpire(server.db+dbid,expire,start))
396 {
397 expired = 1;
398 }
399
400 /* If the key was not expired in this DB, we need to set the
401 * corresponding bit in the new bitmap we set as value.
402 * At the end of the loop if the bitmap is zero, it means we
403 * no longer need to keep track of this key. */
404 if (expire && !expired) {
405 noexpire++;
406 new_dbids |= (uint64_t)1 << dbid;
407 }
408 }
409 dbid++;
410 dbids >>= 1;
411 }
412
413 /* Set the new bitmap as value of the key, in the dictionary
414 * of keys with an expire set directly in the writable slave. Otherwise
415 * if the bitmap is zero, we no longer need to keep track of it. */
416 if (new_dbids)
417 dictSetUnsignedIntegerVal(de,new_dbids);
418 else
419 dictDelete(slaveKeysWithExpire,keyname);
420
421 /* Stop conditions: found 3 keys we can't expire in a row or
422 * time limit was reached. */
423 cycles++;
424 if (noexpire > 3) break;
425 if ((cycles % 64) == 0 && mstime()-start > 1) break;
426 if (dictSize(slaveKeysWithExpire) == 0) break;
427 }
428}
429
430/* Track keys that received an EXPIRE or similar command in the context
431 * of a writable slave. */
432void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
433 if (slaveKeysWithExpire == NULL) {
434 static dictType dt = {
435 dictSdsHash, /* hash function */
436 NULL, /* key dup */
437 NULL, /* val dup */
438 dictSdsKeyCompare, /* key compare */
439 dictSdsDestructor, /* key destructor */
440 NULL, /* val destructor */
441 NULL /* allow to expand */
442 };
443 slaveKeysWithExpire = dictCreate(&dt);
444 }
445 if (db->id > 63) return;
446
447 dictEntry *de = dictAddOrFind(slaveKeysWithExpire,key->ptr);
448 /* If the entry was just created, set it to a copy of the SDS string
449 * representing the key: we don't want to need to take those keys
450 * in sync with the main DB. The keys will be removed by expireSlaveKeys()
451 * as it scans to find keys to remove. */
452 if (de->key == key->ptr) {
453 de->key = sdsdup(key->ptr);
454 dictSetUnsignedIntegerVal(de,0);
455 }
456
457 uint64_t dbids = dictGetUnsignedIntegerVal(de);
458 dbids |= (uint64_t)1 << db->id;
459 dictSetUnsignedIntegerVal(de,dbids);
460}
461
462/* Return the number of keys we are tracking. */
463size_t getSlaveKeyWithExpireCount(void) {
464 if (slaveKeysWithExpire == NULL) return 0;
465 return dictSize(slaveKeysWithExpire);
466}
467
468/* Remove the keys in the hash table. We need to do that when data is
469 * flushed from the server. We may receive new keys from the master with
470 * the same name/db and it is no longer a good idea to expire them.
471 *
472 * Note: technically we should handle the case of a single DB being flushed
473 * but it is not worth it since anyway race conditions using the same set
474 * of key names in a writable slave and in its master will lead to
475 * inconsistencies. This is just a best-effort thing we do. */
476void flushSlaveKeysWithExpireList(void) {
477 if (slaveKeysWithExpire) {
478 dictRelease(slaveKeysWithExpire);
479 slaveKeysWithExpire = NULL;
480 }
481}
482
483int checkAlreadyExpired(long long when) {
484 /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
485 * should never be executed as a DEL when load the AOF or in the context
486 * of a slave instance.
487 *
488 * Instead we add the already expired key to the database with expire time
489 * (possibly in the past) and wait for an explicit DEL from the master. */
490 return (when <= mstime() && !server.loading && !server.masterhost);
491}
492
493#define EXPIRE_NX (1<<0)
494#define EXPIRE_XX (1<<1)
495#define EXPIRE_GT (1<<2)
496#define EXPIRE_LT (1<<3)
497
498/* Parse additional flags of expire commands
499 *
500 * Supported flags:
501 * - NX: set expiry only when the key has no expiry
502 * - XX: set expiry only when the key has an existing expiry
503 * - GT: set expiry only when the new expiry is greater than current one
504 * - LT: set expiry only when the new expiry is less than current one */
505int parseExtendedExpireArgumentsOrReply(client *c, int *flags) {
506 int nx = 0, xx = 0, gt = 0, lt = 0;
507
508 int j = 3;
509 while (j < c->argc) {
510 char *opt = c->argv[j]->ptr;
511 if (!strcasecmp(opt,"nx")) {
512 *flags |= EXPIRE_NX;
513 nx = 1;
514 } else if (!strcasecmp(opt,"xx")) {
515 *flags |= EXPIRE_XX;
516 xx = 1;
517 } else if (!strcasecmp(opt,"gt")) {
518 *flags |= EXPIRE_GT;
519 gt = 1;
520 } else if (!strcasecmp(opt,"lt")) {
521 *flags |= EXPIRE_LT;
522 lt = 1;
523 } else {
524 addReplyErrorFormat(c, "Unsupported option %s", opt);
525 return C_ERR;
526 }
527 j++;
528 }
529
530 if ((nx && xx) || (nx && gt) || (nx && lt)) {
531 addReplyError(c, "NX and XX, GT or LT options at the same time are not compatible");
532 return C_ERR;
533 }
534
535 if (gt && lt) {
536 addReplyError(c, "GT and LT options at the same time are not compatible");
537 return C_ERR;
538 }
539
540 return C_OK;
541}
542
543/*-----------------------------------------------------------------------------
544 * Expires Commands
545 *----------------------------------------------------------------------------*/
546
547/* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT
548 * and PEXPIREAT. Because the command second argument may be relative or absolute
549 * the "basetime" argument is used to signal what the base time is (either 0
550 * for *AT variants of the command, or the current time for relative expires).
551 *
552 * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
553 * the argv[2] parameter. The basetime is always specified in milliseconds.
554 *
555 * Additional flags are supported and parsed via parseExtendedExpireArguments */
556void expireGenericCommand(client *c, long long basetime, int unit) {
557 robj *key = c->argv[1], *param = c->argv[2];
558 long long when; /* unix time in milliseconds when the key will expire. */
559 long long current_expire = -1;
560 int flag = 0;
561
562 /* checking optional flags */
563 if (parseExtendedExpireArgumentsOrReply(c, &flag) != C_OK) {
564 return;
565 }
566
567 if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
568 return;
569
570 /* EXPIRE allows negative numbers, but we can at least detect an
571 * overflow by either unit conversion or basetime addition. */
572 if (unit == UNIT_SECONDS) {
573 if (when > LLONG_MAX / 1000 || when < LLONG_MIN / 1000) {
574 addReplyErrorExpireTime(c);
575 return;
576 }
577 when *= 1000;
578 }
579
580 if (when > LLONG_MAX - basetime) {
581 addReplyErrorExpireTime(c);
582 return;
583 }
584 when += basetime;
585
586 /* No key, return zero. */
587 if (lookupKeyWrite(c->db,key) == NULL) {
588 addReply(c,shared.czero);
589 return;
590 }
591
592 if (flag) {
593 current_expire = getExpire(c->db, key);
594
595 /* NX option is set, check current expiry */
596 if (flag & EXPIRE_NX) {
597 if (current_expire != -1) {
598 addReply(c,shared.czero);
599 return;
600 }
601 }
602
603 /* XX option is set, check current expiry */
604 if (flag & EXPIRE_XX) {
605 if (current_expire == -1) {
606 /* reply 0 when the key has no expiry */
607 addReply(c,shared.czero);
608 return;
609 }
610 }
611
612 /* GT option is set, check current expiry */
613 if (flag & EXPIRE_GT) {
614 /* When current_expire is -1, we consider it as infinite TTL,
615 * so expire command with gt always fail the GT. */
616 if (when <= current_expire || current_expire == -1) {
617 /* reply 0 when the new expiry is not greater than current */
618 addReply(c,shared.czero);
619 return;
620 }
621 }
622
623 /* LT option is set, check current expiry */
624 if (flag & EXPIRE_LT) {
625 /* When current_expire -1, we consider it as infinite TTL,
626 * but 'when' can still be negative at this point, so if there is
627 * an expiry on the key and it's not less than current, we fail the LT. */
628 if (current_expire != -1 && when >= current_expire) {
629 /* reply 0 when the new expiry is not less than current */
630 addReply(c,shared.czero);
631 return;
632 }
633 }
634 }
635
636 if (checkAlreadyExpired(when)) {
637 robj *aux;
638
639 int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
640 dbSyncDelete(c->db,key);
641 serverAssertWithInfo(c,key,deleted);
642 server.dirty++;
643
644 /* Replicate/AOF this as an explicit DEL or UNLINK. */
645 aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
646 rewriteClientCommandVector(c,2,aux,key);
647 signalModifiedKey(c,c->db,key);
648 notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
649 addReply(c, shared.cone);
650 return;
651 } else {
652 setExpire(c,c->db,key,when);
653 addReply(c,shared.cone);
654 /* Propagate as PEXPIREAT millisecond-timestamp */
655 robj *when_obj = createStringObjectFromLongLong(when);
656 rewriteClientCommandVector(c, 3, shared.pexpireat, key, when_obj);
657 decrRefCount(when_obj);
658 signalModifiedKey(c,c->db,key);
659 notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
660 server.dirty++;
661 return;
662 }
663}
664
665/* EXPIRE key seconds [ NX | XX | GT | LT] */
666void expireCommand(client *c) {
667 expireGenericCommand(c,mstime(),UNIT_SECONDS);
668}
669
670/* EXPIREAT key unix-time-seconds [ NX | XX | GT | LT] */
671void expireatCommand(client *c) {
672 expireGenericCommand(c,0,UNIT_SECONDS);
673}
674
675/* PEXPIRE key milliseconds [ NX | XX | GT | LT] */
676void pexpireCommand(client *c) {
677 expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
678}
679
680/* PEXPIREAT key unix-time-milliseconds [ NX | XX | GT | LT] */
681void pexpireatCommand(client *c) {
682 expireGenericCommand(c,0,UNIT_MILLISECONDS);
683}
684
685/* Implements TTL, PTTL, EXPIRETIME and PEXPIRETIME */
686void ttlGenericCommand(client *c, int output_ms, int output_abs) {
687 long long expire, ttl = -1;
688
689 /* If the key does not exist at all, return -2 */
690 if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == NULL) {
691 addReplyLongLong(c,-2);
692 return;
693 }
694
695 /* The key exists. Return -1 if it has no expire, or the actual
696 * TTL value otherwise. */
697 expire = getExpire(c->db,c->argv[1]);
698 if (expire != -1) {
699 ttl = output_abs ? expire : expire-mstime();
700 if (ttl < 0) ttl = 0;
701 }
702 if (ttl == -1) {
703 addReplyLongLong(c,-1);
704 } else {
705 addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
706 }
707}
708
709/* TTL key */
710void ttlCommand(client *c) {
711 ttlGenericCommand(c, 0, 0);
712}
713
714/* PTTL key */
715void pttlCommand(client *c) {
716 ttlGenericCommand(c, 1, 0);
717}
718
719/* EXPIRETIME key */
720void expiretimeCommand(client *c) {
721 ttlGenericCommand(c, 0, 1);
722}
723
724/* PEXPIRETIME key */
725void pexpiretimeCommand(client *c) {
726 ttlGenericCommand(c, 1, 1);
727}
728
729/* PERSIST key */
730void persistCommand(client *c) {
731 if (lookupKeyWrite(c->db,c->argv[1])) {
732 if (removeExpire(c->db,c->argv[1])) {
733 signalModifiedKey(c,c->db,c->argv[1]);
734 notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
735 addReply(c,shared.cone);
736 server.dirty++;
737 } else {
738 addReply(c,shared.czero);
739 }
740 } else {
741 addReply(c,shared.czero);
742 }
743}
744
745/* TOUCH key1 [key2 key3 ... keyN] */
746void touchCommand(client *c) {
747 int touched = 0;
748 for (int j = 1; j < c->argc; j++)
749 if (lookupKeyRead(c->db,c->argv[j]) != NULL) touched++;
750 addReplyLongLong(c,touched);
751}
752