unsigned lru:22; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
- /* VM fields, this are only allocated if VM is active, otherwise the
+ /* VM fields are only allocated if VM is active, otherwise the
* object allocation function will just allocate
* sizeof(redisObjct) minus sizeof(redisObjectVM), so using
* Redis without VM active will not have any overhead. */
#define REDIS_SHARED_INTEGERS 10000
struct sharedObjectsStruct {
- robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
+ robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space,
*colon, *nullbulk, *nullmultibulk, *queued,
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*outofrangeerr, *plus,
} iojob;
/*================================ Prototypes =============================== */
+char *redisGitSHA1(void);
+char *redisGitDirty(void);
static void freeStringObject(robj *o);
static void freeListObject(robj *o);
static void renamenxCommand(redisClient *c);
static void lpushCommand(redisClient *c);
static void rpushCommand(redisClient *c);
+static void lpushxCommand(redisClient *c);
+static void rpushxCommand(redisClient *c);
+static void linsertCommand(redisClient *c);
static void lpopCommand(redisClient *c);
static void rpopCommand(redisClient *c);
static void llenCommand(redisClient *c);
{"mget",mgetCommand,-2,REDIS_CMD_INLINE,NULL,1,-1,1},
{"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
{"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
+ {"rpushx",rpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
+ {"lpushx",lpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
+ {"linsert",linsertCommand,5,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
{"rpop",rpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"lpop",lpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"brpop",brpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1},
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCompare, /* key compare */
- dictSdsDestructor, /* key destructor */
+ NULL, /* key destructor */
NULL /* val destructor */
};
shared.emptybulk = createObject(REDIS_STRING,sdsnew("$0\r\n\r\n"));
shared.czero = createObject(REDIS_STRING,sdsnew(":0\r\n"));
shared.cone = createObject(REDIS_STRING,sdsnew(":1\r\n"));
+ shared.cnegone = createObject(REDIS_STRING,sdsnew(":-1\r\n"));
shared.nullbulk = createObject(REDIS_STRING,sdsnew("$-1\r\n"));
shared.nullmultibulk = createObject(REDIS_STRING,sdsnew("*-1\r\n"));
shared.emptymultibulk = createObject(REDIS_STRING,sdsnew("*0\r\n"));
/* Delete a key, value, and associated expiration entry if any, from the DB */
static int dbDelete(redisDb *db, robj *key) {
- int retval;
-
- if (dictSize(db->expires)) dictDelete(db->expires,key->ptr);
- retval = dictDelete(db->dict,key->ptr);
-
- return retval == DICT_OK;
+ /* Deleting an entry from the expires dict will not free the sds of
+ * the key, because it is shared with the main dictionary. */
+ if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
+ return dictDelete(db->dict,key->ptr) == DICT_OK;
}
/*============================ RDB saving/loading =========================== */
while(hashlen--) {
robj *key, *val;
- if ((key = rdbLoadStringObject(fp)) == NULL) return NULL;
- if ((val = rdbLoadStringObject(fp)) == NULL) return NULL;
+ if ((key = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
+ if ((val = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
/* If we are using a zipmap and there are too big values
* the object is converted to real hash table encoding. */
if (o->encoding != REDIS_ENCODING_HT &&
- (sdslen(key->ptr) > server.hash_max_zipmap_value ||
- sdslen(val->ptr) > server.hash_max_zipmap_value))
+ ((key->encoding == REDIS_ENCODING_RAW &&
+ sdslen(key->ptr) > server.hash_max_zipmap_value) ||
+ (val->encoding == REDIS_ENCODING_RAW &&
+ sdslen(val->ptr) > server.hash_max_zipmap_value)))
{
convertToRealHash(o);
}
if (o->encoding == REDIS_ENCODING_ZIPMAP) {
unsigned char *zm = o->ptr;
+ robj *deckey, *decval;
- zm = zipmapSet(zm,key->ptr,sdslen(key->ptr),
- val->ptr,sdslen(val->ptr),NULL);
+ /* We need raw string objects to add them to the zipmap */
+ deckey = getDecodedObject(key);
+ decval = getDecodedObject(val);
+ zm = zipmapSet(zm,deckey->ptr,sdslen(deckey->ptr),
+ decval->ptr,sdslen(decval->ptr),NULL);
o->ptr = zm;
+ decrRefCount(deckey);
+ decrRefCount(decval);
decrRefCount(key);
decrRefCount(val);
} else {
/* Check if we need to convert the ziplist */
listTypeTryConversion(subject,value);
if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
- ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
+ ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LIST);
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
return value;
}
+static 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_LIST) {
+ 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");
+ }
+}
+
/* Compare the given object with the entry at the current position. */
static int listTypeEqual(listTypeEntry *entry, robj *o) {
listTypeIterator *li = entry->li;
pushGenericCommand(c,REDIS_TAIL);
}
+static 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_LIST);
+ server.dirty++;
+ } else {
+ /* Notify client of a failed insert */
+ addReply(c,shared.cnegone);
+ return;
+ }
+ } else {
+ listTypePush(subject,val,where);
+ server.dirty++;
+ }
+
+ addReplyUlong(c,listTypeLength(subject));
+}
+
+static void lpushxCommand(redisClient *c) {
+ pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD);
+}
+
+static void rpushxCommand(redisClient *c) {
+ pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL);
+}
+
+static void linsertCommand(redisClient *c) {
+ if (strcasecmp(c->argv[2]->ptr,"after") == 0) {
+ pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL);
+ } else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
+ pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_HEAD);
+ } else {
+ addReply(c,shared.syntaxerr);
+ }
+}
+
static void llenCommand(redisClient *c) {
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
"vm_enabled:%d\r\n"
"role:%s\r\n"
,REDIS_VERSION,
- REDIS_GIT_SHA1,
- strtol(REDIS_GIT_DIRTY,NULL,10) > 0,
+ redisGitSHA1(),
+ strtol(redisGitDirty(),NULL,10) > 0,
(sizeof(long) == 8) ? "64" : "32",
aeGetApiName(),
(long) getpid(),
/* ================================= Expire ================================= */
static int removeExpire(redisDb *db, robj *key) {
+ /* An expire may only be removed if there is a corresponding entry in the
+ * main dict. Otherwise, the key will never be freed. */
+ redisAssert(dictFind(db->dict,key->ptr) != NULL);
if (dictDelete(db->expires,key->ptr) == DICT_OK) {
return 1;
} else {
}
static int setExpire(redisDb *db, robj *key, time_t when) {
- sds copy = sdsdup(key->ptr);
- if (dictAdd(db->expires,copy,(void*)when) == DICT_ERR) {
- sdsfree(copy);
+ dictEntry *de;
+
+ /* Reuse the sds from the main dict in the expire dict */
+ redisAssert((de = dictFind(db->dict,key->ptr)) != NULL);
+ if (dictAdd(db->expires,dictGetEntryKey(de),(void*)when) == DICT_ERR) {
return 0;
} else {
return 1;
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);
}
static int expireIfNeeded(redisDb *db, robj *key) {
- time_t when;
- dictEntry *de;
-
- /* No expire? return ASAP */
- if (dictSize(db->expires) == 0 ||
- (de = dictFind(db->expires,key->ptr)) == NULL) return 0;
+ time_t when = getExpire(db,key);
+ if (when < 0) return 0;
- /* Lookup the expire */
- when = (time_t) dictGetEntryVal(de);
+ /* Return when this key has not expired */
if (time(NULL) <= when) return 0;
/* Delete the key */
- dbDelete(db,key);
server.stat_expiredkeys++;
- return 1;
+ server.dirty++;
+ return dbDelete(db,key);
}
static int deleteIfVolatile(redisDb *db, robj *key) {
- dictEntry *de;
-
- /* No expire? return ASAP */
- if (dictSize(db->expires) == 0 ||
- (de = dictFind(db->expires,key->ptr)) == NULL) return 0;
+ if (getExpire(db,key) < 0) return 0;
/* Delete the key */
- server.dirty++;
server.stat_expiredkeys++;
- dictDelete(db->expires,key->ptr);
- return dictDelete(db->dict,key->ptr) == DICT_OK;
+ server.dirty++;
+ return dbDelete(db,key);
}
static void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
freeClientMultiState(c);
initClientMultiState(c);
c->flags &= (~REDIS_MULTI);
+ unwatchAllKeys(c);
addReply(c,shared.ok);
}
static void version() {
printf("Redis server version %s (%s:%d)\n", REDIS_VERSION,
- REDIS_GIT_SHA1, atoi(REDIS_GIT_DIRTY) > 0);
+ redisGitSHA1(), atoi(redisGitDirty()) > 0);
exit(0);
}