#define REDIS_HASH 3
/* Object types only used for dumping to disk */
+#define REDIS_EXPIRETIME 253
#define REDIS_SELECTDB 254
#define REDIS_EOF 255
/* A redis object, that is a type able to hold a string / list / set */
typedef struct redisObject {
- int type;
void *ptr;
+ int type;
int refcount;
} robj;
int cronloops; /* number of times the cron function run */
list *objfreelist; /* A list of freed objects to avoid malloc() */
time_t lastsave; /* Unix time of last save succeeede */
- int usedmemory; /* Used memory in megabytes */
+ size_t usedmemory; /* Used memory in megabytes */
/* Fields used only for stats */
time_t stat_starttime; /* server start time */
long long stat_numcommands; /* number of processed commands */
static int expireIfNeeded(redisDb *db, robj *key);
static int deleteIfVolatile(redisDb *db, robj *key);
static int deleteKey(redisDb *db, robj *key);
+static time_t getExpire(redisDb *db, robj *key);
+static int setExpire(redisDb *db, robj *key, time_t when);
static void authCommand(redisClient *c);
static void pingCommand(redisClient *c);
/* Show information about connected clients */
if (!(loops % 5)) {
- redisLog(REDIS_DEBUG,"%d clients connected (%d slaves), %d bytes in use",
+ redisLog(REDIS_DEBUG,"%d clients connected (%d slaves), %zu bytes in use",
listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves),
server.usedmemory,
return 0;
}
+static int rdbSaveTime(FILE *fp, time_t t) {
+ int32_t t32 = (int32_t) t;
+ if (fwrite(&t32,4,1,fp) == 0) return -1;
+ return 0;
+}
+
/* check rdbLoadLen() comments for more info */
static int rdbSaveLen(FILE *fp, uint32_t len) {
unsigned char buf[2];
if ((out = zmalloc(outlen)) == NULL) return 0;
comprlen = lzf_compress(obj->ptr, sdslen(obj->ptr), out, outlen);
if (comprlen == 0) {
- free(out);
+ zfree(out);
return 0;
}
/* Data compressed! Let's save it on disk */
if (rdbSaveLen(fp,comprlen) == -1) goto writeerr;
if (rdbSaveLen(fp,sdslen(obj->ptr)) == -1) goto writeerr;
if (fwrite(out,comprlen,1,fp) == 0) goto writeerr;
- free(out);
+ zfree(out);
return comprlen;
writeerr:
- free(out);
+ zfree(out);
return -1;
}
}
/* Try LZF compression - under 20 bytes it's unable to compress even
- * aaaaaaaaaaaaaaaaaa so to try is just useful to make the CPU hot */
- if (0 && len > 20) {
+ * aaaaaaaaaaaaaaaaaa so skip it */
+ if (len > 20) {
int retval;
retval = rdbSaveLzfStringObject(fp,obj);
FILE *fp;
char tmpfile[256];
int j;
+ time_t now = time(NULL);
snprintf(tmpfile,256,"temp-%d.%ld.rdb",(int)time(NULL),(long int)random());
fp = fopen(tmpfile,"w");
}
if (fwrite("REDIS0001",9,1,fp) == 0) goto werr;
for (j = 0; j < server.dbnum; j++) {
- dict *d = server.db[j].dict;
+ redisDb *db = server.db+j;
+ dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetIterator(d);
if (!di) {
while((de = dictNext(di)) != NULL) {
robj *key = dictGetEntryKey(de);
robj *o = dictGetEntryVal(de);
-
+ time_t expiretime = getExpire(db,key);
+
+ /* Save the expire time */
+ if (expiretime != -1) {
+ /* If this key is already expired skip it */
+ if (expiretime < now) continue;
+ if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr;
+ if (rdbSaveTime(fp,expiretime) == -1) goto werr;
+ }
+ /* Save the key and associated value */
if (rdbSaveType(fp,o->type) == -1) goto werr;
if (rdbSaveStringObject(fp,key) == -1) goto werr;
if (o->type == REDIS_STRING) {
return type;
}
+static time_t rdbLoadTime(FILE *fp) {
+ int32_t t32;
+ if (fread(&t32,4,1,fp) == 0) return -1;
+ return (time_t) t32;
+}
+
/* Load an encoded length from the DB, see the REDIS_RDB_* defines on the top
* of this file for a description of how this are stored on disk.
*
return createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",val));
}
+static robj *rdbLoadLzfStringObject(FILE*fp, int rdbver) {
+ unsigned int len, clen;
+ unsigned char *c = NULL;
+ sds val = NULL;
+
+ if ((clen = rdbLoadLen(fp,rdbver,NULL)) == REDIS_RDB_LENERR) return NULL;
+ if ((len = rdbLoadLen(fp,rdbver,NULL)) == REDIS_RDB_LENERR) return NULL;
+ if ((c = zmalloc(clen)) == NULL) goto err;
+ if ((val = sdsnewlen(NULL,len)) == NULL) goto err;
+ if (fread(c,clen,1,fp) == 0) goto err;
+ if (lzf_decompress(c,clen,val,len) == 0) goto err;
+ return createObject(REDIS_STRING,val);
+err:
+ zfree(c);
+ sdsfree(val);
+ return NULL;
+}
+
static robj *rdbLoadStringObject(FILE*fp, int rdbver) {
int isencoded;
uint32_t len;
case REDIS_RDB_ENC_INT16:
case REDIS_RDB_ENC_INT32:
return tryObjectSharing(rdbLoadIntegerObject(fp,len));
+ case REDIS_RDB_ENC_LZF:
+ return tryObjectSharing(rdbLoadLzfStringObject(fp,rdbver));
default:
assert(0!=0);
}
FILE *fp;
robj *keyobj = NULL;
uint32_t dbid;
- int type;
- int retval;
+ int type, retval, rdbver;
dict *d = server.db[0].dict;
+ redisDb *db = server.db+0;
char buf[1024];
- int rdbver;
+ time_t expiretime = -1, now = time(NULL);
+
fp = fopen(filename,"r");
if (!fp) return REDIS_ERR;
if (fread(buf,9,1,fp) == 0) goto eoferr;
/* Read type. */
if ((type = rdbLoadType(fp)) == -1) goto eoferr;
+ if (type == REDIS_EXPIRETIME) {
+ if ((expiretime = rdbLoadTime(fp)) == -1) goto eoferr;
+ /* We read the time so we need to read the object type again */
+ if ((type = rdbLoadType(fp)) == -1) goto eoferr;
+ }
if (type == REDIS_EOF) break;
/* Handle SELECT DB opcode as a special case */
if (type == REDIS_SELECTDB) {
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum);
exit(1);
}
- d = server.db[dbid].dict;
+ db = server.db+dbid;
+ d = db->dict;
continue;
}
/* Read key */
redisLog(REDIS_WARNING,"Loading DB, duplicated key (%s) found! Unrecoverable error, exiting now.", keyobj->ptr);
exit(1);
}
+ /* Set the expire time if needed */
+ if (expiretime != -1) {
+ setExpire(db,keyobj,expiretime);
+ /* Delete this key if already expired */
+ if (expiretime < now) deleteKey(db,keyobj);
+ expiretime = -1;
+ }
keyobj = o = NULL;
}
fclose(fp);
while(1) {
de = dictGetRandomKey(c->db->dict);
- if (expireIfNeeded(c->db,dictGetEntryKey(de)) == 0) break;
+ if (!de || expireIfNeeded(c->db,dictGetEntryKey(de)) == 0) break;
}
if (de == NULL) {
+ addReply(c,shared.plus);
addReply(c,shared.crlf);
} else {
addReply(c,shared.plus);
}
static void saveCommand(redisClient *c) {
+ if (server.bgsaveinprogress) {
+ addReplySds(c,sdsnew("-ERR background save in progress\r\n"));
+ return;
+ }
if (rdbSave(server.dbfilename) == REDIS_OK) {
addReply(c,shared.ok);
} else {
deleteKey(c->db,dstkey);
dictAdd(c->db->dict,dstkey,dstset);
incrRefCount(dstkey);
+ server.dirty++;
}
/* Iterate all the elements of the first (smallest) set, and test
} else {
dictAdd(dstset->ptr,ele,NULL);
incrRefCount(ele);
+ server.dirty++;
}
}
dictReleaseIterator(di);
static void flushdbCommand(redisClient *c) {
dictEmpty(c->db->dict);
dictEmpty(c->db->expires);
+ server.dirty++;
addReply(c,shared.ok);
rdbSave(server.dbfilename);
}
static void flushallCommand(redisClient *c) {
emptyDb();
+ server.dirty++;
addReply(c,shared.ok);
rdbSave(server.dbfilename);
}
"redis_version:%s\r\n"
"connected_clients:%d\r\n"
"connected_slaves:%d\r\n"
- "used_memory:%d\r\n"
+ "used_memory:%zu\r\n"
"changes_since_last_save:%lld\r\n"
"last_save_time:%d\r\n"
"total_connections_received:%lld\r\n"
}
}
+/* 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) */
+static time_t getExpire(redisDb *db, robj *key) {
+ dictEntry *de;
+
+ /* No expire? return ASAP */
+ if (dictSize(db->expires) == 0 ||
+ (de = dictFind(db->expires,key)) == NULL) return -1;
+
+ return (time_t) dictGetEntryVal(de);
+}
+
static int expireIfNeeded(redisDb *db, robj *key) {
time_t when;
dictEntry *de;
(de = dictFind(db->expires,key)) == NULL) return 0;
/* Delete the key */
+ server.dirty++;
dictDelete(db->expires,key);
return dictDelete(db->dict,key) == DICT_OK;
}