Initial implementation of EXPIRE

This commit is contained in:
antirez 2009-04-01 13:22:35 +02:00
parent 300827b60d
commit 3305306f09
5 changed files with 252 additions and 196 deletions

View File

@ -6,7 +6,7 @@ DEBUG?= -g
CFLAGS?= -std=c99 -pedantic -O2 -Wall -W -DSDS_ABORT_ON_OOM CFLAGS?= -std=c99 -pedantic -O2 -Wall -W -DSDS_ABORT_ON_OOM
CCOPT= $(CFLAGS) CCOPT= $(CFLAGS)
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o fastlz.o
BENCHOBJ = ae.o anet.o benchmark.o sds.o adlist.o zmalloc.o BENCHOBJ = ae.o anet.o benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o

1
TODO
View File

@ -1,4 +1,5 @@
- GETSET - GETSET
- Fix pure-PHP lib for the new protocol
- keys expire - keys expire
- sunion ssub - sunion ssub
- network layer stresser in test in demo - network layer stresser in test in demo

4
dict.h
View File

@ -107,8 +107,8 @@ typedef struct dictIterator {
#define dictGetEntryKey(he) ((he)->key) #define dictGetEntryKey(he) ((he)->key)
#define dictGetEntryVal(he) ((he)->val) #define dictGetEntryVal(he) ((he)->val)
#define dictGetHashTableSize(ht) ((ht)->size) #define dictSlots(ht) ((ht)->size)
#define dictGetHashTableUsed(ht) ((ht)->used) #define dictSize(ht) ((ht)->used)
/* API */ /* API */
dict *dictCreate(dictType *type, void *privDataPtr); dict *dictCreate(dictType *type, void *privDataPtr);

View File

@ -102,6 +102,7 @@ static struct redisCommand cmdTable[] = {
{"sort",-2,REDIS_CMD_INLINE}, {"sort",-2,REDIS_CMD_INLINE},
{"info",1,REDIS_CMD_INLINE}, {"info",1,REDIS_CMD_INLINE},
{"mget",-2,REDIS_CMD_INLINE}, {"mget",-2,REDIS_CMD_INLINE},
{"expire",3,REDIS_CMD_INLINE},
{NULL,0,0} {NULL,0,0}
}; };

440
redis.c
View File

