X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/f69f2cbafeceb03a0bc945227f08657ea1c09251..fd8ccf4419ab94ab5a9250c0dc7a8d1761056b48:/redis.c diff --git a/redis.c b/redis.c index b934b40d..bfc33dc9 100644 --- a/redis.c +++ b/redis.c @@ -27,7 +27,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define REDIS_VERSION "1.001" +#define REDIS_VERSION "1.050" #include "fmacros.h" #include "config.h" @@ -102,7 +102,8 @@ #define REDIS_STRING 0 #define REDIS_LIST 1 #define REDIS_SET 2 -#define REDIS_HASH 3 +#define REDIS_ZSET 3 +#define REDIS_HASH 4 /* Objects encoding */ #define REDIS_ENCODING_RAW 0 /* Raw representation */ @@ -181,6 +182,8 @@ /* Anti-warning macro... */ #define REDIS_NOTUSED(V) ((void) V) +#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */ +#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ /*================================= Data types ============================== */ @@ -304,6 +307,27 @@ typedef struct _redisSortOperation { robj *pattern; } redisSortOperation; +/* ZSETs use a specialized version of Skiplists */ + +typedef struct zskiplistNode { + struct zskiplistNode **forward; + double score; + robj *obj; +} zskiplistNode; + +typedef struct zskiplist { + struct zskiplistNode *header; + long length; + int level; +} zskiplist; + +typedef struct zset { + dict *dict; + zskiplist *zsl; +} zset; + +/* Our shared "common" objects */ + struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, *colon, *nullbulk, *nullmultibulk, @@ -345,6 +369,8 @@ static void setupSigSegvAction(void); static void rdbRemoveTempFile(pid_t childpid); static size_t stringObjectLen(robj *o); static void processInputBuffer(redisClient *c); +static zskiplist *zslCreate(void); +static void zslFree(zskiplist *zsl); static void authCommand(redisClient *c); static void pingCommand(redisClient *c); @@ -385,6 +411,7 @@ static void smoveCommand(redisClient *c); static void sismemberCommand(redisClient *c); static void scardCommand(redisClient *c); static void spopCommand(redisClient *c); +static void srandmemberCommand(redisClient *c); static void sinterCommand(redisClient *c); static void sinterstoreCommand(redisClient *c); static void sunionCommand(redisClient *c); @@ -406,6 +433,7 @@ static void slaveofCommand(redisClient *c); static void debugCommand(redisClient *c); static void msetCommand(redisClient *c); static void msetnxCommand(redisClient *c); +static void zaddCommand(redisClient *c); /*================================= Globals ================================= */ @@ -436,6 +464,7 @@ static struct redisCommand cmdTable[] = { {"sismember",sismemberCommand,3,REDIS_CMD_BULK}, {"scard",scardCommand,2,REDIS_CMD_INLINE}, {"spop",spopCommand,2,REDIS_CMD_INLINE}, + {"srandmember",srandmemberCommand,2,REDIS_CMD_INLINE}, {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, @@ -443,6 +472,7 @@ static struct redisCommand cmdTable[] = { {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"smembers",sinterCommand,2,REDIS_CMD_INLINE}, + {"zadd",zaddCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM}, {"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, @@ -631,6 +661,12 @@ static void redisLog(int level, const char *fmt, ...) { * keys and radis objects as values (objects can hold SDS strings, * lists, sets). */ +static void dictVanillaFree(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + zfree(val); +} + static int sdsDictKeyCompare(void *privdata, const void *key1, const void *key2) { @@ -707,6 +743,15 @@ static dictType setDictType = { NULL /* val destructor */ }; +static dictType zsetDictType = { + dictEncObjHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictEncObjKeyCompare, /* key compare */ + dictRedisObjectDestructor, /* key destructor */ + dictVanillaFree /* val destructor */ +}; + static dictType hashDictType = { dictObjHash, /* hash function */ NULL, /* key dup */ @@ -928,7 +973,6 @@ static void createSharedObjects(void) { static void appendServerSaveParams(time_t seconds, int changes) { server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1)); - if (server.saveparams == NULL) oom("appendServerSaveParams"); server.saveparams[server.saveparamslen].seconds = seconds; server.saveparams[server.saveparamslen].changes = changes; server.saveparamslen++; @@ -985,8 +1029,6 @@ static void initServer() { server.el = aeCreateEventLoop(); server.db = zmalloc(sizeof(redisDb)*server.dbnum); server.sharingpool = dictCreate(&setDictType,NULL); - if (!server.db || !server.clients || !server.slaves || !server.monitors || !server.el || !server.objfreelist) - oom("server initialization"); /* Fatal OOM */ server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr); if (server.fd == -1) { redisLog(REDIS_WARNING, "Opening TCP port: %s", server.neterr); @@ -1237,7 +1279,7 @@ static void glueReplyBuffersIfNeeded(redisClient *c) { } /* Now the output buffer is empty, add the new single element */ o = createObject(REDIS_STRING,sdsnewlen(buf,totlen)); - if (!listAddNodeTail(c->reply,o)) oom("listAddNodeTail"); + listAddNodeTail(c->reply,o); } } @@ -1487,7 +1529,6 @@ static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int di outv = static_outv; } else { outv = zmalloc(sizeof(robj*)*(argc*2+1)); - if (!outv) oom("replicationFeedSlaves"); } for (j = 0; j < argc; j++) { @@ -1575,12 +1616,10 @@ again: return; } argv = sdssplitlen(query,sdslen(query)," ",1,&argc); - if (argv == NULL) oom("sdssplitlen"); sdsfree(query); if (c->argv) zfree(c->argv); c->argv = zmalloc(sizeof(robj*)*argc); - if (c->argv == NULL) oom("allocating arguments list for client"); for (j = 0; j < argc; j++) { if (sdslen(argv[j])) { @@ -1684,7 +1723,7 @@ static redisClient *createClient(int fd) { c->lastinteraction = time(NULL); c->authenticated = 0; c->replstate = REDIS_REPL_NONE; - if ((c->reply = listCreate()) == NULL) oom("listCreate"); + c->reply = listCreate(); listSetFreeMethod(c->reply,decrRefCount); listSetDupMethod(c->reply,dupClientReplyValue); if (aeCreateFileEvent(server.el, c->fd, AE_READABLE, @@ -1692,7 +1731,7 @@ static redisClient *createClient(int fd) { freeClient(c); return NULL; } - if (!listAddNodeTail(server.clients,c)) oom("listAddNodeTail"); + listAddNodeTail(server.clients,c); return c; } @@ -1707,7 +1746,7 @@ static void addReply(redisClient *c, robj *obj) { } else { incrRefCount(obj); } - if (!listAddNodeTail(c->reply,obj)) oom("listAddNodeTail"); + listAddNodeTail(c->reply,obj); } static void addReplySds(redisClient *c, sds s) { @@ -1782,7 +1821,6 @@ static robj *createObject(int type, void *ptr) { } else { o = zmalloc(sizeof(*o)); } - if (!o) oom("createObject"); o->type = type; o->encoding = REDIS_ENCODING_RAW; o->ptr = ptr; @@ -1797,17 +1835,23 @@ static robj *createStringObject(char *ptr, size_t len) { static robj *createListObject(void) { list *l = listCreate(); - if (!l) oom("listCreate"); listSetFreeMethod(l,decrRefCount); return createObject(REDIS_LIST,l); } static robj *createSetObject(void) { dict *d = dictCreate(&setDictType,NULL); - if (!d) oom("dictCreate"); return createObject(REDIS_SET,d); } +static robj *createZsetObject(void) { + zset *zs = zmalloc(sizeof(*zs)); + + zs->dict = dictCreate(&zsetDictType,NULL); + zs->zsl = zslCreate(); + return createObject(REDIS_ZSET,zs); +} + static void freeStringObject(robj *o) { if (o->encoding == REDIS_ENCODING_RAW) { sdsfree(o->ptr); @@ -1822,6 +1866,14 @@ static void freeSetObject(robj *o) { dictRelease((dict*) o->ptr); } +static void freeZsetObject(robj *o) { + zset *zs = o->ptr; + + dictRelease(zs->dict); + zslFree(zs->zsl); + zfree(zs); +} + static void freeHashObject(robj *o) { dictRelease((dict*) o->ptr); } @@ -1846,6 +1898,7 @@ static void decrRefCount(void *obj) { 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: assert(0 != 0); break; } @@ -2242,7 +2295,6 @@ static int rdbSave(char *filename) { dictIterator *di = dictGetIterator(set); dictEntry *de; - if (!set) oom("dictGetIteraotr"); if (rdbSaveLen(fp,dictSize(set)) == -1) goto werr; while((de = dictNext(di)) != NULL) { robj *eleobj = dictGetEntryKey(de); @@ -2507,11 +2559,9 @@ static int rdbLoad(char *filename) { if ((ele = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr; tryObjectEncoding(ele); if (type == REDIS_LIST) { - if (!listAddNodeTail((list*)o->ptr,ele)) - oom("listAddNodeTail"); + listAddNodeTail((list*)o->ptr,ele); } else { - if (dictAdd((dict*)o->ptr,ele,NULL) == DICT_ERR) - oom("dictAdd"); + dictAdd((dict*)o->ptr,ele,NULL); } } } else { @@ -2764,7 +2814,6 @@ static void keysCommand(redisClient *c) { robj *lenobj = createObject(REDIS_STRING,NULL); di = dictGetIterator(c->db->dict); - if (!di) oom("dictGetIterator"); addReply(c,lenobj); decrRefCount(lenobj); while((de = dictNext(di)) != NULL) { @@ -2960,9 +3009,9 @@ static void pushGenericCommand(redisClient *c, int where) { lobj = createListObject(); list = lobj->ptr; if (where == REDIS_HEAD) { - if (!listAddNodeHead(list,c->argv[2])) oom("listAddNodeHead"); + listAddNodeHead(list,c->argv[2]); } else { - if (!listAddNodeTail(list,c->argv[2])) oom("listAddNodeTail"); + listAddNodeTail(list,c->argv[2]); } dictAdd(c->db->dict,c->argv[1],lobj); incrRefCount(c->argv[1]); @@ -2974,9 +3023,9 @@ static void pushGenericCommand(redisClient *c, int where) { } list = lobj->ptr; if (where == REDIS_HEAD) { - if (!listAddNodeHead(list,c->argv[2])) oom("listAddNodeHead"); + listAddNodeHead(list,c->argv[2]); } else { - if (!listAddNodeTail(list,c->argv[2])) oom("listAddNodeTail"); + listAddNodeTail(list,c->argv[2]); } incrRefCount(c->argv[2]); } @@ -3387,6 +3436,31 @@ static void spopCommand(redisClient *c) { } } +static void srandmemberCommand(redisClient *c) { + robj *set; + dictEntry *de; + + set = lookupKeyRead(c->db,c->argv[1]); + if (set == NULL) { + addReply(c,shared.nullbulk); + } else { + if (set->type != REDIS_SET) { + addReply(c,shared.wrongtypeerr); + return; + } + de = dictGetRandomKey(set->ptr); + if (de == NULL) { + addReply(c,shared.nullbulk); + } else { + robj *ele = dictGetEntryKey(de); + + addReplyBulkLen(c,ele); + addReply(c,ele); + addReply(c,shared.crlf); + } + } +} + static int qsortCompareSetsByCardinality(const void *s1, const void *s2) { dict **d1 = (void*) s1, **d2 = (void*) s2; @@ -3400,7 +3474,6 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r robj *lenobj = NULL, *dstset = NULL; int j, cardinality = 0; - if (!dv) oom("sinterGenericCommand"); for (j = 0; j < setsnum; j++) { robj *setobj; @@ -3447,7 +3520,6 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r * the element against all the other sets, if at least one set does * not include the element it is discarded */ di = dictGetIterator(dv[0]); - if (!di) oom("dictGetIterator"); while((de = dictNext(di)) != NULL) { robj *ele; @@ -3504,7 +3576,6 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu robj *dstset = NULL; int j, cardinality = 0; - if (!dv) oom("sunionDiffGenericCommand"); for (j = 0; j < setsnum; j++) { robj *setobj; @@ -3535,7 +3606,6 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu if (!dv[j]) continue; /* non existing keys are like empty sets */ di = dictGetIterator(dv[j]); - if (!di) oom("dictGetIterator"); while((de = dictNext(di)) != NULL) { robj *ele; @@ -3562,7 +3632,6 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu if (!dstkey) { addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",cardinality)); di = dictGetIterator(dstset->ptr); - if (!di) oom("dictGetIterator"); while((de = dictNext(di)) != NULL) { robj *ele; @@ -3607,6 +3676,151 @@ static void sdiffstoreCommand(redisClient *c) { sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_DIFF); } +/* ==================================== ZSets =============================== */ + +/* ZSETs are ordered sets using two data structures to hold the same elements + * in order to get O(log(N)) INSERT and REMOVE operations into a sorted + * data structure. + * + * The elements are added to an hash table mapping Redis objects to scores. + * At the same time the elements are added to a skip list mapping scores + * to Redis objects (so objects are sorted by scores in this "view"). */ + +/* This skiplist implementation is almost a C translation of the original + * algorithm described by William Pugh in "Skip Lists: A Probabilistic + * Alternative to Balanced Trees", modified in three ways: + * a) this implementation allows for repeated values. + * b) the comparison is not just by key (our 'score') but by satellite data. + * c) there is a back pointer, so it's a doubly linked list with the back + * pointers being only at "level 1". This allows to traverse the list + * from tail to head, useful for ZREVRANGE. */ + +static zskiplistNode *zslCreateNode(int level, double score, robj *obj) { + zskiplistNode *zn = zmalloc(sizeof(*zn)); + + zn->forward = zmalloc(sizeof(zskiplistNode*) * level); + zn->score = score; + zn->obj = obj; + return zn; +} + +static zskiplist *zslCreate(void) { + int j; + zskiplist *zsl; + + zsl = zmalloc(sizeof(*zsl)); + zsl->level = 1; + zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL); + for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) + zsl->header->forward[j] = NULL; + return zsl; +} + +static void zslFreeNode(zskiplistNode *node) { + decrRefCount(node->obj); + zfree(node); +} + +static void zslFree(zskiplist *zsl) { + zskiplistNode *node = zsl->header->forward[1], *next; + + while(node) { + next = node->forward[1]; + zslFreeNode(node); + node = next; + } +} + +static int zslRandomLevel(void) { + int level = 1; + while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) + level += 1; + return level; +} + +static void zslInsert(zskiplist *zsl, double score, robj *obj) { + zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; + int i, level; + + x = zsl->header; + for (i = zsl->level-1; i >= 0; i--) { + while (x->forward[i]->score < score) + x = x->forward[i]; + update[i] = x; + } + x = x->forward[1]; + /* we assume the key is not already inside, since we allow duplicated + * scores, and the re-insertion of score and redis object should never + * happpen since the caller of zslInsert() should test in the hash table + * if the element is already inside or not. */ + level = zslRandomLevel(); + if (level > zsl->level) { + for (i = zsl->level; i < level; i++) + update[i] = zsl->header; + zsl->level = level; + } + x = zslCreateNode(level,score,obj); + for (i = 0; i < level; i++) { + x->forward[i] = update[i]->forward[i]; + update[i]->forward[i] = x; + } +} + +static int zslDelete(zskiplist *zsl, double score, robj *obj) { + return 1; +} + +/* The actual Z-commands implementations */ + +static void zaddCommand(redisClient *c) { + robj *zsetobj; + zset *zs; + double *score; + + zsetobj = lookupKeyWrite(c->db,c->argv[1]); + if (zsetobj == NULL) { + zsetobj = createZsetObject(); + dictAdd(c->db->dict,c->argv[1],zsetobj); + incrRefCount(c->argv[1]); + } else { + if (zsetobj->type != REDIS_ZSET) { + addReply(c,shared.wrongtypeerr); + return; + } + } + score = zmalloc(sizeof(double)); + *score = strtod(c->argv[2]->ptr,NULL); + zs = zsetobj->ptr; + if (dictAdd(zs->dict,c->argv[3],score) == DICT_OK) { + /* case 1: New element */ + incrRefCount(c->argv[3]); /* added to hash */ + zslInsert(zs->zsl,*score,c->argv[3]); + incrRefCount(c->argv[3]); /* added to skiplist */ + server.dirty++; + addReply(c,shared.cone); + } else { + dictEntry *de; + double *oldscore; + + /* case 2: Score update operation */ + de = dictFind(zs->dict,c->argv[3]); + assert(de != NULL); + oldscore = dictGetEntryVal(de); + if (*score != *oldscore) { + int deleted; + + deleted = zslDelete(zs->zsl,*score,c->argv[3]); + assert(deleted != 0); + zslInsert(zs->zsl,*score,c->argv[3]); + incrRefCount(c->argv[3]); + server.dirty++; + } + addReply(c,shared.czero); + } +} + +/* ========================= Non type-specific commands ==================== */ + static void flushdbCommand(redisClient *c) { server.dirty += dictSize(c->db->dict); dictEmpty(c->db->dict); @@ -3623,7 +3837,6 @@ static void flushallCommand(redisClient *c) { static redisSortOperation *createSortOperation(int type, robj *pattern) { redisSortOperation *so = zmalloc(sizeof(*so)); - if (!so) oom("createSortOperation"); so->type = type; so->pattern = pattern; return so; @@ -3810,7 +4023,6 @@ static void sortCommand(redisClient *c) { listLength((list*)sortval->ptr) : dictSize((dict*)sortval->ptr); vector = zmalloc(sizeof(redisSortObject)*vectorlen); - if (!vector) oom("allocating objects vector for SORT"); j = 0; if (sortval->type == REDIS_LIST) { list *list = sortval->ptr; @@ -3830,7 +4042,6 @@ static void sortCommand(redisClient *c) { dictEntry *setele; di = dictGetIterator(set); - if (!di) oom("dictGetIterator"); while((setele = dictNext(di)) != NULL) { vector[j].obj = dictGetEntryKey(setele); vector[j].u.score = 0; @@ -4009,7 +4220,7 @@ static void monitorCommand(redisClient *c) { c->flags |= (REDIS_SLAVE|REDIS_MONITOR); c->slaveseldb = 0; - if (!listAddNodeTail(server.monitors,c)) oom("listAddNodeTail"); + listAddNodeTail(server.monitors,c); addReply(c,shared.ok); } @@ -4249,7 +4460,6 @@ static void syncCommand(redisClient *c) { * another slave. Set the right state, and copy the buffer. */ listRelease(c->reply); c->reply = listDup(slave->reply); - if (!c->reply) oom("listDup copying slave reply list"); c->replstate = REDIS_REPL_WAIT_BGSAVE_END; redisLog(REDIS_NOTICE,"Waiting for end of BGSAVE for SYNC"); } else { @@ -4271,7 +4481,7 @@ static void syncCommand(redisClient *c) { c->repldbfd = -1; c->flags |= REDIS_SLAVE; c->slaveseldb = 0; - if (!listAddNodeTail(server.slaves,c)) oom("listAddNodeTail"); + listAddNodeTail(server.slaves,c); return; } @@ -4629,6 +4839,7 @@ static struct redisFunctionSym symsTable[] = { {"sismemberCommand", (unsigned long)sismemberCommand}, {"scardCommand", (unsigned long)scardCommand}, {"spopCommand", (unsigned long)spopCommand}, +{"srandmemberCommand", (unsigned long)srandmemberCommand}, {"sinterCommand", (unsigned long)sinterCommand}, {"sinterstoreCommand", (unsigned long)sinterstoreCommand}, {"sunionCommand", (unsigned long)sunionCommand}, @@ -4690,7 +4901,7 @@ static void *getMcontextEip(ucontext_t *uc) { #elif defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) return (void*) uc->uc_mcontext->__ss.__eip; #elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) - #ifdef _STRUCT_X86_THREAD_STATE64 + #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) return (void*) uc->uc_mcontext->__ss.__rip; #else return (void*) uc->uc_mcontext->__ss.__eip;