void closeTimedoutClients(void) { redisClient *c; listNode *ln; time_t now = time(NULL); listIter li; listRewind(server.clients,&li); while ((ln = listNext(&li)) != NULL) { c = listNodeValue(ln); if (server.maxidletime && !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ !(c->flags & REDIS_MASTER) && /* no timeout for masters */ !(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */ dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */ listLength(c->pubsub_patterns) == 0 && (now - c->lastinteraction > server.maxidletime)) { redisLog(REDIS_VERBOSE,"Closing idle client"); freeClient(c); } else if (c->flags & REDIS_BLOCKED) { if (c->blockingto != 0 && c->blockingto < now) { addReply(c,shared.nullmultibulk); unblockClientWaitingData(c); } } } }
/* This should be called from any function PUSHing into lists. * 'c' is the "pushing client", 'key' is the key it is pushing data against, * 'ele' is the element pushed. * * If the function returns 0 there was no client waiting for a list push * against this key. * * If the function returns 1 there was a client waiting for a list push * against this key, the element was passed to this client thus it's not * needed to actually add it to the list and the caller should return asap. */ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) { struct dictEntry *de; redisClient *receiver; int numclients; list *clients; listNode *ln; robj *dstkey, *dstobj; de = dictFind(c->db->blocking_keys,key); if (de == NULL) return 0; clients = dictGetVal(de); numclients = listLength(clients); /* Try to handle the push as long as there are clients waiting for a push. * Note that "numclients" is used because the list of clients waiting for a * push on "key" is deleted by unblockClient() when empty. * * This loop will have more than 1 iteration when there is a BRPOPLPUSH * that cannot push the target list because it does not contain a list. If * this happens, it simply tries the next client waiting for a push. */ while (numclients--) { ln = listFirst(clients); redisAssertWithInfo(c,key,ln != NULL); receiver = ln->value; dstkey = receiver->bpop.target; /* Protect receiver->bpop.target, that will be freed by * the next unblockClientWaitingData() call. */ if (dstkey) incrRefCount(dstkey); /* This should remove the first element of the "clients" list. */ unblockClientWaitingData(receiver); if (dstkey == NULL) { /* BRPOP/BLPOP */ addReplyMultiBulkLen(receiver,2); addReplyBulk(receiver,key); addReplyBulk(receiver,ele); return 1; /* Serve just the first client as in B[RL]POP semantics */ } else { /* BRPOPLPUSH, note that receiver->db is always equal to c->db. */ dstobj = lookupKeyWrite(receiver->db,dstkey); if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) { rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele); decrRefCount(dstkey); return 1; } decrRefCount(dstkey); } } return 0; }
/* Unblock a client calling the right function depending on the kind * of operation the client is blocking for. * 不阻塞客户端 * 取消给定的客户端的阻塞状态*/ void unblockClient(redisClient *c) { if (c->btype == REDIS_BLOCKED_LIST) { unblockClientWaitingData(c); } else if (c->btype == REDIS_BLOCKED_WAIT) { unblockClientWaitingReplicas(c); } else { redisPanic("Unknown btype in unblockClient()."); } /* Clear the flags, and put the client in the unblocked list so that * we'll process new commands in its query buffer ASAP. */ c->flags &= ~REDIS_BLOCKED; c->flags |= REDIS_UNBLOCKED; c->btype = REDIS_BLOCKED_NONE; server.bpop_blocked_clients--; listAddNodeTail(server.unblocked_clients,c); }
/* This should be called from any function PUSHing into lists. * 'c' is the "pushing client", 'key' is the key it is pushing data against, * 'ele' is the element pushed. * * If the function returns 0 there was no client waiting for a list push * against this key. * * If the function returns 1 there was a client waiting for a list push * against this key, the element was passed to this client thus it's not * needed to actually add it to the list and the caller should return asap. */ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) { struct dictEntry *de; redisClient *receiver; list *l; listNode *ln; de = dictFind(c->db->blocking_keys,key); if (de == NULL) return 0; l = dictGetEntryVal(de); ln = listFirst(l); redisAssert(ln != NULL); receiver = ln->value; addReplySds(receiver,sdsnew("*2\r\n")); addReplyBulk(receiver,key); addReplyBulk(receiver,ele); unblockClientWaitingData(receiver); return 1; }
/* Unblock a client calling the right function depending on the kind * of operation the client is blocking for. */ void unblockClient(client *c) { if (c->btype == BLOCKED_LIST) { unblockClientWaitingData(c); } else if (c->btype == BLOCKED_WAIT) { unblockClientWaitingReplicas(c); } else { serverPanic("Unknown btype in unblockClient()."); } /* Clear the flags, and put the client in the unblocked list so that * we'll process new commands in its query buffer ASAP. */ c->flags &= ~CLIENT_BLOCKED; c->btype = BLOCKED_NONE; c->vel->bpop_blocked_clients--; /* The client may already be into the unblocked list because of a previous * blocking operation, don't add back it into the list multiple times. */ if (!(c->flags & CLIENT_UNBLOCKED)) { c->flags |= CLIENT_UNBLOCKED; dlistAddNodeTail(c->vel->unblocked_clients,c); } }
void freeClient(redisClient *c) { listNode *ln; /* Note that if the client we are freeing is blocked into a blocking * call, we have to set querybuf to NULL *before* to call * unblockClientWaitingData() to avoid processInputBuffer() will get * called. Also it is important to remove the file events after * this, because this call adds the READABLE event. */ sdsfree(c->querybuf); c->querybuf = NULL; if (c->flags & REDIS_BLOCKED) unblockClientWaitingData(c); /* UNWATCH all the keys */ unwatchAllKeys(c); listRelease(c->watched_keys); /* Unsubscribe from all the pubsub channels */ pubsubUnsubscribeAllChannels(c,0); pubsubUnsubscribeAllPatterns(c,0); dictRelease(c->pubsub_channels); listRelease(c->pubsub_patterns); /* Obvious cleanup */ aeDeleteFileEvent(server.el,c->fd,AE_READABLE); aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); listRelease(c->reply); freeClientArgv(c); close(c->fd); /* Remove from the list of clients */ ln = listSearchKey(server.clients,c); redisAssert(ln != NULL); listDelNode(server.clients,ln); /* Remove from the list of clients waiting for swapped keys, or ready * to be restarted, but not yet woken up again. */ if (c->flags & REDIS_IO_WAIT) { redisAssert(server.vm_enabled); if (listLength(c->io_keys) == 0) { ln = listSearchKey(server.io_ready_clients,c); /* When this client is waiting to be woken up (REDIS_IO_WAIT), * it should be present in the list io_ready_clients */ redisAssert(ln != NULL); listDelNode(server.io_ready_clients,ln); } else { while (listLength(c->io_keys)) { ln = listFirst(c->io_keys); dontWaitForSwappedKey(c,ln->value); } } server.vm_blocked_clients--; } listRelease(c->io_keys); /* Master/slave cleanup. * Case 1: we lost the connection with a slave. */ if (c->flags & REDIS_SLAVE) { if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) close(c->repldbfd); list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves; ln = listSearchKey(l,c); redisAssert(ln != NULL); listDelNode(l,ln); } /* Case 2: we lost the connection with the master. */ if (c->flags & REDIS_MASTER) { server.master = NULL; server.replstate = REDIS_REPL_CONNECT; /* Since we lost the connection with the master, we should also * close the connection with all our slaves if we have any, so * when we'll resync with the master the other slaves will sync again * with us as well. Note that also when the slave is not connected * to the master it will keep refusing connections by other slaves. */ while (listLength(server.slaves)) { ln = listFirst(server.slaves); freeClient((redisClient*)ln->value); } } /* Release memory */ zfree(c->argv); zfree(c->mbargv); freeClientMultiState(c); zfree(c); }
/* 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. */ } }
void freeClient(redisClient *c) { listNode *ln; /* Note that if the client we are freeing is blocked into a blocking * call, we have to set querybuf to NULL *before* to call * unblockClientWaitingData() to avoid processInputBuffer() will get * called. Also it is important to remove the file events after * this, because this call adds the READABLE event. */ sdsfree(c->querybuf); c->querybuf = NULL; if (c->flags & REDIS_BLOCKED) unblockClientWaitingData(c); /* UNWATCH all the keys */ unwatchAllKeys(c); listRelease(c->watched_keys); /* Unsubscribe from all the pubsub channels */ pubsubUnsubscribeAllChannels(c,0); pubsubUnsubscribeAllPatterns(c,0); dictRelease(c->pubsub_channels); listRelease(c->pubsub_patterns); /* Obvious cleanup */ aeDeleteFileEvent(server.el,c->fd,AE_READABLE); aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); listRelease(c->reply); freeClientArgv(c); close(c->fd); /* Remove from the list of clients */ ln = listSearchKey(server.clients,c); redisAssert(ln != NULL); listDelNode(server.clients,ln); /* Remove from the list of clients that are now ready to be restarted * after waiting for swapped keys */ if (c->flags & REDIS_IO_WAIT && listLength(c->io_keys) == 0) { ln = listSearchKey(server.io_ready_clients,c); if (ln) { listDelNode(server.io_ready_clients,ln); server.vm_blocked_clients--; } } /* Remove from the list of clients waiting for swapped keys */ while (server.vm_enabled && listLength(c->io_keys)) { ln = listFirst(c->io_keys); dontWaitForSwappedKey(c,ln->value); } listRelease(c->io_keys); /* Master/slave cleanup */ if (c->flags & REDIS_SLAVE) { if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) close(c->repldbfd); list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves; ln = listSearchKey(l,c); redisAssert(ln != NULL); listDelNode(l,ln); } if (c->flags & REDIS_MASTER) { server.master = NULL; server.replstate = REDIS_REPL_CONNECT; } /* Release memory */ zfree(c->argv); zfree(c->mbargv); freeClientMultiState(c); zfree(c); }