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]); robj *touchedkey = c->argv[1]; if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); /* We saved touched key, and protect it, since rpoplpushHandlePush * may change the client command argument vector. */ incrRefCount(touchedkey); rpoplpushHandlePush(c,c,c->argv[2],dobj,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,touchedkey); signalModifiedKey(c->db,touchedkey); decrRefCount(touchedkey); server.dirty++; } }
/* 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 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) { /* This may only happen after loading very old RDB files. Recent * versions of Redis delete keys of empty lists. */ addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2]); robj *touchedkey = c->argv[1]; if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); /* We saved touched key, and protect it, since rpoplpushHandlePush * may change the client command argument vector (it does not * currently). */ incrRefCount(touchedkey); rpoplpushHandlePush(c,c->argv[2],dobj,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,touchedkey); } signalModifiedKey(c->db,touchedkey); decrRefCount(touchedkey); } }
//POP命令的底层实现,where保存pop的位置 void popGenericCommand(client *c, int where) { //以写操作取出key对象的value值 robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); // 如果key没找到或value对象不是列表类型则直接返回 if (o == NULL || checkType(c,o,OBJ_LIST)) return; //从where 弹出一个value robj *value = listTypePop(o,where); //如果value为空,则发送空信息 if (value == NULL) { addReply(c,shared.nullbulk); } else { //保存时间名称 char *event = (where == LIST_HEAD) ? "lpop" : "rpop"; //发送value给client addReplyBulk(c,value); //释放value对象 decrRefCount(value); //发送事件通知 notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id); //如果弹出一个元素后,列表为空 if (listTypeLength(o) == 0) { //发送"del"时间通知 notifyKeyspaceEvent(NOTIFY_GENERIC,"del", c->argv[1],c->db->id); //从数据库中删除当前的key dbDelete(c->db,c->argv[1]); } //当数据库的键被改动,则会调用该函数发送信号 signalModifiedKey(c->db,c->argv[1]); //更新脏键 server.dirty++; } }
/* Blocking RPOP/LPOP */ void blockingPopGenericCommand(redisClient *c, int where) { robj *o; mstime_t timeout; int j; int slotnum; if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout,UNIT_SECONDS) != REDIS_OK) return; for (j = 1; j < c->argc-1; j++) { slotnum = keyHashSlot(c->argv[j]->ptr, sdslen(c->argv[j]->ptr)); o = lookupKeyWrite(c->db,c->argv[j],slotnum); if (o != NULL) { if (o->type != REDIS_LIST) { addReply(c,shared.wrongtypeerr); return; } else { if (listTypeLength(o) != 0) { /* Non empty list, this is like a non normal [LR]POP. */ char *event = (where == REDIS_HEAD) ? "lpop" : "rpop"; robj *value = listTypePop(o,where); redisAssert(value != NULL); addReplyMultiBulkLen(c,2); addReplyBulk(c,c->argv[j]); addReplyBulk(c,value); decrRefCount(value); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event, c->argv[j],c->db->id); if (listTypeLength(o) == 0) { dbDelete(c->db,c->argv[j],slotnum); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", c->argv[j],c->db->id); } signalModifiedKey(c->db,c->argv[j],slotnum); server.dirty++; /* Replicate it as an [LR]POP instead of B[LR]POP. */ rewriteClientCommandVector(c,2, (where == REDIS_HEAD) ? shared.lpop : shared.rpop, c->argv[j]); return; } } } } /* If we are inside a MULTI/EXEC and the list is empty the only thing * we can do is treating it as a timeout (even with timeout 0). */ if (c->flags & REDIS_MULTI) { addReply(c,shared.nullmultibulk); return; } /* If the list is empty or the key does not exists we must block */ blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL); }
// RPOPLPUSH source destination // RPOPLPUSH命令的实现 void rpoplpushCommand(client *c) { robj *sobj, *value; //以写操作读取source对象的值,并且检查数据类型是否为OBJ_LIST if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,sobj,OBJ_LIST)) return; //如果列表长度为0,没有元素,直接发送空信息 if (listTypeLength(sobj) == 0) { /* This may only happen after loading very old RDB files. Recent * versions of Redis delete keys of empty lists. */ addReply(c,shared.nullbulk); } else { //以写操作读取destination对象的值 robj *dobj = lookupKeyWrite(c->db,c->argv[2]); robj *touchedkey = c->argv[1]; //将source键备份 //如果目标对象类型是否是列表类型 if (dobj && checkType(c,dobj,OBJ_LIST)) return; //从source尾部弹出一个元素 value = listTypePop(sobj,LIST_TAIL); /* We saved touched key, and protect it, since rpoplpushHandlePush * may change the client command argument vector (it does not * currently). */ //备份一份source键,因为rpoplpushHandlePush可能会更改client命令行参数 incrRefCount(touchedkey); //将一个value推入到destination列表头部,如果destination列表不存在,则新创建一个 rpoplpushHandlePush(c,c->argv[2],dobj,value); /* listTypePop returns an object with its refcount incremented */ decrRefCount(value); //将弹出的value释放 /* Delete the source list when it is empty */ //发送"rpop"时间通知 notifyKeyspaceEvent(NOTIFY_LIST,"rpop",touchedkey,c->db->id); //如果source列表为空了,则删除key if (listTypeLength(sobj) == 0) { //删除之前备份的key键 dbDelete(c->db,touchedkey); //发送"rpop"时间通知 notifyKeyspaceEvent(NOTIFY_GENERIC,"del", touchedkey,c->db->id); } //当数据库的键被改动,则会调用该函数发送信号 signalModifiedKey(c->db,touchedkey); //释放备份的source键 decrRefCount(touchedkey); //脏键加1 server.dirty++; } }
void popGenericCommand(redisClient *c, int where) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; robj *value = listTypePop(o,where); if (value == NULL) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,value); decrRefCount(value); if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]); server.dirty++; } }
void popGenericCommand(redisClient *c, int where) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; robj *value = listTypePop(o,where); if (value == NULL) { addReply(c,shared.nullbulk); } else { char *event = (where == REDIS_HEAD) ? "lpop" : "rpop"; addReplyBulk(c,value); decrRefCount(value); notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id); if (listTypeLength(o) == 0) { notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del", c->argv[1],c->db->id); dbDelete(c->db,c->argv[1]); } signalModifiedKey(c->db,c->argv[1]); server.dirty++; } }
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); rpoplpushHandlePush(c,c->argv[2],dobj,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 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) { /* This may only happen after loading very old RDB files. Recent * versions of Redis delete keys of empty lists. */ addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2]); robj *touchedkey = c->argv[1]; if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); /* We saved touched key, and protect it, since rpoplpushHandlePush * may change the client command argument vector. */ incrRefCount(touchedkey); rpoplpushHandlePush(c,c,c->argv[2],dobj,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,touchedkey); signalModifiedKey(c->db,touchedkey); decrRefCount(touchedkey); server.dirty++; /* Replicate this as a simple RPOP since the LPUSH side is replicated * by rpoplpushHandlePush() call if needed (it may not be needed * if a client is blocking wait a push against the list). */ rewriteClientCommandVector(c,2, resetRefCount(createStringObject("RPOP",4)), 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 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. */ } }
// BRPOP BLPOP 命令的底层实现 // BLPOP key [key ...] timeout void blockingPopGenericCommand(client *c, int where) { robj *o; mstime_t timeout; int j; // 以秒为单位取出timeout值 if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout,UNIT_SECONDS) != C_OK) return; //遍历所有的key for (j = 1; j < c->argc-1; j++) { //以写操作取出当前key的值 o = lookupKeyWrite(c->db,c->argv[j]); // value对象不为空 if (o != NULL) { // 如果value对象的类型不是列表类型,发送类型错误信息 if (o->type != OBJ_LIST) { addReply(c,shared.wrongtypeerr); return; } else { // 列表长度不为0 if (listTypeLength(o) != 0) { /* Non empty list, this is like a non normal [LR]POP. */ // 保存事件名称 char *event = (where == LIST_HEAD) ? "lpop" : "rpop"; // 保存弹出的value对象 robj *value = listTypePop(o,where); serverAssert(value != NULL); // 发送回复给client addReplyMultiBulkLen(c,2); addReplyBulk(c,c->argv[j]); addReplyBulk(c,value); // 释放value decrRefCount(value); // 发送事件通知 notifyKeyspaceEvent(NOTIFY_LIST,event, c->argv[j],c->db->id); //如果弹出元素后列表为空 if (listTypeLength(o) == 0) { //从数据库中删除当前的key dbDelete(c->db,c->argv[j]); // 发送"del"的事件通知 notifyKeyspaceEvent(NOTIFY_GENERIC,"del", c->argv[j],c->db->id); } //数据库的键被修改,发送信号 signalModifiedKey(c->db,c->argv[j]); //更新脏键 server.dirty++; /* Replicate it as an [LR]POP instead of B[LR]POP. */ // 传播一个[LR]POP 而不是B[LR]POP rewriteClientCommandVector(c,2, (where == LIST_HEAD) ? shared.lpop : shared.rpop, c->argv[j]); return; } } } } /* If we are inside a MULTI/EXEC and the list is empty the only thing * we can do is treating it as a timeout (even with timeout 0). */ // 如果命令在一个事务中执行,则发送一个空回复以避免死等待 if (c->flags & CLIENT_MULTI) { addReply(c,shared.nullmultibulk); return; } /* If the list is empty or the key does not exists we must block */ // 参数中的所有键都不存在,则阻塞这些键 blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL); }
//函数会在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. */ } }
/* 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. */ } }