/* Mass-unblock clients because something changed in the instance that makes * blocking no longer safe. For example clients blocked in list operations * in an instance which turns from master to slave is unsafe, so this function * is called when a master turns into a slave. * * The semantics is to send an -UNBLOCKED error to the client, disconnecting * it at the same time. */ void disconnectAllBlockedClients(void) { listNode *ln; listIter li; listRewind(server.clients,&li); while((ln = listNext(&li))) { client *c = listNodeValue(ln); if (c->flags & CLIENT_BLOCKED) { addReplySds(c,sdsnew( "-UNBLOCKED force unblock from blocking operation, " "instance state changed (master -> slave?)\r\n")); unblockClient(c); c->flags |= CLIENT_CLOSE_AFTER_REPLY; } } }
/* 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) { listNode *ln = listFirst(l); readyList *rl = ln->value; int slotnum = keyHashSlot(rl->key->ptr, sdslen(rl->key->ptr)); redisDb *db = &(rl->db)[slotnum]; /* First of all remove this key from db->ready_keys so that * we can safely call signalListAsReady() against this key. */ dictDelete(db->ready_keys,rl->key); /* If the key exists and it's a list, serve blocked clients * with data. */ robj *o = lookupKeyWrite(rl->db,rl->key,slotnum); 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(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 unblockClient() * call. */ if (dstkey) incrRefCount(dstkey); unblockClient(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,slotnum); /* 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. */ } }
//函数会在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. */ } }