@ -158,11 +158,17 @@ typedef struct redisObject {
int refcount; int refcount;
} robj; } robj;
typedef struct redisDb {
dict *dict;
dict *expires;
int id;
} redisDb;
/* With multiplexing we need to take per-clinet state. /* With multiplexing we need to take per-clinet state.
* Clients are taken in a liked list. */ * Clients are taken in a liked list. */
typedef struct redisClient { typedef struct redisClient {
int fd; int fd;
dict *dict; redisDb *db;
int dictid; int dictid;
sds querybuf; sds querybuf;
robj *argv[REDIS_MAX_ARGS]; robj *argv[REDIS_MAX_ARGS];
@ -185,7 +191,7 @@ struct saveparam {
struct redisServer { struct redisServer {
int port; int port;
int fd; int fd;
dict **dict; redisDb *db;
dict *sharingpool; dict *sharingpool;
unsigned int sharingpoolsize; unsigned int sharingpoolsize;
long long dirty; /* changes to DB from the last save */ long long dirty; /* changes to DB from the last save */
@ -276,6 +282,9 @@ static robj *createStringObject(char *ptr, size_t len);
static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc); static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc);
static int syncWithMaster(void); static int syncWithMaster(void);
static robj *tryObjectSharing(robj *o); static robj *tryObjectSharing(robj *o);
static int removeExpire(redisDb *db, robj *key);
static int expireIfNeeded(redisDb *db, robj *key);
static int deleteIfVolatile(redisDb *db, robj *key);
static void authCommand(redisClient *c); static void authCommand(redisClient *c);
static void pingCommand(redisClient *c); static void pingCommand(redisClient *c);
@ -324,6 +333,7 @@ static void lremCommand(redisClient *c);
static void infoCommand(redisClient *c); static void infoCommand(redisClient *c);
static void mgetCommand(redisClient *c); static void mgetCommand(redisClient *c);
static void monitorCommand(redisClient *c); static void monitorCommand(redisClient *c);
static void expireCommand(redisClient *c);
/*================================= Globals ================================= */ /*================================= Globals ================================= */
@ -378,6 +388,7 @@ static struct redisCommand cmdTable[] = {
{"sort",sortCommand,-2,REDIS_CMD_INLINE}, {"sort",sortCommand,-2,REDIS_CMD_INLINE},
{"info",infoCommand,1,REDIS_CMD_INLINE}, {"info",infoCommand,1,REDIS_CMD_INLINE},
{"monitor",monitorCommand,1,REDIS_CMD_INLINE}, {"monitor",monitorCommand,1,REDIS_CMD_INLINE},
{"expire",expireCommand,3,REDIS_CMD_INLINE},
{NULL,NULL,0,0} {NULL,NULL,0,0}
}; };
@ -628,8 +639,8 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* If the percentage of used slots in the HT reaches REDIS_HT_MINFILL /* If the percentage of used slots in the HT reaches REDIS_HT_MINFILL
* we resize the hash table to save memory */ * we resize the hash table to save memory */
for (j = 0; j < server.dbnum; j++) { for (j = 0; j < server.dbnum; j++) {
size = dictGetHashTableSize(server.dict[j]); size = dictSlots(server.db[j].dict);
used = dictGetHashTableUsed(server.dict[j]); used = dictSize(server.db[j].dict);
if (!(loops % 5) && used > 0) { if (!(loops % 5) && used > 0) {
redisLog(REDIS_DEBUG,"DB %d: %d keys in %d slots HT.",j,used,size); redisLog(REDIS_DEBUG,"DB %d: %d keys in %d slots HT.",j,used,size);
/* dictPrintStats(server.dict); */ /* dictPrintStats(server.dict); */
@ -637,7 +648,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
if (size && used && size > REDIS_HT_MINSLOTS && if (size && used && size > REDIS_HT_MINSLOTS &&
(used*100/size < REDIS_HT_MINFILL)) { (used*100/size < REDIS_HT_MINFILL)) {
redisLog(REDIS_NOTICE,"The hash table %d is too sparse, resize it...",j); redisLog(REDIS_NOTICE,"The hash table %d is too sparse, resize it...",j);
dictResize(server.dict[j]); dictResize(server.db[j].dict);
redisLog(REDIS_NOTICE,"Hash table %d resized.",j); redisLog(REDIS_NOTICE,"Hash table %d resized.",j);
} }
} }
@ -648,7 +659,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
listLength(server.clients)-listLength(server.slaves), listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves), listLength(server.slaves),
server.usedmemory, server.usedmemory,
dictGetHashTableUsed(server.sharingpool)); dictSize(server.sharingpool));
} }
/* Close connections of timedout clients */ /* Close connections of timedout clients */
@ -787,18 +798,21 @@ static void initServer() {
server.objfreelist = listCreate(); server.objfreelist = listCreate();
createSharedObjects(); createSharedObjects();
server.el = aeCreateEventLoop(); server.el = aeCreateEventLoop();
server.dict = zmalloc(sizeof(dict*)*server.dbnum); server.db = zmalloc(sizeof(redisDb)*server.dbnum);
server.sharingpool = dictCreate(&setDictType,NULL); server.sharingpool = dictCreate(&setDictType,NULL);
server.sharingpoolsize = 1024; server.sharingpoolsize = 1024;
if (!server.dict || !server.clients || !server.slaves || !server.monitors || !server.el || !server.objfreelist) if (!server.db || !server.clients || !server.slaves || !server.monitors || !server.el || !server.objfreelist)
oom("server initialization"); /* Fatal OOM */ oom("server initialization"); /* Fatal OOM */
server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr); server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr);
if (server.fd == -1) { if (server.fd == -1) {
redisLog(REDIS_WARNING, "Opening TCP port: %s", server.neterr); redisLog(REDIS_WARNING, "Opening TCP port: %s", server.neterr);
exit(1); exit(1);
} }
for (j = 0; j < server.dbnum; j++) for (j = 0; j < server.dbnum; j++) {
server.dict[j] = dictCreate(&hashDictType,NULL); server.db[j].dict = dictCreate(&hashDictType,NULL);
server.db[j].expires = dictCreate(&setDictType,NULL);
server.db[j].id = j;
}
server.cronloops = 0; server.cronloops = 0;
server.bgsaveinprogress = 0; server.bgsaveinprogress = 0;
server.lastsave = time(NULL); server.lastsave = time(NULL);
@ -814,8 +828,10 @@ static void initServer() {
static void emptyDb() { static void emptyDb() {
int j; int j;
for (j = 0; j < server.dbnum; j++) for (j = 0; j < server.dbnum; j++) {
dictEmpty(server.dict[j]); dictEmpty(server.db[j].dict);
dictEmpty(server.db[j].expires);
}
} }
/* I agree, this is a very rudimental way to load a configuration... /* I agree, this is a very rudimental way to load a configuration...
@ -1150,9 +1166,9 @@ static int processCommand(redisClient *c) {
dirty = server.dirty; dirty = server.dirty;
cmd->proc(c); cmd->proc(c);
if (server.dirty-dirty != 0 && listLength(server.slaves)) if (server.dirty-dirty != 0 && listLength(server.slaves))
replicationFeedSlaves(server.slaves,cmd,c->dictid,c->argv,c->argc); replicationFeedSlaves(server.slaves,cmd,c->db->id,c->argv,c->argc);
if (listLength(server.monitors)) if (listLength(server.monitors))
replicationFeedSlaves(server.monitors,cmd,c->dictid,c->argv,c->argc); replicationFeedSlaves(server.monitors,cmd,c->db->id,c->argv,c->argc);
server.stat_numcommands++; server.stat_numcommands++;
/* Prepare the client for the next command */ /* Prepare the client for the next command */
@ -1310,8 +1326,7 @@ again:
static int selectDb(redisClient *c, int id) { static int selectDb(redisClient *c, int id) {
if (id < 0 || id >= server.dbnum) if (id < 0 || id >= server.dbnum)
return REDIS_ERR; return REDIS_ERR;
c->dict = server.dict[id]; c->db = &server.db[id];
c->dictid = id;
return REDIS_OK; return REDIS_OK;
} }
@ -1413,14 +1428,6 @@ static robj *createSetObject(void) {
return createObject(REDIS_SET,d); return createObject(REDIS_SET,d);
} }
#if 0
static robj *createHashObject(void) {
dict *d = dictCreate(&hashDictType,NULL);
if (!d) oom("dictCreate");
return createObject(REDIS_SET,d);
}
#endif
static void freeStringObject(robj *o) { static void freeStringObject(robj *o) {
sdsfree(o->ptr); sdsfree(o->ptr);
} }
@ -1462,7 +1469,7 @@ static robj *tryObjectSharing(robj *o) {
struct dictEntry *de; struct dictEntry *de;
unsigned long c; unsigned long c;
if (server.shareobjects == 0) return o; if (o == NULL || server.shareobjects == 0) return o;
assert(o->type == REDIS_STRING); assert(o->type == REDIS_STRING);
de = dictFind(server.sharingpool,o); de = dictFind(server.sharingpool,o);
@ -1479,7 +1486,7 @@ static robj *tryObjectSharing(robj *o) {
* shared we increment its count, everytime there is a miss we * shared we increment its count, everytime there is a miss we
* recrement the counter of a random object. If this object reaches * recrement the counter of a random object. If this object reaches
* zero we remove the object and put the current object instead. */ * zero we remove the object and put the current object instead. */
if (dictGetHashTableUsed(server.sharingpool) >= if (dictSize(server.sharingpool) >=
server.sharingpoolsize) { server.sharingpoolsize) {
de = dictGetRandomKey(server.sharingpool); de = dictGetRandomKey(server.sharingpool);
assert(de != NULL); assert(de != NULL);
@ -1502,6 +1509,26 @@ static robj *tryObjectSharing(robj *o) {
} }
} }
static robj *lookupKey(redisDb *db, robj *key) {
dictEntry *de = dictFind(db->dict,key);
return de ? dictGetEntryVal(de) : NULL;
}
static robj *lookupKeyRead(redisDb *db, robj *key) {
expireIfNeeded(db,key);
return lookupKey(db,key);
}
static robj *lookupKeyWrite(redisDb *db, robj *key) {
deleteIfVolatile(db,key);
return lookupKey(db,key);
}
static int deleteKey(redisDb *db, robj *key) {
if (dictSize(db->expires)) dictDelete(db->expires,key);
return dictDelete(db->dict,key) == DICT_OK;
}
/*============================ DB saving/loading ============================ */ /*============================ DB saving/loading ============================ */
static int rdbSaveType(FILE *fp, unsigned char type) { static int rdbSaveType(FILE *fp, unsigned char type) {
@ -1604,8 +1631,8 @@ static int rdbSave(char *filename) {
} }
if (fwrite("REDIS0001",9,1,fp) == 0) goto werr; if (fwrite("REDIS0001",9,1,fp) == 0) goto werr;
for (j = 0; j < server.dbnum; j++) { for (j = 0; j < server.dbnum; j++) {
dict *d = server.dict[j]; dict *d = server.db[j].dict;
if (dictGetHashTableUsed(d) == 0) continue; if (dictSize(d) == 0) continue;
di = dictGetIterator(d); di = dictGetIterator(d);
if (!di) { if (!di) {
fclose(fp); fclose(fp);
@ -1645,7 +1672,7 @@ static int rdbSave(char *filename) {
dictEntry *de; dictEntry *de;
if (!set) oom("dictGetIteraotr"); if (!set) oom("dictGetIteraotr");
if (rdbSaveLen(fp,dictGetHashTableUsed(set)) == -1) goto werr; if (rdbSaveLen(fp,dictSize(set)) == -1) goto werr;
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
robj *eleobj = dictGetEntryKey(de); robj *eleobj = dictGetEntryKey(de);
@ -1785,7 +1812,7 @@ static robj *rdbLoadStringObject(FILE*fp, int rdbver) {
case REDIS_RDB_ENC_INT8: case REDIS_RDB_ENC_INT8:
case REDIS_RDB_ENC_INT16: case REDIS_RDB_ENC_INT16:
case REDIS_RDB_ENC_INT32: case REDIS_RDB_ENC_INT32:
return rdbLoadIntegerObject(fp,len); return tryObjectSharing(rdbLoadIntegerObject(fp,len));
default: default:
assert(0!=0); assert(0!=0);
} }
@ -1806,7 +1833,7 @@ static int rdbLoad(char *filename) {
uint32_t dbid; uint32_t dbid;
int type; int type;
int retval; int retval;
dict *d = server.dict[0]; dict *d = server.db[0].dict;
char buf[1024]; char buf[1024];
int rdbver; int rdbver;
fp = fopen(filename,"r"); fp = fopen(filename,"r");
@ -1838,7 +1865,7 @@ static int rdbLoad(char *filename) {
redisLog(REDIS_WARNING,"FATAL: Data file was created with a Redis server configured to handle more than %d databases. Exiting\n", server.dbnum); 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); exit(1);
} }
d = server.dict[dbid]; d = server.db[dbid].dict;
continue; continue;
} }
/* Read key */ /* Read key */
@ -1916,10 +1943,10 @@ static void echoCommand(redisClient *c) {
static void setGenericCommand(redisClient *c, int nx) { static void setGenericCommand(redisClient *c, int nx) {
int retval; int retval;
retval = dictAdd(c->dict,c->argv[1],c->argv[2]); retval = dictAdd(c->db->dict,c->argv[1],c->argv[2]);
if (retval == DICT_ERR) { if (retval == DICT_ERR) {
if (!nx) { if (!nx) {
dictReplace(c->dict,c->argv[1],c->argv[2]); dictReplace(c->db->dict,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
} else { } else {
addReply(c,shared.czero); addReply(c,shared.czero);
@ -1930,6 +1957,7 @@ static void setGenericCommand(redisClient *c, int nx) {
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
} }
server.dirty++; server.dirty++;
removeExpire(c->db,c->argv[1]);
addReply(c, nx ? shared.cone : shared.ok); addReply(c, nx ? shared.cone : shared.ok);
} }
@ -1942,14 +1970,11 @@ static void setnxCommand(redisClient *c) {
} }
static void getCommand(redisClient *c) { static void getCommand(redisClient *c) {
dictEntry *de; robj *o = lookupKeyRead(c->db,c->argv[1]);
de = dictFind(c->dict,c->argv[1]); if (o == NULL) {
if (de == NULL) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_STRING) { if (o->type != REDIS_STRING) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -1961,17 +1986,14 @@ static void getCommand(redisClient *c) {
} }
static void mgetCommand(redisClient *c) { static void mgetCommand(redisClient *c) {
dictEntry *de;
int j; int j;
addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1)); addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1));
for (j = 1; j < c->argc; j++) { for (j = 1; j < c->argc; j++) {
de = dictFind(c->dict,c->argv[j]); robj *o = lookupKeyRead(c->db,c->argv[j]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_STRING) { if (o->type != REDIS_STRING) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
@ -1984,17 +2006,14 @@ static void mgetCommand(redisClient *c) {
} }
static void incrDecrCommand(redisClient *c, int incr) { static void incrDecrCommand(redisClient *c, int incr) {
dictEntry *de;
long long value; long long value;
int retval; int retval;
robj *o; robj *o;
de = dictFind(c->dict,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
value = 0; value = 0;
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_STRING) { if (o->type != REDIS_STRING) {
value = 0; value = 0;
} else { } else {
@ -2006,9 +2025,10 @@ static void incrDecrCommand(redisClient *c, int incr) {
value += incr; value += incr;
o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value)); o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value));
retval = dictAdd(c->dict,c->argv[1],o); retval = dictAdd(c->db->dict,c->argv[1],o);
if (retval == DICT_ERR) { if (retval == DICT_ERR) {
dictReplace(c->dict,c->argv[1],o); dictReplace(c->db->dict,c->argv[1],o);
removeExpire(c->db,c->argv[1]);
} else { } else {
incrRefCount(c->argv[1]); incrRefCount(c->argv[1]);
} }
@ -2039,7 +2059,7 @@ static void decrbyCommand(redisClient *c) {
/* ========================= Type agnostic commands ========================= */ /* ========================= Type agnostic commands ========================= */
static void delCommand(redisClient *c) { static void delCommand(redisClient *c) {
if (dictDelete(c->dict,c->argv[1]) == DICT_OK) { if (deleteKey(c->db,c->argv[1])) {
server.dirty++; server.dirty++;
addReply(c,shared.cone); addReply(c,shared.cone);
} else { } else {
@ -2048,13 +2068,7 @@ static void delCommand(redisClient *c) {
} }
static void existsCommand(redisClient *c) { static void existsCommand(redisClient *c) {
dictEntry *de; addReply(c,lookupKeyRead(c->db,c->argv[1]) ? shared.cone : shared.czero);
de = dictFind(c->dict,c->argv[1]);
if (de == NULL)
addReply(c,shared.czero);
else
addReply(c,shared.cone);
} }
static void selectCommand(redisClient *c) { static void selectCommand(redisClient *c) {
@ -2069,8 +2083,11 @@ static void selectCommand(redisClient *c) {
static void randomkeyCommand(redisClient *c) { static void randomkeyCommand(redisClient *c) {
dictEntry *de; dictEntry *de;
de = dictGetRandomKey(c->dict); while(1) {
de = dictGetRandomKey(c->db->dict);
if (expireIfNeeded(c->db,dictGetEntryKey(de)) == 0) break;
}
if (de == NULL) { if (de == NULL) {
addReply(c,shared.crlf); addReply(c,shared.crlf);
} else { } else {
@ -2088,20 +2105,23 @@ static void keysCommand(redisClient *c) {
int numkeys = 0, keyslen = 0; int numkeys = 0, keyslen = 0;
robj *lenobj = createObject(REDIS_STRING,NULL); robj *lenobj = createObject(REDIS_STRING,NULL);
di = dictGetIterator(c->dict); di = dictGetIterator(c->db->dict);
if (!di) oom("dictGetIterator"); if (!di) oom("dictGetIterator");
addReply(c,lenobj); addReply(c,lenobj);
decrRefCount(lenobj); decrRefCount(lenobj);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
robj *keyobj = dictGetEntryKey(de); robj *keyobj = dictGetEntryKey(de);
sds key = keyobj->ptr; sds key = keyobj->ptr;
if ((pattern[0] == '*' && pattern[1] == '\0') || if ((pattern[0] == '*' && pattern[1] == '\0') ||
stringmatchlen(pattern,plen,key,sdslen(key),0)) { stringmatchlen(pattern,plen,key,sdslen(key),0)) {
if (numkeys != 0) if (expireIfNeeded(c->db,keyobj) == 0) {
addReply(c,shared.space); if (numkeys != 0)
addReply(c,keyobj); addReply(c,shared.space);
numkeys++; addReply(c,keyobj);
keyslen += sdslen(key); numkeys++;
keyslen += sdslen(key);
}
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
@ -2111,7 +2131,7 @@ static void keysCommand(redisClient *c) {
static void dbsizeCommand(redisClient *c) { static void dbsizeCommand(redisClient *c) {
addReplySds(c, addReplySds(c,
sdscatprintf(sdsempty(),":%lu\r\n",dictGetHashTableUsed(c->dict))); sdscatprintf(sdsempty(),":%lu\r\n",dictSize(c->db->dict)));
} }
static void lastsaveCommand(redisClient *c) { static void lastsaveCommand(redisClient *c) {
@ -2120,15 +2140,13 @@ static void lastsaveCommand(redisClient *c) {
} }
static void typeCommand(redisClient *c) { static void typeCommand(redisClient *c) {
dictEntry *de; robj *o;
char *type; char *type;
de = dictFind(c->dict,c->argv[1]); o = lookupKeyRead(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
type = "+none"; type = "+none";
} else { } else {
robj *o = dictGetEntryVal(de);
switch(o->type) { switch(o->type) {
case REDIS_STRING: type = "+string"; break; case REDIS_STRING: type = "+string"; break;
case REDIS_LIST: type = "+list"; break; case REDIS_LIST: type = "+list"; break;
@ -2175,7 +2193,6 @@ static void shutdownCommand(redisClient *c) {
} }
static void renameGenericCommand(redisClient *c, int nx) { static void renameGenericCommand(redisClient *c, int nx) {
dictEntry *de;
robj *o; robj *o;
/* To use the same key as src and dst is probably an error */ /* To use the same key as src and dst is probably an error */
@ -2184,24 +2201,24 @@ static void renameGenericCommand(redisClient *c, int nx) {
return; return;
} }
de = dictFind(c->dict,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nokeyerr); addReply(c,shared.nokeyerr);
return; return;
} }
o = dictGetEntryVal(de);
incrRefCount(o); incrRefCount(o);
if (dictAdd(c->dict,c->argv[2],o) == DICT_ERR) { deleteIfVolatile(c->db,c->argv[2]);
if (dictAdd(c->db->dict,c->argv[2],o) == DICT_ERR) {
if (nx) { if (nx) {
decrRefCount(o); decrRefCount(o);
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} }
dictReplace(c->dict,c->argv[2],o); dictReplace(c->db->dict,c->argv[2],o);
} else { } else {
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
} }
dictDelete(c->dict,c->argv[1]); deleteKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
addReply(c,nx ? shared.cone : shared.ok); addReply(c,nx ? shared.cone : shared.ok);
} }
@ -2215,21 +2232,19 @@ static void renamenxCommand(redisClient *c) {
} }
static void moveCommand(redisClient *c) { static void moveCommand(redisClient *c) {
dictEntry *de; robj *o;
robj *o, *key; redisDb *src, *dst;
dict *src, *dst;
int srcid; int srcid;
/* Obtain source and target DB pointers */ /* Obtain source and target DB pointers */
src = c->dict; src = c->db;
srcid = c->dictid; srcid = c->db->id;
if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
addReply(c,shared.outofrangeerr); addReply(c,shared.outofrangeerr);
return; return;
} }
dst = c->dict; dst = c->db;
c->dict = src; selectDb(c,srcid); /* Back to the source DB */
c->dictid = srcid;
/* If the user is moving using as target the same /* If the user is moving using as target the same
* DB as the source DB it is probably an error. */ * DB as the source DB it is probably an error. */
@ -2239,24 +2254,23 @@ static void moveCommand(redisClient *c) {
} }
/* Check if the element exists and get a reference */ /* Check if the element exists and get a reference */
de = dictFind(c->dict,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
if (!de) { if (!o) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} }
/* Try to add the element to the target DB */ /* Try to add the element to the target DB */
key = dictGetEntryKey(de); deleteIfVolatile(dst,c->argv[1]);
o = dictGetEntryVal(de); if (dictAdd(dst->dict,c->argv[1],o) == DICT_ERR) {
if (dictAdd(dst,key,o) == DICT_ERR) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} }
incrRefCount(key); incrRefCount(c->argv[1]);
incrRefCount(o); incrRefCount(o);
/* OK! key moved, free the entry in the source DB */ /* OK! key moved, free the entry in the source DB */
dictDelete(src,c->argv[1]); deleteKey(src,c->argv[1]);
server.dirty++; server.dirty++;
addReply(c,shared.cone); addReply(c,shared.cone);
} }
@ -2264,11 +2278,10 @@ static void moveCommand(redisClient *c) {
/* =================================== Lists ================================ */ /* =================================== Lists ================================ */
static void pushGenericCommand(redisClient *c, int where) { static void pushGenericCommand(redisClient *c, int where) {
robj *lobj; robj *lobj;
dictEntry *de;
list *list; list *list;
de = dictFind(c->dict,c->argv[1]); lobj = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (lobj == NULL) {
lobj = createListObject(); lobj = createListObject();
list = lobj->ptr; list = lobj->ptr;
if (where == REDIS_HEAD) { if (where == REDIS_HEAD) {
@ -2276,11 +2289,10 @@ static void pushGenericCommand(redisClient *c, int where) {
} else { } else {
if (!listAddNodeTail(list,c->argv[2])) oom("listAddNodeTail"); if (!listAddNodeTail(list,c->argv[2])) oom("listAddNodeTail");
} }
dictAdd(c->dict,c->argv[1],lobj); dictAdd(c->db->dict,c->argv[1],lobj);
incrRefCount(c->argv[1]); incrRefCount(c->argv[1]);
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
} else { } else {
lobj = dictGetEntryVal(de);
if (lobj->type != REDIS_LIST) { if (lobj->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
return; return;
@ -2306,15 +2318,14 @@ static void rpushCommand(redisClient *c) {
} }
static void llenCommand(redisClient *c) { static void llenCommand(redisClient *c) {
dictEntry *de; robj *o;
list *l; list *l;
de = dictFind(c->dict,c->argv[1]); o = lookupKeyRead(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -2325,15 +2336,13 @@ static void llenCommand(redisClient *c) {
} }
static void lindexCommand(redisClient *c) { static void lindexCommand(redisClient *c) {
dictEntry *de; robj *o;
int index = atoi(c->argv[2]->ptr); int index = atoi(c->argv[2]->ptr);
de = dictFind(c->dict,c->argv[1]); o = lookupKeyRead(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -2354,15 +2363,13 @@ static void lindexCommand(redisClient *c) {
} }
static void lsetCommand(redisClient *c) { static void lsetCommand(redisClient *c) {
dictEntry *de; robj *o;
int index = atoi(c->argv[2]->ptr); int index = atoi(c->argv[2]->ptr);
de = dictFind(c->dict,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nokeyerr); addReply(c,shared.nokeyerr);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -2386,14 +2393,12 @@ static void lsetCommand(redisClient *c) {
} }
static void popGenericCommand(redisClient *c, int where) { static void popGenericCommand(redisClient *c, int where) {
dictEntry *de; robj *o;
de = dictFind(c->dict,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -2428,16 +2433,14 @@ static void rpopCommand(redisClient *c) {
} }
static void lrangeCommand(redisClient *c) { static void lrangeCommand(redisClient *c) {
dictEntry *de; robj *o;
int start = atoi(c->argv[2]->ptr); int start = atoi(c->argv[2]->ptr);
int end = atoi(c->argv[3]->ptr); int end = atoi(c->argv[3]->ptr);
de = dictFind(c->dict,c->argv[1]); o = lookupKeyRead(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nullmultibulk); addReply(c,shared.nullmultibulk);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -2477,16 +2480,14 @@ static void lrangeCommand(redisClient *c) {
} }
static void ltrimCommand(redisClient *c) { static void ltrimCommand(redisClient *c) {
dictEntry *de; robj *o;
int start = atoi(c->argv[2]->ptr); int start = atoi(c->argv[2]->ptr);
int end = atoi(c->argv[3]->ptr); int end = atoi(c->argv[3]->ptr);
de = dictFind(c->dict,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nokeyerr); addReply(c,shared.nokeyerr);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -2528,14 +2529,12 @@ static void ltrimCommand(redisClient *c) {
} }
static void lremCommand(redisClient *c) { static void lremCommand(redisClient *c) {
dictEntry *de; robj *o;
de = dictFind(c->dict,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.nokeyerr); addReply(c,shared.nokeyerr);
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_LIST) { if (o->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
@ -2570,16 +2569,14 @@ static void lremCommand(redisClient *c) {
/* ==================================== Sets ================================ */ /* ==================================== Sets ================================ */
static void saddCommand(redisClient *c) { static void saddCommand(redisClient *c) {
dictEntry *de;
robj *set; robj *set;
de = dictFind(c->dict,c->argv[1]); set = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (set == NULL) {
set = createSetObject(); set = createSetObject();
dictAdd(c->dict,c->argv[1],set); dictAdd(c->db->dict,c->argv[1],set);
incrRefCount(c->argv[1]); incrRefCount(c->argv[1]);
} else { } else {
set = dictGetEntryVal(de);
if (set->type != REDIS_SET) { if (set->type != REDIS_SET) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
return; return;
@ -2595,15 +2592,12 @@ static void saddCommand(redisClient *c) {
} }
static void sremCommand(redisClient *c) { static void sremCommand(redisClient *c) {
dictEntry *de; robj *set;
de = dictFind(c->dict,c->argv[1]); set = lookupKeyWrite(c->db,c->argv[1]);
if (de == NULL) { if (set == NULL) {
addReply(c,shared.czero); addReply(c,shared.czero);
} else { } else {
robj *set;
set = dictGetEntryVal(de);
if (set->type != REDIS_SET) { if (set->type != REDIS_SET) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
return; return;
@ -2618,15 +2612,12 @@ static void sremCommand(redisClient *c) {
} }
static void sismemberCommand(redisClient *c) { static void sismemberCommand(redisClient *c) {
dictEntry *de; robj *set;
de = dictFind(c->dict,c->argv[1]); set = lookupKeyRead(c->db,c->argv[1]);
if (de == NULL) { if (set == NULL) {
addReply(c,shared.czero); addReply(c,shared.czero);
} else { } else {
robj *set;
set = dictGetEntryVal(de);
if (set->type != REDIS_SET) { if (set->type != REDIS_SET) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
return; return;
@ -2639,21 +2630,20 @@ static void sismemberCommand(redisClient *c) {
} }
static void scardCommand(redisClient *c) { static void scardCommand(redisClient *c) {
dictEntry *de; robj *o;
dict *s; dict *s;
de = dictFind(c->dict,c->argv[1]); o = lookupKeyRead(c->db,c->argv[1]);
if (de == NULL) { if (o == NULL) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
} else { } else {
robj *o = dictGetEntryVal(de);
if (o->type != REDIS_SET) { if (o->type != REDIS_SET) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
} else { } else {
s = o->ptr; s = o->ptr;
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n", addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",
dictGetHashTableUsed(s))); dictSize(s)));
} }
} }
} }
@ -2661,7 +2651,7 @@ static void scardCommand(redisClient *c) {
static int qsortCompareSetsByCardinality(const void *s1, const void *s2) { static int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
dict **d1 = (void*) s1, **d2 = (void*) s2; dict **d1 = (void*) s1, **d2 = (void*) s2;
return dictGetHashTableUsed(*d1)-dictGetHashTableUsed(*d2); return dictSize(*d1)-dictSize(*d2);
} }
static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj *dstkey) { static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj *dstkey) {
@ -2674,15 +2664,15 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r
if (!dv) oom("sinterCommand"); if (!dv) oom("sinterCommand");
for (j = 0; j < setsnum; j++) { for (j = 0; j < setsnum; j++) {
robj *setobj; robj *setobj;
dictEntry *de;
setobj = dstkey ?
de = dictFind(c->dict,setskeys[j]); lookupKeyWrite(c->db,setskeys[j]) :
if (!de) { lookupKeyRead(c->db,setskeys[j]);
if (!setobj) {
zfree(dv); zfree(dv);
addReply(c,shared.nokeyerr); addReply(c,shared.nokeyerr);
return; return;
} }
setobj = dictGetEntryVal(de);
if (setobj->type != REDIS_SET) { if (setobj->type != REDIS_SET) {
zfree(dv); zfree(dv);
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
@ -2707,8 +2697,8 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, int setsnum, r
/* If we have a target key where to store the resulting set /* If we have a target key where to store the resulting set
* create this key with an empty set inside */ * create this key with an empty set inside */
dstset = createSetObject(); dstset = createSetObject();
dictDelete(c->dict,dstkey); deleteKey(c->db,dstkey);
dictAdd(c->dict,dstkey,dstset); dictAdd(c->db->dict,dstkey,dstset);
incrRefCount(dstkey); incrRefCount(dstkey);
} }
@ -2754,7 +2744,8 @@ static void sinterstoreCommand(redisClient *c) {
} }
static void flushdbCommand(redisClient *c) { static void flushdbCommand(redisClient *c) {
dictEmpty(c->dict); dictEmpty(c->db->dict);
dictEmpty(c->db->expires);
addReply(c,shared.ok); addReply(c,shared.ok);
rdbSave(server.dbfilename); rdbSave(server.dbfilename);
} }
@ -2775,12 +2766,11 @@ redisSortOperation *createSortOperation(int type, robj *pattern) {
/* Return the value associated to the key with a name obtained /* Return the value associated to the key with a name obtained
* substituting the first occurence of '*' in 'pattern' with 'subst' */ * substituting the first occurence of '*' in 'pattern' with 'subst' */
robj *lookupKeyByPattern(dict *dict, robj *pattern, robj *subst) { robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
char *p; char *p;
sds spat, ssub; sds spat, ssub;
robj keyobj; robj keyobj;
int prefixlen, sublen, postfixlen; int prefixlen, sublen, postfixlen;
dictEntry *de;
/* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */ /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */
struct { struct {
long len; long len;
@ -2788,7 +2778,6 @@ robj *lookupKeyByPattern(dict *dict, robj *pattern, robj *subst) {
char buf[REDIS_SORTKEY_MAX+1]; char buf[REDIS_SORTKEY_MAX+1];
} keyname; } keyname;
spat = pattern->ptr; spat = pattern->ptr;
ssub = subst->ptr; ssub = subst->ptr;
if (sdslen(spat)+sdslen(ssub)-1 > REDIS_SORTKEY_MAX) return NULL; if (sdslen(spat)+sdslen(ssub)-1 > REDIS_SORTKEY_MAX) return NULL;
@ -2808,10 +2797,8 @@ robj *lookupKeyByPattern(dict *dict, robj *pattern, robj *subst) {
keyobj.type = REDIS_STRING; keyobj.type = REDIS_STRING;
keyobj.ptr = ((char*)&keyname)+(sizeof(long)*2); keyobj.ptr = ((char*)&keyname)+(sizeof(long)*2);
de = dictFind(dict,&keyobj);
/* printf("lookup '%s' => %p\n", keyname.buf,de); */ /* printf("lookup '%s' => %p\n", keyname.buf,de); */
if (!de) return NULL; return lookupKeyRead(db,&keyobj);
return dictGetEntryVal(de);
} }
/* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with
@ -2856,7 +2843,6 @@ static int sortCompare(const void *s1, const void *s2) {
/* The SORT command is the most complex command in Redis. Warning: this code /* The SORT command is the most complex command in Redis. Warning: this code
* is optimized for speed and a bit less for readability */ * is optimized for speed and a bit less for readability */
static void sortCommand(redisClient *c) { static void sortCommand(redisClient *c) {
dictEntry *de;
list *operations; list *operations;
int outputlen = 0; int outputlen = 0;
int desc = 0, alpha = 0; int desc = 0, alpha = 0;
@ -2867,12 +2853,11 @@ static void sortCommand(redisClient *c) {
redisSortObject *vector; /* Resulting vector to sort */ redisSortObject *vector; /* Resulting vector to sort */
/* Lookup the key to sort. It must be of the right types */ /* Lookup the key to sort. It must be of the right types */
de = dictFind(c->dict,c->argv[1]); sortval = lookupKeyRead(c->db,c->argv[1]);
if (de == NULL) { if (sortval == NULL) {
addReply(c,shared.nokeyerr); addReply(c,shared.nokeyerr);
return; return;
} }
sortval = dictGetEntryVal(de);
if (sortval->type != REDIS_SET && sortval->type != REDIS_LIST) { if (sortval->type != REDIS_SET && sortval->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr); addReply(c,shared.wrongtypeerr);
return; return;
@ -2937,7 +2922,7 @@ static void sortCommand(redisClient *c) {
/* Load the sorting vector with all the objects to sort */ /* Load the sorting vector with all the objects to sort */
vectorlen = (sortval->type == REDIS_LIST) ? vectorlen = (sortval->type == REDIS_LIST) ?
listLength((list*)sortval->ptr) : listLength((list*)sortval->ptr) :
dictGetHashTableUsed((dict*)sortval->ptr); dictSize((dict*)sortval->ptr);
vector = zmalloc(sizeof(redisSortObject)*vectorlen); vector = zmalloc(sizeof(redisSortObject)*vectorlen);
if (!vector) oom("allocating objects vector for SORT"); if (!vector) oom("allocating objects vector for SORT");
j = 0; j = 0;
@ -2975,7 +2960,7 @@ static void sortCommand(redisClient *c) {
if (sortby) { if (sortby) {
robj *byval; robj *byval;
byval = lookupKeyByPattern(c->dict,sortby,vector[j].obj); byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
if (!byval || byval->type != REDIS_STRING) continue; if (!byval || byval->type != REDIS_STRING) continue;
if (alpha) { if (alpha) {
vector[j].u.cmpobj = byval; vector[j].u.cmpobj = byval;
@ -3020,7 +3005,7 @@ static void sortCommand(redisClient *c) {
} }
while(ln) { while(ln) {
redisSortOperation *sop = ln->value; redisSortOperation *sop = ln->value;
robj *val = lookupKeyByPattern(c->dict,sop->pattern, robj *val = lookupKeyByPattern(c->db,sop->pattern,
vector[j].obj); vector[j].obj);
if (sop->type == REDIS_SORT_GET) { if (sop->type == REDIS_SORT_GET) {
@ -3080,6 +3065,85 @@ static void infoCommand(redisClient *c) {
addReply(c,shared.crlf); addReply(c,shared.crlf);
} }
static void monitorCommand(redisClient *c) {
/* ignore MONITOR if aleady slave or in monitor mode */
if (c->flags & REDIS_SLAVE) return;
c->flags |= (REDIS_SLAVE|REDIS_MONITOR);
c->slaveseldb = 0;
if (!listAddNodeTail(server.monitors,c)) oom("listAddNodeTail");
addReply(c,shared.ok);
}
/* ================================= Expire ================================= */
static int removeExpire(redisDb *db, robj *key) {
if (dictDelete(db->expires,key) == DICT_OK) {
return 1;
} else {
return 0;
}
}
static int setExpire(redisDb *db, robj *key, time_t when) {
if (dictAdd(db->expires,key,(void*)when) == DICT_ERR) {
return 0;
} else {
incrRefCount(key);
return 1;
}
}
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)) == NULL) return 0;
/* Lookup the expire */
when = (time_t) dictGetEntryVal(de);
if (time(NULL) <= when) return 0;
/* Delete the key */
dictDelete(db->expires,key);
return dictDelete(db->dict,key) == DICT_OK;
}
static int deleteIfVolatile(redisDb *db, robj *key) {
dictEntry *de;
/* No expire? return ASAP */
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires,key)) == NULL) return 0;
/* Delete the key */
dictDelete(db->expires,key);
return dictDelete(db->dict,key) == DICT_OK;
}
static void expireCommand(redisClient *c) {
dictEntry *de;
int seconds = atoi(c->argv[2]->ptr);
de = dictFind(c->db->dict,c->argv[1]);
if (de == NULL) {
addReply(c,shared.czero);
return;
}
if (seconds <= 0) {
addReply(c, shared.czero);
return;
} else {
time_t when = time(NULL)+seconds;
if (setExpire(c->db,c->argv[1],when))
addReply(c,shared.cone);
else
addReply(c,shared.czero);
return;
}
}
/* =============================== Replication ============================= */ /* =============================== Replication ============================= */
/* Send the whole output buffer syncronously to the slave. This a general operation in theory, but it is actually useful only for replication. */ /* Send the whole output buffer syncronously to the slave. This a general operation in theory, but it is actually useful only for replication. */
@ -3280,16 +3344,6 @@ static int syncWithMaster(void) {
return REDIS_OK; return REDIS_OK;
} }
static void monitorCommand(redisClient *c) {
/* ignore MONITOR if aleady slave or in monitor mode */
if (c->flags & REDIS_SLAVE) return;
c->flags |= (REDIS_SLAVE|REDIS_MONITOR);
c->slaveseldb = 0;
if (!listAddNodeTail(server.monitors,c)) oom("listAddNodeTail");
addReply(c,shared.ok);
}
/* =================================== Main! ================================ */ /* =================================== Main! ================================ */
static void daemonize(void) { static void daemonize(void) {