/* Stores pointer to current the entry in the provided entry structure * and advances the position of the iterator. Returns 1 when the current * entry is in fact an entry, 0 otherwise. */ int listTypeNext(listTypeIterator *li, listTypeEntry *entry) { /* Protect from converting when iterating */ redisAssert(li->subject->encoding == li->encoding); entry->li = li; if (li->encoding == REDIS_ENCODING_ZIPLIST) { entry->zi = li->zi; if (entry->zi != NULL) { if (li->direction == REDIS_TAIL) li->zi = ziplistNext(li->subject->ptr,li->zi); else li->zi = ziplistPrev(li->subject->ptr,li->zi); return 1; } } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) { entry->ln = li->ln; if (entry->ln != NULL) { if (li->direction == REDIS_TAIL) li->ln = li->ln->next; else li->ln = li->ln->prev; return 1; } } else { redisPanic("Unknown list encoding"); } return 0; }
void zremCommand(redisClient *c) { robj *zsetobj; zset *zs; dictEntry *de; double *oldscore; int deleted; if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,zsetobj,REDIS_ZSET)) return; zs = zsetobj->ptr; de = dictFind(zs->dict,c->argv[2]); if (de == NULL) { addReply(c,shared.czero); return; } /* Delete from the skiplist */ oldscore = dictGetEntryVal(de); deleted = zslDelete(zs->zsl,*oldscore,c->argv[2]); redisAssert(deleted != 0); /* Delete from the hash table */ dictDelete(zs->dict,c->argv[2]); if (htNeedsResize(zs->dict)) dictResize(zs->dict); if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]); server.dirty++; addReply(c,shared.cone); }
/* Set a client in blocking mode for the specified key, with the specified * timeout */ void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout) { dictEntry *de; list *l; int j; c->blocking_keys = zmalloc(sizeof(robj*)*numkeys); c->blocking_keys_num = numkeys; c->blockingto = timeout; for (j = 0; j < numkeys; j++) { /* Add the key in the client structure, to map clients -> keys */ c->blocking_keys[j] = keys[j]; incrRefCount(keys[j]); /* And in the other "side", to map keys -> clients */ de = dictFind(c->db->blocking_keys,keys[j]); if (de == NULL) { int retval; /* For every key we take a list of clients blocked for it */ l = listCreate(); retval = dictAdd(c->db->blocking_keys,keys[j],l); incrRefCount(keys[j]); redisAssert(retval == DICT_OK); } else { l = dictGetEntryVal(de); } listAddNodeTail(l,c); } /* Mark the client as a blocked client */ c->flags |= REDIS_BLOCKED; server.blpop_blocked_clients++; }
void brpoplpushCommand(redisClient *c) { time_t timeout; if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK) return; robj *key = lookupKeyWrite(c->db, c->argv[1]); if (key == NULL) { if (c->flags & REDIS_MULTI) { /* Blocking against an empty list in a multi state * returns immediately. */ addReply(c, shared.nullbulk); } else { /* The list is empty and the client blocks. */ blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]); } } else { if (key->type != REDIS_LIST) { addReply(c, shared.wrongtypeerr); } else { /* The list exists and has elements, so * the regular rpoplpushCommand is executed. */ redisAssert(listTypeLength(key) > 0); rpoplpushCommand(c); } } }
/* Emit the commands needed to rebuild a S3 object. * The function returns 0 on error, 1 on success */ int rewriteSssObject(rio *r, robj *key, robj *o) { sssTypeIterator *si; sssEntry *se; si = sssTypeInitIterator(o); while ((se = sssIterNext(si)) != NULL) { robj *ks, *svc, *skey, *val; long long idx, vll; int kv_mode; char *cmd; int cmdlen; if (sssIterPeek(se, &ks, &svc, &skey, &idx, &val, &vll, &kv_mode) == REDIS_ERR) return 0; if(kv_mode == SSS_KV_LIST) { cmd = "S3LADDAT"; cmdlen = 8; } else { redisAssert(kv_mode == SSS_KV_SET); cmd = "S3SADDAT"; cmdlen = 8; } if (rioWriteBulkCount(r, '*', 7) == 0) return 0; if (rioWriteBulkString(r, cmd, cmdlen) == 0) return 0; if (rioWriteBulkObject(r, ks) == 0) return 0; if (rioWriteBulkObject(r, key) == 0) return 0; if (rioWriteBulkObject(r, svc) == 0) return 0; if (rioWriteBulkObject(r, skey) == 0) return 0; /* idx is not used */ if (rioWriteBulkObject(r, val) == 0) return 0; if (rioWriteBulkLongLong(r, vll) == 0) return 0; } sssTypeReleaseIterator(si); return 1; }
int setTypeAdd(robj *subject, robj *value) { long long llval; if (subject->encoding == REDIS_ENCODING_HT) { if (dictAdd(subject->ptr,value,NULL) == DICT_OK) { incrRefCount(value); return 1; } } else if (subject->encoding == REDIS_ENCODING_INTSET) { if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) { uint8_t success = 0; subject->ptr = intsetAdd(subject->ptr,llval,&success); if (success) { /* Convert to regular set when the intset contains * too many entries. */ if (intsetLen(subject->ptr) > server.set_max_intset_entries) setTypeConvert(subject,REDIS_ENCODING_HT); return 1; } } else { /* Failed to get integer from object, convert to regular set. */ setTypeConvert(subject,REDIS_ENCODING_HT); /* The set *was* an intset and this value is not integer * encodable, so dictAdd should always work. */ redisAssert(dictAdd(subject->ptr,value,NULL) == DICT_OK); incrRefCount(value); return 1; } } else { redisPanic("Unknown set encoding"); } return 0; }
/* Unwatch all the keys watched by this client. To clean the EXEC dirty * flag is up to the caller. */ void unwatchAllKeys(redisClient *c) { listIter li; listNode *ln; if (listLength(c->watched_keys) == 0) return; listRewind(c->watched_keys,&li); while((ln = listNext(&li))) { list *clients; watchedKey *wk; /* Lookup the watched key -> clients list and remove the client * from the list */ wk = listNodeValue(ln); clients = dictFetchValue(wk->db->watched_keys, wk->key); redisAssert(clients != NULL); listDelNode(clients,listSearchKey(clients,c)); /* Kill the entry at all if this was the only client */ if (listLength(clients) == 0) dictDelete(wk->db->watched_keys, wk->key); /* Remove this watched key from the client->watched list */ listDelNode(c->watched_keys,ln); decrRefCount(wk->key); zfree(wk); } }
void setExpire(redisDb *db, robj *key, time_t when) { dictEntry *de; /* Reuse the sds from the main dict in the expire dict */ de = dictFind(db->dict,key->ptr); redisAssert(de != NULL); dictReplace(db->expires,dictGetEntryKey(de),(void*)when); }
int isObjectRepresentableAsLongLong(robj *o, long long *llongval) { redisAssert(o->type == REDIS_STRING); if (o->encoding == REDIS_ENCODING_INT) { if (llongval) *llongval = (long) o->ptr; return REDIS_OK; } else { return isStringRepresentableAsLongLong(o->ptr,llongval); } }
int isObjectRepresentableAsLongLong(robj *o, long long *llval) { redisAssert(o->type == REDIS_STRING); if (o->encoding == REDIS_ENCODING_INT) { if (llval) *llval = (long) o->ptr; return REDIS_OK; } else { return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR; } }
/* 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); }
/* This function can be called when a non blocking connection is currently * in progress to undo it. */ void undoConnectWithMaster(void) { int fd = server.repl_transfer_s; redisAssert(server.repl_state == REDIS_REPL_CONNECTING || server.repl_state == REDIS_REPL_RECEIVE_PONG); aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE); close(fd); server.repl_transfer_s = -1; server.repl_state = REDIS_REPL_CONNECT; }
/* Abort the async download of the bulk dataset while SYNC-ing with master */ void replicationAbortSyncTransfer(void) { redisAssert(server.replstate == REDIS_REPL_TRANSFER); aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE); close(server.repl_transfer_s); close(server.repl_transfer_fd); unlink(server.repl_transfer_tmpfile); zfree(server.repl_transfer_tmpfile); server.replstate = REDIS_REPL_CONNECT; }
/* Prepare the string object stored at 'key' to be modified destructively * to implement commands like SETBIT or APPEND. * * An object is usually ready to be modified unless one of the two conditions * are true: * * 1) The object 'o' is shared (refcount > 1), we don't want to affect * other users. * 2) The object encoding is not "RAW". * * If the object is found in one of the above conditions (or both) by the * function, an unshared / not-encoded copy of the string object is stored * at 'key' in the specified 'db'. Otherwise the object 'o' itself is * returned. * * USAGE: * * The object 'o' is what the caller already obtained by looking up 'key' * in 'db', the usage pattern looks like this: * * o = lookupKeyWrite(db,key); * if (checkType(c,o,REDIS_STRING)) return; * o = dbUnshareStringValue(db,key,o); * * At this point the caller is ready to modify the object, for example * using an sdscat() call to append some data, or anything else. */ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) { redisAssert(o->type == REDIS_STRING); if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) { robj *decoded = getDecodedObject(o); o = createStringObject(decoded->ptr, sdslen(decoded->ptr)); decrRefCount(decoded); dbOverwrite(db,key,o); } return o; }
/* Preload keys for any command with first, last and step values for * the command keys prototype, as defined in the command table. */ void waitForMultipleSwappedKeys(redisClient *c, struct redisCommand *cmd, int argc, robj **argv) { int j, last; if (cmd->vm_firstkey == 0) return; last = cmd->vm_lastkey; if (last < 0) last = argc+last; for (j = cmd->vm_firstkey; j <= last; j += cmd->vm_keystep) { redisAssert(j < argc); waitForSwappedKey(c,argv[j]); } }
size_t stringObjectLen(robj *o) { redisAssert(o->type == REDIS_STRING); if (o->encoding == REDIS_ENCODING_RAW) { return sdslen(o->ptr); } else { char buf[32]; return ll2string(buf,32,(long)o->ptr); } }
static void decrRefCount(void *obj) { robj *o = obj; /* Object is a key of a swapped out value, or in the process of being * loaded. */ if (server.vm_enabled && (o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING)) { if (o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING) { redisAssert(o->refcount == 1); } if (o->storage == REDIS_VM_LOADING) vmCancelThreadedIOJob(obj); redisAssert(o->type == REDIS_STRING); freeStringObject(o); vmMarkPagesFree(o->vm.page,o->vm.usedpages); pthread_mutex_lock(&server.obj_freelist_mutex); if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX || !listAddNodeHead(server.objfreelist,o)) zfree(o); pthread_mutex_unlock(&server.obj_freelist_mutex); server.vm_stats_swapped_objects--; return; } /* Object is in memory, or in the process of being swapped out. */ if (--(o->refcount) == 0) { if (server.vm_enabled && o->storage == REDIS_VM_SWAPPING) vmCancelThreadedIOJob(obj); switch(o->type) { case REDIS_STRING: freeStringObject(o); break; case REDIS_LIST: freeListObject(o); break; case REDIS_SET: freeSetObject(o); break; case REDIS_ZSET: freeZsetObject(o); break; case REDIS_HASH: freeHashObject(o); break; default: redisAssert(0); break; } if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex); if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX || !listAddNodeHead(server.objfreelist,o)) zfree(o); if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex); } }
/* Compare the given object with the entry at the current position. */ int listTypeEqual(listTypeEntry *entry, robj *o) { listTypeIterator *li = entry->li; if (li->encoding == REDIS_ENCODING_ZIPLIST) { redisAssert(o->encoding == REDIS_ENCODING_RAW); return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr)); } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) { return equalStringObjects(o,listNodeValue(entry->ln)); } else { redisPanic("Unknown list encoding"); } }
/* Return the expire time of the specified key, or -1 if no expire * is associated with this key (i.e. the key is non volatile) */ time_t getExpire(redisDb *db, robj *key) { dictEntry *de; /* No expire? return ASAP */ if (dictSize(db->expires) == 0 || (de = dictFind(db->expires,key->ptr)) == NULL) return -1; /* The entry was found in the expire dict, this means it should also * be present in the main dict (safety check). */ redisAssert(dictFind(db->dict,key->ptr) != NULL); return (time_t) dictGetEntryVal(de); }
void *IOThreadEntryPoint(void *arg) { iojob *j; listNode *ln; REDIS_NOTUSED(arg); pthread_detach(pthread_self()); while(1) { /* Get a new job to process */ lockThreadedIO(); if (listLength(server.io_newjobs) == 0) { /* No new jobs in queue, exit. */ redisLog(REDIS_DEBUG,"Thread %ld exiting, nothing to do", (long) pthread_self()); server.io_active_threads--; unlockThreadedIO(); return NULL; } ln = listFirst(server.io_newjobs); j = ln->value; listDelNode(server.io_newjobs,ln); /* Add the job in the processing queue */ j->thread = pthread_self(); listAddNodeTail(server.io_processing,j); ln = listLast(server.io_processing); /* We use ln later to remove it */ unlockThreadedIO(); redisLog(REDIS_DEBUG,"Thread %ld got a new job (type %d): %p about key '%s'", (long) pthread_self(), j->type, (void*)j, (char*)j->key->ptr); /* Process the Job */ if (j->type == REDIS_IOJOB_LOAD) { vmpointer *vp = (vmpointer*)j->id; j->val = vmReadObjectFromSwap(j->page,vp->vtype); } else if (j->type == REDIS_IOJOB_PREPARE_SWAP) { j->pages = rdbSavedObjectPages(j->val); } else if (j->type == REDIS_IOJOB_DO_SWAP) { if (vmWriteObjectOnSwap(j->val,j->page) == REDIS_ERR) j->canceled = 1; } /* Done: insert the job into the processed queue */ redisLog(REDIS_DEBUG,"Thread %ld completed the job: %p (key %s)", (long) pthread_self(), (void*)j, (char*)j->key->ptr); lockThreadedIO(); listDelNode(server.io_processing,ln); listAddNodeTail(server.io_processed,j); unlockThreadedIO(); /* Signal the main thread there is new stuff to process */ redisAssert(write(server.io_ready_pipe_write,"x",1) == 1); } return NULL; /* never reached */ }
/* Set the event loop to listen for write events on the client's socket. * Typically gets called every time a reply is built. */ int _installWriteEvent(redisClient *c) { /* When CLOSE_AFTER_REPLY is set, no more replies may be added! */ redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY)); if (c->fd <= 0) return REDIS_ERR; if (c->bufpos == 0 && listLength(c->reply) == 0 && (c->replstate == REDIS_REPL_NONE || c->replstate == REDIS_REPL_ONLINE) && aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c) == AE_ERR) return REDIS_ERR; return REDIS_OK; }
/* 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 = dictGetEntryVal(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); redisAssert(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; }
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. */ redisAssert(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)); }
/* Return the logic clock of the specified key, or 0 if no key exist */ uint16_t getLogiClock(redisDb *db, robj *key) { uint16_t logiclock; dictEntry *de; de = dictFind(db->dict,key->ptr); if (de == NULL) { return 0; } sds skey = (sds) dictGetEntryKey(de); logiclock = sdslogiclock(skey); redisAssert(logiclock != 0); return logiclock; }
/* Get the value from a ziplist encoded hash, identified by field. * Returns -1 when the field cannot be found. */ int hashTypeGetFromZiplist(robj *o, robj *field, unsigned char **vstr, unsigned int *vlen, PORT_LONGLONG *vll) { unsigned char *zl, *fptr = NULL, *vptr = NULL; int ret; redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); field = getDecodedObject(field); zl = o->ptr; fptr = ziplistIndex(zl, ZIPLIST_HEAD); if (fptr != NULL) { fptr = ziplistFind(fptr, field->ptr, (unsigned int)sdslen(field->ptr), 1); WIN_PORT_FIX /* cast (unsigned int) */ if (fptr != NULL) { /* Grab pointer to the value (fptr points to the field) */ vptr = ziplistNext(zl, fptr); redisAssert(vptr != NULL); } }
static void freeHashObject(robj *o) { switch (o->encoding) { case REDIS_ENCODING_HT: dictRelease((dict*) o->ptr); break; case REDIS_ENCODING_ZIPMAP: zfree(o->ptr); break; default: redisAssert(0); break; } }
/* Transfers the 'val' object to disk. Store all the information * a 'vmpointer' object containing all the information needed to load the * object back later is returned. * * If we can't find enough contiguous empty pages to swap the object on disk * NULL is returned. */ vmpointer *vmSwapObjectBlocking(robj *val) { off pages = rdbSavedObjectPages(val); off page; vmpointer *vp; redisAssert(val->storage == REDIS_VM_MEMORY); redisAssert(val->refcount == 1); if (vmFindContiguousPages(&page,pages) == REDIS_ERR) return NULL; if (vmWriteObjectOnSwap(val,page) == REDIS_ERR) return NULL; vp = createVmPointer(val); vp->page = page; vp->usedpages = pages; decrRefCount(val); /* Deallocate the object from memory. */ vmMarkPagesUsed(page,pages); redisLog(REDIS_DEBUG,"VM: object %p swapped out at %lld (%lld pages)", (void*) val, (unsigned long long) page, (unsigned long long) pages); server.vm_stats_swapped_objects++; server.vm_stats_swapouts++; return vp; }
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or * 0 if the client was not subscribed to the specified channel. */ int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) { struct dictEntry *de; list *clients; listNode *ln; int retval = 0; /* Remove the channel from the client -> channels hash table */ incrRefCount(channel); /* channel may be just a pointer to the same object we have in the hash tables. Protect it... */ if (dictDelete(c->pubsub_channels,channel) == DICT_OK) { retval = 1; /* Remove the client from the channel -> clients list hash table */ de = dictFind(server.pubsub_channels,channel); redisAssert(de != NULL); clients = dictGetEntryVal(de); ln = listSearchKey(clients,c); redisAssert(ln != NULL); listDelNode(clients,ln); if (listLength(clients) == 0) { /* Free the list and associated hash entry at all if this was * the latest client, so that it will be possible to abuse * Redis PUBSUB creating millions of channels. */ dictDelete(server.pubsub_channels,channel); } } /* Notify the client */ if (notify) { addReply(c,shared.mbulk3); addReply(c,shared.unsubscribebulk); addReplyBulk(c,channel); addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } decrRefCount(channel); /* it is finally safe to release it */ return retval; }
/* Return entry or NULL at the current position of the iterator. */ robj *listTypeGet(listTypeEntry *entry) { listTypeIterator *li = entry->li; robj *value = NULL; if (li->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *vstr; unsigned int vlen; long long vlong; redisAssert(entry->zi != NULL); if (ziplistGet(entry->zi,&vstr,&vlen,&vlong)) { if (vstr) { value = createStringObject((char*)vstr,vlen); } else { value = createStringObjectFromLongLong(vlong); } } } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) { redisAssert(entry->ln != NULL); value = listNodeValue(entry->ln); incrRefCount(value); } else { redisPanic("Unknown list encoding"); } return value; }
void expirekeysGenericCommand(redisClient *c, robj *keypattern, robj *param, long offset) { dictIterator *di; dictEntry *de; sds pattern = keypattern->ptr; int plen = sdslen(pattern); unsigned long numkeys = 0, allkeys; time_t seconds; time_t when; if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return; seconds -= offset; when = time(NULL)+seconds; di = dictGetIterator(c->db->dict); allkeys = (pattern[0] == '*' && pattern[1] == '\0'); while((de = dictNext(di)) != NULL) { sds key = dictGetEntryKey(de); robj *keyobj; if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); if (seconds <= 0 && !server.loading && !server.masterhost) { robj *aux; redisAssert(dbDelete(c->db,keyobj)); server.dirty++; numkeys++; /* Replicate/AOF this as an explicit DEL. */ aux = createStringObject("DEL",3); rewriteClientCommandVector(c,2,aux,keyobj); decrRefCount(aux); touchWatchedKey(c->db,keyobj); } else { time_t when = time(NULL)+seconds; setExpire(c->db,keyobj,when); touchWatchedKey(c->db,keyobj); server.dirty++; numkeys++; } decrRefCount(keyobj); } } dictReleaseIterator(di); addReplyLongLong(c,numkeys); }