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. */ |
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 | 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 | |
113 | void 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. */ |
369 | dict *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. */ |
373 | void 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. */ |
432 | void 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. */ |
463 | size_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. */ |
476 | void flushSlaveKeysWithExpireList(void) { |
477 | if (slaveKeysWithExpire) { |
478 | dictRelease(slaveKeysWithExpire); |
479 | slaveKeysWithExpire = NULL; |
480 | } |
481 | } |
482 | |
483 | int 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 */ |
505 | int 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 */ |
556 | void 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] */ |
666 | void expireCommand(client *c) { |
667 | expireGenericCommand(c,mstime(),UNIT_SECONDS); |
668 | } |
669 | |
670 | /* EXPIREAT key unix-time-seconds [ NX | XX | GT | LT] */ |
671 | void expireatCommand(client *c) { |
672 | expireGenericCommand(c,0,UNIT_SECONDS); |
673 | } |
674 | |
675 | /* PEXPIRE key milliseconds [ NX | XX | GT | LT] */ |
676 | void pexpireCommand(client *c) { |
677 | expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); |
678 | } |
679 | |
680 | /* PEXPIREAT key unix-time-milliseconds [ NX | XX | GT | LT] */ |
681 | void pexpireatCommand(client *c) { |
682 | expireGenericCommand(c,0,UNIT_MILLISECONDS); |
683 | } |
684 | |
685 | /* Implements TTL, PTTL, EXPIRETIME and PEXPIRETIME */ |
686 | void 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 */ |
710 | void ttlCommand(client *c) { |
711 | ttlGenericCommand(c, 0, 0); |
712 | } |
713 | |
714 | /* PTTL key */ |
715 | void pttlCommand(client *c) { |
716 | ttlGenericCommand(c, 1, 0); |
717 | } |
718 | |
719 | /* EXPIRETIME key */ |
720 | void expiretimeCommand(client *c) { |
721 | ttlGenericCommand(c, 0, 1); |
722 | } |
723 | |
724 | /* PEXPIRETIME key */ |
725 | void pexpiretimeCommand(client *c) { |
726 | ttlGenericCommand(c, 1, 1); |
727 | } |
728 | |
729 | /* PERSIST key */ |
730 | void 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] */ |
746 | void 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 | |