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. */ 10 void 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 17 void 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 41 robj *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 78 unsigned 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. */ 89 listTypeIterator *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. */ 105 void 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. */ 112 int 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. */ 142 robj *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 167 void 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. */ 198 int 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. */ 211 void 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 235 void 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 261 void pushGenericCommand(redisClient *c, int where) { 262 robj *lobj = lookupKeyWrite(c->db,c->argv[1]); 263 c->argv[2] = tryObjectEncoding(c->argv[2]); 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])) { 277 signalModifiedKey(c->db,c->argv[1]); 278 addReply(c,shared.cone); 279 return; 280 } 281 } 282 listTypePush(lobj,c->argv[2],where); 283 addReplyLongLong(c,listTypeLength(lobj)); 284 signalModifiedKey(c->db,c->argv[1]); 285 server.dirty++; 286 } 287 288 void lpushCommand(redisClient *c) { 289 pushGenericCommand(c,REDIS_HEAD); 290 } 291 292 void rpushCommand(redisClient *c) { 293 pushGenericCommand(c,REDIS_TAIL); 294 } 295 296 void 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); 333 signalModifiedKey(c->db,c->argv[1]); 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); 342 signalModifiedKey(c->db,c->argv[1]); 343 server.dirty++; 344 } 345 346 addReplyLongLong(c,listTypeLength(subject)); 347 } 348 349 void lpushxCommand(redisClient *c) { 350 c->argv[2] = tryObjectEncoding(c->argv[2]); 351 pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD); 352 } 353 354 void rpushxCommand(redisClient *c) { 355 c->argv[2] = tryObjectEncoding(c->argv[2]); 356 pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL); 357 } 358 359 void linsertCommand(redisClient *c) { 360 c->argv[4] = tryObjectEncoding(c->argv[4]); 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 370 void llenCommand(redisClient *c) { 371 robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero); 372 if (o == NULL || checkType(c,o,REDIS_LIST)) return; 373 addReplyLongLong(c,listTypeLength(o)); 374 } 375 376 void 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 412 void 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); 416 robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3])); 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); 430 signalModifiedKey(c->db,c->argv[1]); 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); 442 signalModifiedKey(c->db,c->argv[1]); 443 server.dirty++; 444 } 445 } else { 446 redisPanic("Unknown list encoding"); 447 } 448 } 449 450 void 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]); 461 signalModifiedKey(c->db,c->argv[1]); 462 server.dirty++; 463 } 464 } 465 466 void lpopCommand(redisClient *c) { 467 popGenericCommand(c,REDIS_HEAD); 468 } 469 470 void rpopCommand(redisClient *c) { 471 popGenericCommand(c,REDIS_TAIL); 472 } 473 474 void lrangeCommand(redisClient *c) { 475 robj *o; 476 int start = atoi(c->argv[2]->ptr); 477 int end = atoi(c->argv[3]->ptr); 478 int llen; 479 int rangelen; 480 481 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL 482 || checkType(c,o,REDIS_LIST)) return; 483 llen = listTypeLength(o); 484 485 /* convert negative indexes */ 486 if (start < 0) start = llen+start; 487 if (end < 0) end = llen+end; 488 if (start < 0) start = 0; 489 490 /* Invariant: start >= 0, so this test will be true when end < 0. 491 * The range is empty when start > end or start >= length. */ 492 if (start > end || start >= llen) { 493 addReply(c,shared.emptymultibulk); 494 return; 495 } 496 if (end >= llen) end = llen-1; 497 rangelen = (end-start)+1; 498 499 /* Return the result in form of a multi-bulk reply */ 500 addReplyMultiBulkLen(c,rangelen); 501 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 502 unsigned char *p = ziplistIndex(o->ptr,start); 503 unsigned char *vstr; 504 unsigned int vlen; 505 long long vlong; 506 507 while(rangelen--) { 508 ziplistGet(p,&vstr,&vlen,&vlong); 509 if (vstr) { 510 addReplyBulkCBuffer(c,vstr,vlen); 511 } else { 512 addReplyBulkLongLong(c,vlong); 513 } 514 p = ziplistNext(o->ptr,p); 515 } 516 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { 517 listNode *ln = listIndex(o->ptr,start); 518 519 while(rangelen--) { 520 addReplyBulk(c,ln->value); 521 ln = ln->next; 522 } 523 } else { 524 redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!"); 525 } 526 } 527 528 void ltrimCommand(redisClient *c) { 529 robj *o; 530 int start = atoi(c->argv[2]->ptr); 531 int end = atoi(c->argv[3]->ptr); 532 int llen; 533 int j, ltrim, rtrim; 534 list *list; 535 listNode *ln; 536 537 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.ok)) == NULL || 538 checkType(c,o,REDIS_LIST)) return; 539 llen = listTypeLength(o); 540 541 /* convert negative indexes */ 542 if (start < 0) start = llen+start; 543 if (end < 0) end = llen+end; 544 if (start < 0) start = 0; 545 546 /* Invariant: start >= 0, so this test will be true when end < 0. 547 * The range is empty when start > end or start >= length. */ 548 if (start > end || start >= llen) { 549 /* Out of range start or start > end result in empty list */ 550 ltrim = llen; 551 rtrim = 0; 552 } else { 553 if (end >= llen) end = llen-1; 554 ltrim = start; 555 rtrim = llen-end-1; 556 } 557 558 /* Remove list elements to perform the trim */ 559 if (o->encoding == REDIS_ENCODING_ZIPLIST) { 560 o->ptr = ziplistDeleteRange(o->ptr,0,ltrim); 561 o->ptr = ziplistDeleteRange(o->ptr,-rtrim,rtrim); 562 } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { 563 list = o->ptr; 564 for (j = 0; j < ltrim; j++) { 565 ln = listFirst(list); 566 listDelNode(list,ln); 567 } 568 for (j = 0; j < rtrim; j++) { 569 ln = listLast(list); 570 listDelNode(list,ln); 571 } 572 } else { 573 redisPanic("Unknown list encoding"); 574 } 575 if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); 576 signalModifiedKey(c->db,c->argv[1]); 577 server.dirty++; 578 addReply(c,shared.ok); 579 } 580 581 void lremCommand(redisClient *c) { 582 robj *subject, *obj; 583 obj = c->argv[3] = tryObjectEncoding(c->argv[3]); 584 int toremove = atoi(c->argv[2]->ptr); 585 int removed = 0; 586 listTypeEntry entry; 587 588 subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero); 589 if (subject == NULL || checkType(c,subject,REDIS_LIST)) return; 590 591 /* Make sure obj is raw when we're dealing with a ziplist */ 592 if (subject->encoding == REDIS_ENCODING_ZIPLIST) 593 obj = getDecodedObject(obj); 594 595 listTypeIterator *li; 596 if (toremove < 0) { 597 toremove = -toremove; 598 li = listTypeInitIterator(subject,-1,REDIS_HEAD); 599 } else { 600 li = listTypeInitIterator(subject,0,REDIS_TAIL); 601 } 602 603 while (listTypeNext(li,&entry)) { 604 if (listTypeEqual(&entry,obj)) { 605 listTypeDelete(&entry); 606 server.dirty++; 607 removed++; 608 if (toremove && removed == toremove) break; 609 } 610 } 611 listTypeReleaseIterator(li); 612 613 /* Clean up raw encoded object */ 614 if (subject->encoding == REDIS_ENCODING_ZIPLIST) 615 decrRefCount(obj); 616 617 if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]); 618 addReplyLongLong(c,removed); 619 if (removed) signalModifiedKey(c->db,c->argv[1]); 620 } 621 622 /* This is the semantic of this command: 623 * RPOPLPUSH srclist dstlist: 624 * IF LLEN(srclist) > 0 625 * element = RPOP srclist 626 * LPUSH dstlist element 627 * RETURN element 628 * ELSE 629 * RETURN nil 630 * END 631 * END 632 * 633 * The idea is to be able to get an element from a list in a reliable way 634 * since the element is not just returned but pushed against another list 635 * as well. This command was originally proposed by Ezra Zygmuntowicz. 636 */ 637 638 void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) { 639 if (!handleClientsWaitingListPush(c,dstkey,value)) { 640 /* Create the list if the key does not exist */ 641 if (!dstobj) { 642 dstobj = createZiplistObject(); 643 dbAdd(c->db,dstkey,dstobj); 644 } else { 645 signalModifiedKey(c->db,dstkey); 646 server.dirty++; 647 } 648 listTypePush(dstobj,value,REDIS_HEAD); 649 } 650 651 /* Always send the pushed value to the client. */ 652 addReplyBulk(c,value); 653 } 654 655 void rpoplpushCommand(redisClient *c) { 656 robj *sobj, *value; 657 if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || 658 checkType(c,sobj,REDIS_LIST)) return; 659 660 if (listTypeLength(sobj) == 0) { 661 addReply(c,shared.nullbulk); 662 } else { 663 robj *dobj = lookupKeyWrite(c->db,c->argv[2]); 664 if (dobj && checkType(c,dobj,REDIS_LIST)) return; 665 value = listTypePop(sobj,REDIS_TAIL); 666 rpoplpushHandlePush(c,c->argv[2],dobj,value); 667 668 /* listTypePop returns an object with its refcount incremented */ 669 decrRefCount(value); 670 671 /* Delete the source list when it is empty */ 672 if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]); 673 signalModifiedKey(c->db,c->argv[1]); 674 server.dirty++; 675 } 676 } 677 678 /*----------------------------------------------------------------------------- 679 * Blocking POP operations 680 *----------------------------------------------------------------------------*/ 681 682 /* Currently Redis blocking operations support is limited to list POP ops, 683 * so the current implementation is not fully generic, but it is also not 684 * completely specific so it will not require a rewrite to support new 685 * kind of blocking operations in the future. 686 * 687 * Still it's important to note that list blocking operations can be already 688 * used as a notification mechanism in order to implement other blocking 689 * operations at application level, so there must be a very strong evidence 690 * of usefulness and generality before new blocking operations are implemented. 691 * 692 * This is how the current blocking POP works, we use BLPOP as example: 693 * - If the user calls BLPOP and the key exists and contains a non empty list 694 * then LPOP is called instead. So BLPOP is semantically the same as LPOP 695 * if there is not to block. 696 * - If instead BLPOP is called and the key does not exists or the list is 697 * empty we need to block. In order to do so we remove the notification for 698 * new data to read in the client socket (so that we'll not serve new 699 * requests if the blocking request is not served). Also we put the client 700 * in a dictionary (db->blocking_keys) mapping keys to a list of clients 701 * blocking for this keys. 702 * - If a PUSH operation against a key with blocked clients waiting is 703 * performed, we serve the first in the list: basically instead to push 704 * the new element inside the list we return it to the (first / oldest) 705 * blocking client, unblock the client, and remove it form the list. 706 * 707 * The above comment and the source code should be enough in order to understand 708 * the implementation and modify / fix it later. 709 */ 710 711 /* Set a client in blocking mode for the specified key, with the specified 712 * timeout */ 713 void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) { 714 dictEntry *de; 715 list *l; 716 int j; 717 718 c->bpop.keys = zmalloc(sizeof(robj*)*numkeys); 719 c->bpop.count = numkeys; 720 c->bpop.timeout = timeout; 721 c->bpop.target = target; 722 723 if (target != NULL) { 724 incrRefCount(target); 725 } 726 727 for (j = 0; j < numkeys; j++) { 728 /* Add the key in the client structure, to map clients -> keys */ 729 c->bpop.keys[j] = keys[j]; 730 incrRefCount(keys[j]); 731 732 /* And in the other "side", to map keys -> clients */ 733 de = dictFind(c->db->blocking_keys,keys[j]); 734 if (de == NULL) { 735 int retval; 736 737 /* For every key we take a list of clients blocked for it */ 738 l = listCreate(); 739 retval = dictAdd(c->db->blocking_keys,keys[j],l); 740 incrRefCount(keys[j]); 741 redisAssert(retval == DICT_OK); 742 } else { 743 l = dictGetEntryVal(de); 744 } 745 listAddNodeTail(l,c); 746 } 747 /* Mark the client as a blocked client */ 748 c->flags |= REDIS_BLOCKED; 749 server.bpop_blocked_clients++; 750 } 751 752 /* Unblock a client that's waiting in a blocking operation such as BLPOP */ 753 void unblockClientWaitingData(redisClient *c) { 754 dictEntry *de; 755 list *l; 756 int j; 757 758 redisAssert(c->bpop.keys != NULL); 759 /* The client may wait for multiple keys, so unblock it for every key. */ 760 for (j = 0; j < c->bpop.count; j++) { 761 /* Remove this client from the list of clients waiting for this key. */ 762 de = dictFind(c->db->blocking_keys,c->bpop.keys[j]); 763 redisAssert(de != NULL); 764 l = dictGetEntryVal(de); 765 listDelNode(l,listSearchKey(l,c)); 766 /* If the list is empty we need to remove it to avoid wasting memory */ 767 if (listLength(l) == 0) 768 dictDelete(c->db->blocking_keys,c->bpop.keys[j]); 769 decrRefCount(c->bpop.keys[j]); 770 } 771 772 /* Cleanup the client structure */ 773 zfree(c->bpop.keys); 774 c->bpop.keys = NULL; 775 c->bpop.target = NULL; 776 c->flags &= (~REDIS_BLOCKED); 777 server.bpop_blocked_clients--; 778 listAddNodeTail(server.unblocked_clients,c); 779 } 780 781 /* This should be called from any function PUSHing into lists. 782 * 'c' is the "pushing client", 'key' is the key it is pushing data against, 783 * 'ele' is the element pushed. 784 * 785 * If the function returns 0 there was no client waiting for a list push 786 * against this key. 787 * 788 * If the function returns 1 there was a client waiting for a list push 789 * against this key, the element was passed to this client thus it's not 790 * needed to actually add it to the list and the caller should return asap. */ 791 int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) { 792 struct dictEntry *de; 793 redisClient *receiver; 794 int numclients; 795 list *clients; 796 listNode *ln; 797 robj *dstkey, *dstobj; 798 799 de = dictFind(c->db->blocking_keys,key); 800 if (de == NULL) return 0; 801 clients = dictGetEntryVal(de); 802 numclients = listLength(clients); 803 804 /* Try to handle the push as long as there are clients waiting for a push. 805 * Note that "numclients" is used because the list of clients waiting for a 806 * push on "key" is deleted by unblockClient() when empty. 807 * 808 * This loop will have more than 1 iteration when there is a BRPOPLPUSH 809 * that cannot push the target list because it does not contain a list. If 810 * this happens, it simply tries the next client waiting for a push. */ 811 while (numclients--) { 812 ln = listFirst(clients); 813 redisAssert(ln != NULL); 814 receiver = ln->value; 815 dstkey = receiver->bpop.target; 816 817 /* This should remove the first element of the "clients" list. */ 818 unblockClientWaitingData(receiver); 819 redisAssert(ln != listFirst(clients)); 820 821 if (dstkey == NULL) { 822 /* BRPOP/BLPOP */ 823 addReplyMultiBulkLen(receiver,2); 824 addReplyBulk(receiver,key); 825 addReplyBulk(receiver,ele); 826 return 1; 827 } else { 828 /* BRPOPLPUSH */ 829 dstobj = lookupKeyWrite(receiver->db,dstkey); 830 if (dstobj && checkType(receiver,dstobj,REDIS_LIST)) { 831 decrRefCount(dstkey); 832 } else { 833 rpoplpushHandlePush(receiver,dstkey,dstobj,ele); 834 decrRefCount(dstkey); 835 return 1; 836 } 837 } 838 } 839 840 return 0; 841 } 842 843 int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) { 844 long tval; 845 846 if (getLongFromObjectOrReply(c,object,&tval, 847 "timeout is not an integer or out of range") != REDIS_OK) 848 return REDIS_ERR; 849 850 if (tval < 0) { 851 addReplyError(c,"timeout is negative"); 852 return REDIS_ERR; 853 } 854 855 if (tval > 0) tval += time(NULL); 856 *timeout = tval; 857 858 return REDIS_OK; 859 } 860 861 /* Blocking RPOP/LPOP */ 862 void blockingPopGenericCommand(redisClient *c, int where) { 863 robj *o; 864 time_t timeout; 865 int j; 866 867 if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout) != REDIS_OK) 868 return; 869 870 for (j = 1; j < c->argc-1; j++) { 871 o = lookupKeyWrite(c->db,c->argv[j]); 872 if (o != NULL) { 873 if (o->type != REDIS_LIST) { 874 addReply(c,shared.wrongtypeerr); 875 return; 876 } else { 877 if (listTypeLength(o) != 0) { 878 /* If the list contains elements fall back to the usual 879 * non-blocking POP operation */ 880 robj *argv[2], **orig_argv; 881 int orig_argc; 882 883 /* We need to alter the command arguments before to call 884 * popGenericCommand() as the command takes a single key. */ 885 orig_argv = c->argv; 886 orig_argc = c->argc; 887 argv[1] = c->argv[j]; 888 c->argv = argv; 889 c->argc = 2; 890 891 /* Also the return value is different, we need to output 892 * the multi bulk reply header and the key name. The 893 * "real" command will add the last element (the value) 894 * for us. If this souds like an hack to you it's just 895 * because it is... */ 896 addReplyMultiBulkLen(c,2); 897 addReplyBulk(c,argv[1]); 898 899 popGenericCommand(c,where); 900 901 /* Fix the client structure with the original stuff */ 902 c->argv = orig_argv; 903 c->argc = orig_argc; 904 905 return; 906 } 907 } 908 } 909 } 910 911 /* If we are inside a MULTI/EXEC and the list is empty the only thing 912 * we can do is treating it as a timeout (even with timeout 0). */ 913 if (c->flags & REDIS_MULTI) { 914 addReply(c,shared.nullmultibulk); 915 return; 916 } 917 918 /* If the list is empty or the key does not exists we must block */ 919 blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL); 920 } 921 922 void blpopCommand(redisClient *c) { 923 blockingPopGenericCommand(c,REDIS_HEAD); 924 } 925 926 void brpopCommand(redisClient *c) { 927 blockingPopGenericCommand(c,REDIS_TAIL); 928 } 929 930 void brpoplpushCommand(redisClient *c) { 931 time_t timeout; 932 933 if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK) 934 return; 935 936 robj *key = lookupKeyWrite(c->db, c->argv[1]); 937 938 if (key == NULL) { 939 if (c->flags & REDIS_MULTI) { 940 941 /* Blocking against an empty list in a multi state 942 * returns immediately. */ 943 addReply(c, shared.nullmultibulk); 944 } else { 945 /* The list is empty and the client blocks. */ 946 blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]); 947 } 948 } else { 949 if (key->type != REDIS_LIST) { 950 addReply(c, shared.wrongtypeerr); 951 } else { 952 953 /* The list exists and has elements, so 954 * the regular rpoplpushCommand is executed. */ 955 redisAssert(listTypeLength(key) > 0); 956 rpoplpushCommand(c); 957 } 958 } 959 } 960