void pushGenericCommand(redisClient *c, int where) { robj *lobj = lookupKeyWrite(c->db,c->argv[1]); c->argv[2] = tryObjectEncoding(c->argv[2]); if (lobj == NULL) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { addReply(c,shared.cone); return; } lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj); } else { if (lobj->type != REDIS_LIST) { addReply(c,shared.wrongtypeerr); return; } if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { touchWatchedKey(c->db,c->argv[1]); addReply(c,shared.cone); return; } } listTypePush(lobj,c->argv[2],where); addReplyLongLong(c,listTypeLength(lobj)); touchWatchedKey(c->db,c->argv[1]); server.dirty++; }
void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey, robj *dstobj, robj *value) { if (!handleClientsWaitingListPush(origclient,dstkey,value)) { /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createZiplistObject(); dbAdd(c->db,dstkey,dstobj); } else { signalModifiedKey(c->db,dstkey); } listTypePush(dstobj,value,REDIS_HEAD); /* Additionally propagate this PUSH operation together with * the operation performed by the command. */ { robj **argv = zmalloc(sizeof(robj*)*3); argv[0] = createStringObject("LPUSH",5); argv[1] = dstkey; argv[2] = value; incrRefCount(argv[1]); incrRefCount(argv[2]); alsoPropagate(server.lpushCommand,c->db->id,argv,3, REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL); } } /* Always send the pushed value to the client. */ addReplyBulk(c,value); }
void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey, robj *dstobj, robj *value) { robj *aux; if (!handleClientsWaitingListPush(c,dstkey,value)) { /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createZiplistObject(); dbAdd(c->db,dstkey,dstobj); } else { signalModifiedKey(c->db,dstkey); } listTypePush(dstobj,value,REDIS_HEAD); /* If we are pushing as a result of LPUSH against a key * watched by BLPOPLPUSH, we need to rewrite the command vector. * But if this is called directly by RPOPLPUSH (either directly * or via a BRPOPLPUSH where the popped list exists) * we should replicate the BRPOPLPUSH command itself. */ if (c != origclient) { aux = createStringObject("LPUSH",5); rewriteClientCommandVector(origclient,3,aux,dstkey,value); decrRefCount(aux); } else { /* Make sure to always use RPOPLPUSH in the replication / AOF, * even if the original command was BRPOPLPUSH. */ aux = createStringObject("RPOPLPUSH",9); rewriteClientCommandVector(origclient,3,aux,c->argv[1],c->argv[2]); decrRefCount(aux); } server.dirty++; } /* Always send the pushed value to the client. */ addReplyBulk(c,value); }
/* This is the semantic of this command: * RPOPLPUSH srclist dstlist: * IF LLEN(srclist) > 0 * element = RPOP srclist * LPUSH dstlist element * RETURN element * ELSE * RETURN nil * END * END * * The idea is to be able to get an element from a list in a reliable way * since the element is not just returned but pushed against another list * as well. This command was originally proposed by Ezra Zygmuntowicz. */ void rpoplpushcommand(redisClient *c) { robj *sobj, *value; if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,sobj,REDIS_LIST)) return; if (listTypeLength(sobj) == 0) { addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2]); if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); /* Add the element to the target list (unless it's directly * passed to some BLPOP-ing client */ if (!handleClientsWaitingListPush(c,c->argv[2],value)) { /* Create the list if the key does not exist */ if (!dobj) { dobj = createZiplistObject(); dbAdd(c->db,c->argv[2],dobj); } listTypePush(dobj,value,REDIS_HEAD); } /* Send the element to the client as reply as well */ addReplyBulk(c,value); /* listTypePop returns an object with its refcount incremented */ decrRefCount(value); /* Delete the source list when it is empty */ if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]); server.dirty++; } }
void pushGenericCommand(redisClient *c, int where) { int j, addlen = 0, pushed = 0; robj *lobj = lookupKeyWrite(c->db,c->argv[1]); int may_have_waiting_clients = (lobj == NULL); if (lobj && lobj->type != REDIS_LIST) { addReply(c,shared.wrongtypeerr); return; } for (j = 2; j < c->argc; j++) { c->argv[j] = tryObjectEncoding(c->argv[j]); if (may_have_waiting_clients) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[j])) { addlen++; continue; } else { may_have_waiting_clients = 0; } } if (!lobj) { lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj); } listTypePush(lobj,c->argv[j],where); pushed++; } addReplyLongLong(c,addlen + (lobj ? listTypeLength(lobj) : 0)); if (pushed) signalModifiedKey(c->db,c->argv[1]); server.dirty += pushed; }
void pushGenericCommand(redisClient *c, int where) { int j, waiting = 0, pushed = 0; robj *lobj = lookupKeyWrite(c->db,c->argv[1]); if (lobj && lobj->type != REDIS_LIST) { addReply(c,shared.wrongtypeerr); return; } for (j = 2; j < c->argc; j++) { c->argv[j] = tryObjectEncoding(c->argv[j]); if (!lobj) { lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj); } listTypePush(lobj,c->argv[j],where); pushed++; } addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0)); if (pushed) { char *event = (where == REDIS_HEAD) ? "lpush" : "rpush"; signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id); } server.dirty += pushed; }
void pushGenericCommand(redisClient *c, int where) { int j, waiting = 0, pushed = 0; robj *lobj = lookupKeyWrite(c->db,c->argv[1]); int may_have_waiting_clients = (lobj == NULL); if (lobj && lobj->type != REDIS_LIST) {//检查类型是否是list类型 addReply(c,shared.wrongtypeerr); return; } if (may_have_waiting_clients) signalListAsReady(c,c->argv[1]); for (j = 2; j < c->argc; j++) { c->argv[j] = tryObjectEncoding(c->argv[j]); if (!lobj) {//该键不存在 创建压缩列表 lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj);//该键添加到db中 } listTypePush(lobj,c->argv[j],where);//把value添加列表中 pushed++; } addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0)); if (pushed) { char *event = (where == REDIS_HEAD) ? "lpush" : "rpush"; signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);//使用键空间通知 } //改变最后一次保存的节点 server.dirty += pushed; }
//当key存在时则push,PUSHX,INSERT命令的底层实现 void pushxGenericCommand(client *c, robj *refval, robj *val, int where) { robj *subject; listTypeIterator *iter; listTypeEntry entry; int inserted = 0; //以写操作读取key对象的value //如果读取失败或读取的value对象不是列表类型则返回 if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,subject,OBJ_LIST)) return; //寻找基准值refval if (refval != NULL) { /* Seek refval from head to tail */ //创建一个列表的迭代器 iter = listTypeInitIterator(subject,0,LIST_TAIL); //将指向当前的entry节点保存到列表类型的entry中,然后指向下一个entry节点 while (listTypeNext(iter,&entry)) { //当前的entry节点的值与基准值refval是否相等 if (listTypeEqual(&entry,refval)) { //如果相等,根据where插入val对象 listTypeInsert(&entry,val,where); inserted = 1; //设置插入的标识,跳出循环 break; } } //事项迭代器 listTypeReleaseIterator(iter); //如果插入成功,键值被修改,则发送信号并且发送"linsert"时间通知 if (inserted) { signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_LIST,"linsert", c->argv[1],c->db->id); server.dirty++; //更新脏键 } else { /* Notify client of a failed insert */ //如果没有插入,则发送插入失败的信息 addReply(c,shared.cnegone); return; } //如果基准值为空 } else { //根据where判断出事件名称 char *event = (where == LIST_HEAD) ? "lpush" : "rpush"; //将val对象推入到列表的头部或尾部 listTypePush(subject,val,where); //当数据库的键被改动,则会调用该函数发送信号 signalModifiedKey(c->db,c->argv[1]); //发送事件通知 notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id); server.dirty++; //更新脏键 } //将插入val后的列表的元素个数发送给client addReplyLongLong(c,listTypeLength(subject)); }
void pushnGenericCommand(redisClient *c, int where) { /* return_value must be null,otherwise it have memory leak */ c->returncode = REDIS_ERR; robj *lobj = lookupKeyWriteWithVersion(c->db, c->argv[1], &c->version); robj* key = c->argv[1]; if(lobj != NULL) { if(checkType(c, lobj, REDIS_LIST)) { c->returncode = REDIS_ERR_WRONG_TYPE_ERROR; return; } uint16_t version = sdsversion(key->ptr); if(c->version_care && version != 0 && version != c->version) { c->returncode = REDIS_ERR_VERSION_ERROR; return; } else { sdsversion_change(key->ptr, c->version); } } else { sdsversion_change(key->ptr, 0); } if(c->version_care) { sdsversion_add(key->ptr, 1); } c->return_value = (void*)zmalloc(sizeof(push_return_value)); if(c->return_value == NULL) { c->returncode = REDIS_ERR_MEMORY_ALLOCATE_ERROR; return; } int i = 2; for(; i < c->argc; i++) { if(lobj == NULL) { c->argv[i] = tryObjectEncoding(c->argv[i]); lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj); } unsigned long list_len = listTypeLength(lobj); if (list_len >= (unsigned long)(c->server->list_max_size)) { break; } listTypePush(c,lobj,c->argv[i],where); c->server->dirty++; } if (i != 2) { dbUpdateKey(c->db, key); EXPIRE_OR_NOT }
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) { /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createZiplistObject(); dbAdd(c->db,dstkey,dstobj); } signalModifiedKey(c->db,dstkey); listTypePush(dstobj,value,REDIS_HEAD); /* Always send the pushed value to the client. */ addReplyBulk(c,value); }
void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { robj *subject; listTypeIterator *iter; listTypeEntry entry; int inserted = 0; int slotnum = keyHashSlot(c->argv[1]->ptr, sdslen(c->argv[1]->ptr)); if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,subject,REDIS_LIST)) return; if (refval != NULL) { /* We're not sure if this value can be inserted yet, but we cannot * convert the list inside the iterator. We don't want to loop over * the list twice (once to see if the value can be inserted and once * to do the actual insert), so we assume this value can be inserted * and convert the ziplist to a regular list if necessary. */ listTypeTryConversion(subject,val); /* Seek refval from head to tail */ iter = listTypeInitIterator(subject,0,REDIS_TAIL); while (listTypeNext(iter,&entry)) { if (listTypeEqual(&entry,refval)) { listTypeInsert(&entry,val,where); inserted = 1; break; } } listTypeReleaseIterator(iter); if (inserted) { /* Check if the length exceeds the ziplist length threshold. */ if (subject->encoding == REDIS_ENCODING_ZIPLIST && ziplistLen(subject->ptr) > server.list_max_ziplist_entries) listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST); signalModifiedKey(c->db,c->argv[1],slotnum); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"linsert", c->argv[1],c->db->id); server.dirty++; } else { /* Notify client of a failed insert */ addReply(c,shared.cnegone); return; } } else { char *event = (where == REDIS_HEAD) ? "lpush" : "rpush"; listTypePush(subject,val,where); signalModifiedKey(c->db,c->argv[1],slotnum); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id); server.dirty++; } addReplyLongLong(c,listTypeLength(subject)); }
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value, int slotnum) { /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createZiplistObject(); dbAdd(c->db,dstkey,dstobj,slotnum); } signalModifiedKey(c->db,dstkey,slotnum); listTypePush(dstobj,value,REDIS_HEAD); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"lpush",dstkey,c->db->id); /* Always send the pushed value to the client. */ addReplyBulk(c,value); }
void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { robj *subject; listTypeIterator *iter; listTypeEntry entry; int inserted = 0; if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,subject,REDIS_LIST)) return; if (refval != NULL) { /* Note: we expect refval to be string-encoded because it is *not* the * last argument of the multi-bulk LINSERT. */ redisAssertWithInfo(c,refval,refval->encoding == REDIS_ENCODING_RAW); /* We're not sure if this value can be inserted yet, but we cannot * convert the list inside the iterator. We don't want to loop over * the list twice (once to see if the value can be inserted and once * to do the actual insert), so we assume this value can be inserted * and convert the ziplist to a regular list if necessary. */ listTypeTryConversion(subject,val); /* Seek refval from head to tail */ iter = listTypeInitIterator(subject,0,REDIS_TAIL); while (listTypeNext(iter,&entry)) { if (listTypeEqual(&entry,refval)) { listTypeInsert(&entry,val,where); inserted = 1; break; } } listTypeReleaseIterator(iter); if (inserted) { /* Check if the length exceeds the ziplist length threshold. */ if (subject->encoding == REDIS_ENCODING_ZIPLIST && ziplistLen(subject->ptr) > server.list_max_ziplist_entries) listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST); signalModifiedKey(c->db,c->argv[1]); server.dirty++; } else { /* Notify client of a failed insert */ addReply(c,shared.cnegone); return; } } else { listTypePush(subject,val,where); signalModifiedKey(c->db,c->argv[1]); server.dirty++; } addReplyLongLong(c,listTypeLength(subject)); }
void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value) { /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createQuicklistObject(); quicklistSetOptions(dstobj->ptr, server.list_max_ziplist_size, server.list_compress_depth); dbAdd(c->db,dstkey,dstobj); } signalModifiedKey(c->db,dstkey); listTypePush(dstobj,value,LIST_HEAD); notifyKeyspaceEvent(NOTIFY_LIST,"lpush",dstkey,c->db->id); /* Always send the pushed value to the client. */ addReplyBulk(c,value); }
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) { if (!handleClientsWaitingListPush(c,dstkey,value)) { /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createZiplistObject(); dbAdd(c->db,dstkey,dstobj); } else { touchWatchedKey(c->db,dstkey); server.dirty++; } listTypePush(dstobj,value,REDIS_HEAD); } /* Always send the pushed value to the client. */ addReplyBulk(c,value); }
void pushGenericCommand(redisClient *c, int where) { int j, waiting = 0, pushed = 0; robj *lobj = lookupKeyWrite(c->db,c->argv[1]); int may_have_waiting_clients = (lobj == NULL); if (lobj && lobj->type != REDIS_LIST) { addReply(c,shared.wrongtypeerr); return; } for (j = 2; j < c->argc; j++) { c->argv[j] = tryObjectEncoding(c->argv[j]); if (may_have_waiting_clients) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[j])) { waiting++; continue; } else { may_have_waiting_clients = 0; } } if (!lobj) { lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj); } listTypePush(lobj,c->argv[j],where); pushed++; } addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0)); if (pushed) signalModifiedKey(c->db,c->argv[1]); server.dirty += pushed; /* Alter the replication of the command accordingly to the number of * list elements delivered to clients waiting into a blocking operation. * We do that only if there were waiting clients, and only if still some * element was pushed into the list (othewise dirty is 0 and nothign will * be propagated). */ if (waiting && pushed) { /* CMD KEY a b C D E */ for (j = 0; j < waiting; j++) decrRefCount(c->argv[j+2]); memmove(c->argv+2,c->argv+2+waiting,sizeof(robj*)*pushed); c->argc -= waiting; } }
void pushxGenericCommand(client *c, robj *refval, robj *val, int where) { robj *subject; listTypeIterator *iter; listTypeEntry entry; int inserted = 0; if ((subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,subject,OBJ_LIST)) return; if (refval != NULL) { /* Seek refval from head to tail */ iter = listTypeInitIterator(subject,0,LIST_TAIL); while (listTypeNext(iter,&entry)) { if (listTypeEqual(&entry,refval)) { listTypeInsert(&entry,val,where); inserted = 1; break; } } listTypeReleaseIterator(iter); if (inserted) { signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_LIST,"linsert", c->argv[1],c->db->id); server.dirty++; } else { /* Notify client of a failed insert */ addReply(c,shared.cnegone); return; } } else { char *event = (where == LIST_HEAD) ? "lpush" : "rpush"; listTypePush(subject,val,where); signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id); server.dirty++; } addReplyLongLong(c,listTypeLength(subject)); }
//PUSH命令的底层实现,where保存push的位置 void pushGenericCommand(client *c, int where) { int j, waiting = 0, pushed = 0; robj *lobj = lookupKeyWrite(c->db,c->argv[1]); //以写操作读取key对象的value //如果value对象不是列表类型则发送错误信息,返回 if (lobj && lobj->type != OBJ_LIST) { addReply(c,shared.wrongtypeerr); return; } //从第一个value开始遍历 for (j = 2; j < c->argc; j++) { c->argv[j] = tryObjectEncoding(c->argv[j]); //将value对象优化编码 //如果没有找到key对象 if (!lobj) { //创建一个quicklist类型的对象 lobj = createQuicklistObject(); //设置ziplist最大的长度和压缩程度,配置文件指定 quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size, server.list_compress_depth); //将新的key对象和优化编码过的value对象进行组成键值对 dbAdd(c->db,c->argv[1],lobj); } //在where推入一个value对象 listTypePush(lobj,c->argv[j],where); pushed++; //更新计数器 } //发送当前列表中元素的个数 addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0)); //如果推入元素成功 if (pushed) { char *event = (where == LIST_HEAD) ? "lpush" : "rpush"; //当数据库的键被改动,则会调用该函数发送信号 signalModifiedKey(c->db,c->argv[1]); //发送"lpush"或"rpush"事件通知 notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id); } server.dirty += pushed; //更新脏键 }
//将一个value推入到列表头部,被rpoplpushCommand调用 void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value) { /* Create the list if the key does not exist */ //如果目标dstkey不存在 if (!dstobj) { //创建一个quicklist对象 dstobj = createQuicklistObject(); //设置ziplist的最大长度和压缩程度 quicklistSetOptions(dstobj->ptr, server.list_max_ziplist_size, server.list_compress_depth); //将key添加到数据库中 dbAdd(c->db,dstkey,dstobj); } //当数据库的键被改动,则会调用该函数发送信号 signalModifiedKey(c->db,dstkey); //将vlaue推入到列表的头部 listTypePush(dstobj,value,LIST_HEAD); //发送"lpush"时间通知 notifyKeyspaceEvent(NOTIFY_LIST,"lpush",dstkey,c->db->id); /* Always send the pushed value to the client. */ //将value值发送给client addReplyBulk(c,value); }
void pushGenericCommand(redisClient *c, int where) { int j, waiting = 0, pushed = 0; robj *lobj = lookupKeyWrite(c->db,c->argv[1]); if (lobj && lobj->type != REDIS_LIST) { addReply(c,shared.wrongtypeerr); return; } for (j = 2; j < c->argc; j++) { c->argv[j] = tryObjectEncoding(c->argv[j]); if (!lobj) { lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj); } listTypePush(lobj,c->argv[j],where); pushed++; } addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0)); if (pushed) { signalModifiedKey(c->db,c->argv[1]); } }
/* This function should be called by Redis every time a single command, * a MULTI/EXEC block, or a Lua script, terminated its execution after * being called by a client. * * All the keys with at least one client blocked that received at least * one new element via some PUSH/XADD operation are accumulated into * the server.ready_keys list. This function will run the list and will * serve clients accordingly. Note that the function will iterate again and * again as a result of serving BRPOPLPUSH we can have new blocking clients * to serve because of the PUSH side of BRPOPLPUSH. */ void handleClientsBlockedOnKeys(void) { while(listLength(server.ready_keys) != 0) { list *l; /* Point server.ready_keys to a fresh list and save the current one * locally. This way as we run the old list we are free to call * signalKeyAsReady() that may push new elements in server.ready_keys * when handling clients blocked into BRPOPLPUSH. */ l = server.ready_keys; server.ready_keys = listCreate(); while(listLength(l) != 0) { listNode *ln = listFirst(l); readyList *rl = ln->value; /* First of all remove this key from db->ready_keys so that * we can safely call signalKeyAsReady() against this key. */ dictDelete(rl->db->ready_keys,rl->key); /* Serve clients blocked on list key. */ robj *o = lookupKeyWrite(rl->db,rl->key); if (o != NULL && o->type == OBJ_LIST) { dictEntry *de; /* We serve clients in the same order they blocked for * this key, from the first blocked to the last. */ de = dictFind(rl->db->blocking_keys,rl->key); if (de) { list *clients = dictGetVal(de); int numclients = listLength(clients); while(numclients--) { listNode *clientnode = listFirst(clients); client *receiver = clientnode->value; if (receiver->btype != BLOCKED_LIST) { /* Put on the tail, so that at the next call * we'll not run into it again. */ listDelNode(clients,clientnode); listAddNodeTail(clients,receiver); continue; } robj *dstkey = receiver->bpop.target; int where = (receiver->lastcmd && receiver->lastcmd->proc == blpopCommand) ? LIST_HEAD : LIST_TAIL; robj *value = listTypePop(o,where); if (value) { /* Protect receiver->bpop.target, that will be * freed by the next unblockClient() * call. */ if (dstkey) incrRefCount(dstkey); unblockClient(receiver); if (serveClientBlockedOnList(receiver, rl->key,dstkey,rl->db,value, where) == C_ERR) { /* If we failed serving the client we need * to also undo the POP operation. */ listTypePush(o,value,where); } if (dstkey) decrRefCount(dstkey); decrRefCount(value); } else { break; } } } if (listTypeLength(o) == 0) { dbDelete(rl->db,rl->key); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id); } /* We don't call signalModifiedKey() as it was already called * when an element was pushed on the list. */ } /* Serve clients blocked on stream key. */ else if (o != NULL && o->type == OBJ_STREAM) { dictEntry *de = dictFind(rl->db->blocking_keys,rl->key); stream *s = o->ptr; /* We need to provide the new data arrived on the stream * to all the clients that are waiting for an offset smaller * than the current top item. */ if (de) { list *clients = dictGetVal(de); listNode *ln; listIter li; listRewind(clients,&li); while((ln = listNext(&li))) { client *receiver = listNodeValue(ln); if (receiver->btype != BLOCKED_STREAM) continue; streamID *gt = dictFetchValue(receiver->bpop.keys, rl->key); if (s->last_id.ms > gt->ms || (s->last_id.ms == gt->ms && s->last_id.seq > gt->seq)) { streamID start = *gt; start.seq++; /* Can't overflow, it's an uint64_t */ /* If we blocked in the context of a consumer * group, we need to resolve the group and * consumer here. */ streamCG *group = NULL; streamConsumer *consumer = NULL; if (receiver->bpop.xread_group) { group = streamLookupCG(s, receiver->bpop.xread_group->ptr); /* In theory if the group is not found we * just perform the read without the group, * but actually when the group, or the key * itself is deleted (triggering the removal * of the group), we check for blocked clients * and send them an error. */ } if (group) { consumer = streamLookupConsumer(group, receiver->bpop.xread_consumer->ptr, 1); } /* Note that after we unblock the client, 'gt' * and other receiver->bpop stuff are no longer * valid, so we must do the setup above before * this call. */ unblockClient(receiver); /* Emit the two elements sub-array consisting of * the name of the stream and the data we * extracted from it. Wrapped in a single-item * array, since we have just one key. */ addReplyMultiBulkLen(receiver,1); addReplyMultiBulkLen(receiver,2); addReplyBulk(receiver,rl->key); streamPropInfo pi = { rl->key, receiver->bpop.xread_group }; streamReplyWithRange(receiver,s,&start,NULL, receiver->bpop.xread_count, 0, group, consumer, 0, &pi); } } } } /* Free this item. */ decrRefCount(rl->key); zfree(rl); listDelNode(l,ln); } listRelease(l); /* We have the new list on place at this point. */ } }
//函数会在redis每次执行完单个命令,事务块或lua脚本之后被调用 //对于所有被阻塞在client的key来说,只要key被执行了PUSH,那么这个key会被加入到server.ready_keys中 //处理client的阻塞状态 void handleClientsBlockedOnLists(void) { //只要server.ready_keys还有要解除阻塞的key,就循环遍历server.ready_keys链表 while(listLength(server.ready_keys) != 0) { list *l; /* Point server.ready_keys to a fresh list and save the current one * locally. This way as we run the old list we are free to call * signalListAsReady() that may push new elements in server.ready_keys * when handling clients blocked into BRPOPLPUSH. */ //备份一个server.ready_keys链表 l = server.ready_keys; //生成一个新的空链表 server.ready_keys = listCreate(); //只要链表中还有就绪的key while(listLength(l) != 0) { listNode *ln = listFirst(l); //链表头结点地址 readyList *rl = ln->value; //保存链表节点的值,每个值都是readyList结构 /* First of all remove this key from db->ready_keys so that * we can safely call signalListAsReady() against this key. */ //从rl->db->ready_keys中删除就绪的key dictDelete(rl->db->ready_keys,rl->key); /* If the key exists and it's a list, serve blocked clients * with data. */ //以读操作将就绪key的值读出来 robj *o = lookupKeyWrite(rl->db,rl->key); //读出的value对象必须是列表类型 if (o != NULL && o->type == OBJ_LIST) { dictEntry *de; /* We serve clients in the same order they blocked for * this key, from the first blocked to the last. */ // blocking_keys是一个字典,字典的键是造成client阻塞的键,字典的值是链表,保存被阻塞的client // 根据key取出被阻塞的client de = dictFind(rl->db->blocking_keys,rl->key); // 链表非空 if (de) { // 获取de节点的值 list *clients = dictGetVal(de); // 获取链表的长度 int numclients = listLength(clients); //遍历链表的所有节点 while(numclients--) { // 第一个client节点地址 listNode *clientnode = listFirst(clients); // 取出节点的值,是一个client类型 client *receiver = clientnode->value; // 从client类型中的target获得要PUSH出的dstkey,该键保存在target中 robj *dstkey = receiver->bpop.target; // 获取弹出的位置,根据BRPOPLPUSH命令 int where = (receiver->lastcmd && receiver->lastcmd->proc == blpopCommand) ? LIST_HEAD : LIST_TAIL; // 从列表中弹出元素 robj *value = listTypePop(o,where); // 弹出成功 if (value) { /* Protect receiver->bpop.target, that will be * freed by the next unblockClient() * call. */ //增加dstkey的引用计数,保护该键,在unblockClient函数中释放 if (dstkey) incrRefCount(dstkey); //取消client的阻塞状态 unblockClient(receiver); // 将value推入造成client阻塞的键上, if (serveClientBlockedOnList(receiver, rl->key,dstkey,rl->db,value, where) == C_ERR) { /* If we failed serving the client we need * to also undo the POP operation. */ // 如果推入失败,则需要键弹出的value还原回去 listTypePush(o,value,where); } // 释放dstkey和value if (dstkey) decrRefCount(dstkey); decrRefCount(value); } else { break; } } } // 如果弹出了所有元素,将key从数据库中删除 if (listTypeLength(o) == 0) { dbDelete(rl->db,rl->key); } /* We don't call signalModifiedKey() as it was already called * when an element was pushed on the list. */ } /* Free this item. */ //释放所有空间 decrRefCount(rl->key); zfree(rl); listDelNode(l,ln); } //释放原来的ready_keys,因为之前创建了新的链表 listRelease(l); /* We have the new list on place at this point. */ } }
/* The SORT command is the most complex command in Redis. Warning: this code * is optimized for speed and a bit less for readability */ void sortCommand(redisClient *c) { list *operations; unsigned int outputlen = 0; int desc = 0, alpha = 0; long limit_start = 0, limit_count = -1, start, end; int j, dontsort = 0, vectorlen; int getop = 0; /* GET operation counter */ int int_convertion_error = 0; robj *sortval, *sortby = NULL, *storekey = NULL; redisSortObject *vector; /* Resulting vector to sort */ /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST && sortval->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); listSetFreeMethod(operations,zfree); j = 2; /* Now we need to protect sortval incrementing its count, in the future * SORT may have options able to overwrite/delete keys during the sorting * and the sorted key itself may get destroied */ if (sortval) incrRefCount(sortval); else sortval = createListObject(); /* The SORT command has an SQL-alike syntax, parse it */ while(j < c->argc) { int leftargs = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"asc")) { desc = 0; } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { desc = 1; } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { alpha = 1; } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return; j+=2; } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { storekey = c->argv[j+1]; j++; } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { sortby = c->argv[j+1]; /* If the BY pattern does not contain '*', i.e. it is constant, * we don't need to sort nor to lookup the weight keys. */ if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1; j++; } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { listAddNodeTail(operations,createSortOperation( REDIS_SORT_GET,c->argv[j+1])); getop++; j++; } else { decrRefCount(sortval); listRelease(operations); addReply(c,shared.syntaxerr); return; } j++; } /* If we have STORE we need to force sorting for deterministic output * and replication. We use alpha sorting since this is guaranteed to * work with any input. */ if (storekey && dontsort) { dontsort = 0; alpha = 1; sortby = NULL; } /* Destructively convert encoded sorted sets for SORT. */ if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); /* Load the sorting vector with all the objects to sort */ switch(sortval->type) { case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_SET: vectorlen = setTypeSize(sortval); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; if (sortval->type == REDIS_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { vector[j].obj = listTypeGet(&entry); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } listTypeReleaseIterator(li); } else if (sortval->type == REDIS_SET) { setTypeIterator *si = setTypeInitIterator(sortval); robj *ele; while((ele = setTypeNextObject(si)) != NULL) { vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } setTypeReleaseIterator(si); } else if (sortval->type == REDIS_ZSET) { dict *set = ((zset*)sortval->ptr)->dict; dictIterator *di; dictEntry *setele; di = dictGetIterator(set); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetKey(setele); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } dictReleaseIterator(di); } else { redisPanic("Unknown type"); } redisAssertWithInfo(c,sortval,j == vectorlen); /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { robj *byval; if (sortby) { /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); if (!byval) continue; } else { /* use object itself to sort by */ byval = vector[j].obj; } if (alpha) { if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); } else { if (byval->encoding == REDIS_ENCODING_RAW) { char *eptr; vector[j].u.score = strtod(byval->ptr,&eptr); if (eptr[0] != '\0' || errno == ERANGE || isnan(vector[j].u.score)) { int_convertion_error = 1; } } else if (byval->encoding == REDIS_ENCODING_INT) { /* Don't need to decode the object if it's * integer-encoded (the only encoding supported) so * far. We can just cast it */ vector[j].u.score = (long)byval->ptr; } else { redisAssertWithInfo(c,sortval,1 != 1); } } /* when the object was retrieved using lookupKeyByPattern, * its refcount needs to be decreased. */ if (sortby) { decrRefCount(byval); } } } /* We are ready to sort the vector... perform a bit of sanity check * on the LIMIT option too. We'll use a partial version of quicksort. */ start = (limit_start < 0) ? 0 : limit_start; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; if (start >= vectorlen) { start = vectorlen-1; end = vectorlen-2; } if (end >= vectorlen) end = vectorlen-1; server.sort_dontsort = dontsort; if (dontsort == 0) { server.sort_desc = desc; server.sort_alpha = alpha; server.sort_bypattern = sortby ? 1 : 0; if (sortby && (start != 0 || end != vectorlen-1)) pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); else qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); } /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; if (int_convertion_error) { addReplyError(c,"One or more scores can't be converted into double"); } else if (storekey == NULL) { /* STORE option not specified, sent the sorting result to client */ addReplyMultiBulkLen(c,outputlen); for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); decrRefCount(val); } } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } else { robj *sobj = createZiplistObject(); /* STORE option specified, set the sorting result as a List object */ for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) { listTypePush(sobj,vector[j].obj,REDIS_TAIL); } else { listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) val = createStringObject("",0); /* listTypePush does an incrRefCount, so we should take care * care of the incremented refcount caused by either * lookupKeyByPattern or createStringObject("",0) */ listTypePush(sobj,val,REDIS_TAIL); decrRefCount(val); } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } if (outputlen) { setKey(c->db,storekey,sobj); server.dirty += outputlen; } else if (dbDelete(c->db,storekey)) { signalModifiedKey(c->db,storekey); server.dirty++; } decrRefCount(sobj); addReplyLongLong(c,outputlen); } /* Cleanup */ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) for (j = 0; j < vectorlen; j++) decrRefCount(vector[j].obj); decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); }
/* The SORT command is the most complex command in Redis. Warning: this code * is optimized for speed and a bit less for readability */ void sortCommand(redisClient *c) { list *operations; unsigned int outputlen = 0; int desc = 0, alpha = 0; long limit_start = 0, limit_count = -1, start, end; int j, dontsort = 0, vectorlen; int getop = 0; /* GET operation counter */ int int_convertion_error = 0; robj *sortval, *sortby = NULL, *storekey = NULL; redisSortObject *vector; /* Resulting vector to sort */ /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval && sortval->type != REDIS_SET && sortval->type != REDIS_LIST && sortval->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); return; } /* Create a list of operations to perform for every sorted element. * Operations can be GET/DEL/INCR/DECR */ operations = listCreate(); listSetFreeMethod(operations,zfree); j = 2; /* options start at argv[2] */ /* Now we need to protect sortval incrementing its count, in the future * SORT may have options able to overwrite/delete keys during the sorting * and the sorted key itself may get destroyed */ if (sortval) incrRefCount(sortval); else sortval = createListObject(); /* The SORT command has an SQL-alike syntax, parse it */ while(j < c->argc) { int leftargs = c->argc-j-1; if (!strcasecmp(c->argv[j]->ptr,"asc")) { desc = 0; } else if (!strcasecmp(c->argv[j]->ptr,"desc")) { desc = 1; } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) { alpha = 1; } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) { if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL) != REDIS_OK) || (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL) != REDIS_OK)) return; j+=2; } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) { storekey = c->argv[j+1]; j++; } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) { sortby = c->argv[j+1]; /* If the BY pattern does not contain '*', i.e. it is constant, * we don't need to sort nor to lookup the weight keys. */ if (strchr(c->argv[j+1]->ptr,'*') == NULL) dontsort = 1; j++; } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) { listAddNodeTail(operations,createSortOperation( REDIS_SORT_GET,c->argv[j+1])); getop++; j++; } else { decrRefCount(sortval); listRelease(operations); addReply(c,shared.syntaxerr); return; } j++; } /* For the STORE option, or when SORT is called from a Lua script, * we want to force a specific ordering even when no explicit ordering * was asked (SORT BY nosort). This guarantees that replication / AOF * is deterministic. * * However in the case 'dontsort' is true, but the type to sort is a * sorted set, we don't need to do anything as ordering is guaranteed * in this special case. */ if ((storekey || c->flags & REDIS_LUA_CLIENT) && (dontsort && sortval->type != REDIS_ZSET)) { /* Force ALPHA sorting */ dontsort = 0; alpha = 1; sortby = NULL; } /* Destructively convert encoded sorted sets for SORT. */ if (sortval->type == REDIS_ZSET) zsetConvert(sortval, REDIS_ENCODING_SKIPLIST); /* Objtain the length of the object to sort. */ switch(sortval->type) { case REDIS_LIST: vectorlen = listTypeLength(sortval); break; case REDIS_SET: vectorlen = setTypeSize(sortval); break; case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break; default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */ } /* Perform LIMIT start,count sanity checking. */ start = (limit_start < 0) ? 0 : limit_start; end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1; if (start >= vectorlen) { start = vectorlen-1; end = vectorlen-2; } if (end >= vectorlen) end = vectorlen-1; /* Optimization: * * 1) if the object to sort is a sorted set. * 2) There is nothing to sort as dontsort is true (BY <constant string>). * 3) We have a LIMIT option that actually reduces the number of elements * to fetch. * * In this case to load all the objects in the vector is a huge waste of * resources. We just allocate a vector that is big enough for the selected * range length, and make sure to load just this part in the vector. */ if (sortval->type == REDIS_ZSET && dontsort && (start != 0 || end != vectorlen-1)) { vectorlen = end-start+1; } /* Load the sorting vector with all the objects to sort */ vector = zmalloc(sizeof(redisSortObject)*vectorlen); j = 0; if (sortval->type == REDIS_LIST) { listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL); listTypeEntry entry; while(listTypeNext(li,&entry)) { vector[j].obj = listTypeGet(&entry); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } listTypeReleaseIterator(li); } else if (sortval->type == REDIS_SET) { setTypeIterator *si = setTypeInitIterator(sortval); robj *ele; while((ele = setTypeNextObject(si)) != NULL) { vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } setTypeReleaseIterator(si); } else if (sortval->type == REDIS_ZSET && dontsort) { /* Special handling for a sorted set, if 'dontsort' is true. * This makes sure we return elements in the sorted set original * ordering, accordingly to DESC / ASC options. * * Note that in this case we also handle LIMIT here in a direct * way, just getting the required range, as an optimization. */ zset *zs = sortval->ptr; zskiplist *zsl = zs->zsl; zskiplistNode *ln; robj *ele; int rangelen = vectorlen; /* Check if starting point is trivial, before doing log(N) lookup. */ if (desc) { long zsetlen = dictSize(((zset*)sortval->ptr)->dict); ln = zsl->tail; if (start > 0) ln = zslGetElementByRank(zsl,zsetlen-start); } else { ln = zsl->header->level[0].forward; if (start > 0) ln = zslGetElementByRank(zsl,start+1); } while(rangelen--) { redisAssertWithInfo(c,sortval,ln != NULL); ele = ln->obj; vector[j].obj = ele; vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; ln = desc ? ln->backward : ln->level[0].forward; } /* The code producing the output does not know that in the case of * sorted set, 'dontsort', and LIMIT, we are able to get just the * range, already sorted, so we need to adjust "start" and "end" * to make sure start is set to 0. */ end -= start; start = 0; } else if (sortval->type == REDIS_ZSET) { dict *set = ((zset*)sortval->ptr)->dict; dictIterator *di; dictEntry *setele; di = dictGetIterator(set); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetKey(setele); vector[j].u.score = 0; vector[j].u.cmpobj = NULL; j++; } dictReleaseIterator(di); } else { redisPanic("Unknown type"); } redisAssertWithInfo(c,sortval,j == vectorlen); /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { robj *byval; if (sortby) { /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); if (!byval) continue; } else { /* use object itself to sort by */ byval = vector[j].obj; } if (alpha) { if (sortby) vector[j].u.cmpobj = getDecodedObject(byval); } else { if (byval->encoding == REDIS_ENCODING_RAW) { char *eptr; vector[j].u.score = strtod(byval->ptr,&eptr); if (eptr[0] != '\0' || errno == ERANGE || isnan(vector[j].u.score)) { int_convertion_error = 1; } } else if (byval->encoding == REDIS_ENCODING_INT) { /* Don't need to decode the object if it's * integer-encoded (the only encoding supported) so * far. We can just cast it */ vector[j].u.score = (long)byval->ptr; } else { redisAssertWithInfo(c,sortval,1 != 1); } } /* when the object was retrieved using lookupKeyByPattern, * its refcount needs to be decreased. */ if (sortby) { decrRefCount(byval); } } } if (dontsort == 0) { server.sort_desc = desc; server.sort_alpha = alpha; server.sort_bypattern = sortby ? 1 : 0; server.sort_store = storekey ? 1 : 0; if (sortby && (start != 0 || end != vectorlen-1)) pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end); else qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare); } /* Send command output to the output buffer, performing the specified * GET/DEL/INCR/DECR operations if any. */ outputlen = getop ? getop*(end-start+1) : end-start+1; if (int_convertion_error) { addReplyError(c,"One or more scores can't be converted into double"); } else if (storekey == NULL) { /* STORE option not specified, sent the sorting result to client */ addReplyMultiBulkLen(c,outputlen); for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) addReplyBulk(c,vector[j].obj); listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); decrRefCount(val); } } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } else { robj *sobj = createZiplistObject(); /* STORE option specified, set the sorting result as a List object */ for (j = start; j <= end; j++) { listNode *ln; listIter li; if (!getop) { listTypePush(sobj,vector[j].obj,REDIS_TAIL); } else { listRewind(operations,&li); while((ln = listNext(&li))) { redisSortOperation *sop = ln->value; robj *val = lookupKeyByPattern(c->db,sop->pattern, vector[j].obj); if (sop->type == REDIS_SORT_GET) { if (!val) val = createStringObject("",0); /* listTypePush does an incrRefCount, so we should take care * care of the incremented refcount caused by either * lookupKeyByPattern or createStringObject("",0) */ listTypePush(sobj,val,REDIS_TAIL); decrRefCount(val); } else { /* Always fails */ redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET); } } } } if (outputlen) { setKey(c->db,storekey,sobj); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey, c->db->id); server.dirty += outputlen; } else if (dbDelete(c->db,storekey)) { signalModifiedKey(c->db,storekey); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id); server.dirty++; } decrRefCount(sobj); addReplyLongLong(c,outputlen); } /* Cleanup */ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET) for (j = 0; j < vectorlen; j++) decrRefCount(vector[j].obj); decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); }
/* This function should be called by Redis every time a single command, * a MULTI/EXEC block, or a Lua script, terminated its execution after * being called by a client. * * All the keys with at least one client blocked that received at least * one new element via some PUSH operation are accumulated into * the server.ready_keys list. This function will run the list and will * serve clients accordingly. Note that the function will iterate again and * again as a result of serving BRPOPLPUSH we can have new blocking clients * to serve because of the PUSH side of BRPOPLPUSH. */ void handleClientsBlockedOnLists(void) { while(listLength(server.ready_keys) != 0) { list *l; /* Point server.ready_keys to a fresh list and save the current one * locally. This way as we run the old list we are free to call * signalListAsReady() that may push new elements in server.ready_keys * when handling clients blocked into BRPOPLPUSH. */ l = server.ready_keys; server.ready_keys = listCreate(); while(listLength(l) != 0) { robj *o; listNode *ln = listFirst(l); readyList *rl = ln->value; /* First of all remove this key from db->ready_keys so that * we can safely call signalListAsReady() against this key. */ dictDelete(rl->db->ready_keys,rl->key); /* If the key exists and it's a list, serve blocked clients * with data. */ o = lookupKeyWrite(rl->db,rl->key); if (o != NULL && o->type == REDIS_LIST) { dictEntry *de; /* We serve clients in the same order they blocked for * this key, from the first blocked to the last. */ de = dictFind(rl->db->blocking_keys,rl->key); if (de) { list *clients = dictGetVal(de); int numclients = listLength(clients); while(numclients--) { listNode *clientnode = listFirst(clients); redisClient *receiver = clientnode->value; robj *dstkey = receiver->bpop.target; int where = (receiver->lastcmd && receiver->lastcmd->proc == blpopCommand) ? REDIS_HEAD : REDIS_TAIL; robj *value = listTypePop(o,where); if (value) { /* Protect receiver->bpop.target, that will be * freed by the next unblockClientWaitingData() * call. */ if (dstkey) incrRefCount(dstkey); unblockClientWaitingData(receiver); if (serveClientBlockedOnList(receiver, rl->key,dstkey,rl->db,value, where) == REDIS_ERR) { /* If we failed serving the client we need * to also undo the POP operation. */ listTypePush(o,value,where); } if (dstkey) decrRefCount(dstkey); decrRefCount(value); } else { break; } } } if (listTypeLength(o) == 0) dbDelete(rl->db,rl->key); /* We don't call signalModifiedKey() as it was already called * when an element was pushed on the list. */ } /* Free this item. */ decrRefCount(rl->key); zfree(rl); listDelNode(l,ln); } listRelease(l); /* We have the new list on place at this point. */ } }