]> git.saurik.com Git - redis.git/blame - src/t_list.c
Refactor code for BRPOPLPUSH.
[redis.git] / src / t_list.c
CommitLineData
e2641e09 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 robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
75b41de8 263 c->argv[2] = tryObjectEncoding(c->argv[2]);
e2641e09 264 if (lobj == NULL) {
265 if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
266 addReply(c,shared.cone);
267 return;
268 }
269 lobj = createZiplistObject();
270 dbAdd(c->db,c->argv[1],lobj);
271 } else {
272 if (lobj->type != REDIS_LIST) {
273 addReply(c,shared.wrongtypeerr);
274 return;
275 }
276 if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
5b4bff9c 277 touchWatchedKey(c->db,c->argv[1]);
e2641e09 278 addReply(c,shared.cone);
279 return;
280 }
281 }
282 listTypePush(lobj,c->argv[2],where);
283 addReplyLongLong(c,listTypeLength(lobj));
5b4bff9c 284 touchWatchedKey(c->db,c->argv[1]);
e2641e09 285 server.dirty++;
286}
287
288void lpushCommand(redisClient *c) {
289 pushGenericCommand(c,REDIS_HEAD);
290}
291
292void rpushCommand(redisClient *c) {
293 pushGenericCommand(c,REDIS_TAIL);
294}
295
296void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
297 robj *subject;
298 listTypeIterator *iter;
299 listTypeEntry entry;
300 int inserted = 0;
301
302 if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
303 checkType(c,subject,REDIS_LIST)) return;
304
305 if (refval != NULL) {
306 /* Note: we expect refval to be string-encoded because it is *not* the
307 * last argument of the multi-bulk LINSERT. */
308 redisAssert(refval->encoding == REDIS_ENCODING_RAW);
309
310 /* We're not sure if this value can be inserted yet, but we cannot
311 * convert the list inside the iterator. We don't want to loop over
312 * the list twice (once to see if the value can be inserted and once
313 * to do the actual insert), so we assume this value can be inserted
314 * and convert the ziplist to a regular list if necessary. */
315 listTypeTryConversion(subject,val);
316
317 /* Seek refval from head to tail */
318 iter = listTypeInitIterator(subject,0,REDIS_TAIL);
319 while (listTypeNext(iter,&entry)) {
320 if (listTypeEqual(&entry,refval)) {
321 listTypeInsert(&entry,val,where);
322 inserted = 1;
323 break;
324 }
325 }
326 listTypeReleaseIterator(iter);
327
328 if (inserted) {
329 /* Check if the length exceeds the ziplist length threshold. */
330 if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
331 ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
332 listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
5b4bff9c 333 touchWatchedKey(c->db,c->argv[1]);
e2641e09 334 server.dirty++;
335 } else {
336 /* Notify client of a failed insert */
337 addReply(c,shared.cnegone);
338 return;
339 }
340 } else {
341 listTypePush(subject,val,where);
5b4bff9c 342 touchWatchedKey(c->db,c->argv[1]);
e2641e09 343 server.dirty++;
344 }
345
b70d3555 346 addReplyLongLong(c,listTypeLength(subject));
e2641e09 347}
348
349void lpushxCommand(redisClient *c) {
75b41de8 350 c->argv[2] = tryObjectEncoding(c->argv[2]);
e2641e09 351 pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD);
352}
353
354void rpushxCommand(redisClient *c) {
75b41de8 355 c->argv[2] = tryObjectEncoding(c->argv[2]);
e2641e09 356 pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL);
357}
358
359void linsertCommand(redisClient *c) {
75b41de8 360 c->argv[4] = tryObjectEncoding(c->argv[4]);
e2641e09 361 if (strcasecmp(c->argv[2]->ptr,"after") == 0) {
362 pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL);
363 } else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
364 pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_HEAD);
365 } else {
366 addReply(c,shared.syntaxerr);
367 }
368}
369
370void llenCommand(redisClient *c) {
371 robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
372 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
b70d3555 373 addReplyLongLong(c,listTypeLength(o));
e2641e09 374}
375
376void lindexCommand(redisClient *c) {
377 robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk);
378 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
379 int index = atoi(c->argv[2]->ptr);
380 robj *value = NULL;
381
382 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
383 unsigned char *p;
384 unsigned char *vstr;
385 unsigned int vlen;
386 long long vlong;
387 p = ziplistIndex(o->ptr,index);
388 if (ziplistGet(p,&vstr,&vlen,&vlong)) {
389 if (vstr) {
390 value = createStringObject((char*)vstr,vlen);
391 } else {
392 value = createStringObjectFromLongLong(vlong);
393 }
394 addReplyBulk(c,value);
395 decrRefCount(value);
396 } else {
397 addReply(c,shared.nullbulk);
398 }
399 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
400 listNode *ln = listIndex(o->ptr,index);
401 if (ln != NULL) {
402 value = listNodeValue(ln);
403 addReplyBulk(c,value);
404 } else {
405 addReply(c,shared.nullbulk);
406 }
407 } else {
408 redisPanic("Unknown list encoding");
409 }
410}
411
412void lsetCommand(redisClient *c) {
413 robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
414 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
415 int index = atoi(c->argv[2]->ptr);
75b41de8 416 robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
e2641e09 417
418 listTypeTryConversion(o,value);
419 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
420 unsigned char *p, *zl = o->ptr;
421 p = ziplistIndex(zl,index);
422 if (p == NULL) {
423 addReply(c,shared.outofrangeerr);
424 } else {
425 o->ptr = ziplistDelete(o->ptr,&p);
426 value = getDecodedObject(value);
427 o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
428 decrRefCount(value);
429 addReply(c,shared.ok);
5b4bff9c 430 touchWatchedKey(c->db,c->argv[1]);
e2641e09 431 server.dirty++;
432 }
433 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
434 listNode *ln = listIndex(o->ptr,index);
435 if (ln == NULL) {
436 addReply(c,shared.outofrangeerr);
437 } else {
438 decrRefCount((robj*)listNodeValue(ln));
439 listNodeValue(ln) = value;
440 incrRefCount(value);
441 addReply(c,shared.ok);
5b4bff9c 442 touchWatchedKey(c->db,c->argv[1]);
e2641e09 443 server.dirty++;
444 }
445 } else {
446 redisPanic("Unknown list encoding");
447 }
448}
449
450void popGenericCommand(redisClient *c, int where) {
451 robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk);
452 if (o == NULL || checkType(c,o,REDIS_LIST)) return;
453
454 robj *value = listTypePop(o,where);
455 if (value == NULL) {
456 addReply(c,shared.nullbulk);
457 } else {
458 addReplyBulk(c,value);
459 decrRefCount(value);
460 if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
5b4bff9c 461 touchWatchedKey(c->db,c->argv[1]);
e2641e09 462 server.dirty++;
463 }
464}
465
466void lpopCommand(redisClient *c) {
467 popGenericCommand(c,REDIS_HEAD);
468}
469
470void rpopCommand(redisClient *c) {
471 popGenericCommand(c,REDIS_TAIL);
472}
473
474void lrangeCommand(redisClient *c) {
475 robj *o, *value;
476 int start = atoi(c->argv[2]->ptr);
477 int end = atoi(c->argv[3]->ptr);
478 int llen;
479 int rangelen, j;
480 listTypeEntry entry;
481
482 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
483 || checkType(c,o,REDIS_LIST)) return;
484 llen = listTypeLength(o);
485
486 /* convert negative indexes */
487 if (start < 0) start = llen+start;
488 if (end < 0) end = llen+end;
489 if (start < 0) start = 0;
e2641e09 490
d0a4e24e
PN
491 /* Invariant: start >= 0, so this test will be true when end < 0.
492 * The range is empty when start > end or start >= length. */
e2641e09 493 if (start > end || start >= llen) {
e2641e09 494 addReply(c,shared.emptymultibulk);
495 return;
496 }
497 if (end >= llen) end = llen-1;
498 rangelen = (end-start)+1;
499
500 /* Return the result in form of a multi-bulk reply */
0537e7bf 501 addReplyMultiBulkLen(c,rangelen);
e2641e09 502 listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL);
503 for (j = 0; j < rangelen; j++) {
504 redisAssert(listTypeNext(li,&entry));
505 value = listTypeGet(&entry);
506 addReplyBulk(c,value);
507 decrRefCount(value);
508 }
509 listTypeReleaseIterator(li);
510}
511
512void ltrimCommand(redisClient *c) {
513 robj *o;
514 int start = atoi(c->argv[2]->ptr);
515 int end = atoi(c->argv[3]->ptr);
516 int llen;
517 int j, ltrim, rtrim;
518 list *list;
519 listNode *ln;
520
521 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.ok)) == NULL ||
522 checkType(c,o,REDIS_LIST)) return;
523 llen = listTypeLength(o);
524
525 /* convert negative indexes */
526 if (start < 0) start = llen+start;
527 if (end < 0) end = llen+end;
528 if (start < 0) start = 0;
e2641e09 529
d0a4e24e
PN
530 /* Invariant: start >= 0, so this test will be true when end < 0.
531 * The range is empty when start > end or start >= length. */
e2641e09 532 if (start > end || start >= llen) {
533 /* Out of range start or start > end result in empty list */
534 ltrim = llen;
535 rtrim = 0;
536 } else {
537 if (end >= llen) end = llen-1;
538 ltrim = start;
539 rtrim = llen-end-1;
540 }
541
542 /* Remove list elements to perform the trim */
543 if (o->encoding == REDIS_ENCODING_ZIPLIST) {
544 o->ptr = ziplistDeleteRange(o->ptr,0,ltrim);
545 o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim);
546 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
547 list = o->ptr;
548 for (j = 0; j < ltrim; j++) {
549 ln = listFirst(list);
550 listDelNode(list,ln);
551 }
552 for (j = 0; j < rtrim; j++) {
553 ln = listLast(list);
554 listDelNode(list,ln);
555 }
556 } else {
557 redisPanic("Unknown list encoding");
558 }
559 if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
5b4bff9c 560 touchWatchedKey(c->db,c->argv[1]);
e2641e09 561 server.dirty++;
562 addReply(c,shared.ok);
563}
564
565void lremCommand(redisClient *c) {
75b41de8
PN
566 robj *subject, *obj;
567 obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
e2641e09 568 int toremove = atoi(c->argv[2]->ptr);
569 int removed = 0;
570 listTypeEntry entry;
571
572 subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero);
573 if (subject == NULL || checkType(c,subject,REDIS_LIST)) return;
574
575 /* Make sure obj is raw when we're dealing with a ziplist */
576 if (subject->encoding == REDIS_ENCODING_ZIPLIST)
577 obj = getDecodedObject(obj);
578
579 listTypeIterator *li;
580 if (toremove < 0) {
581 toremove = -toremove;
582 li = listTypeInitIterator(subject,-1,REDIS_HEAD);
583 } else {
584 li = listTypeInitIterator(subject,0,REDIS_TAIL);
585 }
586
587 while (listTypeNext(li,&entry)) {
588 if (listTypeEqual(&entry,obj)) {
589 listTypeDelete(&entry);
590 server.dirty++;
591 removed++;
592 if (toremove && removed == toremove) break;
593 }
594 }
595 listTypeReleaseIterator(li);
596
597 /* Clean up raw encoded object */
598 if (subject->encoding == REDIS_ENCODING_ZIPLIST)
599 decrRefCount(obj);
600
601 if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
b70d3555 602 addReplyLongLong(c,removed);
5b4bff9c 603 if (removed) touchWatchedKey(c->db,c->argv[1]);
e2641e09 604}
605
606/* This is the semantic of this command:
607 * RPOPLPUSH srclist dstlist:
608 * IF LLEN(srclist) > 0
609 * element = RPOP srclist
610 * LPUSH dstlist element
611 * RETURN element
612 * ELSE
613 * RETURN nil
614 * END
615 * END
616 *
617 * The idea is to be able to get an element from a list in a reliable way
618 * since the element is not just returned but pushed against another list
619 * as well. This command was originally proposed by Ezra Zygmuntowicz.
620 */
8a979f03 621void rpoplpushCommand(redisClient *c) {
e2641e09 622 robj *sobj, *value;
623 if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
624 checkType(c,sobj,REDIS_LIST)) return;
625
626 if (listTypeLength(sobj) == 0) {
627 addReply(c,shared.nullbulk);
628 } else {
629 robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
630 if (dobj && checkType(c,dobj,REDIS_LIST)) return;
631 value = listTypePop(sobj,REDIS_TAIL);
632
633 /* Add the element to the target list (unless it's directly
634 * passed to some BLPOP-ing client */
635 if (!handleClientsWaitingListPush(c,c->argv[2],value)) {
636 /* Create the list if the key does not exist */
637 if (!dobj) {
638 dobj = createZiplistObject();
639 dbAdd(c->db,c->argv[2],dobj);
640 }
641 listTypePush(dobj,value,REDIS_HEAD);
642 }
643
644 /* Send the element to the client as reply as well */
645 addReplyBulk(c,value);
646
647 /* listTypePop returns an object with its refcount incremented */
648 decrRefCount(value);
649
650 /* Delete the source list when it is empty */
651 if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
5b4bff9c 652 touchWatchedKey(c->db,c->argv[1]);
e2641e09 653 server.dirty++;
654 }
655}
656
657/*-----------------------------------------------------------------------------
658 * Blocking POP operations
659 *----------------------------------------------------------------------------*/
660
661/* Currently Redis blocking operations support is limited to list POP ops,
662 * so the current implementation is not fully generic, but it is also not
663 * completely specific so it will not require a rewrite to support new
664 * kind of blocking operations in the future.
665 *
666 * Still it's important to note that list blocking operations can be already
667 * used as a notification mechanism in order to implement other blocking
668 * operations at application level, so there must be a very strong evidence
669 * of usefulness and generality before new blocking operations are implemented.
670 *
671 * This is how the current blocking POP works, we use BLPOP as example:
672 * - If the user calls BLPOP and the key exists and contains a non empty list
673 * then LPOP is called instead. So BLPOP is semantically the same as LPOP
674 * if there is not to block.
675 * - If instead BLPOP is called and the key does not exists or the list is
676 * empty we need to block. In order to do so we remove the notification for
677 * new data to read in the client socket (so that we'll not serve new
678 * requests if the blocking request is not served). Also we put the client
679 * in a dictionary (db->blocking_keys) mapping keys to a list of clients
680 * blocking for this keys.
681 * - If a PUSH operation against a key with blocked clients waiting is
682 * performed, we serve the first in the list: basically instead to push
683 * the new element inside the list we return it to the (first / oldest)
684 * blocking client, unblock the client, and remove it form the list.
685 *
686 * The above comment and the source code should be enough in order to understand
687 * the implementation and modify / fix it later.
688 */
689
690/* Set a client in blocking mode for the specified key, with the specified
691 * timeout */
ba3b4741 692void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) {
e2641e09 693 dictEntry *de;
694 list *l;
695 int j;
696
357a8417
DJMM
697 c->bstate.keys = zmalloc(sizeof(robj*)*numkeys);
698 c->bstate.count = numkeys;
699 c->bstate.timeout = timeout;
ba3b4741
DJMM
700 c->bstate.target = target;
701
702 if (target != NULL) {
703 incrRefCount(target);
704 }
705
e2641e09 706 for (j = 0; j < numkeys; j++) {
707 /* Add the key in the client structure, to map clients -> keys */
357a8417 708 c->bstate.keys[j] = keys[j];
e2641e09 709 incrRefCount(keys[j]);
710
711 /* And in the other "side", to map keys -> clients */
712 de = dictFind(c->db->blocking_keys,keys[j]);
713 if (de == NULL) {
714 int retval;
715
716 /* For every key we take a list of clients blocked for it */
717 l = listCreate();
718 retval = dictAdd(c->db->blocking_keys,keys[j],l);
719 incrRefCount(keys[j]);
720 redisAssert(retval == DICT_OK);
721 } else {
722 l = dictGetEntryVal(de);
723 }
724 listAddNodeTail(l,c);
725 }
726 /* Mark the client as a blocked client */
727 c->flags |= REDIS_BLOCKED;
728 server.blpop_blocked_clients++;
729}
730
731/* Unblock a client that's waiting in a blocking operation such as BLPOP */
732void unblockClientWaitingData(redisClient *c) {
733 dictEntry *de;
734 list *l;
735 int j;
736
357a8417 737 redisAssert(c->bstate.keys != NULL);
e2641e09 738 /* The client may wait for multiple keys, so unblock it for every key. */
357a8417 739 for (j = 0; j < c->bstate.count; j++) {
e2641e09 740 /* Remove this client from the list of clients waiting for this key. */
357a8417 741 de = dictFind(c->db->blocking_keys,c->bstate.keys[j]);
e2641e09 742 redisAssert(de != NULL);
743 l = dictGetEntryVal(de);
744 listDelNode(l,listSearchKey(l,c));
745 /* If the list is empty we need to remove it to avoid wasting memory */
746 if (listLength(l) == 0)
357a8417
DJMM
747 dictDelete(c->db->blocking_keys,c->bstate.keys[j]);
748 decrRefCount(c->bstate.keys[j]);
e2641e09 749 }
ba3b4741
DJMM
750
751 if (c->bstate.target != NULL) {
752 decrRefCount(c->bstate.target);
753 }
754
e2641e09 755 /* Cleanup the client structure */
357a8417
DJMM
756 zfree(c->bstate.keys);
757 c->bstate.keys = NULL;
ba3b4741 758 c->bstate.target = NULL;
e2641e09 759 c->flags &= (~REDIS_BLOCKED);
760 server.blpop_blocked_clients--;
761 /* We want to process data if there is some command waiting
762 * in the input buffer. Note that this is safe even if
763 * unblockClientWaitingData() gets called from freeClient() because
764 * freeClient() will be smart enough to call this function
765 * *after* c->querybuf was set to NULL. */
766 if (c->querybuf && sdslen(c->querybuf) > 0) processInputBuffer(c);
767}
768
769/* This should be called from any function PUSHing into lists.
770 * 'c' is the "pushing client", 'key' is the key it is pushing data against,
771 * 'ele' is the element pushed.
772 *
773 * If the function returns 0 there was no client waiting for a list push
774 * against this key.
775 *
776 * If the function returns 1 there was a client waiting for a list push
777 * against this key, the element was passed to this client thus it's not
778 * needed to actually add it to the list and the caller should return asap. */
779int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
780 struct dictEntry *de;
781 redisClient *receiver;
782 list *l;
783 listNode *ln;
784
785 de = dictFind(c->db->blocking_keys,key);
786 if (de == NULL) return 0;
787 l = dictGetEntryVal(de);
788 ln = listFirst(l);
789 redisAssert(ln != NULL);
790 receiver = ln->value;
791
357a8417 792 if (receiver->bstate.target == NULL) {
b2a7fd0c
DJMM
793 addReplyMultiBulkLen(receiver,2);
794 addReplyBulk(receiver,key);
795 addReplyBulk(receiver,ele);
796 }
797 else {
357a8417 798 robj *dobj = lookupKeyWrite(receiver->db,receiver->bstate.target);
b2a7fd0c
DJMM
799 if (dobj && checkType(receiver,dobj,REDIS_LIST)) return 0;
800
801 addReplyBulk(receiver,ele);
802
803 /* Create the list if the key does not exist */
804 if (!dobj) {
805 dobj = createZiplistObject();
357a8417 806 dbAdd(receiver->db,receiver->bstate.target,dobj);
b2a7fd0c
DJMM
807 }
808
809 listTypePush(dobj,ele,REDIS_HEAD);
810 }
811
e2641e09 812 unblockClientWaitingData(receiver);
813 return 1;
814}
815
816/* Blocking RPOP/LPOP */
817void blockingPopGenericCommand(redisClient *c, int where) {
818 robj *o;
819 time_t timeout;
820 int j;
821
ba3b4741 822 if (checkTimeout(c, c->argv[c->argc - 1], &timeout) != REDIS_OK) {
94364d53
PN
823 return;
824 }
825
e2641e09 826 for (j = 1; j < c->argc-1; j++) {
827 o = lookupKeyWrite(c->db,c->argv[j]);
828 if (o != NULL) {
829 if (o->type != REDIS_LIST) {
830 addReply(c,shared.wrongtypeerr);
831 return;
832 } else {
833 if (listTypeLength(o) != 0) {
834 /* If the list contains elements fall back to the usual
835 * non-blocking POP operation */
836 robj *argv[2], **orig_argv;
837 int orig_argc;
838
ba3b4741
DJMM
839 /* We need to alter the command arguments before to call
840 * popGenericCommand() as the command takes a single key. */
841 orig_argv = c->argv;
842 orig_argc = c->argc;
843 argv[1] = c->argv[j];
844 c->argv = argv;
845 c->argc = 2;
846
847 /* Also the return value is different, we need to output
848 * the multi bulk reply header and the key name. The
849 * "real" command will add the last element (the value)
850 * for us. If this souds like an hack to you it's just
851 * because it is... */
852 addReplyMultiBulkLen(c,2);
853 addReplyBulk(c,argv[1]);
854
855 popGenericCommand(c,where);
856
857 /* Fix the client structure with the original stuff */
858 c->argv = orig_argv;
859 c->argc = orig_argc;
b2a7fd0c 860
e2641e09 861 return;
862 }
863 }
864 }
865 }
94364d53 866
fb92ecec 867 /* If we are inside a MULTI/EXEC and the list is empty the only thing
868 * we can do is treating it as a timeout (even with timeout 0). */
869 if (c->flags & REDIS_MULTI) {
870 addReply(c,shared.nullmultibulk);
871 return;
872 }
873
e2641e09 874 /* If the list is empty or the key does not exists we must block */
e2641e09 875 if (timeout > 0) timeout += time(NULL);
ba3b4741
DJMM
876 blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
877}
878
879int checkTimeout(redisClient *c, robj *object, time_t *timeout) {
880 long long lltimeout;
881
882 if (getLongLongFromObject(object, &lltimeout) != REDIS_OK) {
883 addReplyError(c, "timeout is not an integer");
884 return REDIS_ERR;
885 }
886
887 if (lltimeout < 0) {
888 addReplyError(c, "timeout is negative");
889 return REDIS_ERR;
890 }
891
892 *timeout = lltimeout;
893
894 return REDIS_OK;
e2641e09 895}
896
897void blpopCommand(redisClient *c) {
898 blockingPopGenericCommand(c,REDIS_HEAD);
899}
900
901void brpopCommand(redisClient *c) {
902 blockingPopGenericCommand(c,REDIS_TAIL);
903}
b2a7fd0c
DJMM
904
905void brpoplpushCommand(redisClient *c) {
ba3b4741 906 time_t timeout;
b2a7fd0c 907
ba3b4741
DJMM
908 if (checkTimeout(c, c->argv[3], &timeout) != REDIS_OK) {
909 return;
910 }
911
912 robj *key = lookupKeyWrite(c->db, c->argv[1]);
913
914
915 if (key == NULL) {
916 // block
917 if (c->flags & REDIS_MULTI) {
918 addReply(c,shared.nullmultibulk);
919 } else {
920 if (timeout > 0) timeout += time(NULL);
921 blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]);
922 }
923 } else if (key->type != REDIS_LIST) {
924 addReply(c, shared.wrongtypeerr);
925 } else {
926 // The list exists and has elements.
927 redisAssert(listTypeLength(key) > 0);
928 rpoplpushCommand(c);
929 }
b2a7fd0c 930}