X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/599379dddaed98b8ab037a4303d9aa3ed6c9896d..44b38ef43259e8805b01db01ad9a1c67479c6194:/redis.c diff --git a/redis.c b/redis.c index c45beecf..19abda20 100644 --- a/redis.c +++ b/redis.c @@ -57,6 +57,11 @@ #include #include #include +#include + +#if defined(__sun) +#include "solarisfixes.h" +#endif #include "redis.h" #include "ae.h" /* Event driven programming library */ @@ -256,6 +261,9 @@ struct redisServer { int maxidletime; int dbnum; int daemonize; + int appendonly; + int appendfd; + int appendseldb; char *pidfile; int bgsaveinprogress; pid_t bgsavechildpid; @@ -264,6 +272,7 @@ struct redisServer { char *logfile; char *bindaddr; char *dbfilename; + char *appendfilename; char *requirepass; int shareobjects; /* Replication related */ @@ -318,7 +327,7 @@ typedef struct zskiplistNode { typedef struct zskiplist { struct zskiplistNode *header, *tail; - long length; + unsigned long length; int level; } zskiplist; @@ -338,6 +347,12 @@ struct sharedObjectsStruct { *select5, *select6, *select7, *select8, *select9; } shared; +/* Global vars that are actally used as constants. The following double + * values are used for double on-disk serialization, and are initialized + * at runtime to avoid strange compiler optimizations. */ + +static double R_Zero, R_PosInf, R_NegInf, R_Nan; + /*================================ Prototypes =============================== */ static void freeStringObject(robj *o); @@ -353,6 +368,7 @@ static void incrRefCount(robj *o); static int rdbSaveBackground(char *filename); static robj *createStringObject(char *ptr, size_t len); static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc); +static void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc); static int syncWithMaster(void); static robj *tryObjectSharing(robj *o); static int tryObjectEncoding(robj *o); @@ -372,6 +388,7 @@ static size_t stringObjectLen(robj *o); static void processInputBuffer(redisClient *c); static zskiplist *zslCreate(void); static void zslFree(zskiplist *zsl); +static void zslInsert(zskiplist *zsl, double score, robj *obj); static void authCommand(redisClient *c); static void pingCommand(redisClient *c); @@ -428,6 +445,7 @@ static void infoCommand(redisClient *c); static void mgetCommand(redisClient *c); static void monitorCommand(redisClient *c); static void expireCommand(redisClient *c); +static void expireatCommand(redisClient *c); static void getsetCommand(redisClient *c); static void ttlCommand(redisClient *c); static void slaveofCommand(redisClient *c); @@ -436,9 +454,12 @@ static void msetCommand(redisClient *c); static void msetnxCommand(redisClient *c); static void zaddCommand(redisClient *c); static void zrangeCommand(redisClient *c); +static void zrangebyscoreCommand(redisClient *c); static void zrevrangeCommand(redisClient *c); -static void zlenCommand(redisClient *c); +static void zcardCommand(redisClient *c); static void zremCommand(redisClient *c); +static void zscoreCommand(redisClient *c); +static void zremrangebyscoreCommand(redisClient *c); /*================================= Globals ================================= */ @@ -479,9 +500,12 @@ static struct redisCommand cmdTable[] = { {"smembers",sinterCommand,2,REDIS_CMD_INLINE}, {"zadd",zaddCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM}, {"zrem",zremCommand,3,REDIS_CMD_BULK}, + {"zremrangebyscore",zremrangebyscoreCommand,4,REDIS_CMD_INLINE}, {"zrange",zrangeCommand,4,REDIS_CMD_INLINE}, + {"zrangebyscore",zrangebyscoreCommand,4,REDIS_CMD_INLINE}, {"zrevrange",zrevrangeCommand,4,REDIS_CMD_INLINE}, - {"zlen",zlenCommand,2,REDIS_CMD_INLINE}, + {"zcard",zcardCommand,2,REDIS_CMD_INLINE}, + {"zscore",zscoreCommand,3,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}, @@ -493,6 +517,7 @@ static struct redisCommand cmdTable[] = { {"rename",renameCommand,3,REDIS_CMD_INLINE}, {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE}, {"expire",expireCommand,3,REDIS_CMD_INLINE}, + {"expireat",expireatCommand,3,REDIS_CMD_INLINE}, {"keys",keysCommand,2,REDIS_CMD_INLINE}, {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE}, {"auth",authCommand,2,REDIS_CMD_INLINE}, @@ -653,7 +678,7 @@ static void redisLog(int level, const char *fmt, ...) { time_t now; now = time(NULL); - strftime(buf,64,"%d %b %H:%M:%S",gmtime(&now)); + strftime(buf,64,"%d %b %H:%M:%S",localtime(&now)); fprintf(fp,"%s %c ",buf,c[level]); vfprintf(fp, fmt, ap); fprintf(fp,"\n"); @@ -1003,8 +1028,12 @@ static void initServerConfig() { server.bindaddr = NULL; server.glueoutputbuf = 1; server.daemonize = 0; + server.appendonly = 0; + server.appendfd = -1; + server.appendseldb = -1; /* Make sure the first time will not match */ server.pidfile = "/var/run/redis.pid"; server.dbfilename = "dump.rdb"; + server.appendfilename = "appendonly.log"; server.requirepass = NULL; server.shareobjects = 0; server.sharingpoolsize = 1024; @@ -1021,6 +1050,12 @@ static void initServerConfig() { server.masterport = 6379; server.master = NULL; server.replstate = REDIS_REPL_NONE; + + /* Double constants initialization */ + R_Zero = 0.0; + R_PosInf = 1.0/R_Zero; + R_NegInf = -1.0/R_Zero; + R_Nan = R_Zero/R_Zero; } static void initServer() { @@ -1058,6 +1093,15 @@ static void initServer() { server.stat_numconnections = 0; server.stat_starttime = time(NULL); aeCreateTimeEvent(server.el, 1000, serverCron, NULL, NULL); + + if (server.appendonly) { + server.appendfd = open(server.appendfilename,O_WRONLY|O_APPEND|O_CREAT); + if (server.appendfd == -1) { + redisLog(REDIS_WARNING, "Can't open the append-only file: %s", + strerror(errno)); + exit(1); + } + } } /* Empty the whole database */ @@ -1197,6 +1241,10 @@ static void loadServerConfig(char *filename) { if ((server.daemonize = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; } + } else if (!strcasecmp(argv[0],"appendonly") && argc == 2) { + if ((server.appendonly = yesnotoi(argv[1])) == -1) { + err = "argument must be 'yes' or 'no'"; goto loaderr; + } } else if (!strcasecmp(argv[0],"requirepass") && argc == 2) { server.requirepass = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"pidfile") && argc == 2) { @@ -1512,6 +1560,8 @@ static int processCommand(redisClient *c) { /* Exec the command */ dirty = server.dirty; cmd->proc(c); + if (server.appendonly != 0) + feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc); if (server.dirty-dirty != 0 && listLength(server.slaves)) replicationFeedSlaves(server.slaves,cmd,c->db->id,c->argv,c->argc); if (listLength(server.monitors)) @@ -1596,6 +1646,54 @@ static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int di if (outv != static_outv) zfree(outv); } +/* TODO: translate EXPIREs into EXPIRETOs */ +static void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { + sds buf = sdsempty(); + int j; + ssize_t nwritten; + + /* The DB this command was targetting is not the same as the last command + * we appendend. To issue a SELECT command is needed. */ + if (dictid != server.appendseldb) { + char seldb[64]; + + snprintf(seldb,sizeof(seldb),"%d",dictid); + buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n", + strlen(seldb),seldb); + } + /* Append the actual command */ + buf = sdscatprintf(buf,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + robj *o = argv[j]; + + if (o->encoding != REDIS_ENCODING_RAW) + o = getDecodedObject(o); + buf = sdscatprintf(buf,"$%d\r\n",sdslen(o->ptr)); + buf = sdscatlen(buf,o->ptr,sdslen(o->ptr)); + buf = sdscatlen(buf,"\r\n",2); + if (o != argv[j]) + decrRefCount(o); + } + /* We want to perform a single write. This should be guaranteed atomic + * at least if the filesystem we are writing is a real physical one. + * While this will save us against the server being killed I don't think + * there is much to do about the whole server stopping for power problems + * or alike */ + nwritten = write(server.appendfd,buf,sdslen(buf)); + if (nwritten != (unsigned)sdslen(buf)) { + /* Ooops, we are in troubles. The best thing to do for now is + * to simply exit instead to give the illusion that everything is + * working as expected. */ + if (nwritten == -1) { + redisLog(REDIS_WARNING,"Aborting on error writing to the append-only file: %s",strerror(errno)); + } else { + redisLog(REDIS_WARNING,"Aborting on short write while writing to the append-only file: %s",strerror(errno)); + } + abort(); + } + fsync(server.appendfd); /* Let's try to get this data on the disk */ +} + static void processInputBuffer(redisClient *c) { again: if (c->bulklen == -1) { @@ -2056,24 +2154,31 @@ static robj *getDecodedObject(const robj *o) { } } +/* Compare two string objects via strcmp() or alike. + * Note that the objects may be integer-encoded. In such a case we + * use snprintf() to get a string representation of the numbers on the stack + * and compare the strings, it's much faster than calling getDecodedObject(). */ static int compareStringObjects(robj *a, robj *b) { assert(a->type == REDIS_STRING && b->type == REDIS_STRING); + char bufa[128], bufb[128], *astr, *bstr; + int bothsds = 1; if (a == b) return 0; - if (a->encoding == REDIS_ENCODING_INT && b->encoding == REDIS_ENCODING_INT){ - return (long)a->ptr - (long)b->ptr; + if (a->encoding != REDIS_ENCODING_RAW) { + snprintf(bufa,sizeof(bufa),"%ld",(long) a->ptr); + astr = bufa; + bothsds = 0; } else { - int retval; - - incrRefCount(a); - incrRefCount(b); - if (a->encoding != REDIS_ENCODING_RAW) a = getDecodedObject(a); - if (b->encoding != REDIS_ENCODING_RAW) b = getDecodedObject(a); - retval = sdscmp(a->ptr,b->ptr); - decrRefCount(a); - decrRefCount(b); - return retval; + astr = a->ptr; } + if (b->encoding != REDIS_ENCODING_RAW) { + snprintf(bufb,sizeof(bufb),"%ld",(long) b->ptr); + bstr = bufb; + bothsds = 0; + } else { + bstr = b->ptr; + } + return bothsds ? sdscmp(astr,bstr) : strcmp(astr,bstr); } static size_t stringObjectLen(robj *o) { @@ -2238,6 +2343,33 @@ static int rdbSaveStringObject(FILE *fp, robj *obj) { } } +/* Save a double value. Doubles are saved as strings prefixed by an unsigned + * 8 bit integer specifing the length of the representation. + * This 8 bit integer has special values in order to specify the following + * conditions: + * 253: not a number + * 254: + inf + * 255: - inf + */ +static int rdbSaveDoubleValue(FILE *fp, double val) { + unsigned char buf[128]; + int len; + + if (isnan(val)) { + buf[0] = 253; + len = 1; + } else if (!isfinite(val)) { + len = 1; + buf[0] = (val < 0) ? 255 : 254; + } else { + snprintf((char*)buf+1,sizeof(buf)-1,"%.16g",val); + buf[0] = strlen((char*)buf); + len = buf[0]+1; + } + if (fwrite(buf,len,1,fp) == 0) return -1; + return 0; +} + /* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success */ static int rdbSave(char *filename) { dictIterator *di = NULL; @@ -2312,6 +2444,21 @@ static int rdbSave(char *filename) { if (rdbSaveStringObject(fp,eleobj) == -1) goto werr; } dictReleaseIterator(di); + } else if (o->type == REDIS_ZSET) { + /* Save a set value */ + zset *zs = o->ptr; + dictIterator *di = dictGetIterator(zs->dict); + dictEntry *de; + + if (rdbSaveLen(fp,dictSize(zs->dict)) == -1) goto werr; + while((de = dictNext(di)) != NULL) { + robj *eleobj = dictGetEntryKey(de); + double *score = dictGetEntryVal(de); + + if (rdbSaveStringObject(fp,eleobj) == -1) goto werr; + if (rdbSaveDoubleValue(fp,*score) == -1) goto werr; + } + dictReleaseIterator(di); } else { assert(0 != 0); } @@ -2500,6 +2647,23 @@ static robj *rdbLoadStringObject(FILE*fp, int rdbver) { return tryObjectSharing(createObject(REDIS_STRING,val)); } +/* For information about double serialization check rdbSaveDoubleValue() */ +static int rdbLoadDoubleValue(FILE *fp, double *val) { + char buf[128]; + unsigned char len; + + if (fread(&len,1,1,fp) == 0) return -1; + switch(len) { + case 255: *val = R_NegInf; return 0; + case 254: *val = R_PosInf; return 0; + case 253: *val = R_Nan; return 0; + default: + if (fread(buf,len,1,fp) == 0) return -1; + sscanf(buf, "%lg", val); + return 0; + } +} + static int rdbLoad(char *filename) { FILE *fp; robj *keyobj = NULL; @@ -2574,6 +2738,27 @@ static int rdbLoad(char *filename) { dictAdd((dict*)o->ptr,ele,NULL); } } + } else if (type == REDIS_ZSET) { + /* Read list/set value */ + uint32_t zsetlen; + zset *zs; + + if ((zsetlen = rdbLoadLen(fp,rdbver,NULL)) == REDIS_RDB_LENERR) + goto eoferr; + o = createZsetObject(); + zs = o->ptr; + /* Load every single element of the list/set */ + while(zsetlen--) { + robj *ele; + double *score = zmalloc(sizeof(double)); + + if ((ele = rdbLoadStringObject(fp,rdbver)) == NULL) goto eoferr; + tryObjectEncoding(ele); + if (rdbLoadDoubleValue(fp,score) == -1) goto eoferr; + dictAdd(zs->dict,ele,score); + zslInsert(zs->zsl,*score,ele); + incrRefCount(ele); /* added to skiplist */ + } } else { assert(0 != 0); } @@ -2610,7 +2795,7 @@ static void authCommand(redisClient *c) { addReply(c,shared.ok); } else { c->authenticated = 0; - addReply(c,shared.err); + addReplySds(c,sdscatprintf(sdsempty(),"-ERR invalid password\r\n")); } } @@ -3731,17 +3916,21 @@ static zskiplist *zslCreate(void) { static void zslFreeNode(zskiplistNode *node) { decrRefCount(node->obj); + zfree(node->forward); zfree(node); } static void zslFree(zskiplist *zsl) { - zskiplistNode *node = zsl->header->forward[1], *next; + zskiplistNode *node = zsl->header->forward[0], *next; + zfree(zsl->header->forward); + zfree(zsl->header); while(node) { next = node->forward[0]; zslFreeNode(node); node = next; } + zfree(zsl); } static int zslRandomLevel(void) { @@ -3757,7 +3946,10 @@ static void zslInsert(zskiplist *zsl, double score, robj *obj) { x = zsl->header; for (i = zsl->level-1; i >= 0; i--) { - while (x->forward[i] && x->forward[i]->score < score) + while (x->forward[i] && + (x->forward[i]->score < score || + (x->forward[i]->score == score && + compareStringObjects(x->forward[i]->obj,obj) < 0))) x = x->forward[i]; update[i] = x; } @@ -3784,44 +3976,104 @@ static void zslInsert(zskiplist *zsl, double score, robj *obj) { zsl->length++; } +/* Delete an element with matching score/object from the skiplist. */ static int zslDelete(zskiplist *zsl, double score, robj *obj) { zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; int i; x = zsl->header; for (i = zsl->level-1; i >= 0; i--) { - while (x->forward[i] && x->forward[i]->score < score) + while (x->forward[i] && + (x->forward[i]->score < score || + (x->forward[i]->score == score && + compareStringObjects(x->forward[i]->obj,obj) < 0))) x = x->forward[i]; update[i] = x; } /* We may have multiple elements with the same score, what we need * is to find the element with both the right score and object. */ x = x->forward[0]; - while(x->score == score) { - if (compareStringObjects(x->obj,obj) == 0) { - for (i = 0; i < zsl->level; i++) { - if (update[i]->forward[i] != x) break; - update[i]->forward[i] = x->forward[i]; - } - if (x->forward[0]) { - x->forward[0]->backward = (x->backward == zsl->header) ? - NULL : x->backward; - } else { - zsl->tail = x->backward; - } - zslFreeNode(x); - while(zsl->level > 1 && zsl->header->forward[zsl->level-1] == NULL) - zsl->level--; - zsl->length--; - return 1; + if (x && score == x->score && compareStringObjects(x->obj,obj) == 0) { + for (i = 0; i < zsl->level; i++) { + if (update[i]->forward[i] != x) break; + update[i]->forward[i] = x->forward[i]; + } + if (x->forward[0]) { + x->forward[0]->backward = (x->backward == zsl->header) ? + NULL : x->backward; } else { - x = x->forward[0]; - if (!x) return 0; /* end of the list reached, not found */ + zsl->tail = x->backward; } + zslFreeNode(x); + while(zsl->level > 1 && zsl->header->forward[zsl->level-1] == NULL) + zsl->level--; + zsl->length--; + return 1; + } else { + return 0; /* not found */ } return 0; /* not found */ } +/* Delete all the elements with score between min and max from the skiplist. + * Min and mx are inclusive, so a score >= min || score <= max is deleted. + * Note that this function takes the reference to the hash table view of the + * sorted set, in order to remove the elements from the hash table too. */ +static unsigned long zslDeleteRange(zskiplist *zsl, double min, double max, dict *dict) { + zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; + unsigned long removed = 0; + int i; + + x = zsl->header; + for (i = zsl->level-1; i >= 0; i--) { + while (x->forward[i] && x->forward[i]->score < min) + x = x->forward[i]; + update[i] = x; + } + /* We may have multiple elements with the same score, what we need + * is to find the element with both the right score and object. */ + x = x->forward[0]; + while (x && x->score <= max) { + zskiplistNode *next; + + for (i = 0; i < zsl->level; i++) { + if (update[i]->forward[i] != x) break; + update[i]->forward[i] = x->forward[i]; + } + if (x->forward[0]) { + x->forward[0]->backward = (x->backward == zsl->header) ? + NULL : x->backward; + } else { + zsl->tail = x->backward; + } + next = x->forward[0]; + dictDelete(dict,x->obj); + zslFreeNode(x); + while(zsl->level > 1 && zsl->header->forward[zsl->level-1] == NULL) + zsl->level--; + zsl->length--; + removed++; + x = next; + } + return removed; /* not found */ +} + +/* Find the first node having a score equal or greater than the specified one. + * Returns NULL if there is no match. */ +static zskiplistNode *zslFirstWithScore(zskiplist *zsl, double score) { + zskiplistNode *x; + int i; + + x = zsl->header; + for (i = zsl->level-1; i >= 0; i--) { + while (x->forward[i] && x->forward[i]->score < score) + x = x->forward[i]; + } + /* We may have multiple elements with the same score, what we need + * is to find the element with both the right score and object. */ + return x->forward[0]; +} + /* The actual Z-commands implementations */ static void zaddCommand(redisClient *c) { @@ -3865,7 +4117,10 @@ static void zaddCommand(redisClient *c) { assert(deleted != 0); zslInsert(zs->zsl,*score,c->argv[3]); incrRefCount(c->argv[3]); + dictReplace(zs->dict,c->argv[3],score); server.dirty++; + } else { + zfree(score); } addReply(c,shared.czero); } @@ -3906,6 +4161,30 @@ static void zremCommand(redisClient *c) { } } +static void zremrangebyscoreCommand(redisClient *c) { + double min = strtod(c->argv[2]->ptr,NULL); + double max = strtod(c->argv[3]->ptr,NULL); + robj *zsetobj; + zset *zs; + + zsetobj = lookupKeyWrite(c->db,c->argv[1]); + if (zsetobj == NULL) { + addReply(c,shared.czero); + } else { + long deleted; + + if (zsetobj->type != REDIS_ZSET) { + addReply(c,shared.wrongtypeerr); + return; + } + zs = zsetobj->ptr; + deleted = zslDeleteRange(zs->zsl,min,max,zs->dict); + if (htNeedsResize(zs->dict)) dictResize(zs->dict); + server.dirty += deleted; + addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",deleted)); + } +} + static void zrangeGenericCommand(redisClient *c, int reverse) { robj *o; int start = atoi(c->argv[2]->ptr); @@ -3972,7 +4251,53 @@ static void zrevrangeCommand(redisClient *c) { zrangeGenericCommand(c,1); } -static void zlenCommand(redisClient *c) { +static void zrangebyscoreCommand(redisClient *c) { + robj *o; + double min = strtod(c->argv[2]->ptr,NULL); + double max = strtod(c->argv[3]->ptr,NULL); + + o = lookupKeyRead(c->db,c->argv[1]); + if (o == NULL) { + addReply(c,shared.nullmultibulk); + } else { + if (o->type != REDIS_ZSET) { + addReply(c,shared.wrongtypeerr); + } else { + zset *zsetobj = o->ptr; + zskiplist *zsl = zsetobj->zsl; + zskiplistNode *ln; + robj *ele, *lenobj; + unsigned int rangelen = 0; + + /* Get the first node with the score >= min */ + ln = zslFirstWithScore(zsl,min); + if (ln == NULL) { + /* No element matching the speciifed interval */ + addReply(c,shared.emptymultibulk); + return; + } + + /* We don't know in advance how many matching elements there + * are in the list, so we push this object that will represent + * the multi-bulk length in the output buffer, and will "fix" + * it later */ + lenobj = createObject(REDIS_STRING,NULL); + addReply(c,lenobj); + + while(ln && ln->score <= max) { + ele = ln->obj; + addReplyBulkLen(c,ele); + addReply(c,ele); + addReply(c,shared.crlf); + ln = ln->forward[0]; + rangelen++; + } + lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",rangelen); + } + } +} + +static void zcardCommand(redisClient *c) { robj *o; zset *zs; @@ -3990,6 +4315,36 @@ static void zlenCommand(redisClient *c) { } } +static void zscoreCommand(redisClient *c) { + robj *o; + zset *zs; + + o = lookupKeyRead(c->db,c->argv[1]); + if (o == NULL) { + addReply(c,shared.czero); + return; + } else { + if (o->type != REDIS_ZSET) { + addReply(c,shared.wrongtypeerr); + } else { + dictEntry *de; + + zs = o->ptr; + de = dictFind(zs->dict,c->argv[2]); + if (!de) { + addReply(c,shared.nullbulk); + } else { + char buf[128]; + double *score = dictGetEntryVal(de); + + snprintf(buf,sizeof(buf),"%.16g",*score); + addReplySds(c,sdscatprintf(sdsempty(),"$%d\r\n%s\r\n", + strlen(buf),buf)); + } + } + } +} + /* ========================= Non type-specific commands ==================== */ static void flushdbCommand(redisClient *c) { @@ -4367,7 +4722,7 @@ static void infoCommand(redisClient *c) { server.masterport, (server.replstate == REDIS_REPL_CONNECTED) ? "up" : "down", - (int)(time(NULL)-server.master->lastinteraction) + server.master ? ((int)(time(NULL)-server.master->lastinteraction)) : -1 ); } for (j = 0; j < server.dbnum; j++) { @@ -4455,21 +4810,21 @@ static int deleteIfVolatile(redisDb *db, robj *key) { return dictDelete(db->dict,key) == DICT_OK; } -static void expireCommand(redisClient *c) { +static void expireGenericCommand(redisClient *c, robj *key, time_t seconds) { dictEntry *de; - int seconds = atoi(c->argv[2]->ptr); - de = dictFind(c->db->dict,c->argv[1]); + de = dictFind(c->db->dict,key); if (de == NULL) { addReply(c,shared.czero); return; } - if (seconds <= 0) { - addReply(c, shared.czero); + if (seconds < 0) { + if (deleteKey(c->db,key)) server.dirty++; + addReply(c, shared.cone); return; } else { time_t when = time(NULL)+seconds; - if (setExpire(c->db,c->argv[1],when)) { + if (setExpire(c->db,key,when)) { addReply(c,shared.cone); server.dirty++; } else { @@ -4479,6 +4834,14 @@ static void expireCommand(redisClient *c) { } } +static void expireCommand(redisClient *c) { + expireGenericCommand(c,c->argv[1],strtol(c->argv[2]->ptr,NULL,10)); +} + +static void expireatCommand(redisClient *c) { + expireGenericCommand(c,c->argv[1],strtol(c->argv[2]->ptr,NULL,10)-time(NULL)); +} + static void ttlCommand(redisClient *c) { time_t expire; int ttl = -1; @@ -4790,6 +5153,11 @@ static int syncWithMaster(void) { strerror(errno)); return REDIS_ERR; } + if (buf[0] != '$') { + close(fd); + redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?"); + return REDIS_ERR; + } dumpsize = atoi(buf+1); redisLog(REDIS_NOTICE,"Receiving %d bytes data dump from MASTER",dumpsize); /* Read the bulk write data on a temp file */ @@ -5026,6 +5394,7 @@ static struct redisFunctionSym symsTable[] = { {"mgetCommand", (unsigned long)mgetCommand}, {"monitorCommand", (unsigned long)monitorCommand}, {"expireCommand", (unsigned long)expireCommand}, +{"expireatCommand", (unsigned long)expireatCommand}, {"getsetCommand", (unsigned long)getsetCommand}, {"ttlCommand", (unsigned long)ttlCommand}, {"slaveofCommand", (unsigned long)slaveofCommand}, @@ -5050,6 +5419,8 @@ static struct redisFunctionSym symsTable[] = { {"zrangeCommand",(unsigned long)zrangeCommand}, {"zrevrangeCommand",(unsigned long)zrevrangeCommand}, {"zremCommand",(unsigned long)zremCommand}, +{"rdbSaveDoubleValue",(unsigned long)rdbSaveDoubleValue}, +{"rdbLoadDoubleValue",(unsigned long)rdbLoadDoubleValue}, {NULL,0} };