/* 模式订阅,即设置客户端订阅某模式。如果订阅成功则返回1,如果客户端已经订阅了该模式则返回0。*/ int pubsubSubscribePattern(redisClient *c, robj *pattern) { int retval = 0; // 先在客户端的c->pubsub_patterns链表中查找,判断客户端是否已经订阅了该模式 if (listSearchKey(c->pubsub_patterns,pattern) == NULL) { // 客户端并没有订阅该模式 retval = 1; pubsubPattern *pat; // 将制定模式添加到c->pubsub_patterns链表中 listAddNodeTail(c->pubsub_patterns,pattern); incrRefCount(pattern); pat = zmalloc(sizeof(*pat)); pat->pattern = getDecodedObject(pattern); pat->client = c; // 将pubsubPattern结构添加到server.pubsub_patterns链表中 listAddNodeTail(server.pubsub_patterns,pat); } /* Notify the client */ // 回复客户端 addReply(c,shared.mbulkhdr[3]); // 回复“psubscribe”字符串 addReply(c,shared.psubscribebulk); // 回复被订阅的模式字符串 addReplyBulk(c,pattern); // 回复客户端订阅的频道和模式总数目 addReplyLongLong(c,clientSubscriptionsCount(c)); return retval; }
void getsetCommand(redisClient *c) { if (getGenericCommand(c) == REDIS_ERR) return; dbReplace(c->db,c->argv[1],c->argv[2]); incrRefCount(c->argv[2]); server.dirty++; removeExpire(c->db,c->argv[1]); }
void msetGenericCommand(redisClient *c, int nx) { int j, busykeys = 0; if ((c->argc % 2) == 0) { addReplySds(c,sdsnew("-ERR wrong number of arguments for MSET\r\n")); return; } /* Handle the NX flag. The MSETNX semantic is to return zero and don't * set nothing at all if at least one already key exists. */ if (nx) { for (j = 1; j < c->argc; j += 2) { if (lookupKeyWrite(c->db,c->argv[j]) != NULL) { busykeys++; } } } if (busykeys) { addReply(c, shared.czero); return; } for (j = 1; j < c->argc; j += 2) { c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); dbReplace(c->db,c->argv[j],c->argv[j+1]); incrRefCount(c->argv[j+1]); removeExpire(c->db,c->argv[j]); } server.dirty += (c->argc-1)/2; addReply(c, nx ? shared.cone : shared.ok); }
void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { listNode *ln; listIter li; int j; listRewind(slaves,&li); while((ln = listNext(&li))) { redisClient *slave = ln->value; /* Don't feed slaves that are still waiting for BGSAVE to start */ if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) continue; /* Feed slaves that are waiting for the initial SYNC (so these commands * are queued in the output buffer until the intial SYNC completes), * or are already in sync with the master. */ if (slave->slaveseldb != dictid) { robj *selectcmd; if (dictid >= 0 && dictid < REDIS_SHARED_SELECT_CMDS) { selectcmd = shared.select[dictid]; incrRefCount(selectcmd); } else { selectcmd = createObject(REDIS_STRING, sdscatprintf(sdsempty(),"select %d\r\n",dictid)); } addReply(slave,selectcmd); decrRefCount(selectcmd); slave->slaveseldb = dictid; } addReplyMultiBulkLen(slave,argc); for (j = 0; j < argc; j++) addReplyBulk(slave,argv[j]); } }
/* Higher level function of hashTypeGet*() that always returns a Redis * object (either new or with refcount incremented), so that the caller * can retain a reference or call decrRefCount after the usage. * * The lower level function can prevent copy on write so it is * the preferred way of doing read operations. */ robj *hashTypeGetObject(robj *o, robj *field) { robj *value = NULL; if (o->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) { if (vstr) { value = createStringObject((char*)vstr, vlen); } else { value = createStringObjectFromLongLong(vll); } } } else if (o->encoding == REDIS_ENCODING_HT) { robj *aux; if (hashTypeGetFromHashTable(o, field, &aux) == 0) { incrRefCount(aux); value = aux; } } else { logicError("Unknown hash encoding"); } return value; }
void renameGenericCommand(redisClient *c, int nx) { robj *o; long long expire; /* To use the same key as src and dst is probably an error */ if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { addReply(c,shared.sameobjecterr); return; } if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) return; incrRefCount(o); expire = getExpire(c->db,c->argv[1]); if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { if (nx) { decrRefCount(o); addReply(c,shared.czero); return; } /* Overwrite: delete the old key before creating the new one with the same name. */ dbDelete(c->db,c->argv[2]); } dbAdd(c->db,c->argv[2],o); if (expire != -1) setExpire(c->db,c->argv[2],expire); dbDelete(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[1]); signalModifiedKey(c->db,c->argv[2]); server.dirty++; addReply(c,nx ? shared.cone : shared.ok); }
/* Define a lua function with the specified function name and body. * The function name musts be a 2 characters long string, since all the * functions we defined in the Lua context are in the form: * * f_<hex sha1 sum> * * On success REDIS_OK is returned, and nothing is left on the Lua stack. * On error REDIS_ERR is returned and an appropriate error is set in the * client context. */ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body) { sds funcdef = sdsempty(); funcdef = sdscat(funcdef,"function "); funcdef = sdscatlen(funcdef,funcname,42); funcdef = sdscatlen(funcdef,"() ",3); funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr)); funcdef = sdscatlen(funcdef," end",4); if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { addReplyErrorFormat(c,"Error compiling script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); sdsfree(funcdef); return REDIS_ERR; } sdsfree(funcdef); if (lua_pcall(lua,0,0,0)) { addReplyErrorFormat(c,"Error running script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); return REDIS_ERR; } /* We also save a SHA1 -> Original script map in a dictionary * so that we can replicate / write in the AOF all the * EVALSHA commands as EVAL using the original script. */ { int retval = dictAdd(server.lua_scripts, sdsnewlen(funcname+2,40),body); redisAssertWithInfo(c,NULL,retval == DICT_OK); incrRefCount(body); } return REDIS_OK; }
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); server.dirty++; } }
void spopCommand(redisClient *c) { robj *set, *ele, *aux; int64_t llele; int encoding; if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,set,REDIS_SET)) return; encoding = setTypeRandomElement(set,&ele,&llele); if (encoding == REDIS_ENCODING_INTSET) { ele = createStringObjectFromLongLong(llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { incrRefCount(ele); setTypeRemove(set,ele); } notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id); /* Replicate/AOF this command as an SREM operation */ aux = createStringObject("SREM",4); rewriteClientCommandVector(c,3,aux,c->argv[1],ele); decrRefCount(ele); decrRefCount(aux); addReplyBulk(c,ele); if (setTypeSize(set) == 0) { dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id); } signalModifiedKey(c->db,c->argv[1]); server.dirty++; }
/* Remove the 'key' from the list of blocked keys for a given client. * * The function returns 1 when there are no longer blocking keys after * the current one was removed (and the client can be unblocked). */ int dontWaitForSwappedKey(redisClient *c, robj *key) { list *l; listNode *ln; listIter li; struct dictEntry *de; /* The key object might be destroyed when deleted from the c->io_keys * list (and the "key" argument is physically the same object as the * object inside the list), so we need to protect it. */ incrRefCount(key); /* Remove the key from the list of keys this client is waiting for. */ listRewind(c->io_keys,&li); while ((ln = listNext(&li)) != NULL) { if (equalStringObjects(ln->value,key)) { listDelNode(c->io_keys,ln); break; } } redisAssert(ln != NULL); /* Remove the client form the key => waiting clients map. */ de = dictFind(c->db->io_keys,key); redisAssert(de != NULL); l = dictGetEntryVal(de); ln = listSearchKey(l,c); redisAssert(ln != NULL); listDelNode(l,ln); if (listLength(l) == 0) dictDelete(c->db->io_keys,key); decrRefCount(key); return listLength(c->io_keys) == 0; }
/* Propagate expires into slaves and the AOF file. * When a key expires in the master, a DEL operation for this key is sent * to all the slaves and the AOF file if enabled. * * This way the key expiry is centralized in one place, and since both * AOF and the master->slave link guarantee operation ordering, everything * will be consistent even if we allow write operations against expiring * keys. */ void propagateExpire(redisDb *db, robj *key) { robj *argv[2]; argv[0] = shared.del; argv[1] = key; incrRefCount(argv[0]); incrRefCount(argv[1]); if (server.aof_state != REDIS_AOF_OFF) feedAppendOnlyFile(server.delCommand,db->id,argv,2); if (listLength(server.slaves)) replicationFeedSlaves(server.slaves,db->id,argv,2); decrRefCount(argv[0]); decrRefCount(argv[1]); }
void listTypeInsert(listTypeEntry *entry, robj *value, int where) { robj *subject = entry->li->subject; if (entry->li->encoding == REDIS_ENCODING_ZIPLIST) { value = getDecodedObject(value); if (where == REDIS_TAIL) { unsigned char *next = ziplistNext(subject->ptr,entry->zi); /* When we insert after the current element, but the current element * is the tail of the list, we need to do a push. */ if (next == NULL) { subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),REDIS_TAIL); } else { subject->ptr = ziplistInsert(subject->ptr,next,value->ptr,sdslen(value->ptr)); } } else { subject->ptr = ziplistInsert(subject->ptr,entry->zi,value->ptr,sdslen(value->ptr)); } decrRefCount(value); } else if (entry->li->encoding == REDIS_ENCODING_LINKEDLIST) { if (where == REDIS_TAIL) { listInsertNode(subject->ptr,entry->ln,value,AL_START_TAIL); } else { listInsertNode(subject->ptr,entry->ln,value,AL_START_HEAD); } incrRefCount(value); } else { redisPanic("Unknown list encoding"); } }
/* 退订模式,即取消客户端对某模式的订阅。如果取消成功返回1,如果客户端并没有订阅该模式则返回0。*/ int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) { listNode *ln; pubsubPattern pat; int retval = 0; incrRefCount(pattern); /* Protect the object. May be the same we remove */ // 遍历c->pubsub_patterns链表,判断该客户端是否订阅了该模式 if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) { // 客户端订阅了该模式 retval = 1; // 从c->pubsub_patterns链表中删除该模式 listDelNode(c->pubsub_patterns,ln); pat.client = c; pat.pattern = pattern; // 从server.pubsub_patterns链表中删除该模式 ln = listSearchKey(server.pubsub_patterns,&pat); listDelNode(server.pubsub_patterns,ln); } /* Notify the client */ // 回复客户端 if (notify) { addReply(c,shared.mbulkhdr[3]); addReply(c,shared.punsubscribebulk); addReplyBulk(c,pattern); addReplyLongLong(c,dictSize(c->pubsub_channels)+ listLength(c->pubsub_patterns)); } decrRefCount(pattern); return retval; }
/* Try to encode a string object in order to save space */ robj *tryObjectEncoding(robj *o) { long value; sds s = o->ptr; size_t len; if (o->encoding != REDIS_ENCODING_RAW) return o; /* Already encoded */ /* It's not safe to encode shared objects: shared objects can be shared * everywhere in the "object space" of Redis. Encoded objects can only * appear as "values" (and not, for instance, as keys) */ if (o->refcount > 1) return o; /* Currently we try to encode only strings */ redisAssertWithInfo(NULL,o,o->type == REDIS_STRING); /* Check if we can represent this string as a long integer */ len = sdslen(s); if (len > 21 || !string2l(s,len,&value)) { /* We can't encode the object... * * Do the last try, and at least optimize the SDS string inside * the string object to require little space, in case there * is more than 10% of free space at the end of the SDS string. * * We do that for larger strings, using the arbitrary value * of 32 bytes. This code was backported from the unstable branch * where this is performed when the object is too large to be * encoded as EMBSTR. */ if (len > 32 && o->encoding == REDIS_ENCODING_RAW && sdsavail(s) > len/10) { o->ptr = sdsRemoveFreeSpace(o->ptr); } /* Return the original object. */ return o; } /* Ok, this object can be encoded... * * Can I use a shared object? Only if the object is inside a given range * * Note that we also avoid using shared integers when maxmemory is used * because every object needs to have a private LRU field for the LRU * algorithm to work well. */ if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS) { decrRefCount(o); incrRefCount(shared.integers[value]); return shared.integers[value]; } else { o->encoding = REDIS_ENCODING_INT; sdsfree(o->ptr); o->ptr = (void*) value; return o; } }
int vmSwapObjectThreaded(robj *key, robj *val, redisDb *db) { iojob *j; j = zmalloc(sizeof(*j)); j->type = REDIS_IOJOB_PREPARE_SWAP; j->db = db; j->key = key; incrRefCount(key); j->id = j->val = val; incrRefCount(val); j->canceled = 0; j->thread = (pthread_t) -1; val->storage = REDIS_VM_SWAPPING; lockThreadedIO(); queueIOJob(j); unlockThreadedIO(); return REDIS_OK; }
/* High level Set operation. This function can be used in order to set * a key, whatever it was existing or not, to a new object. * * 1) The ref count of the value object is incremented. * 2) clients WATCHing for the destination key notified. * 3) The expire time of the key is reset (the key is made persistent). */ void setKey(redisDb *db, robj *key, robj *val) { if (lookupKeyWrite(db,key) == NULL) { dbAdd(db,key,val); } else { dbOverwrite(db,key,val); } incrRefCount(val); removeExpire(db,key); signalModifiedKey(db,key); }
/* Set a client in blocking mode for the specified key (list or stream), with * the specified timeout. The 'type' argument is BLOCKED_LIST or BLOCKED_STREAM * depending on the kind of operation we are waiting for an empty key in * order to awake the client. The client is blocked for all the 'numkeys' * keys as in the 'keys' argument. When we block for stream keys, we also * provide an array of streamID structures: clients will be unblocked only * when items with an ID greater or equal to the specified one is appended * to the stream. */ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids) { dictEntry *de; list *l; int j; c->bpop.timeout = timeout; c->bpop.target = target; if (target != NULL) incrRefCount(target); for (j = 0; j < numkeys; j++) { /* The value associated with the key name in the bpop.keys dictionary * is NULL for lists, or the stream ID for streams. */ void *key_data = NULL; if (btype == BLOCKED_STREAM) { key_data = zmalloc(sizeof(streamID)); memcpy(key_data,ids+j,sizeof(streamID)); } /* If the key already exists in the dictionary ignore it. */ if (dictAdd(c->bpop.keys,keys[j],key_data) != DICT_OK) { zfree(key_data); continue; } 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]); serverAssertWithInfo(c,keys[j],retval == DICT_OK); } else { l = dictGetVal(de); } listAddNodeTail(l,c); } blockClient(c,btype); }
void incrRefCount_Array(Array array, MemoryIncrementer incrementer){ incrRefCount(array.memory); if(incrementer != NULL){ unsigned i=0; for(; i < array.length; ++i){ incrementer(getItemPointer(&array, i)); } } }
/* Try to share an object against the shared objects pool */ static robj *tryObjectSharing(robj *o) { struct dictEntry *de; unsigned long c; if (o == NULL || server.shareobjects == 0) return o; de = dictFind(server.sharingpool,o); if (de) { robj *shared = dictGetEntryKey(de); c = ((unsigned long) dictGetEntryVal(de))+1; dictGetEntryVal(de) = (void*) c; incrRefCount(shared); decrRefCount(o); return shared; } else { /* Here we are using a stream algorihtm: Every time an object is * shared we increment its count, everytime there is a miss we * recrement the counter of a random object. If this object reaches * zero we remove the object and put the current object instead. */ if (dictSize(server.sharingpool) >= server.sharingpoolsize) { de = dictGetRandomKey(server.sharingpool); c = ((unsigned long) dictGetEntryVal(de))-1; dictGetEntryVal(de) = (void*) c; if (c == 0) { dictDelete(server.sharingpool,de->key); } } else { c = 0; /* If the pool is empty we want to add this object */ } if (c == 0) { int retval; retval = dictAdd(server.sharingpool,o,(void*)1); redisAssert(retval == DICT_OK); incrRefCount(o); } return o; } }
// keys是一个key的数组,个数为numkeys个 // timeout保存超时时间 // target保存解除阻塞时的key对象,用于BRPOPLPUSH函数 // 根据给定的key将client阻塞 void blockForKeys(client *c, robj **keys, int numkeys, mstime_t timeout, robj *target) { dictEntry *de; list *l; int j; //设置超时时间和target c->bpop.timeout = timeout; c->bpop.target = target; //增加target的引用计数 if (target != NULL) incrRefCount(target); //将当前client的numkeys个key设置为阻塞 for (j = 0; j < numkeys; j++) { /* If the key already exists in the dict ignore it. */ //bpop.keys记录所有造成client阻塞的键 //将要阻塞的键放入bpop.keys字典中 if (dictAdd(c->bpop.keys,keys[j],NULL) != DICT_OK) continue; //当前的key引用计数加1 incrRefCount(keys[j]); /* And in the other "side", to map keys -> clients */ //db->blocking_keys是一个字典,字典的键为bpop.keys中的一个键,值是一个列表,保存着所有被该键阻塞的client //当前造成client被阻塞的键有没有当前的key de = dictFind(c->db->blocking_keys,keys[j]); if (de == NULL) { //没有当前的key,添加进去 int retval; /* For every key we take a list of clients blocked for it */ //创建一个列表 l = listCreate(); //将造成阻塞的键和列表添加到db->blocking_keys字典中 retval = dictAdd(c->db->blocking_keys,keys[j],l); incrRefCount(keys[j]); serverAssertWithInfo(c,keys[j],retval == DICT_OK); } else { //如果已经有了,则当前key的值保存起来,值是一个列表 l = dictGetVal(de); } listAddNodeTail(l,c); //将当前client加入到阻塞的client的列表 } blockClient(c,BLOCKED_LIST); //阻塞client }
/* * 监视给定 key * * T = O(N) */ void watchForKey(redisClient *c, robj *key) { list *clients = NULL; listIter li; listNode *ln; watchedKey *wk; /* Check if we are already watching for this key */ // 检查该 key 是否已经被 WATCH // (出现在 WATCH 命令调用时一个 key 被输入多次的情况) // 如果是的话,直接返回 // O(N) listRewind(c->watched_keys,&li); while((ln = listNext(&li))) { wk = listNodeValue(ln); if (wk->db == c->db && equalStringObjects(key,wk->key)) return; /* Key already watched */ } // key 未被监视 // 根据 key ,将客户端加入到 DB 的监视 key 字典中 /* This key is not already watched in this DB. Let's add it */ // O(1) clients = dictFetchValue(c->db->watched_keys,key); if (!clients) { clients = listCreate(); dictAdd(c->db->watched_keys,key,clients); incrRefCount(key); } listAddNodeTail(clients,c); // 将 key 添加到客户端的监视列表中 /* Add the new key to the lits of keys watched by this client */ // O(1) wk = zmalloc(sizeof(*wk)); wk->key = key; wk->db = c->db; incrRefCount(key); listAddNodeTail(c->watched_keys,wk); }
void movekeysCommand(redisClient *c) { redisDb *src, *dst; int srcid; dictIterator *di; dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys; unsigned long numkeys = 0; /* Obtain source and target DB pointers */ src = c->db; srcid = c->db->id; if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { addReply(c,shared.outofrangeerr); return; } dst = c->db; selectDb(c,srcid); /* Back to the source DB */ /* If the user is moving using as target the same * DB as the source DB it is probably an error. */ if (src == dst) { addReply(c,shared.sameobjecterr); return; } 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 (expireIfNeeded(c->db,keyobj) == 0) { robj *val = dictGetEntryVal(de); /* Try to add the element to the target DB */ if (dbAdd(dst,keyobj,val) != REDIS_ERR) { incrRefCount(val); /* OK! key moved, free the entry in the source DB */ dbDelete(src,keyobj); server.dirty++; numkeys++; } } decrRefCount(keyobj); } } dictReleaseIterator(di); addReplyLongLong(c,numkeys); }
void moveCommand(client *c) { robj *o; redisDb *src, *dst; int srcid; long long dbid, expire; if (server.cluster_enabled) { addReplyError(c,"MOVE is not allowed in cluster mode"); return; } /* Obtain source and target DB pointers */ src = c->db; srcid = c->db->id; if (getLongLongFromObject(c->argv[2],&dbid) == C_ERR || dbid < INT_MIN || dbid > INT_MAX || selectDb(c,dbid) == C_ERR) { addReply(c,shared.outofrangeerr); return; } dst = c->db; selectDb(c,srcid); /* Back to the source DB */ /* If the user is moving using as target the same * DB as the source DB it is probably an error. */ if (src == dst) { addReply(c,shared.sameobjecterr); return; } /* Check if the element exists and get a reference */ o = lookupKeyWrite(c->db,c->argv[1]); if (!o) { addReply(c,shared.czero); return; } expire = getExpire(c->db,c->argv[1]); /* Return zero if the key already exists in the target DB */ if (lookupKeyWrite(dst,c->argv[1]) != NULL) { addReply(c,shared.czero); return; } dbAdd(dst,c->argv[1],o); if (expire != -1) setExpire(dst,c->argv[1],expire); incrRefCount(o); /* OK! key moved, free the entry in the source DB */ dbDelete(src,c->argv[1]); server.dirty++; addReply(c,shared.cone); }
/* 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; }
void hgetCommand(caller_t *c) { int ret; robj *o; robj *field = c->argv[2]; robj *result; if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { caller_set_err(c, ERR_NIL); return; } if (o->type != REDIS_HASH) { caller_set_err(c, ERR_TYPE); return; } if (o->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *vstr = NULL; unsigned int vlen = UINT_MAX; long long vll = LLONG_MAX; ret = hashTypeGetFromZiplist(o,field,&vstr,&vlen,&vll); if (ret < 0) { caller_set_err(c, ERR_NIL); return; } else { if (vstr) { result = createObject(REDIS_STRING, sdsnewlen(vstr, vlen)); caller_add_result(c, result); return; } else { result = createObject(REDIS_STRING, (void*)vll); result->encoding = REDIS_ENCODING_INT; caller_add_result(c, result); return; } } } else if (o->encoding == REDIS_ENCODING_HT) { ret = hashTypeGetFromHashTable(o,field,&result); if (ret < 0) { caller_set_err(c, ERR_NIL); return; } else { caller_add_result(c, result); incrRefCount(result); return; } } else { logicError("Unknown hash encoding"); } }
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire) { int retval; long seconds = 0; /* initialized to avoid an harmness warning */ #ifdef AUTH_FEATURE /* Check mod permissions */ if (authCheckModOrReply(c) == REDIS_ERR) return; /* Check permissions */ if (authCheckPathOrReply(c, key) == REDIS_ERR) return; #endif if (expire) { if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK) return; if (seconds <= 0) { addReplyError(c,"invalid expire time in SETEX"); return; } } retval = dbAdd(c->db,key,val); if (retval == REDIS_ERR) { if (!nx) { dbReplace(c->db,key,val); incrRefCount(val); } else { addReply(c,shared.czero); return; } } else { incrRefCount(val); } touchWatchedKey(c->db,key); server.dirty++; removeExpire(c->db,key); if (expire) setExpire(c->db,key,time(NULL)+seconds); addReply(c, nx ? shared.cone : shared.ok); }
/* If the specified key has clients blocked waiting for list pushes, this * function will put the key reference into the server.ready_keys list. * Note that db->ready_keys is a hash table that allows us to avoid putting * the same key again and again in the list in case of multiple pushes * made by a script or in the context of MULTI/EXEC. * * The list will be finally processed by handleClientsBlockedOnLists() */ void signalListAsReady(redisDb *db, robj *key) { readyList *rl; /* No clients blocking for this key? No need to queue it. */ if (dictFind(db->blocking_keys,key) == NULL) return; /* Key was already signaled? No need to queue it again. */ if (dictFind(db->ready_keys,key) != NULL) return; /* Ok, we need to queue this key into server.ready_keys. */ rl = zmalloc(sizeof(*rl)); rl->key = key; rl->db = db; incrRefCount(key); listAddNodeTail(server.ready_keys,rl); /* We also add the key in the db->ready_keys dictionary in order * to avoid adding it multiple times into a list with a simple O(1) * check. */ incrRefCount(key); redisAssert(dictAdd(db->ready_keys,key,NULL) == DICT_OK); }
// WATCH 某个 KEY void watchForKey(redisClient *c, robj *key) { list *clients = NULL; listIter li; listNode *ln; watchedKey *wk; /* Check if we are already watching for this key */ // 所有被 WATCHED 的 KEY 都被放在 redisClient.watched_keys 链表中 // 遍历这个链表,查看这个 KEY 是否已经处于监视状态(WATCHED) listRewind(c->watched_keys,&li); while((ln = listNext(&li))) { wk = listNodeValue(ln); if (wk->db == c->db && equalStringObjects(key,wk->key)) return; /* Key already watched */ } /* This key is not already watched in this DB. Let's add it */ // 如果 KEY 还没有被 WATCH 过,那么对它进行 WATCH clients = dictFetchValue(c->db->watched_keys,key); if (!clients) { // 如果 clients 链表不存在 // 说明这个客户端是第一个监视这个 DB 的这个 KEY 的客户端 // 那么 clients 创建链表,并将它添加到 c->db->watched_keys 字典中 clients = listCreate(); dictAdd(c->db->watched_keys,key,clients); incrRefCount(key); } // 将客户端添加到 clients 链表 listAddNodeTail(clients,c); /* Add the new key to the lits of keys watched by this client */ // 除了 c->db->watched_keys 之外 // 还要将被 WATCH 的 KEY 添加到 c->watched_keys wk = zmalloc(sizeof(*wk)); wk->key = key; wk->db = c->db; incrRefCount(key); listAddNodeTail(c->watched_keys,wk); }
/* Define a lua function with the specified function name and body. * * 根据给定函数名和代码体(body),创建 Lua 函数。 * * The function name musts be a 2 characters long string, since all the * functions we defined in the Lua context are in the form: * * 所有函数名称的长度都必须大于 2 ,因为我们使用以下格式来创建函数名: * * f_<hex sha1 sum> * * On success REDIS_OK is returned, and nothing is left on the Lua stack. * 创建成功返回 REDIS_OK ,并清除 Lua 栈中的所有资料。 * * On error REDIS_ERR is returned and an appropriate error is set in the * client context. */ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body) { sds funcdef = sdsempty(); // 函数定义 funcdef = sdscat(funcdef,"function "); funcdef = sdscatlen(funcdef,funcname,42); funcdef = sdscatlen(funcdef,"() ",3); funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr)); funcdef = sdscatlen(funcdef," end",4); // 将函数定义载入到 Lua 中(并编译),但不执行该定义 if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { // 如果编译出错,那么返回错误 addReplyErrorFormat(c,"Error compiling script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); sdsfree(funcdef); return REDIS_ERR; } sdsfree(funcdef); // 定义函数 if (lua_pcall(lua,0,0,0)) { addReplyErrorFormat(c,"Error running script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); return REDIS_ERR; } /* We also save a SHA1 -> Original script map in a dictionary * so that we can replicate / write in the AOF all the * EVALSHA commands as EVAL using the original script. * * 创建一个从 SHA1 映射到原始脚本的字典, * 从而使得脚本可以用于复制,以及写入到 AOF 文件 */ { int retval = dictAdd(server.lua_scripts, // SHA1 值,不包括前缀 f_ sdsnewlen(funcname+2,40), // 代码体 body); redisAssertWithInfo(c,NULL,retval == DICT_OK); incrRefCount(body); } return REDIS_OK; }
void spopCommand(redisClient *c) { robj *set, *ele, *aux; int64_t llele; int encoding; if (c->argc == 3) { spopWithCountCommand(c); return; } else if (c->argc > 3) { addReply(c,shared.syntaxerr); return; } /* Make sure a key with the name inputted exists, and that it's type is * indeed a set */ if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,set,REDIS_SET)) return; /* Get a random element from the set */ encoding = setTypeRandomElement(set,&ele,&llele); /* Remove the element from the set */ if (encoding == REDIS_ENCODING_INTSET) { ele = createStringObjectFromLongLong(llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { incrRefCount(ele); setTypeRemove(set,ele); } notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id); /* Replicate/AOF this command as an SREM operation */ aux = createStringObject("SREM",4); rewriteClientCommandVector(c,3,aux,c->argv[1],ele); decrRefCount(ele); decrRefCount(aux); /* Add the element to the reply */ addReplyBulk(c,ele); /* Delete the set if it's empty */ if (setTypeSize(set) == 0) { dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id); } /* Set has been modified */ signalModifiedKey(c->db,c->argv[1]); server.dirty++; }