]> git.saurik.com Git - redis.git/blame_incremental - src/t_list.c
Avoid unnecessary calls to time(3)
[redis.git] / src / t_list.c
... / ...
CommitLineData
1#include "redis.h"
2
3/*-----------------------------------------------------------------------------
4 * List API
5 *----------------------------------------------------------------------------*/
6
7/* Check the argument length to see if it requires us to convert the ziplist
8 * to a real list. Only check raw-encoded objects because integer encoded
9 * objects are never too long. */
10void listTypeTryConversion(robj *subject, robj *value) {
11 if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
12 if (value->encoding == REDIS_ENCODING_RAW &&
13 sdslen(value->ptr) > server.list_max_ziplist_value)
14 listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
15}
16
17void listTypePush(robj *subject, robj *value, int where) {
18 /* Check if we need to convert the ziplist */
19 listTypeTryConversion(subject,value);
20 if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
21 ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
22 listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
23
24 if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
25 int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
26 value = getDecodedObject(value);
27 subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
28 decrRefCount(value);
29 } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
30 if (where == REDIS_HEAD) {
31 listAddNodeHead(subject->ptr,value);
32 } else {
33 listAddNodeTail(subject->ptr,value);
34 }
35 incrRefCount(value);
36 } else {
37 redisPanic("Unknown list encoding");
38 }
39}
40
41robj *listTypePop(robj *subject, int where) {
42 robj *value = NULL;
43 if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
44 unsigned char *p;
45 unsigned char *vstr;
46 unsigned int vlen;
47 long long vlong;
48 int pos = (where == REDIS_HEAD) ? 0 : -1;
49 p = ziplistIndex(subject->ptr,pos);
50 if (ziplistGet(p,&vstr,&vlen,&vlong)) {
51 if (vstr) {
52 value = createStringObject((char*)vstr,vlen);
53 } else {
54 value = createStringObjectFromLongLong(vlong);
55 }
56 /* We only need to delete an element when it exists */
57 subject->ptr = ziplistDelete(subject->ptr,&p);
58 }
59 } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
60 list *list = subject->ptr;
61 listNode *ln;
62 if (where == REDIS_HEAD) {
63 ln = listFirst(list);
64 } else {
65 ln = listLast(list);
66 }
67 if (ln != NULL) {
68 value = listNodeValue(ln);
69 incrRefCount(value);
70 listDelNode(list,ln);
71 }
72 } else {
73 redisPanic("Unknown list encoding");
74 }
75 return value;
76}
77
78unsigned long listTypeLength(robj *subject) {
79 if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
80 return ziplistLen(subject->ptr);
81 } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
82 return listLength((list*)subject->ptr);
83 } else {
84 redisPanic("Unknown list encoding");
85 }
86}
87
88/* Initialize an iterator at the specified index. */
89listTypeIterator *listTypeInitIterator(robj *subject, int index, unsigned char direction) {
90 listTypeIterator *li = zmalloc(sizeof(listTypeIterator));
91 li->subject = subject;
92 li->encoding = subject->encoding;
93 li->direction = direction;
94 if (li->encoding == REDIS_ENCODING_ZIPLIST) {
95 li->zi = ziplistIndex(subject->ptr,index);
96 } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
97 li->ln = listIndex(subject->ptr,index);
98 } else {
99 redisPanic("Unknown list encoding");
100 }
101 return li;
102}
103
104/* Clean up the iterator. */
105void listTypeReleaseIterator(listTypeIterator *li) {
106 zfree(li);
107}
108
109/* Stores pointer to current the entry in the provided entry structure
110 * and advances the position of the iterator. Returns 1 when the current
111 * entry is in fact an entry, 0 otherwise. */
112int listTypeNext(listTypeIterator *li, listTypeEntry *entry) {
113 /* Protect from converting when iterating */
114 redisAssert(li->subject->encoding == li->encoding);
115
116 entry->li = li;
117 if (li->encoding == REDIS_ENCODING_ZIPLIST) {
118 entry->zi = li->zi;
119 if (entry->zi != NULL) {
120 if (li->direction == REDIS_TAIL)
121 li->zi = ziplistNext(li->subject->ptr,li->zi);
122 else
123 li->zi = ziplistPrev(li->subject->ptr,li->zi);
124 return 1;
125 }
126 } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
127 entry->ln = li->ln;
128 if (entry->ln != NULL) {
129 if (li->direction == REDIS_TAIL)
130 li->ln = li->ln->next;
131 else
132 li->ln = li->ln->prev;
133 return 1;
134 }
135 } else {
136 redisPanic("Unknown list encoding");
137 }
138 return 0;
139}
140
141/* Return entry or NULL at the current position of the iterator. */
142robj *listTypeGet(listTypeEntry *entry) {
143 listTypeIterator *li = entry->li;
144 robj *value = NULL;
145 if (li->encoding == REDIS_ENCODING_ZIPLIST) {
146 unsigned char *vstr;
147 unsigned int vlen;
148 long long vlong;
149 redisAssert(entry->zi != NULL);
150 if (ziplistGet(entry->zi,&vstr,&vlen,&vlong)) {
151 if (vstr) {
152 value = createStringObject((char*)vstr,vlen);
153 } else {
154 value = createStringObjectFromLongLong(vlong);
155 }
156 }
157 } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
158 redisAssert(entry->ln != NULL);
159 value = listNodeValue(entry->ln);
160 incrRefCount(value);
161 } else {
162 redisPanic("Unknown list encoding");
163 }
164 return value;
165}
166
167void listTypeInsert(listTypeEntry *entry, robj *value, int where) {
168 robj *subject = entry->li->subject;
169 if (entry->li->encoding == REDIS_ENCODING_ZIPLIST) {
170 value = getDecodedObject(value);
171 if (where == REDIS_TAIL) {
172 unsigned char *next = ziplistNext(subject->ptr,entry->zi);
173
174 /* When we insert after the current element, but the current element
175 * is the tail of the list, we need to do a push. */
176 if (next == NULL) {
177 subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),REDIS_TAIL);
178 } else {
179 subject->ptr = ziplistInsert(subject->ptr,next,value->ptr,sdslen(value->ptr));
180 }
181 } else {
182 subject->ptr = ziplistInsert(subject->ptr,entry->zi,value->ptr,sdslen(value->ptr));
183 }
184 decrRefCount(value);
185 } else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
186 if (where == REDIS_TAIL) {
187 listInsertNode(subject->ptr,entry->ln,value,AL_START_TAIL);
188 } else {
189 listInsertNode(subject->ptr,entry->ln,value,AL_START_HEAD);
190 }
191 incrRefCount(value);
192 } else {
193 redisPanic("Unknown list encoding");
194 }
195}
196
197/* Compare the given object with the entry at the current position. */
198int listTypeEqual(listTypeEntry *entry, robj *o) {
199 listTypeIterator *li = entry->li;
200 if (li->encoding == REDIS_ENCODING_ZIPLIST) {
201 redisAssert(o->encoding == REDIS_ENCODING_RAW);
202 return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr));
203 } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) {
204 return equalStringObjects(o,listNodeValue(entry->ln));
205 } else {
206 redisPanic("Unknown list encoding");
207 }
208}
209
210/* Delete the element pointed to. */
211void listTypeDelete(listTypeEntry *entry) {
212 listTypeIterator *li = entry->li;
213 if (li->encoding == REDIS_ENCODING_ZIPLIST) {
214 unsigned char *p = entry->zi;
215 li->subject->ptr = ziplistDelete(li->subject->ptr,&p);
216
217 /* Update position of the iterator depending on the direction */
218 if (li->direction == REDIS_TAIL)
219 li->zi = p;
220 else
221 li->zi = ziplistPrev(li->subject->ptr,p);
222 } else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) {
223 listNode *next;
224 if (li->direction == REDIS_TAIL)
225 next = entry->ln->next;
226 else
227 next = entry->ln->prev;
228 listDelNode(li->subject->ptr,entry->ln);
229 li->ln = next;
230 } else {
231 redisPanic("Unknown list encoding");
232 }
233}
234
235void listTypeConvert(robj *subject, int enc) {
236 listTypeIterator *li;
237 listTypeEntry entry;
238 redisAssert(subject->type == REDIS_LIST);
239
240 if (enc == REDIS_ENCODING_LINKEDLIST) {
241 list *l = listCreate();
242 listSetFreeMethod(l,decrRefCount);
243
244 /* listTypeGet returns a robj with incremented refcount */
245 li = listTypeInitIterator(subject,0,REDIS_TAIL);
246 while (listTypeNext(li,&entry)) listAddNodeTail(l,listTypeGet(&entry));
247 listTypeReleaseIterator(li);
248
249 subject->encoding = REDIS_ENCODING_LINKEDLIST;
250 zfree(subject->ptr);
251 subject->ptr = l;
252 } else {
253 redisPanic("Unsupported list conversion");
254 }
255}
256
257/*-----------------------------------------------------------------------------
258 * List Commands
259 *----------------------------------------------------------------------------*/
260
261void pushGenericCommand(redisClient *c, int where) {
262 int j, addlen = 0, pushed = 0;
263 robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
264 int may_have_waiting_clients = (lobj == NULL);
265
266 if (lobj && lobj->type != REDIS_LIST) {
267 addReply(c,shared.wrongtypeerr);
268 return;
269 }
270
271 for (j = 2; j < c->argc; j++) {
272 c->argv[j] = tryObjectEncoding(c->argv[j]);
273 if (may_have_waiting_clients) {
274 if (handleClientsWaitingListPush(c,c->argv[1],c->argv[j])) {
275 addlen++;
276 continue;
277 } else {
278 may_have_waiting_clients = 0;
279 }
280 }
281 if (!lobj) {
282 lobj = createZiplistObject();
283 dbAdd(c->db,c->argv[1],lobj);
284 }
285 listTypePush(lobj,c->argv[j],where);
286 pushed++;
287 }
288 addReplyLongLong(c,addlen + (lobj ? listTypeLength(lobj) : 0));
289 if (pushed) signalModifiedKey(c->db,c->argv[1]);
290 server.dirty += pushed;
291}
292
293void lpushCommand(redisClient *c) {
294 pushGenericCommand(c,REDIS_HEAD);
295}
296
297void rpushCommand(redisClient *c) {
298 pushGenericCommand(c,REDIS_TAIL);
299}
300
301void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
302 robj *subject;
303 listTypeIterator *iter;
304 listTypeEntry entry;
305 int inserted = 0;
306
307 if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
308 checkType(c,subject,REDIS_LIST)) return;
309
310 if (refval != NULL) {
311 /* Note: we expect refval to be string-encoded because it is *not* the
312 * last argument of the multi-bulk LINSERT. */
313 redisAssert(refval->encoding == REDIS_ENCODING_RAW);
314
315 /* We're not sure if this value can be inserted yet, but we cannot
316 * convert the list inside the iterator. We don't want to loop over
317 * the list twice (once to see if the value can be inserted and once
318 * to do the actual insert), so we assume this value can be inserted
319 * and convert the ziplist to a regular list if necessary. */
320 listTypeTryConversion(subject,val);
321
322 /* Seek refval from head to tail */
323 iter = listTypeInitIterator(subject,0,REDIS_TAIL);
324 while (listTypeNext(iter,&entry)) {
325 if (listTypeEqual(&entry,refval)) {
326 listTypeInsert(&entry,val,where);
327 inserted = 1;
328 break;
329 }
330 }
331 listTypeReleaseIterator(iter);
332
333 if (inserted) {
334 /* Check if the length exceeds the ziplist length threshold. */
335 if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
336 ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
337 listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
338 signalModifiedKey(c->db,c->argv[1]);
339 server.dirty++;
340 } else {
341 /* Notify client of a failed insert */
342 addReply(c,shared.cnegone);
343 return;
344 }
345 } else {
346 listTypePush(subject,val,where);
347 signalModifiedKey(c->db,c->argv[1]);
348 server.dirty++;
349 }
350
351 addReplyLongLong(c,listTypeLength(subject));
352}
353
354void lpushxCommand(redisClient *c) {
355 c->argv[2] = tryObjectEncoding(c->argv[2]);
356 pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD);
357}
358
359void rpushxCommand(redisClient *c) {
360 c->argv[2] = tryObjectEncoding(c->argv[2]);
361 pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL);
362}
363
364void linsertCommand(redisClient *c) {
365 c->argv[4] = tryObjectEncoding(c->argv[4]);
366 if (strcasecmp(c->argv[2]->ptr,"after") == 0) {
367 pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL);
368 } else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
369 pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_HEAD);
370 } else {
371 addReply(c,shared.syntaxerr);
372 }
373}
374
375void llenCommand(redisClient *c) {
376 robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
377 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
378 addReplyLongLong(c,listTypeLength(o));
379}
380
381void lindexCommand(redisClient *c) {
382 robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk);
383 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
384 int index = atoi(c->argv[2]->ptr);
385 robj *value = NULL;
386
387 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
388 unsigned char *p;
389 unsigned char *vstr;
390 unsigned int vlen;
391 long long vlong;
392 p = ziplistIndex(o->ptr,index);
393 if (ziplistGet(p,&vstr,&vlen,&vlong)) {
394 if (vstr) {
395 value = createStringObject((char*)vstr,vlen);
396 } else {
397 value = createStringObjectFromLongLong(vlong);
398 }
399 addReplyBulk(c,value);
400 decrRefCount(value);
401 } else {
402 addReply(c,shared.nullbulk);
403 }
404 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
405 listNode *ln = listIndex(o->ptr,index);
406 if (ln != NULL) {
407 value = listNodeValue(ln);
408 addReplyBulk(c,value);
409 } else {
410 addReply(c,shared.nullbulk);
411 }
412 } else {
413 redisPanic("Unknown list encoding");
414 }
415}
416
417void lsetCommand(redisClient *c) {
418 robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
419 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
420 int index = atoi(c->argv[2]->ptr);
421 robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
422
423 listTypeTryConversion(o,value);
424 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
425 unsigned char *p, *zl = o->ptr;
426 p = ziplistIndex(zl,index);
427 if (p == NULL) {
428 addReply(c,shared.outofrangeerr);
429 } else {
430 o->ptr = ziplistDelete(o->ptr,&p);
431 value = getDecodedObject(value);
432 o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
433 decrRefCount(value);
434 addReply(c,shared.ok);
435 signalModifiedKey(c->db,c->argv[1]);
436 server.dirty++;
437 }
438 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
439 listNode *ln = listIndex(o->ptr,index);
440 if (ln == NULL) {
441 addReply(c,shared.outofrangeerr);
442 } else {
443 decrRefCount((robj*)listNodeValue(ln));
444 listNodeValue(ln) = value;
445 incrRefCount(value);
446 addReply(c,shared.ok);
447 signalModifiedKey(c->db,c->argv[1]);
448 server.dirty++;
449 }
450 } else {
451 redisPanic("Unknown list encoding");
452 }
453}
454
455void popGenericCommand(redisClient *c, int where) {
456 robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk);
457 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
458
459 robj *value = listTypePop(o,where);
460 if (value == NULL) {
461 addReply(c,shared.nullbulk);
462 } else {
463 addReplyBulk(c,value);
464 decrRefCount(value);
465 if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
466 signalModifiedKey(c->db,c->argv[1]);
467 server.dirty++;
468 }
469}
470
471void lpopCommand(redisClient *c) {
472 popGenericCommand(c,REDIS_HEAD);
473}
474
475void rpopCommand(redisClient *c) {
476 popGenericCommand(c,REDIS_TAIL);
477}
478
479void lrangeCommand(redisClient *c) {
480 robj *o;
481 int start = atoi(c->argv[2]->ptr);
482 int end = atoi(c->argv[3]->ptr);
483 int llen;
484 int rangelen;
485
486 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
487 || checkType(c,o,REDIS_LIST)) return;
488 llen = listTypeLength(o);
489
490 /* convert negative indexes */
491 if (start < 0) start = llen+start;
492 if (end < 0) end = llen+end;
493 if (start < 0) start = 0;
494
495 /* Invariant: start >= 0, so this test will be true when end < 0.
496 * The range is empty when start > end or start >= length. */
497 if (start > end || start >= llen) {
498 addReply(c,shared.emptymultibulk);
499 return;
500 }
501 if (end >= llen) end = llen-1;
502 rangelen = (end-start)+1;
503
504 /* Return the result in form of a multi-bulk reply */
505 addReplyMultiBulkLen(c,rangelen);
506 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
507 unsigned char *p = ziplistIndex(o->ptr,start);
508 unsigned char *vstr;
509 unsigned int vlen;
510 long long vlong;
511
512 while(rangelen--) {
513 ziplistGet(p,&vstr,&vlen,&vlong);
514 if (vstr) {
515 addReplyBulkCBuffer(c,vstr,vlen);
516 } else {
517 addReplyBulkLongLong(c,vlong);
518 }
519 p = ziplistNext(o->ptr,p);
520 }
521 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
522 listNode *ln = listIndex(o->ptr,start);
523
524 while(rangelen--) {
525 addReplyBulk(c,ln->value);
526 ln = ln->next;
527 }
528 } else {
529 redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
530 }
531}
532
533void ltrimCommand(redisClient *c) {
534 robj *o;
535 int start = atoi(c->argv[2]->ptr);
536 int end = atoi(c->argv[3]->ptr);
537 int llen;
538 int j, ltrim, rtrim;
539 list *list;
540 listNode *ln;
541
542 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.ok)) == NULL ||
543 checkType(c,o,REDIS_LIST)) return;
544 llen = listTypeLength(o);
545
546 /* convert negative indexes */
547 if (start < 0) start = llen+start;
548 if (end < 0) end = llen+end;
549 if (start < 0) start = 0;
550
551 /* Invariant: start >= 0, so this test will be true when end < 0.
552 * The range is empty when start > end or start >= length. */
553 if (start > end || start >= llen) {
554 /* Out of range start or start > end result in empty list */
555 ltrim = llen;
556 rtrim = 0;
557 } else {
558 if (end >= llen) end = llen-1;
559 ltrim = start;
560 rtrim = llen-end-1;
561 }
562
563 /* Remove list elements to perform the trim */
564 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
565 o->ptr = ziplistDeleteRange(o->ptr,0,ltrim);
566 o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim);
567 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
568 list = o->ptr;
569 for (j = 0; j < ltrim; j++) {
570 ln = listFirst(list);
571 listDelNode(list,ln);
572 }
573 for (j = 0; j < rtrim; j++) {
574 ln = listLast(list);
575 listDelNode(list,ln);
576 }
577 } else {
578 redisPanic("Unknown list encoding");
579 }
580 if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
581 signalModifiedKey(c->db,c->argv[1]);
582 server.dirty++;
583 addReply(c,shared.ok);
584}
585
586void lremCommand(redisClient *c) {
587 robj *subject, *obj;
588 obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
589 int toremove = atoi(c->argv[2]->ptr);
590 int removed = 0;
591 listTypeEntry entry;
592
593 subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
594 if (subject == NULL || checkType(c,subject,REDIS_LIST)) return;
595
596 /* Make sure obj is raw when we're dealing with a ziplist */
597 if (subject->encoding == REDIS_ENCODING_ZIPLIST)
598 obj = getDecodedObject(obj);
599
600 listTypeIterator *li;
601 if (toremove < 0) {
602 toremove = -toremove;
603 li = listTypeInitIterator(subject,-1,REDIS_HEAD);
604 } else {
605 li = listTypeInitIterator(subject,0,REDIS_TAIL);
606 }
607
608 while (listTypeNext(li,&entry)) {
609 if (listTypeEqual(&entry,obj)) {
610 listTypeDelete(&entry);
611 server.dirty++;
612 removed++;
613 if (toremove && removed == toremove) break;
614 }
615 }
616 listTypeReleaseIterator(li);
617
618 /* Clean up raw encoded object */
619 if (subject->encoding == REDIS_ENCODING_ZIPLIST)
620 decrRefCount(obj);
621
622 if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
623 addReplyLongLong(c,removed);
624 if (removed) signalModifiedKey(c->db,c->argv[1]);
625}
626
627/* This is the semantic of this command:
628 * RPOPLPUSH srclist dstlist:
629 * IF LLEN(srclist) > 0
630 * element = RPOP srclist
631 * LPUSH dstlist element
632 * RETURN element
633 * ELSE
634 * RETURN nil
635 * END
636 * END
637 *
638 * The idea is to be able to get an element from a list in a reliable way
639 * since the element is not just returned but pushed against another list
640 * as well. This command was originally proposed by Ezra Zygmuntowicz.
641 */
642
643void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
644 robj *aux;
645
646 if (!handleClientsWaitingListPush(origclient,dstkey,value)) {
647 /* Create the list if the key does not exist */
648 if (!dstobj) {
649 dstobj = createZiplistObject();
650 dbAdd(c->db,dstkey,dstobj);
651 } else {
652 signalModifiedKey(c->db,dstkey);
653 }
654 listTypePush(dstobj,value,REDIS_HEAD);
655 /* If we are pushing as a result of LPUSH against a key
656 * watched by BRPOPLPUSH, we need to rewrite the command vector
657 * as an LPUSH.
658 *
659 * If this is called directly by RPOPLPUSH (either directly
660 * or via a BRPOPLPUSH where the popped list exists)
661 * we should replicate the RPOPLPUSH command itself. */
662 if (c != origclient) {
663 aux = createStringObject("LPUSH",5);
664 rewriteClientCommandVector(origclient,3,aux,dstkey,value);
665 decrRefCount(aux);
666 } else {
667 /* Make sure to always use RPOPLPUSH in the replication / AOF,
668 * even if the original command was BRPOPLPUSH. */
669 aux = createStringObject("RPOPLPUSH",9);
670 rewriteClientCommandVector(origclient,3,aux,c->argv[1],c->argv[2]);
671 decrRefCount(aux);
672 }
673 server.dirty++;
674 }
675
676 /* Always send the pushed value to the client. */
677 addReplyBulk(c,value);
678}
679
680void rpoplpushCommand(redisClient *c) {
681 robj *sobj, *value;
682 if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
683 checkType(c,sobj,REDIS_LIST)) return;
684
685 if (listTypeLength(sobj) == 0) {
686 addReply(c,shared.nullbulk);
687 } else {
688 robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
689 robj *touchedkey = c->argv[1];
690
691 if (dobj && checkType(c,dobj,REDIS_LIST)) return;
692 value = listTypePop(sobj,REDIS_TAIL);
693 /* We saved touched key, and protect it, since rpoplpushHandlePush
694 * may change the client command argument vector. */
695 incrRefCount(touchedkey);
696 rpoplpushHandlePush(c,c,c->argv[2],dobj,value);
697
698 /* listTypePop returns an object with its refcount incremented */
699 decrRefCount(value);
700
701 /* Delete the source list when it is empty */
702 if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
703 signalModifiedKey(c->db,touchedkey);
704 decrRefCount(touchedkey);
705 server.dirty++;
706 }
707}
708
709/*-----------------------------------------------------------------------------
710 * Blocking POP operations
711 *----------------------------------------------------------------------------*/
712
713/* Currently Redis blocking operations support is limited to list POP ops,
714 * so the current implementation is not fully generic, but it is also not
715 * completely specific so it will not require a rewrite to support new
716 * kind of blocking operations in the future.
717 *
718 * Still it's important to note that list blocking operations can be already
719 * used as a notification mechanism in order to implement other blocking
720 * operations at application level, so there must be a very strong evidence
721 * of usefulness and generality before new blocking operations are implemented.
722 *
723 * This is how the current blocking POP works, we use BLPOP as example:
724 * - If the user calls BLPOP and the key exists and contains a non empty list
725 * then LPOP is called instead. So BLPOP is semantically the same as LPOP
726 * if there is not to block.
727 * - If instead BLPOP is called and the key does not exists or the list is
728 * empty we need to block. In order to do so we remove the notification for
729 * new data to read in the client socket (so that we'll not serve new
730 * requests if the blocking request is not served). Also we put the client
731 * in a dictionary (db->blocking_keys) mapping keys to a list of clients
732 * blocking for this keys.
733 * - If a PUSH operation against a key with blocked clients waiting is
734 * performed, we serve the first in the list: basically instead to push
735 * the new element inside the list we return it to the (first / oldest)
736 * blocking client, unblock the client, and remove it form the list.
737 *
738 * The above comment and the source code should be enough in order to understand
739 * the implementation and modify / fix it later.
740 */
741
742/* Set a client in blocking mode for the specified key, with the specified
743 * timeout */
744void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) {
745 dictEntry *de;
746 list *l;
747 int j;
748
749 c->bpop.keys = zmalloc(sizeof(robj*)*numkeys);
750 c->bpop.count = numkeys;
751 c->bpop.timeout = timeout;
752 c->bpop.target = target;
753
754 if (target != NULL) {
755 incrRefCount(target);
756 }
757
758 for (j = 0; j < numkeys; j++) {
759 /* Add the key in the client structure, to map clients -> keys */
760 c->bpop.keys[j] = keys[j];
761 incrRefCount(keys[j]);
762
763 /* And in the other "side", to map keys -> clients */
764 de = dictFind(c->db->blocking_keys,keys[j]);
765 if (de == NULL) {
766 int retval;
767
768 /* For every key we take a list of clients blocked for it */
769 l = listCreate();
770 retval = dictAdd(c->db->blocking_keys,keys[j],l);
771 incrRefCount(keys[j]);
772 redisAssert(retval == DICT_OK);
773 } else {
774 l = dictGetEntryVal(de);
775 }
776 listAddNodeTail(l,c);
777 }
778 /* Mark the client as a blocked client */
779 c->flags |= REDIS_BLOCKED;
780 server.bpop_blocked_clients++;
781}
782
783/* Unblock a client that's waiting in a blocking operation such as BLPOP */
784void unblockClientWaitingData(redisClient *c) {
785 dictEntry *de;
786 list *l;
787 int j;
788
789 redisAssert(c->bpop.keys != NULL);
790 /* The client may wait for multiple keys, so unblock it for every key. */
791 for (j = 0; j < c->bpop.count; j++) {
792 /* Remove this client from the list of clients waiting for this key. */
793 de = dictFind(c->db->blocking_keys,c->bpop.keys[j]);
794 redisAssert(de != NULL);
795 l = dictGetEntryVal(de);
796 listDelNode(l,listSearchKey(l,c));
797 /* If the list is empty we need to remove it to avoid wasting memory */
798 if (listLength(l) == 0)
799 dictDelete(c->db->blocking_keys,c->bpop.keys[j]);
800 decrRefCount(c->bpop.keys[j]);
801 }
802
803 /* Cleanup the client structure */
804 zfree(c->bpop.keys);
805 c->bpop.keys = NULL;
806 if (c->bpop.target) decrRefCount(c->bpop.target);
807 c->bpop.target = NULL;
808 c->flags &= ~REDIS_BLOCKED;
809 c->flags |= REDIS_UNBLOCKED;
810 server.bpop_blocked_clients--;
811 listAddNodeTail(server.unblocked_clients,c);
812}
813
814/* This should be called from any function PUSHing into lists.
815 * 'c' is the "pushing client", 'key' is the key it is pushing data against,
816 * 'ele' is the element pushed.
817 *
818 * If the function returns 0 there was no client waiting for a list push
819 * against this key.
820 *
821 * If the function returns 1 there was a client waiting for a list push
822 * against this key, the element was passed to this client thus it's not
823 * needed to actually add it to the list and the caller should return asap. */
824int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
825 struct dictEntry *de;
826 redisClient *receiver;
827 int numclients;
828 list *clients;
829 listNode *ln;
830 robj *dstkey, *dstobj;
831
832 de = dictFind(c->db->blocking_keys,key);
833 if (de == NULL) return 0;
834 clients = dictGetEntryVal(de);
835 numclients = listLength(clients);
836
837 /* Try to handle the push as long as there are clients waiting for a push.
838 * Note that "numclients" is used because the list of clients waiting for a
839 * push on "key" is deleted by unblockClient() when empty.
840 *
841 * This loop will have more than 1 iteration when there is a BRPOPLPUSH
842 * that cannot push the target list because it does not contain a list. If
843 * this happens, it simply tries the next client waiting for a push. */
844 while (numclients--) {
845 ln = listFirst(clients);
846 redisAssert(ln != NULL);
847 receiver = ln->value;
848 dstkey = receiver->bpop.target;
849
850 /* Protect receiver->bpop.target, that will be freed by
851 * the next unblockClientWaitingData() call. */
852 if (dstkey) incrRefCount(dstkey);
853
854 /* This should remove the first element of the "clients" list. */
855 unblockClientWaitingData(receiver);
856
857 if (dstkey == NULL) {
858 /* BRPOP/BLPOP */
859 addReplyMultiBulkLen(receiver,2);
860 addReplyBulk(receiver,key);
861 addReplyBulk(receiver,ele);
862 return 1; /* Serve just the first client as in B[RL]POP semantics */
863 } else {
864 /* BRPOPLPUSH, note that receiver->db is always equal to c->db. */
865 dstobj = lookupKeyWrite(receiver->db,dstkey);
866 if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) {
867 rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele);
868 decrRefCount(dstkey);
869 return 1;
870 }
871 decrRefCount(dstkey);
872 }
873 }
874
875 return 0;
876}
877
878int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) {
879 long tval;
880
881 if (getLongFromObjectOrReply(c,object,&tval,
882 "timeout is not an integer or out of range") != REDIS_OK)
883 return REDIS_ERR;
884
885 if (tval < 0) {
886 addReplyError(c,"timeout is negative");
887 return REDIS_ERR;
888 }
889
890 if (tval > 0) tval += time(NULL);
891 *timeout = tval;
892
893 return REDIS_OK;
894}
895
896/* Blocking RPOP/LPOP */
897void blockingPopGenericCommand(redisClient *c, int where) {
898 robj *o;
899 time_t timeout;
900 int j;
901
902 if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout) != REDIS_OK)
903 return;
904
905 for (j = 1; j < c->argc-1; j++) {
906 o = lookupKeyWrite(c->db,c->argv[j]);
907 if (o != NULL) {
908 if (o->type != REDIS_LIST) {
909 addReply(c,shared.wrongtypeerr);
910 return;
911 } else {
912 if (listTypeLength(o) != 0) {
913 /* If the list contains elements fall back to the usual
914 * non-blocking POP operation */
915 struct redisCommand *orig_cmd;
916 robj *argv[2], **orig_argv;
917 int orig_argc;
918
919 /* We need to alter the command arguments before to call
920 * popGenericCommand() as the command takes a single key. */
921 orig_argv = c->argv;
922 orig_argc = c->argc;
923 orig_cmd = c->cmd;
924 argv[1] = c->argv[j];
925 c->argv = argv;
926 c->argc = 2;
927
928 /* Also the return value is different, we need to output
929 * the multi bulk reply header and the key name. The
930 * "real" command will add the last element (the value)
931 * for us. If this souds like an hack to you it's just
932 * because it is... */
933 addReplyMultiBulkLen(c,2);
934 addReplyBulk(c,argv[1]);
935
936 popGenericCommand(c,where);
937
938 /* Fix the client structure with the original stuff */
939 c->argv = orig_argv;
940 c->argc = orig_argc;
941 c->cmd = orig_cmd;
942
943 return;
944 }
945 }
946 }
947 }
948
949 /* If we are inside a MULTI/EXEC and the list is empty the only thing
950 * we can do is treating it as a timeout (even with timeout 0). */
951 if (c->flags & REDIS_MULTI) {
952 addReply(c,shared.nullmultibulk);
953 return;
954 }
955
956 /* If the list is empty or the key does not exists we must block */
957 blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
958}
959
960void blpopCommand(redisClient *c) {
961 blockingPopGenericCommand(c,REDIS_HEAD);
962}
963
964void brpopCommand(redisClient *c) {
965 blockingPopGenericCommand(c,REDIS_TAIL);
966}
967
968void brpoplpushCommand(redisClient *c) {
969 time_t timeout;
970
971 if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK)
972 return;
973
974 robj *key = lookupKeyWrite(c->db, c->argv[1]);
975
976 if (key == NULL) {
977 if (c->flags & REDIS_MULTI) {
978
979 /* Blocking against an empty list in a multi state
980 * returns immediately. */
981 addReply(c, shared.nullbulk);
982 } else {
983 /* The list is empty and the client blocks. */
984 blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]);
985 }
986 } else {
987 if (key->type != REDIS_LIST) {
988 addReply(c, shared.wrongtypeerr);
989 } else {
990
991 /* The list exists and has elements, so
992 * the regular rpoplpushCommand is executed. */
993 redisAssert(listTypeLength(key) > 0);
994 rpoplpushCommand(c);
995 }
996 }
997}