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. */
activeExpireCycleTryExpire(redisDb * db,dictEntry * de,long long now)54 int 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 
60         propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
61         if (server.lazyfree_lazy_expire)
62             dbAsyncDelete(db,keyobj);
63         else
64             dbSyncDelete(db,keyobj);
65         notifyKeyspaceEvent(NOTIFY_EXPIRED,
66             "expired",keyobj,db->id);
67         signalModifiedKey(NULL, db, keyobj);
68         decrRefCount(keyobj);
69         server.stat_expiredkeys++;
70         return 1;
71     } else {
72         return 0;
73     }
74 }
75 
76 /* Try to expire a few timed out keys. The algorithm used is adaptive and
77  * will use few CPU cycles if there are few expiring keys, otherwise
78  * it will get more aggressive to avoid that too much memory is used by
79  * keys that can be removed from the keyspace.
80  *
81  * Every expire cycle tests multiple databases: the next call will start
82  * again from the next db, with the exception of exists for time limit: in that
83  * case we restart again from the last database we were processing. Anyway
84  * no more than CRON_DBS_PER_CALL databases are tested at every iteration.
85  *
86  * The function can perform more or less work, depending on the "type"
87  * argument. It can execute a "fast cycle" or a "slow cycle". The slow
88  * cycle is the main way we collect expired cycles: this happens with
89  * the "server.hz" frequency (usually 10 hertz).
90  *
91  * However the slow cycle can exit for timeout, since it used too much time.
92  * For this reason the function is also invoked to perform a fast cycle
93  * at every event loop cycle, in the beforeSleep() function. The fast cycle
94  * will try to perform less work, but will do it much more often.
95  *
96  * The following are the details of the two expire cycles and their stop
97  * conditions:
98  *
99  * If type is ACTIVE_EXPIRE_CYCLE_FAST the function will try to run a
100  * "fast" expire cycle that takes no longer than ACTIVE_EXPIRE_CYCLE_FAST_DURATION
101  * microseconds, and is not repeated again before the same amount of time.
102  * The cycle will also refuse to run at all if the latest slow cycle did not
103  * terminate because of a time limit condition.
104  *
105  * If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
106  * executed, where the time limit is a percentage of the REDIS_HZ period
107  * as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. In the
108  * fast cycle, the check of every database is interrupted once the number
109  * of already expired keys in the database is estimated to be lower than
110  * a given percentage, in order to avoid doing too much work to gain too
111  * little memory.
112  *
113  * The configured expire "effort" will modify the baseline parameters in
114  * order to do more work in both the fast and slow expire cycles.
115  */
116 
117 #define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /* Keys for each DB loop. */
118 #define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds. */
119 #define ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 25 /* Max % of CPU to use. */
120 #define ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE 10 /* % of stale keys after which
121                                                    we do extra efforts. */
122 
activeExpireCycle(int type)123 void activeExpireCycle(int type) {
124     /* Adjust the running parameters according to the configured expire
125      * effort. The default effort is 1, and the maximum configurable effort
126      * is 10. */
127     unsigned long
128     effort = server.active_expire_effort-1, /* Rescale from 0 to 9. */
129     config_keys_per_loop = ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +
130                            ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
131     config_cycle_fast_duration = ACTIVE_EXPIRE_CYCLE_FAST_DURATION +
132                                  ACTIVE_EXPIRE_CYCLE_FAST_DURATION/4*effort,
133     config_cycle_slow_time_perc = ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC +
134                                   2*effort,
135     config_cycle_acceptable_stale = ACTIVE_EXPIRE_CYCLE_ACCEPTABLE_STALE-
136                                     effort;
137 
138     /* This function has some global state in order to continue the work
139      * incrementally across calls. */
140     static unsigned int current_db = 0; /* Last DB tested. */
141     static int timelimit_exit = 0;      /* Time limit hit in previous call? */
142     static long long last_fast_cycle = 0; /* When last fast cycle ran. */
143 
144     int j, iteration = 0;
145     int dbs_per_call = CRON_DBS_PER_CALL;
146     long long start = ustime(), timelimit, elapsed;
147 
148     /* When clients are paused the dataset should be static not just from the
149      * POV of clients not being able to write, but also from the POV of
150      * expires and evictions of keys not being performed. */
151     if (clientsArePaused()) return;
152 
153     if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
154         /* Don't start a fast cycle if the previous cycle did not exit
155          * for time limit, unless the percentage of estimated stale keys is
156          * too high. Also never repeat a fast cycle for the same period
157          * as the fast cycle total duration itself. */
158         if (!timelimit_exit &&
159             server.stat_expired_stale_perc < config_cycle_acceptable_stale)
160             return;
161 
162         if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
163             return;
164 
165         last_fast_cycle = start;
166     }
167 
168     /* We usually should test CRON_DBS_PER_CALL per iteration, with
169      * two exceptions:
170      *
171      * 1) Don't test more DBs than we have.
172      * 2) If last time we hit the time limit, we want to scan all DBs
173      * in this iteration, as there is work to do in some DB and we don't want
174      * expired keys to use memory for too much time. */
175     if (dbs_per_call > server.dbnum || timelimit_exit)
176         dbs_per_call = server.dbnum;
177 
178     /* We can use at max 'config_cycle_slow_time_perc' percentage of CPU
179      * time per iteration. Since this function gets called with a frequency of
180      * server.hz times per second, the following is the max amount of
181      * microseconds we can spend in this function. */
182     timelimit = config_cycle_slow_time_perc*1000000/server.hz/100;
183     timelimit_exit = 0;
184     if (timelimit <= 0) timelimit = 1;
185 
186     if (type == ACTIVE_EXPIRE_CYCLE_FAST)
187         timelimit = config_cycle_fast_duration; /* in microseconds. */
188 
189     /* Accumulate some global stats as we expire keys, to have some idea
190      * about the number of keys that are already logically expired, but still
191      * existing inside the database. */
192     long total_sampled = 0;
193     long total_expired = 0;
194 
195     for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
196         /* Expired and checked in a single loop. */
197         unsigned long expired, sampled;
198 
199         redisDb *db = server.db+(current_db % server.dbnum);
200 
201         /* Increment the DB now so we are sure if we run out of time
202          * in the current DB we'll restart from the next. This allows to
203          * distribute the time evenly across DBs. */
204         current_db++;
205 
206         /* Continue to expire if at the end of the cycle there are still
207          * a big percentage of keys to expire, compared to the number of keys
208          * we scanned. The percentage, stored in config_cycle_acceptable_stale
209          * is not fixed, but depends on the Redis configured "expire effort". */
210         do {
211             unsigned long num, slots;
212             long long now, ttl_sum;
213             int ttl_samples;
214             iteration++;
215 
216             /* If there is nothing to expire try next DB ASAP. */
217             if ((num = dictSize(db->expires)) == 0) {
218                 db->avg_ttl = 0;
219                 break;
220             }
221             slots = dictSlots(db->expires);
222             now = mstime();
223 
224             /* When there are less than 1% filled slots, sampling the key
225              * space is expensive, so stop here waiting for better times...
226              * The dictionary will be resized asap. */
227             if (num && slots > DICT_HT_INITIAL_SIZE &&
228                 (num*100/slots < 1)) break;
229 
230             /* The main collection cycle. Sample random keys among keys
231              * with an expire set, checking for expired ones. */
232             expired = 0;
233             sampled = 0;
234             ttl_sum = 0;
235             ttl_samples = 0;
236 
237             if (num > config_keys_per_loop)
238                 num = config_keys_per_loop;
239 
240             /* Here we access the low level representation of the hash table
241              * for speed concerns: this makes this code coupled with dict.c,
242              * but it hardly changed in ten years.
243              *
244              * Note that certain places of the hash table may be empty,
245              * so we want also a stop condition about the number of
246              * buckets that we scanned. However scanning for free buckets
247              * is very fast: we are in the cache line scanning a sequential
248              * array of NULL pointers, so we can scan a lot more buckets
249              * than keys in the same time. */
250             long max_buckets = num*20;
251             long checked_buckets = 0;
252 
253             while (sampled < num && checked_buckets < max_buckets) {
254                 for (int table = 0; table < 2; table++) {
255                     if (table == 1 && !dictIsRehashing(db->expires)) break;
256 
257                     unsigned long idx = db->expires_cursor;
258                     idx &= db->expires->ht[table].sizemask;
259                     dictEntry *de = db->expires->ht[table].table[idx];
260                     long long ttl;
261 
262                     /* Scan the current bucket of the current table. */
263                     checked_buckets++;
264                     while(de) {
265                         /* Get the next entry now since this entry may get
266                          * deleted. */
267                         dictEntry *e = de;
268                         de = de->next;
269 
270                         ttl = dictGetSignedIntegerVal(e)-now;
271                         if (activeExpireCycleTryExpire(db,e,now)) expired++;
272                         if (ttl > 0) {
273                             /* We want the average TTL of keys yet
274                              * not expired. */
275                             ttl_sum += ttl;
276                             ttl_samples++;
277                         }
278                         sampled++;
279                     }
280                 }
281                 db->expires_cursor++;
282             }
283             total_expired += expired;
284             total_sampled += sampled;
285 
286             /* Update the average TTL stats for this database. */
287             if (ttl_samples) {
288                 long long avg_ttl = ttl_sum/ttl_samples;
289 
290                 /* Do a simple running average with a few samples.
291                  * We just use the current estimate with a weight of 2%
292                  * and the previous estimate with a weight of 98%. */
293                 if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
294                 db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
295             }
296 
297             /* We can't block forever here even if there are many keys to
298              * expire. So after a given amount of milliseconds return to the
299              * caller waiting for the other active expire cycle. */
300             if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
301                 elapsed = ustime()-start;
302                 if (elapsed > timelimit) {
303                     timelimit_exit = 1;
304                     server.stat_expired_time_cap_reached_count++;
305                     break;
306                 }
307             }
308             /* We don't repeat the cycle for the current database if there are
309              * an acceptable amount of stale keys (logically expired but yet
310              * not reclaimed). */
311         } while (sampled == 0 ||
312                  (expired*100/sampled) > config_cycle_acceptable_stale);
313     }
314 
315     elapsed = ustime()-start;
316     server.stat_expire_cycle_time_used += elapsed;
317     latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
318 
319     /* Update our estimate of keys existing but yet to be expired.
320      * Running average with this sample accounting for 5%. */
321     double current_perc;
322     if (total_sampled) {
323         current_perc = (double)total_expired/total_sampled;
324     } else
325         current_perc = 0;
326     server.stat_expired_stale_perc = (current_perc*0.05)+
327                                      (server.stat_expired_stale_perc*0.95);
328 }
329 
330 /*-----------------------------------------------------------------------------
331  * Expires of keys created in writable slaves
332  *
333  * Normally slaves do not process expires: they wait the masters to synthesize
334  * DEL operations in order to retain consistency. However writable slaves are
335  * an exception: if a key is created in the slave and an expire is assigned
336  * to it, we need a way to expire such a key, since the master does not know
337  * anything about such a key.
338  *
339  * In order to do so, we track keys created in the slave side with an expire
340  * set, and call the expireSlaveKeys() function from time to time in order to
341  * reclaim the keys if they already expired.
342  *
343  * Note that the use case we are trying to cover here, is a popular one where
344  * slaves are put in writable mode in order to compute slow operations in
345  * the slave side that are mostly useful to actually read data in a more
346  * processed way. Think at sets intersections in a tmp key, with an expire so
347  * that it is also used as a cache to avoid intersecting every time.
348  *
349  * This implementation is currently not perfect but a lot better than leaking
350  * the keys as implemented in 3.2.
351  *----------------------------------------------------------------------------*/
352 
353 /* The dictionary where we remember key names and database ID of keys we may
354  * want to expire from the slave. Since this function is not often used we
355  * don't even care to initialize the database at startup. We'll do it once
356  * the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
357  * is called.
358  *
359  * The dictionary has an SDS string representing the key as the hash table
360  * key, while the value is a 64 bit unsigned integer with the bits corresponding
361  * to the DB where the keys may exist set to 1. Currently the keys created
362  * with a DB id > 63 are not expired, but a trivial fix is to set the bitmap
363  * to the max 64 bit unsigned value when we know there is a key with a DB
364  * ID greater than 63, and check all the configured DBs in such a case. */
365 dict *slaveKeysWithExpire = NULL;
366 
367 /* Check the set of keys created by the master with an expire set in order to
368  * check if they should be evicted. */
expireSlaveKeys(void)369 void expireSlaveKeys(void) {
370     if (slaveKeysWithExpire == NULL ||
371         dictSize(slaveKeysWithExpire) == 0) return;
372 
373     int cycles = 0, noexpire = 0;
374     mstime_t start = mstime();
375     while(1) {
376         dictEntry *de = dictGetRandomKey(slaveKeysWithExpire);
377         sds keyname = dictGetKey(de);
378         uint64_t dbids = dictGetUnsignedIntegerVal(de);
379         uint64_t new_dbids = 0;
380 
381         /* Check the key against every database corresponding to the
382          * bits set in the value bitmap. */
383         int dbid = 0;
384         while(dbids && dbid < server.dbnum) {
385             if ((dbids & 1) != 0) {
386                 redisDb *db = server.db+dbid;
387                 dictEntry *expire = dictFind(db->expires,keyname);
388                 int expired = 0;
389 
390                 if (expire &&
391                     activeExpireCycleTryExpire(server.db+dbid,expire,start))
392                 {
393                     expired = 1;
394                 }
395 
396                 /* If the key was not expired in this DB, we need to set the
397                  * corresponding bit in the new bitmap we set as value.
398                  * At the end of the loop if the bitmap is zero, it means we
399                  * no longer need to keep track of this key. */
400                 if (expire && !expired) {
401                     noexpire++;
402                     new_dbids |= (uint64_t)1 << dbid;
403                 }
404             }
405             dbid++;
406             dbids >>= 1;
407         }
408 
409         /* Set the new bitmap as value of the key, in the dictionary
410          * of keys with an expire set directly in the writable slave. Otherwise
411          * if the bitmap is zero, we no longer need to keep track of it. */
412         if (new_dbids)
413             dictSetUnsignedIntegerVal(de,new_dbids);
414         else
415             dictDelete(slaveKeysWithExpire,keyname);
416 
417         /* Stop conditions: found 3 keys we can't expire in a row or
418          * time limit was reached. */
419         cycles++;
420         if (noexpire > 3) break;
421         if ((cycles % 64) == 0 && mstime()-start > 1) break;
422         if (dictSize(slaveKeysWithExpire) == 0) break;
423     }
424 }
425 
426 /* Track keys that received an EXPIRE or similar command in the context
427  * of a writable slave. */
rememberSlaveKeyWithExpire(redisDb * db,robj * key)428 void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
429     if (slaveKeysWithExpire == NULL) {
430         static dictType dt = {
431             dictSdsHash,                /* hash function */
432             NULL,                       /* key dup */
433             NULL,                       /* val dup */
434             dictSdsKeyCompare,          /* key compare */
435             dictSdsDestructor,          /* key destructor */
436             NULL                        /* val destructor */
437         };
438         slaveKeysWithExpire = dictCreate(&dt,NULL);
439     }
440     if (db->id > 63) return;
441 
442     dictEntry *de = dictAddOrFind(slaveKeysWithExpire,key->ptr);
443     /* If the entry was just created, set it to a copy of the SDS string
444      * representing the key: we don't want to need to take those keys
445      * in sync with the main DB. The keys will be removed by expireSlaveKeys()
446      * as it scans to find keys to remove. */
447     if (de->key == key->ptr) {
448         de->key = sdsdup(key->ptr);
449         dictSetUnsignedIntegerVal(de,0);
450     }
451 
452     uint64_t dbids = dictGetUnsignedIntegerVal(de);
453     dbids |= (uint64_t)1 << db->id;
454     dictSetUnsignedIntegerVal(de,dbids);
455 }
456 
457 /* Return the number of keys we are tracking. */
getSlaveKeyWithExpireCount(void)458 size_t getSlaveKeyWithExpireCount(void) {
459     if (slaveKeysWithExpire == NULL) return 0;
460     return dictSize(slaveKeysWithExpire);
461 }
462 
463 /* Remove the keys in the hash table. We need to do that when data is
464  * flushed from the server. We may receive new keys from the master with
465  * the same name/db and it is no longer a good idea to expire them.
466  *
467  * Note: technically we should handle the case of a single DB being flushed
468  * but it is not worth it since anyway race conditions using the same set
469  * of key names in a writable slave and in its master will lead to
470  * inconsistencies. This is just a best-effort thing we do. */
flushSlaveKeysWithExpireList(void)471 void flushSlaveKeysWithExpireList(void) {
472     if (slaveKeysWithExpire) {
473         dictRelease(slaveKeysWithExpire);
474         slaveKeysWithExpire = NULL;
475     }
476 }
477 
checkAlreadyExpired(long long when)478 int checkAlreadyExpired(long long when) {
479     /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
480      * should never be executed as a DEL when load the AOF or in the context
481      * of a slave instance.
482      *
483      * Instead we add the already expired key to the database with expire time
484      * (possibly in the past) and wait for an explicit DEL from the master. */
485     return (when <= mstime() && !server.loading && !server.masterhost);
486 }
487 
488 /*-----------------------------------------------------------------------------
489  * Expires Commands
490  *----------------------------------------------------------------------------*/
491 
492 /* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT
493  * and PEXPIREAT. Because the command second argument may be relative or absolute
494  * the "basetime" argument is used to signal what the base time is (either 0
495  * for *AT variants of the command, or the current time for relative expires).
496  *
497  * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
498  * the argv[2] parameter. The basetime is always specified in milliseconds. */
expireGenericCommand(client * c,long long basetime,int unit)499 void expireGenericCommand(client *c, long long basetime, int unit) {
500     robj *key = c->argv[1], *param = c->argv[2];
501     long long when; /* unix time in milliseconds when the key will expire. */
502 
503     if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
504         return;
505 
506     if (unit == UNIT_SECONDS) when *= 1000;
507     when += basetime;
508 
509     /* No key, return zero. */
510     if (lookupKeyWrite(c->db,key) == NULL) {
511         addReply(c,shared.czero);
512         return;
513     }
514 
515     if (checkAlreadyExpired(when)) {
516         robj *aux;
517 
518         int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
519                                                     dbSyncDelete(c->db,key);
520         serverAssertWithInfo(c,key,deleted);
521         server.dirty++;
522 
523         /* Replicate/AOF this as an explicit DEL or UNLINK. */
524         aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
525         rewriteClientCommandVector(c,2,aux,key);
526         signalModifiedKey(c,c->db,key);
527         notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
528         addReply(c, shared.cone);
529         return;
530     } else {
531         setExpire(c,c->db,key,when);
532         addReply(c,shared.cone);
533         signalModifiedKey(c,c->db,key);
534         notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
535         server.dirty++;
536         return;
537     }
538 }
539 
540 /* EXPIRE key seconds */
expireCommand(client * c)541 void expireCommand(client *c) {
542     expireGenericCommand(c,mstime(),UNIT_SECONDS);
543 }
544 
545 /* EXPIREAT key time */
expireatCommand(client * c)546 void expireatCommand(client *c) {
547     expireGenericCommand(c,0,UNIT_SECONDS);
548 }
549 
550 /* PEXPIRE key milliseconds */
pexpireCommand(client * c)551 void pexpireCommand(client *c) {
552     expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
553 }
554 
555 /* PEXPIREAT key ms_time */
pexpireatCommand(client * c)556 void pexpireatCommand(client *c) {
557     expireGenericCommand(c,0,UNIT_MILLISECONDS);
558 }
559 
560 /* Implements TTL and PTTL */
ttlGenericCommand(client * c,int output_ms)561 void ttlGenericCommand(client *c, int output_ms) {
562     long long expire, ttl = -1;
563 
564     /* If the key does not exist at all, return -2 */
565     if (lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH) == NULL) {
566         addReplyLongLong(c,-2);
567         return;
568     }
569     /* The key exists. Return -1 if it has no expire, or the actual
570      * TTL value otherwise. */
571     expire = getExpire(c->db,c->argv[1]);
572     if (expire != -1) {
573         ttl = expire-mstime();
574         if (ttl < 0) ttl = 0;
575     }
576     if (ttl == -1) {
577         addReplyLongLong(c,-1);
578     } else {
579         addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
580     }
581 }
582 
583 /* TTL key */
ttlCommand(client * c)584 void ttlCommand(client *c) {
585     ttlGenericCommand(c, 0);
586 }
587 
588 /* PTTL key */
pttlCommand(client * c)589 void pttlCommand(client *c) {
590     ttlGenericCommand(c, 1);
591 }
592 
593 /* PERSIST key */
persistCommand(client * c)594 void persistCommand(client *c) {
595     if (lookupKeyWrite(c->db,c->argv[1])) {
596         if (removeExpire(c->db,c->argv[1])) {
597             signalModifiedKey(c,c->db,c->argv[1]);
598             notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
599             addReply(c,shared.cone);
600             server.dirty++;
601         } else {
602             addReply(c,shared.czero);
603         }
604     } else {
605         addReply(c,shared.czero);
606     }
607 }
608 
609 /* TOUCH key1 [key2 key3 ... keyN] */
touchCommand(client * c)610 void touchCommand(client *c) {
611     int touched = 0;
612     for (int j = 1; j < c->argc; j++)
613         if (lookupKeyRead(c->db,c->argv[j]) != NULL) touched++;
614     addReplyLongLong(c,touched);
615 }
616 
617