X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/3ea27d37d1e6884a9fd0640f25b937fb9fe06aed..55017f9da0c8ee8709029c4dbdbf4f1d4db4a181:/redis.c diff --git a/redis.c b/redis.c index 6f38b610..1bdd1421 100644 --- a/redis.c +++ b/redis.c @@ -27,7 +27,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define REDIS_VERSION "1.3.7" +#define REDIS_VERSION "1.3.8" #include "fmacros.h" #include "config.h" @@ -86,12 +86,12 @@ #define REDIS_MAXIDLETIME (60*5) /* default client timeout */ #define REDIS_IOBUF_LEN 1024 #define REDIS_LOADBUF_LEN 1024 -#define REDIS_STATIC_ARGS 4 +#define REDIS_STATIC_ARGS 8 #define REDIS_DEFAULT_DBNUM 16 #define REDIS_CONFIGLINE_MAX 1024 #define REDIS_OBJFREELIST_MAX 1000000 /* Max number of objects to cache */ #define REDIS_MAX_SYNC_TIME 60 /* Slave can't take more to sync */ -#define REDIS_EXPIRELOOKUPS_PER_CRON 100 /* try to expire 100 keys/second */ +#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */ #define REDIS_MAX_WRITE_PER_EVENT (1024*64) #define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */ @@ -111,6 +111,7 @@ config file and the server is using more than maxmemory bytes of memory. In short this commands are denied on low memory conditions. */ #define REDIS_CMD_DENYOOM 4 +#define REDIS_CMD_FORCE_REPLICATION 8 /* Force replication even if dirty is 0 */ /* Object types */ #define REDIS_STRING 0 @@ -327,6 +328,8 @@ typedef struct redisClient { * is >= blockingto then the operation timed out. */ list *io_keys; /* Keys this client is waiting to be loaded from the * swap file in order to continue. */ + dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */ + list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */ } redisClient; struct saveparam { @@ -339,8 +342,6 @@ struct redisServer { int port; int fd; redisDb *db; - dict *sharingpool; /* Poll used for object sharing */ - unsigned int sharingpoolsize; long long dirty; /* changes to DB from the last save */ list *clients; list *slaves, *monitors; @@ -353,6 +354,7 @@ struct redisServer { time_t stat_starttime; /* server start time */ long long stat_numcommands; /* number of processed commands */ long long stat_numconnections; /* number of connections received */ + long long stat_expiredkeys; /* number of expired keys */ /* Configuration */ int verbosity; int glueoutputbuf; @@ -377,6 +379,7 @@ struct redisServer { char *requirepass; int shareobjects; int rdbcompression; + int activerehashing; /* Replication related */ int isslave; char *masterauth; @@ -434,9 +437,18 @@ struct redisServer { unsigned long long vm_stats_swapped_objects; unsigned long long vm_stats_swapouts; unsigned long long vm_stats_swapins; + /* Pubsub */ + dict *pubsub_channels; /* Map channels to list of subscribed clients */ + list *pubsub_patterns; /* A list of pubsub_patterns */ + /* Misc */ FILE *devnull; }; +typedef struct pubsubPattern { + redisClient *client; + robj *pattern; +} pubsubPattern; + typedef void redisCommandProc(redisClient *c); struct redisCommand { char *name; @@ -494,13 +506,16 @@ typedef struct zset { /* Our shared "common" objects */ +#define REDIS_SHARED_INTEGERS 10000 struct sharedObjectsStruct { robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space, *colon, *nullbulk, *nullmultibulk, *queued, *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *plus, *select0, *select1, *select2, *select3, *select4, - *select5, *select6, *select7, *select8, *select9; + *select5, *select6, *select7, *select8, *select9, + *messagebulk, *subscribebulk, *unsubscribebulk, *mbulk3, + *psubscribebulk, *punsubscribebulk, *integers[REDIS_SHARED_INTEGERS]; } shared; /* Global vars that are actally used as constants. The following double @@ -520,7 +535,7 @@ typedef struct iojob { robj *val; /* the value to swap for REDIS_IOREQ_*_SWAP, otherwise this * field is populated by the I/O thread for REDIS_IOREQ_LOAD. */ off_t page; /* Swap page where to read/write the object */ - off_t pages; /* Swap pages needed to safe object. PREPARE_SWAP return val */ + off_t pages; /* Swap pages needed to save object. PREPARE_SWAP return val */ int canceled; /* True if this command was canceled by blocking side of VM */ pthread_t thread; /* ID of the thread processing this entry */ } iojob; @@ -540,11 +555,10 @@ static void incrRefCount(robj *o); static int rdbSaveBackground(char *filename); static robj *createStringObject(char *ptr, size_t len); static robj *dupStringObject(robj *o); -static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc); +static void replicationFeedSlaves(list *slaves, 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); +static robj *tryObjectEncoding(robj *o); static robj *getDecodedObject(robj *o); static int removeExpire(redisDb *db, robj *key); static int expireIfNeeded(redisDb *db, robj *key); @@ -600,6 +614,12 @@ static struct redisCommand *lookupCommand(char *name); static void call(redisClient *c, struct redisCommand *cmd); static void resetClient(redisClient *c); static void convertToRealHash(robj *o); +static int pubsubUnsubscribeAllChannels(redisClient *c, int notify); +static int pubsubUnsubscribeAllPatterns(redisClient *c, int notify); +static void freePubsubPattern(void *p); +static int listMatchPubsubPattern(void *a, void *b); +static int compareStringObjects(robj *a, robj *b); +static void usage(); static void authCommand(redisClient *c); static void pingCommand(redisClient *c); @@ -685,7 +705,10 @@ static void substrCommand(redisClient *c); static void zrankCommand(redisClient *c); static void zrevrankCommand(redisClient *c); static void hsetCommand(redisClient *c); +static void hsetnxCommand(redisClient *c); static void hgetCommand(redisClient *c); +static void hmsetCommand(redisClient *c); +static void hmgetCommand(redisClient *c); static void hdelCommand(redisClient *c); static void hlenCommand(redisClient *c); static void zremrangebyrankCommand(redisClient *c); @@ -695,6 +718,13 @@ static void hkeysCommand(redisClient *c); static void hvalsCommand(redisClient *c); static void hgetallCommand(redisClient *c); static void hexistsCommand(redisClient *c); +static void configCommand(redisClient *c); +static void hincrbyCommand(redisClient *c); +static void subscribeCommand(redisClient *c); +static void unsubscribeCommand(redisClient *c); +static void psubscribeCommand(redisClient *c); +static void punsubscribeCommand(redisClient *c); +static void publishCommand(redisClient *c); /*================================= Globals ================================= */ @@ -754,7 +784,11 @@ static struct redisCommand cmdTable[] = { {"zrank",zrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, {"zrevrank",zrevrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, {"hset",hsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hsetnx",hsetnxCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, {"hget",hgetCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, + {"hmset",hmsetCommand,-4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hmget",hmgetCommand,-3,REDIS_CMD_BULK,NULL,1,1,1}, + {"hincrby",hincrbyCommand,4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, {"hdel",hdelCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, {"hlen",hlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, {"hkeys",hkeysCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, @@ -785,7 +819,7 @@ static struct redisCommand cmdTable[] = { {"lastsave",lastsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, {"type",typeCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, {"multi",multiCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"exec",execCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"exec",execCommand,1,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,0,0,0}, {"discard",discardCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, {"sync",syncCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, @@ -796,15 +830,19 @@ static struct redisCommand cmdTable[] = { {"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, {"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, {"debug",debugCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0}, + {"subscribe",subscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"unsubscribe",unsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"psubscribe",psubscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, + {"punsubscribe",punsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, + {"publish",publishCommand,3,REDIS_CMD_BULK|REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0}, {NULL,NULL,0,0,NULL,0,0,0} }; -static void usage(); - /*============================ Utility functions ============================ */ /* Glob-style pattern matching. */ -int stringmatchlen(const char *pattern, int patternLen, +static int stringmatchlen(const char *pattern, int patternLen, const char *string, int stringLen, int nocase) { while(patternLen) { @@ -926,6 +964,10 @@ int stringmatchlen(const char *pattern, int patternLen, return 0; } +static int stringmatch(const char *pattern, const char *string, int nocase) { + return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase); +} + static void redisLog(int level, const char *fmt, ...) { va_list ap; FILE *fp; @@ -1132,7 +1174,9 @@ static void closeTimedoutClients(void) { if (server.maxidletime && !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ !(c->flags & REDIS_MASTER) && /* no timeout for masters */ - (now - c->lastinteraction > server.maxidletime)) + dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */ + listLength(c->pubsub_patterns) == 0 && + (now - c->lastinteraction > server.maxidletime)) { redisLog(REDIS_VERBOSE,"Closing idle client"); freeClient(c); @@ -1160,16 +1204,28 @@ static void tryResizeHashTables(void) { int j; for (j = 0; j < server.dbnum; j++) { - if (htNeedsResize(server.db[j].dict)) { - redisLog(REDIS_VERBOSE,"The hash table %d is too sparse, resize it...",j); + if (htNeedsResize(server.db[j].dict)) dictResize(server.db[j].dict); - redisLog(REDIS_VERBOSE,"Hash table %d resized.",j); - } if (htNeedsResize(server.db[j].expires)) dictResize(server.db[j].expires); } } +/* Our hash table implementation performs rehashing incrementally while + * we write/read from the hash table. Still if the server is idle, the hash + * table will use two tables for a long time. So we try to use 1 millisecond + * of CPU time at every serverCron() loop in order to rehash some key. */ +static void incrementallyRehash(void) { + int j; + + for (j = 0; j < server.dbnum; j++) { + if (dictIsRehashing(server.db[j].dict)) { + dictRehashMilliseconds(server.db[j].dict,1); + break; /* already used our millisecond for this loop... */ + } + } +} + /* A background saving child (BGSAVE) terminated its work. Handle this. */ void backgroundSaveDoneHandler(int statloc) { int exitcode = WEXITSTATUS(statloc); @@ -1184,7 +1240,7 @@ void backgroundSaveDoneHandler(int statloc) { redisLog(REDIS_WARNING, "Background saving error"); } else { redisLog(REDIS_WARNING, - "Background saving terminated by signal"); + "Background saving terminated by signal %d", WTERMSIG(statloc)); rdbRemoveTempFile(server.bgsavechildpid); } server.bgsavechildpid = -1; @@ -1245,7 +1301,8 @@ void backgroundRewriteDoneHandler(int statloc) { redisLog(REDIS_WARNING, "Background append only file rewriting error"); } else { redisLog(REDIS_WARNING, - "Background append only file rewriting terminated by signal"); + "Background append only file rewriting terminated by signal %d", + WTERMSIG(statloc)); } cleanup: sdsfree(server.bgrewritebuf); @@ -1254,6 +1311,19 @@ cleanup: server.bgrewritechildpid = -1; } +/* This function is called once a background process of some kind terminates, + * as we want to avoid resizing the hash tables when there is a child in order + * to play well with copy-on-write (otherwise when a resize happens lots of + * memory pages are copied). The goal of this function is to update the ability + * for dict.c to resize the hash tables accordingly to the fact we have o not + * running childs. */ +static void updateDictResizePolicy(void) { + if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1) + dictEnableResize(); + else + dictDisableResize(); +} + static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { int j, loops = server.cronloops++; REDIS_NOTUSED(eventLoop); @@ -1273,7 +1343,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD size = dictSlots(server.db[j].dict); used = dictSize(server.db[j].dict); vkeys = dictSize(server.db[j].expires); - if (!(loops % 5) && (used || vkeys)) { + if (!(loops % 50) && (used || vkeys)) { redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size); /* dictPrintStats(server.dict); */ } @@ -1285,19 +1355,21 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD * if we resize the HT while there is the saving child at work actually * a lot of memory movements in the parent will cause a lot of pages * copied. */ - if (server.bgsavechildpid == -1) tryResizeHashTables(); + if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1) { + if (!(loops % 10)) tryResizeHashTables(); + if (server.activerehashing) incrementallyRehash(); + } /* Show information about connected clients */ - if (!(loops % 5)) { - redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use, %d shared objects", + if (!(loops % 50)) { + redisLog(REDIS_VERBOSE,"%d clients connected (%d slaves), %zu bytes in use", listLength(server.clients)-listLength(server.slaves), listLength(server.slaves), - zmalloc_used_memory(), - dictSize(server.sharingpool)); + zmalloc_used_memory()); } /* Close connections of timedout clients */ - if ((server.maxidletime && !(loops % 10)) || server.blpop_blocked_clients) + if ((server.maxidletime && !(loops % 100)) || server.blpop_blocked_clients) closeTimedoutClients(); /* Check if a background saving or AOF rewrite in progress terminated */ @@ -1311,6 +1383,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD } else { backgroundRewriteDoneHandler(statloc); } + updateDictResizePolicy(); } } else { /* If there is not a background saving in progress check if @@ -1355,6 +1428,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD if (now > t) { deleteKey(db,dictGetEntryKey(de)); expired++; + server.stat_expiredkeys++; } } } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4); @@ -1372,7 +1446,7 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD retval = (server.vm_max_threads == 0) ? vmSwapOneObjectBlocking() : vmSwapOneObjectThreaded(); - if (retval == REDIS_ERR && (loops % 30) == 0 && + if (retval == REDIS_ERR && !(loops % 300) && zmalloc_used_memory() > (server.vm_max_memory+server.vm_max_memory/10)) { @@ -1387,13 +1461,13 @@ static int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientD } /* Check if we should connect to a MASTER */ - if (server.replstate == REDIS_REPL_CONNECT) { + if (server.replstate == REDIS_REPL_CONNECT && !(loops % 10)) { redisLog(REDIS_NOTICE,"Connecting to MASTER..."); if (syncWithMaster() == REDIS_OK) { redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync succeeded"); } } - return 1000; + return 100; } /* This function gets called every time Redis is entering the @@ -1429,6 +1503,8 @@ static void beforeSleep(struct aeEventLoop *eventLoop) { } static void createSharedObjects(void) { + int j; + shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n")); shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n")); shared.err = createObject(REDIS_STRING,sdsnew("-ERR\r\n")); @@ -1463,6 +1539,16 @@ static void createSharedObjects(void) { shared.select7 = createStringObject("select 7\r\n",10); shared.select8 = createStringObject("select 8\r\n",10); shared.select9 = createStringObject("select 9\r\n",10); + shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13); + shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15); + shared.unsubscribebulk = createStringObject("$11\r\nunsubscribe\r\n",18); + shared.psubscribebulk = createStringObject("$10\r\npsubscribe\r\n",17); + shared.punsubscribebulk = createStringObject("$12\r\npunsubscribe\r\n",19); + shared.mbulk3 = createStringObject("*3\r\n",4); + for (j = 0; j < REDIS_SHARED_INTEGERS; j++) { + shared.integers[j] = createObject(REDIS_STRING,(void*)(long)j); + shared.integers[j]->encoding = REDIS_ENCODING_INT; + } } static void appendServerSaveParams(time_t seconds, int changes) { @@ -1493,13 +1579,13 @@ static void initServerConfig() { server.lastfsync = time(NULL); 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.aof"; + server.pidfile = zstrdup("/var/run/redis.pid"); + server.dbfilename = zstrdup("dump.rdb"); + server.appendfilename = zstrdup("appendonly.aof"); server.requirepass = NULL; server.shareobjects = 0; server.rdbcompression = 1; - server.sharingpoolsize = 1024; + server.activerehashing = 1; server.maxclients = 0; server.blpop_blocked_clients = 0; server.maxmemory = 0; @@ -1552,7 +1638,6 @@ static void initServer() { createSharedObjects(); server.el = aeCreateEventLoop(); server.db = zmalloc(sizeof(redisDb)*server.dbnum); - server.sharingpool = dictCreate(&setDictType,NULL); server.fd = anetTcpServer(server.neterr, server.port, server.bindaddr); if (server.fd == -1) { redisLog(REDIS_WARNING, "Opening TCP port: %s", server.neterr); @@ -1566,6 +1651,10 @@ static void initServer() { server.db[j].io_keys = dictCreate(&keylistDictType,NULL); server.db[j].id = j; } + server.pubsub_channels = dictCreate(&keylistDictType,NULL); + server.pubsub_patterns = listCreate(); + listSetFreeMethod(server.pubsub_patterns,freePubsubPattern); + listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern); server.cronloops = 0; server.bgsavechildpid = -1; server.bgrewritechildpid = -1; @@ -1574,6 +1663,7 @@ static void initServer() { server.dirty = 0; server.stat_numcommands = 0; server.stat_numconnections = 0; + server.stat_expiredkeys = 0; server.stat_starttime = time(NULL); server.unixtime = time(NULL); aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); @@ -1618,15 +1708,12 @@ static void loadServerConfig(char *filename) { char buf[REDIS_CONFIGLINE_MAX+1], *err = NULL; int linenum = 0; sds line = NULL; - char *errormsg = "Fatal error, can't open config file '%s'"; - char *errorbuf = zmalloc(sizeof(char)*(strlen(errormsg)+strlen(filename))); - sprintf(errorbuf, errormsg, filename); if (filename[0] == '-' && filename[1] == '\0') fp = stdin; else { if ((fp = fopen(filename,"r")) == NULL) { - redisLog(REDIS_WARNING, errorbuf); + redisLog(REDIS_WARNING, "Fatal error, can't open config file '%s'", filename); exit(1); } } @@ -1732,10 +1819,9 @@ static void loadServerConfig(char *filename) { if ((server.rdbcompression = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; } - } else if (!strcasecmp(argv[0],"shareobjectspoolsize") && argc == 2) { - server.sharingpoolsize = atoi(argv[1]); - if (server.sharingpoolsize < 1) { - err = "invalid object sharing pool size"; goto loaderr; + } else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) { + if ((server.activerehashing = yesnotoi(argv[1])) == -1) { + err = "argument must be 'yes' or 'no'"; goto loaderr; } } else if (!strcasecmp(argv[0],"daemonize") && argc == 2) { if ((server.daemonize = yesnotoi(argv[1])) == -1) { @@ -1759,8 +1845,10 @@ static void loadServerConfig(char *filename) { } else if (!strcasecmp(argv[0],"requirepass") && argc == 2) { server.requirepass = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"pidfile") && argc == 2) { + zfree(server.pidfile); server.pidfile = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"dbfilename") && argc == 2) { + zfree(server.dbfilename); server.dbfilename = zstrdup(argv[1]); } else if (!strcasecmp(argv[0],"vm-enabled") && argc == 2) { if ((server.vm_enabled = yesnotoi(argv[1])) == -1) { @@ -1826,6 +1914,12 @@ static void freeClient(redisClient *c) { if (c->flags & REDIS_BLOCKED) unblockClientWaitingData(c); + /* Unsubscribe from all the pubsub channels */ + pubsubUnsubscribeAllChannels(c,0); + pubsubUnsubscribeAllPatterns(c,0); + dictRelease(c->pubsub_channels); + listRelease(c->pubsub_patterns); + /* Obvious cleanup */ aeDeleteFileEvent(server.el,c->fd,AE_READABLE); aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); listRelease(c->reply); @@ -1848,7 +1942,7 @@ static void freeClient(redisClient *c) { dontWaitForSwappedKey(c,ln->value); } listRelease(c->io_keys); - /* Other cleanup */ + /* Master/slave cleanup */ if (c->flags & REDIS_SLAVE) { if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) close(c->repldbfd); @@ -1861,6 +1955,7 @@ static void freeClient(redisClient *c) { server.master = NULL; server.replstate = REDIS_REPL_CONNECT; } + /* Release memory */ zfree(c->argv); zfree(c->mbargv); freeClientMultiState(c); @@ -1904,7 +1999,7 @@ static void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) /* Use writev() if we have enough buffers to send */ if (!server.glueoutputbuf && - listLength(c->reply) > REDIS_WRITEV_THRESHOLD && + listLength(c->reply) > REDIS_WRITEV_THRESHOLD && !(c->flags & REDIS_MASTER)) { sendReplyToClientWritev(el, fd, privdata, mask); @@ -1982,7 +2077,7 @@ static void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int o = listNodeValue(node); objlen = sdslen(o->ptr); - if (totwritten + objlen - offset > REDIS_MAX_WRITE_PER_EVENT) + if (totwritten + objlen - offset > REDIS_MAX_WRITE_PER_EVENT) break; if(ion == REDIS_WRITEV_IOVEC_COUNT) @@ -2030,7 +2125,7 @@ static void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int } } - if (totwritten > 0) + if (totwritten > 0) c->lastinteraction = time(NULL); if (listLength(c->reply) == 0) { @@ -2061,12 +2156,15 @@ static void call(redisClient *c, struct redisCommand *cmd) { dirty = server.dirty; cmd->proc(c); - if (server.appendonly && server.dirty-dirty) + dirty = server.dirty-dirty; + + if (server.appendonly && dirty) feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc); - if (server.dirty-dirty && listLength(server.slaves)) - replicationFeedSlaves(server.slaves,cmd,c->db->id,c->argv,c->argc); + if ((dirty || cmd->flags & REDIS_CMD_FORCE_REPLICATION) && + listLength(server.slaves)) + replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc); if (listLength(server.monitors)) - replicationFeedSlaves(server.monitors,cmd,c->db->id,c->argv,c->argc); + replicationFeedSlaves(server.monitors,c->db->id,c->argv,c->argc); server.stat_numcommands++; } @@ -2176,10 +2274,6 @@ static int processCommand(redisClient *c) { cmd->name)); resetClient(c); return 1; - } else if (server.maxmemory && cmd->flags & REDIS_CMD_DENYOOM && zmalloc_used_memory() > server.maxmemory) { - addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n")); - resetClient(c); - return 1; } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { /* This is a bulk command, we have to read the last argument yet. */ int bulklen = atoi(c->argv[c->argc-1]->ptr); @@ -2208,15 +2302,9 @@ static int processCommand(redisClient *c) { return 1; } } - /* Let's try to share objects on the command arguments vector */ - if (server.shareobjects) { - int j; - for(j = 1; j < c->argc; j++) - c->argv[j] = tryObjectSharing(c->argv[j]); - } /* Let's try to encode the bulk object to save space. */ if (cmd->flags & REDIS_CMD_BULK) - tryObjectEncoding(c->argv[c->argc-1]); + c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); /* Check if the user is authenticated */ if (server.requirepass && !c->authenticated && cmd->proc != authCommand) { @@ -2225,6 +2313,25 @@ static int processCommand(redisClient *c) { return 1; } + /* Handle the maxmemory directive */ + if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) && + zmalloc_used_memory() > server.maxmemory) + { + addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n")); + resetClient(c); + return 1; + } + + /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */ + if ((dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns) > 0) + && + cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand && + cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) { + addReplySds(c,sdsnew("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context\r\n")); + resetClient(c); + return 1; + } + /* Exec the command */ if (c->flags & REDIS_MULTI && cmd->proc != execCommand && cmd->proc != discardCommand) { queueMultiCommand(c,cmd); @@ -2240,34 +2347,36 @@ static int processCommand(redisClient *c) { return 1; } -static void replicationFeedSlaves(list *slaves, struct redisCommand *cmd, int dictid, robj **argv, int argc) { +static void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { listNode *ln; listIter li; int outc = 0, j; robj **outv; - /* (args*2)+1 is enough room for args, spaces, newlines */ - robj *static_outv[REDIS_STATIC_ARGS*2+1]; + /* We need 1+(ARGS*3) objects since commands are using the new protocol + * and we one 1 object for the first "*\r\n" multibulk count, then + * for every additional object we have "$\r\n" + object + "\r\n". */ + robj *static_outv[REDIS_STATIC_ARGS*3+1]; + robj *lenobj; if (argc <= REDIS_STATIC_ARGS) { outv = static_outv; } else { - outv = zmalloc(sizeof(robj*)*(argc*2+1)); + outv = zmalloc(sizeof(robj*)*(argc*3+1)); } - - for (j = 0; j < argc; j++) { - if (j != 0) outv[outc++] = shared.space; - if ((cmd->flags & REDIS_CMD_BULK) && j == argc-1) { - robj *lenobj; - lenobj = createObject(REDIS_STRING, - sdscatprintf(sdsempty(),"%lu\r\n", - (unsigned long) stringObjectLen(argv[j]))); - lenobj->refcount = 0; - outv[outc++] = lenobj; - } + lenobj = createObject(REDIS_STRING, + sdscatprintf(sdsempty(), "*%d\r\n", argc)); + lenobj->refcount = 0; + outv[outc++] = lenobj; + for (j = 0; j < argc; j++) { + lenobj = createObject(REDIS_STRING, + sdscatprintf(sdsempty(),"$%lu\r\n", + (unsigned long) stringObjectLen(argv[j]))); + lenobj->refcount = 0; + outv[outc++] = lenobj; outv[outc++] = argv[j]; + outv[outc++] = shared.crlf; } - outv[outc++] = shared.crlf; /* Increment all the refcounts at start and decrement at end in order to * be sure to free objects if there is no slave in a replication state @@ -2327,7 +2436,7 @@ again: if (p) { sds query, *argv; int argc, j; - + query = c->querybuf; c->querybuf = sdsempty(); querylen = 1+(p-(query)); @@ -2419,8 +2528,7 @@ static void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mas } else { return; } - if (!(c->flags & REDIS_BLOCKED)) - processInputBuffer(c); + processInputBuffer(c); } static int selectDb(redisClient *c, int id) { @@ -2435,6 +2543,10 @@ static void *dupClientReplyValue(void *o) { return o; } +static int listMatchObjects(void *a, void *b) { + return compareStringObjects(a,b) == 0; +} + static redisClient *createClient(int fd) { redisClient *c = zmalloc(sizeof(*c)); @@ -2462,6 +2574,10 @@ static redisClient *createClient(int fd) { c->blockingkeysnum = 0; c->io_keys = listCreate(); listSetFreeMethod(c->io_keys,decrRefCount); + c->pubsub_channels = dictCreate(&setDictType,NULL); + c->pubsub_patterns = listCreate(); + listSetFreeMethod(c->pubsub_patterns,decrRefCount); + listSetMatchMethod(c->pubsub_patterns,listMatchObjects); if (aeCreateFileEvent(server.el, c->fd, AE_READABLE, readQueryFromClient, c) == AE_ERR) { freeClient(c); @@ -2515,6 +2631,21 @@ static void addReplyLong(redisClient *c, long l) { addReplySds(c,sdsnewlen(buf,len)); } +static void addReplyLongLong(redisClient *c, long long ll) { + char buf[128]; + size_t len; + + if (ll == 0) { + addReply(c,shared.czero); + return; + } else if (ll == 1) { + addReply(c,shared.cone); + return; + } + len = snprintf(buf,sizeof(buf),":%lld\r\n",ll); + addReplySds(c,sdsnewlen(buf,len)); +} + static void addReplyUlong(redisClient *c, unsigned long ul) { char buf[128]; size_t len; @@ -2557,6 +2688,17 @@ static void addReplyBulk(redisClient *c, robj *obj) { addReply(c,shared.crlf); } +/* In the CONFIG command we need to add vanilla C string as bulk replies */ +static void addReplyBulkCString(redisClient *c, char *s) { + if (s == NULL) { + addReply(c,shared.nullbulk); + } else { + robj *o = createStringObject(s,strlen(s)); + addReplyBulk(c,o); + decrRefCount(o); + } +} + static void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { int cport, cfd; char cip[128]; @@ -2703,7 +2845,6 @@ static void freeHashObject(robj *o) { } static void incrRefCount(robj *o) { - redisAssert(!server.vm_enabled || o->storage == REDIS_VM_MEMORY); o->refcount++; } @@ -2715,9 +2856,6 @@ static void decrRefCount(void *obj) { if (server.vm_enabled && (o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING)) { - if (o->storage == REDIS_VM_SWAPPED || o->storage == REDIS_VM_LOADING) { - redisAssert(o->refcount == 1); - } if (o->storage == REDIS_VM_LOADING) vmCancelThreadedIOJob(obj); redisAssert(o->type == REDIS_STRING); freeStringObject(o); @@ -2830,51 +2968,6 @@ static int deleteKey(redisDb *db, robj *key) { return retval == DICT_OK; } -/* Try to share an object against the shared objects pool */ -static robj *tryObjectSharing(robj *o) { - struct dictEntry *de; - unsigned long c; - - if (o == NULL || server.shareobjects == 0) return o; - - redisAssert(o->type == REDIS_STRING); - de = dictFind(server.sharingpool,o); - if (de) { - robj *shared = dictGetEntryKey(de); - - c = ((unsigned long) dictGetEntryVal(de))+1; - dictGetEntryVal(de) = (void*) c; - incrRefCount(shared); - decrRefCount(o); - return shared; - } else { - /* Here we are using a stream algorihtm: Every time an object is - * shared we increment its count, everytime there is a miss we - * recrement the counter of a random object. If this object reaches - * zero we remove the object and put the current object instead. */ - if (dictSize(server.sharingpool) >= - server.sharingpoolsize) { - de = dictGetRandomKey(server.sharingpool); - redisAssert(de != NULL); - c = ((unsigned long) dictGetEntryVal(de))-1; - dictGetEntryVal(de) = (void*) c; - if (c == 0) { - dictDelete(server.sharingpool,de->key); - } - } else { - c = 0; /* If the pool is empty we want to add this object */ - } - if (c == 0) { - int retval; - - retval = dictAdd(server.sharingpool,o,(void*)1); - redisAssert(retval == DICT_OK); - incrRefCount(o); - } - return o; - } -} - /* Check if the nul-terminated string 's' can be represented by a long * (that is, is a number that fits into long without any other space or * character before or after the digits). @@ -2885,7 +2978,7 @@ static int isStringRepresentableAsLong(sds s, long *longval) { char buf[32], *endptr; long value; int slen; - + value = strtol(s, &endptr, 10); if (endptr[0] != '\0') return REDIS_ERR; slen = snprintf(buf,32,"%ld",value); @@ -2898,36 +2991,42 @@ static int isStringRepresentableAsLong(sds s, long *longval) { } /* Try to encode a string object in order to save space */ -static int tryObjectEncoding(robj *o) { +static robj *tryObjectEncoding(robj *o) { long value; sds s = o->ptr; if (o->encoding != REDIS_ENCODING_RAW) - return REDIS_ERR; /* Already encoded */ + return o; /* Already encoded */ - /* It's not save to encode shared objects: shared objects can be shared + /* It's not safe to encode shared objects: shared objects can be shared * everywhere in the "object space" of Redis. Encoded objects can only * appear as "values" (and not, for instance, as keys) */ - if (o->refcount > 1) return REDIS_ERR; + if (o->refcount > 1) return o; /* Currently we try to encode only strings */ redisAssert(o->type == REDIS_STRING); /* Check if we can represent this string as a long integer */ - if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return REDIS_ERR; + if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o; /* Ok, this object can be encoded */ - o->encoding = REDIS_ENCODING_INT; - sdsfree(o->ptr); - o->ptr = (void*) value; - return REDIS_OK; + if (value >= 0 && value < REDIS_SHARED_INTEGERS) { + decrRefCount(o); + incrRefCount(shared.integers[value]); + return shared.integers[value]; + } else { + o->encoding = REDIS_ENCODING_INT; + sdsfree(o->ptr); + o->ptr = (void*) value; + return o; + } } /* Get a decoded version of an encoded object (returned as a new object). * If the object is already raw-encoded just increment the ref count. */ static robj *getDecodedObject(robj *o) { robj *dec; - + if (o->encoding == REDIS_ENCODING_RAW) { incrRefCount(o); return o; @@ -2985,6 +3084,77 @@ static size_t stringObjectLen(robj *o) { } } +static int getDoubleFromObject(redisClient *c, robj *o, double *value) { + double parsedValue; + char *eptr = NULL; + + if (o && o->type != REDIS_STRING) { + addReplySds(c,sdsnew("-ERR value is not a double\r\n")); + return REDIS_ERR; + } + + if (o == NULL) + parsedValue = 0; + else if (o->encoding == REDIS_ENCODING_RAW) + parsedValue = strtod(o->ptr, &eptr); + else if (o->encoding == REDIS_ENCODING_INT) + parsedValue = (long)o->ptr; + else + redisAssert(1 != 1); + + if (eptr != NULL && *eptr != '\0') { + addReplySds(c,sdsnew("-ERR value is not a double\r\n")); + return REDIS_ERR; + } + + *value = parsedValue; + + return REDIS_OK; +} + +static int getLongLongFromObject(redisClient *c, robj *o, long long *value) { + long long parsedValue; + char *eptr = NULL; + + if (o && o->type != REDIS_STRING) { + addReplySds(c,sdsnew("-ERR value is not an integer\r\n")); + return REDIS_ERR; + } + + if (o == NULL) + parsedValue = 0; + else if (o->encoding == REDIS_ENCODING_RAW) + parsedValue = strtoll(o->ptr, &eptr, 10); + else if (o->encoding == REDIS_ENCODING_INT) + parsedValue = (long)o->ptr; + else + redisAssert(1 != 1); + + if (eptr != NULL && *eptr != '\0') { + addReplySds(c,sdsnew("-ERR value is not an integer\r\n")); + return REDIS_ERR; + } + + *value = parsedValue; + + return REDIS_OK; +} + +static int getLongFromObject(redisClient *c, robj *o, long *value) { + long long actualValue; + + if (getLongLongFromObject(c, o, &actualValue) != REDIS_OK) return REDIS_ERR; + + if (actualValue < LONG_MIN || actualValue > LONG_MAX) { + addReplySds(c,sdsnew("-ERR value is out of range\r\n")); + return REDIS_ERR; + } + + *value = actualValue; + + return REDIS_OK; +} + /*============================ RDB saving/loading =========================== */ static int rdbSaveType(FILE *fp, unsigned char type) { @@ -3257,7 +3427,7 @@ static off_t rdbSavedObjectLen(robj *o, FILE *fp) { /* Return the number of pages required to save this object in the swap file */ static off_t rdbSavedObjectPages(robj *o, FILE *fp) { off_t bytes = rdbSavedObjectLen(o,fp); - + return (bytes+(server.vm_page_size-1))/server.vm_page_size; } @@ -3340,7 +3510,7 @@ static int rdbSave(char *filename) { fflush(fp); fsync(fileno(fp)); fclose(fp); - + /* Use RENAME to make sure the DB file is changed atomically only * if the generate DB file is ok. */ if (rename(tmpfile,filename) == -1) { @@ -3384,6 +3554,7 @@ static int rdbSaveBackground(char *filename) { } redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid); server.bgsavechildpid = childpid; + updateDictResizePolicy(); return REDIS_OK; } return REDIS_OK; /* unreached */ @@ -3493,9 +3664,9 @@ static robj *rdbLoadStringObject(FILE*fp) { case REDIS_RDB_ENC_INT8: case REDIS_RDB_ENC_INT16: case REDIS_RDB_ENC_INT32: - return tryObjectSharing(rdbLoadIntegerObject(fp,len)); + return rdbLoadIntegerObject(fp,len); case REDIS_RDB_ENC_LZF: - return tryObjectSharing(rdbLoadLzfStringObject(fp)); + return rdbLoadLzfStringObject(fp); default: redisAssert(0); } @@ -3507,7 +3678,7 @@ static robj *rdbLoadStringObject(FILE*fp) { sdsfree(val); return NULL; } - return tryObjectSharing(createObject(REDIS_STRING,val)); + return createObject(REDIS_STRING,val); } /* For information about double serialization check rdbSaveDoubleValue() */ @@ -3537,7 +3708,7 @@ static robj *rdbLoadObject(int type, FILE *fp) { if (type == REDIS_STRING) { /* Read string value */ if ((o = rdbLoadStringObject(fp)) == NULL) return NULL; - tryObjectEncoding(o); + o = tryObjectEncoding(o); } else if (type == REDIS_LIST || type == REDIS_SET) { /* Read list/set value */ uint32_t listlen; @@ -3553,7 +3724,7 @@ static robj *rdbLoadObject(int type, FILE *fp) { robj *ele; if ((ele = rdbLoadStringObject(fp)) == NULL) return NULL; - tryObjectEncoding(ele); + ele = tryObjectEncoding(ele); if (type == REDIS_LIST) { listAddNodeTail((list*)o->ptr,ele); } else { @@ -3574,7 +3745,7 @@ static robj *rdbLoadObject(int type, FILE *fp) { double *score = zmalloc(sizeof(double)); if ((ele = rdbLoadStringObject(fp)) == NULL) return NULL; - tryObjectEncoding(ele); + ele = tryObjectEncoding(ele); if (rdbLoadDoubleValue(fp,score) == -1) return NULL; dictAdd(zs->dict,ele,score); zslInsert(zs->zsl,*score,ele); @@ -3613,8 +3784,8 @@ static robj *rdbLoadObject(int type, FILE *fp) { decrRefCount(key); decrRefCount(val); } else { - tryObjectEncoding(key); - tryObjectEncoding(val); + key = tryObjectEncoding(key); + val = tryObjectEncoding(val); dictAdd((dict*)o->ptr,key,val); } } @@ -3769,7 +3940,7 @@ static void setnxCommand(redisClient *c) { static int getGenericCommand(redisClient *c) { robj *o; - + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) return REDIS_OK; @@ -3800,7 +3971,7 @@ static void getsetCommand(redisClient *c) { static void mgetCommand(redisClient *c) { int j; - + addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1)); for (j = 1; j < c->argc; j++) { robj *o = lookupKeyRead(c->db,c->argv[j]); @@ -3840,7 +4011,7 @@ static void msetGenericCommand(redisClient *c, int nx) { for (j = 1; j < c->argc; j += 2) { int retval; - tryObjectEncoding(c->argv[j+1]); + c->argv[j+1] = tryObjectEncoding(c->argv[j+1]); retval = dictAdd(c->db->dict,c->argv[j],c->argv[j+1]); if (retval == DICT_ERR) { dictReplace(c->db->dict,c->argv[j],c->argv[j+1]); @@ -3867,28 +4038,14 @@ static void incrDecrCommand(redisClient *c, long long incr) { long long value; int retval; robj *o; - + o = lookupKeyWrite(c->db,c->argv[1]); - if (o == NULL) { - value = 0; - } else { - if (o->type != REDIS_STRING) { - value = 0; - } else { - char *eptr; - if (o->encoding == REDIS_ENCODING_RAW) - value = strtoll(o->ptr, &eptr, 10); - else if (o->encoding == REDIS_ENCODING_INT) - value = (long)o->ptr; - else - redisAssert(1 != 1); - } - } + if (getLongLongFromObject(c, o, &value) != REDIS_OK) return; value += incr; o = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value)); - tryObjectEncoding(o); + o = tryObjectEncoding(o); retval = dictAdd(c->db->dict,c->argv[1],o); if (retval == DICT_ERR) { dictReplace(c->db->dict,c->argv[1],o); @@ -3911,12 +4068,18 @@ static void decrCommand(redisClient *c) { } static void incrbyCommand(redisClient *c) { - long long incr = strtoll(c->argv[2]->ptr, NULL, 10); + long long incr; + + if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return; + incrDecrCommand(c,incr); } static void decrbyCommand(redisClient *c) { - long long incr = strtoll(c->argv[2]->ptr, NULL, 10); + long long incr; + + if (getLongLongFromObject(c, c->argv[2], &incr) != REDIS_OK) return; + incrDecrCommand(c,-incr); } @@ -3934,7 +4097,7 @@ static void appendCommand(redisClient *c) { totlen = stringObjectLen(c->argv[2]); } else { dictEntry *de; - + de = dictFind(c->db->dict,c->argv[1]); assert(de != NULL); @@ -4023,7 +4186,7 @@ static void existsCommand(redisClient *c) { static void selectCommand(redisClient *c) { int id = atoi(c->argv[1]->ptr); - + if (selectDb(c,id) == REDIS_ERR) { addReplySds(c,sdsnew("-ERR invalid DB index\r\n")); } else { @@ -4033,7 +4196,7 @@ static void selectCommand(redisClient *c) { static void randomkeyCommand(redisClient *c) { dictEntry *de; - + while(1) { de = dictGetRandomKey(c->db->dict); if (!de || expireIfNeeded(c->db,dictGetEntryKey(de)) == 0) break; @@ -4161,7 +4324,7 @@ static void shutdownCommand(redisClient *c) { * in the next cron() Redis will be notified that the background * saving aborted, handling special stuff like slaves pending for * synchronization... */ - redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit"); + redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit"); addReplySds(c, sdsnew("-ERR can't quit, problems saving the DB\r\n")); } @@ -4305,7 +4468,7 @@ static void llenCommand(redisClient *c) { if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_LIST)) return; - + l = o->ptr; addReplyUlong(c,listLength(l)); } @@ -4396,8 +4559,8 @@ static void lrangeCommand(redisClient *c) { listNode *ln; robj *ele; - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL || - checkType(c,o,REDIS_LIST)) return; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL + || checkType(c,o,REDIS_LIST)) return; list = o->ptr; llen = listLength(list); @@ -4661,7 +4824,7 @@ static void scardCommand(redisClient *c) { if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_SET)) return; - + s = o->ptr; addReplyUlong(c,dictSize(s)); } @@ -4730,7 +4893,7 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long server.dirty++; addReply(c,shared.czero); } else { - addReply(c,shared.nullmultibulk); + addReply(c,shared.emptymultibulk); } return; } @@ -4790,17 +4953,14 @@ static void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long if (dictSize((dict*)dstset->ptr) > 0) { dictAdd(c->db->dict,dstkey,dstset); incrRefCount(dstkey); + addReplyLong(c,dictSize((dict*)dstset->ptr)); } else { decrRefCount(dstset); + addReply(c,shared.czero); } - } - - if (!dstkey) { - lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",cardinality); - } else { - addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n", - dictSize((dict*)dstset->ptr))); server.dirty++; + } else { + lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",cardinality); } zfree(dv); } @@ -4873,7 +5033,8 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu } dictReleaseIterator(di); - if (op == REDIS_OP_DIFF && cardinality == 0) break; /* result set is empty */ + /* result set is empty? Exit asap. */ + if (op == REDIS_OP_DIFF && cardinality == 0) break; } /* Output the content of the resulting set, if not in STORE mode */ @@ -4887,6 +5048,7 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu addReplyBulk(c,ele); } dictReleaseIterator(di); + decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ @@ -4894,17 +5056,11 @@ static void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnu if (dictSize((dict*)dstset->ptr) > 0) { dictAdd(c->db->dict,dstkey,dstset); incrRefCount(dstkey); + addReplyLong(c,dictSize((dict*)dstset->ptr)); } else { decrRefCount(dstset); + addReply(c,shared.czero); } - } - - /* Cleanup */ - if (!dstkey) { - decrRefCount(dstset); - } else { - addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n", - dictSize((dict*)dstset->ptr))); server.dirty++; } zfree(dv); @@ -4959,7 +5115,7 @@ static zskiplistNode *zslCreateNode(int level, double score, robj *obj) { static zskiplist *zslCreate(void) { int j; zskiplist *zsl; - + zsl = zmalloc(sizeof(*zsl)); zsl->level = 1; zsl->length = 0; @@ -5001,7 +5157,7 @@ static int zslRandomLevel(void) { int level = 1; while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF)) level += 1; - return level; + return (leveldict,ele); redisAssert(de != NULL); @@ -5321,14 +5477,16 @@ static void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double scor static void zaddCommand(redisClient *c) { double scoreval; - scoreval = strtod(c->argv[2]->ptr,NULL); + if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return; + zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0); } static void zincrbyCommand(redisClient *c) { double scoreval; - scoreval = strtod(c->argv[2]->ptr,NULL); + if (getDoubleFromObject(c, c->argv[2], &scoreval) != REDIS_OK) return; + zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1); } @@ -5362,12 +5520,15 @@ 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); + double min; + double max; long deleted; robj *zsetobj; zset *zs; + if ((getDoubleFromObject(c, c->argv[2], &min) != REDIS_OK) || + (getDoubleFromObject(c, c->argv[3], &max) != REDIS_OK)) return; + if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,zsetobj,REDIS_ZSET)) return; @@ -5380,13 +5541,16 @@ static void zremrangebyscoreCommand(redisClient *c) { } static void zremrangebyrankCommand(redisClient *c) { - int start = atoi(c->argv[2]->ptr); - int end = atoi(c->argv[3]->ptr); + long start; + long end; int llen; long deleted; robj *zsetobj; zset *zs; + if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) || + (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return; + if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,zsetobj,REDIS_ZSET)) return; zs = zsetobj->ptr; @@ -5493,7 +5657,8 @@ static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { if (remaining >= (zsetnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) { j++; remaining--; for (i = 0; i < zsetnum; i++, j++, remaining--) { - src[i].weight = strtod(c->argv[j]->ptr, NULL); + if (getDoubleFromObject(c, c->argv[j], &src[i].weight) != REDIS_OK) + return; } } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) { j++; remaining--; @@ -5599,7 +5764,7 @@ static void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) { addReplyLong(c, dstzset->zsl->length); server.dirty++; } else { - decrRefCount(dstzset); + decrRefCount(dstobj); addReply(c, shared.czero); } zfree(src); @@ -5615,8 +5780,8 @@ static void zinterCommand(redisClient *c) { static void zrangeGenericCommand(redisClient *c, int reverse) { robj *o; - int start = atoi(c->argv[2]->ptr); - int end = atoi(c->argv[3]->ptr); + long start; + long end; int withscores = 0; int llen; int rangelen, j; @@ -5625,6 +5790,9 @@ static void zrangeGenericCommand(redisClient *c, int reverse) { zskiplistNode *ln; robj *ele; + if ((getLongFromObject(c, c->argv[2], &start) != REDIS_OK) || + (getLongFromObject(c, c->argv[3], &end) != REDIS_OK)) return; + if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) { withscores = 1; } else if (c->argc >= 5) { @@ -5632,8 +5800,8 @@ static void zrangeGenericCommand(redisClient *c, int reverse) { return; } - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL || - checkType(c,o,REDIS_ZSET)) return; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL + || checkType(c,o,REDIS_ZSET)) return; zsetobj = o->ptr; zsl = zsetobj->zsl; llen = zsl->length; @@ -5739,7 +5907,7 @@ static void genericZrangebyscoreCommand(redisClient *c, int justcount) { /* Ok, lookup the key and get the range */ o = lookupKeyRead(c->db,c->argv[1]); if (o == NULL) { - addReply(c,justcount ? shared.czero : shared.nullmultibulk); + addReply(c,justcount ? shared.czero : shared.emptymultibulk); } else { if (o->type != REDIS_ZSET) { addReply(c,shared.wrongtypeerr); @@ -5876,225 +6044,378 @@ static void zrevrankCommand(redisClient *c) { zrankGenericCommand(c, 1); } -/* =================================== Hashes =============================== */ -static void hsetCommand(redisClient *c) { - int update = 0; - robj *o = lookupKeyWrite(c->db,c->argv[1]); +/* ========================= Hashes utility functions ======================= */ +#define REDIS_HASH_KEY 1 +#define REDIS_HASH_VALUE 2 - if (o == NULL) { - o = createHashObject(); - dictAdd(c->db->dict,c->argv[1],o); - incrRefCount(c->argv[1]); - } else { - if (o->type != REDIS_HASH) { - addReply(c,shared.wrongtypeerr); +/* Check the length of a number of objects to see if we need to convert a + * zipmap to a real hash. Note that we only check string encoded objects + * as their string length can be queried in constant time. */ +static void hashTryConversion(robj *subject, robj **argv, int start, int end) { + int i; + if (subject->encoding != REDIS_ENCODING_ZIPMAP) return; + + for (i = start; i <= end; i++) { + if (argv[i]->encoding == REDIS_ENCODING_RAW && + sdslen(argv[i]->ptr) > server.hash_max_zipmap_value) + { + convertToRealHash(subject); return; } } - /* We want to convert the zipmap into an hash table right now if the - * entry to be added is too big. Note that we check if the object - * is integer encoded before to try fetching the length in the test below. - * This is because integers are small, but currently stringObjectLen() - * performs a slow conversion: not worth it. */ - if (o->encoding == REDIS_ENCODING_ZIPMAP && - ((c->argv[2]->encoding == REDIS_ENCODING_RAW && - sdslen(c->argv[2]->ptr) > server.hash_max_zipmap_value) || - (c->argv[3]->encoding == REDIS_ENCODING_RAW && - sdslen(c->argv[3]->ptr) > server.hash_max_zipmap_value))) - { - convertToRealHash(o); +} + +/* Get the value from a hash identified by key. Returns either a string + * object or NULL if the value cannot be found. The refcount of the object + * is always increased by 1 when the value was found. */ +static robj *hashGet(robj *o, robj *key) { + robj *value = NULL; + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + unsigned char *v; + unsigned int vlen; + key = getDecodedObject(key); + if (zipmapGet(o->ptr,key->ptr,sdslen(key->ptr),&v,&vlen)) { + value = createStringObject((char*)v,vlen); + } + decrRefCount(key); + } else { + dictEntry *de = dictFind(o->ptr,key); + if (de != NULL) { + value = dictGetEntryVal(de); + incrRefCount(value); + } + } + return value; +} + +/* Test if the key exists in the given hash. Returns 1 if the key + * exists and 0 when it doesn't. */ +static int hashExists(robj *o, robj *key) { + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + key = getDecodedObject(key); + if (zipmapExists(o->ptr,key->ptr,sdslen(key->ptr))) { + decrRefCount(key); + return 1; + } + decrRefCount(key); + } else { + if (dictFind(o->ptr,key) != NULL) { + return 1; + } } + return 0; +} +/* Add an element, discard the old if the key already exists. + * Return 0 on insert and 1 on update. */ +static int hashSet(robj *o, robj *key, robj *value) { + int update = 0; if (o->encoding == REDIS_ENCODING_ZIPMAP) { - unsigned char *zm = o->ptr; - robj *valobj = getDecodedObject(c->argv[3]); - - zm = zipmapSet(zm,c->argv[2]->ptr,sdslen(c->argv[2]->ptr), - valobj->ptr,sdslen(valobj->ptr),&update); - decrRefCount(valobj); - o->ptr = zm; - - /* And here there is the second check for hash conversion... - * we want to do it only if the operation was not just an update as - * zipmapLen() is O(N). */ - if (!update && zipmapLen(zm) > server.hash_max_zipmap_entries) + key = getDecodedObject(key); + value = getDecodedObject(value); + o->ptr = zipmapSet(o->ptr, + key->ptr,sdslen(key->ptr), + value->ptr,sdslen(value->ptr), &update); + decrRefCount(key); + decrRefCount(value); + + /* Check if the zipmap needs to be upgraded to a real hash table */ + if (zipmapLen(o->ptr) > server.hash_max_zipmap_entries) convertToRealHash(o); } else { - tryObjectEncoding(c->argv[2]); - /* note that c->argv[3] is already encoded, as the latest arg - * of a bulk command is always integer encoded if possible. */ - if (dictReplace(o->ptr,c->argv[2],c->argv[3])) { - incrRefCount(c->argv[2]); + value = tryObjectEncoding(value); + if (dictReplace(o->ptr,key,value)) { + /* Insert */ + incrRefCount(key); } else { + /* Update */ update = 1; } - incrRefCount(c->argv[3]); + incrRefCount(value); + } + return update; +} + +/* Delete an element from a hash. + * Return 1 on deleted and 0 on not found. */ +static int hashDelete(robj *o, robj *key) { + int deleted = 0; + if (o->encoding == REDIS_ENCODING_ZIPMAP) { + key = getDecodedObject(key); + o->ptr = zipmapDel(o->ptr,key->ptr,sdslen(key->ptr), &deleted); + decrRefCount(key); + } else { + deleted = dictDelete((dict*)o->ptr,key) == DICT_OK; + /* Always check if the dictionary needs a resize after a delete. */ + if (deleted && htNeedsResize(o->ptr)) dictResize(o->ptr); + } + return deleted; +} + +/* Return the number of elements in a hash. */ +static unsigned long hashLength(robj *o) { + return (o->encoding == REDIS_ENCODING_ZIPMAP) ? + zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr); +} + +/* Structure to hold hash iteration abstration. Note that iteration over + * hashes involves both fields and values. Because it is possible that + * not both are required, store pointers in the iterator to avoid + * unnecessary memory allocation for fields/values. */ +typedef struct { + int encoding; + unsigned char *zi; + unsigned char *zk, *zv; + unsigned int zklen, zvlen; + + dictIterator *di; + dictEntry *de; +} hashIterator; + +static hashIterator *hashInitIterator(robj *subject) { + hashIterator *hi = zmalloc(sizeof(hashIterator)); + hi->encoding = subject->encoding; + if (hi->encoding == REDIS_ENCODING_ZIPMAP) { + hi->zi = zipmapRewind(subject->ptr); + } else if (hi->encoding == REDIS_ENCODING_HT) { + hi->di = dictGetIterator(subject->ptr); + } else { + redisAssert(NULL); + } + return hi; +} + +static void hashReleaseIterator(hashIterator *hi) { + if (hi->encoding == REDIS_ENCODING_HT) { + dictReleaseIterator(hi->di); + } + zfree(hi); +} + +/* Move to the next entry in the hash. Return REDIS_OK when the next entry + * could be found and REDIS_ERR when the iterator reaches the end. */ +static int hashNext(hashIterator *hi) { + if (hi->encoding == REDIS_ENCODING_ZIPMAP) { + if ((hi->zi = zipmapNext(hi->zi, &hi->zk, &hi->zklen, + &hi->zv, &hi->zvlen)) == NULL) return REDIS_ERR; + } else { + if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR; + } + return REDIS_OK; +} + +/* Get key or value object at current iteration position. + * This increases the refcount of the field object by 1. */ +static robj *hashCurrent(hashIterator *hi, int what) { + robj *o; + if (hi->encoding == REDIS_ENCODING_ZIPMAP) { + if (what & REDIS_HASH_KEY) { + o = createStringObject((char*)hi->zk,hi->zklen); + } else { + o = createStringObject((char*)hi->zv,hi->zvlen); + } + } else { + if (what & REDIS_HASH_KEY) { + o = dictGetEntryKey(hi->de); + } else { + o = dictGetEntryVal(hi->de); + } + incrRefCount(o); } + return o; +} + +static robj *hashLookupWriteOrCreate(redisClient *c, robj *key) { + robj *o = lookupKeyWrite(c->db,key); + if (o == NULL) { + o = createHashObject(); + dictAdd(c->db->dict,key,o); + incrRefCount(key); + } else { + if (o->type != REDIS_HASH) { + addReply(c,shared.wrongtypeerr); + return NULL; + } + } + return o; +} + +/* ============================= Hash commands ============================== */ +static void hsetCommand(redisClient *c) { + int update; + robj *o; + + if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTryConversion(o,c->argv,2,3); + update = hashSet(o,c->argv[2],c->argv[3]); + addReply(c, update ? shared.czero : shared.cone); server.dirty++; - addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",update == 0)); } -static void hgetCommand(redisClient *c) { +static void hsetnxCommand(redisClient *c) { + robj *o; + if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTryConversion(o,c->argv,2,3); + + if (hashExists(o, c->argv[2])) { + addReply(c, shared.czero); + } else { + hashSet(o,c->argv[2],c->argv[3]); + addReply(c, shared.cone); + server.dirty++; + } +} + +static void hmsetCommand(redisClient *c) { + int i; robj *o; + if ((c->argc % 2) == 1) { + addReplySds(c,sdsnew("-ERR wrong number of arguments for HMSET\r\n")); + return; + } + + if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTryConversion(o,c->argv,2,c->argc-1); + for (i = 2; i < c->argc; i += 2) { + hashSet(o,c->argv[i],c->argv[i+1]); + } + addReply(c, shared.ok); +} + +static void hincrbyCommand(redisClient *c) { + long long value, incr; + robj *o, *current, *new; + + if (getLongLongFromObject(c,c->argv[3],&incr) != REDIS_OK) return; + if ((o = hashLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + if ((current = hashGet(o,c->argv[2])) != NULL) { + if (current->encoding == REDIS_ENCODING_RAW) + value = strtoll(current->ptr,NULL,10); + else if (current->encoding == REDIS_ENCODING_INT) + value = (long)current->ptr; + else + redisAssert(1 != 1); + decrRefCount(current); + } else { + value = 0; + } + + value += incr; + new = createObject(REDIS_STRING,sdscatprintf(sdsempty(),"%lld",value)); + hashSet(o,c->argv[2],new); + decrRefCount(new); + addReplyLongLong(c,value); + server.dirty++; +} + +static void hgetCommand(redisClient *c) { + robj *o, *value; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,REDIS_HASH)) return; - if (o->encoding == REDIS_ENCODING_ZIPMAP) { - unsigned char *zm = o->ptr; - unsigned char *val; - unsigned int vlen; - robj *field; - - field = getDecodedObject(c->argv[2]); - if (zipmapGet(zm,field->ptr,sdslen(field->ptr), &val,&vlen)) { - addReplySds(c,sdscatprintf(sdsempty(),"$%u\r\n", vlen)); - addReplySds(c,sdsnewlen(val,vlen)); - addReply(c,shared.crlf); - decrRefCount(field); - return; - } else { - addReply(c,shared.nullbulk); - decrRefCount(field); - return; - } + if ((value = hashGet(o,c->argv[2])) != NULL) { + addReplyBulk(c,value); + decrRefCount(value); } else { - struct dictEntry *de; + addReply(c,shared.nullbulk); + } +} - de = dictFind(o->ptr,c->argv[2]); - if (de == NULL) { - addReply(c,shared.nullbulk); - } else { - robj *e = dictGetEntryVal(de); +static void hmgetCommand(redisClient *c) { + int i; + robj *o, *value; + o = lookupKeyRead(c->db,c->argv[1]); + if (o != NULL && o->type != REDIS_HASH) { + addReply(c,shared.wrongtypeerr); + } - addReplyBulk(c,e); + /* Note the check for o != NULL happens inside the loop. This is + * done because objects that cannot be found are considered to be + * an empty hash. The reply should then be a series of NULLs. */ + addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2)); + for (i = 2; i < c->argc; i++) { + if (o != NULL && (value = hashGet(o,c->argv[i])) != NULL) { + addReplyBulk(c,value); + decrRefCount(value); + } else { + addReply(c,shared.nullbulk); } } } static void hdelCommand(redisClient *c) { robj *o; - int deleted = 0; - if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_HASH)) return; - if (o->encoding == REDIS_ENCODING_ZIPMAP) { - robj *field = getDecodedObject(c->argv[2]); - - o->ptr = zipmapDel((unsigned char*) o->ptr, - (unsigned char*) field->ptr, - sdslen(field->ptr), &deleted); - decrRefCount(field); - if (zipmapLen((unsigned char*) o->ptr) == 0) - deleteKey(c->db,c->argv[1]); + if (hashDelete(o,c->argv[2])) { + if (hashLength(o) == 0) deleteKey(c->db,c->argv[1]); + addReply(c,shared.cone); + server.dirty++; } else { - deleted = dictDelete((dict*)o->ptr,c->argv[2]) == DICT_OK; - if (htNeedsResize(o->ptr)) dictResize(o->ptr); - if (dictSize((dict*)o->ptr) == 0) deleteKey(c->db,c->argv[1]); + addReply(c,shared.czero); } - if (deleted) server.dirty++; - addReply(c,deleted ? shared.cone : shared.czero); } static void hlenCommand(redisClient *c) { robj *o; - unsigned long len; - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_HASH)) return; - len = (o->encoding == REDIS_ENCODING_ZIPMAP) ? - zipmapLen((unsigned char*)o->ptr) : dictSize((dict*)o->ptr); - addReplyUlong(c,len); + addReplyUlong(c,hashLength(o)); } -#define REDIS_GETALL_KEYS 1 -#define REDIS_GETALL_VALS 2 static void genericHgetallCommand(redisClient *c, int flags) { - robj *o, *lenobj; + robj *o, *lenobj, *obj; unsigned long count = 0; + hashIterator *hi; - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullmultibulk)) == NULL + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,o,REDIS_HASH)) return; lenobj = createObject(REDIS_STRING,NULL); addReply(c,lenobj); decrRefCount(lenobj); - if (o->encoding == REDIS_ENCODING_ZIPMAP) { - unsigned char *p = zipmapRewind(o->ptr); - unsigned char *field, *val; - unsigned int flen, vlen; - - while((p = zipmapNext(p,&field,&flen,&val,&vlen)) != NULL) { - robj *aux; - - if (flags & REDIS_GETALL_KEYS) { - aux = createStringObject((char*)field,flen); - addReplyBulk(c,aux); - decrRefCount(aux); - count++; - } - if (flags & REDIS_GETALL_VALS) { - aux = createStringObject((char*)val,vlen); - addReplyBulk(c,aux); - decrRefCount(aux); - count++; - } + hi = hashInitIterator(o); + while (hashNext(hi) != REDIS_ERR) { + if (flags & REDIS_HASH_KEY) { + obj = hashCurrent(hi,REDIS_HASH_KEY); + addReplyBulk(c,obj); + decrRefCount(obj); + count++; } - } else { - dictIterator *di = dictGetIterator(o->ptr); - dictEntry *de; - - while((de = dictNext(di)) != NULL) { - robj *fieldobj = dictGetEntryKey(de); - robj *valobj = dictGetEntryVal(de); - - if (flags & REDIS_GETALL_KEYS) { - addReplyBulk(c,fieldobj); - count++; - } - if (flags & REDIS_GETALL_VALS) { - addReplyBulk(c,valobj); - count++; - } + if (flags & REDIS_HASH_VALUE) { + obj = hashCurrent(hi,REDIS_HASH_VALUE); + addReplyBulk(c,obj); + decrRefCount(obj); + count++; } - dictReleaseIterator(di); } + hashReleaseIterator(hi); + lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",count); } static void hkeysCommand(redisClient *c) { - genericHgetallCommand(c,REDIS_GETALL_KEYS); + genericHgetallCommand(c,REDIS_HASH_KEY); } static void hvalsCommand(redisClient *c) { - genericHgetallCommand(c,REDIS_GETALL_VALS); + genericHgetallCommand(c,REDIS_HASH_VALUE); } static void hgetallCommand(redisClient *c) { - genericHgetallCommand(c,REDIS_GETALL_KEYS|REDIS_GETALL_VALS); + genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); } static void hexistsCommand(redisClient *c) { robj *o; - int exists = 0; - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_HASH)) return; - if (o->encoding == REDIS_ENCODING_ZIPMAP) { - robj *field; - unsigned char *zm = o->ptr; - - field = getDecodedObject(c->argv[2]); - exists = zipmapExists(zm,field->ptr,sdslen(field->ptr)); - decrRefCount(field); - } else { - exists = dictFind(o->ptr,c->argv[2]) != NULL; - } - addReply(c,exists ? shared.cone : shared.czero); + addReply(c, hashExists(o,c->argv[2]) ? shared.cone : shared.czero); } static void convertToRealHash(robj *o) { @@ -6109,8 +6430,8 @@ static void convertToRealHash(robj *o) { keyobj = createStringObject((char*)key,klen); valobj = createStringObject((char*)val,vlen); - tryObjectEncoding(keyobj); - tryObjectEncoding(valobj); + keyobj = tryObjectEncoding(keyobj); + valobj = tryObjectEncoding(valobj); dictAdd(dict,keyobj,valobj); } o->encoding = REDIS_ENCODING_HT; @@ -6130,6 +6451,10 @@ static void flushdbCommand(redisClient *c) { static void flushallCommand(redisClient *c) { server.dirty += emptyDb(); addReply(c,shared.ok); + if (server.bgsavechildpid != -1) { + kill(server.bgsavechildpid,SIGKILL); + rdbRemoveTempFile(server.bgsavechildpid); + } rdbSave(server.dbfilename); server.dirty++; } @@ -6142,23 +6467,26 @@ static redisSortOperation *createSortOperation(int type, robj *pattern) { } /* 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'. + * The returned object will always have its refcount increased by 1 + * when it is non-NULL. */ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { - char *p; + char *p, *f; sds spat, ssub; - robj keyobj; - int prefixlen, sublen, postfixlen; + robj keyobj, fieldobj, *o; + int prefixlen, sublen, postfixlen, fieldlen; /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */ struct { long len; long free; char buf[REDIS_SORTKEY_MAX+1]; - } keyname; + } keyname, fieldname; /* If the pattern is "#" return the substitution object itself in order * to implement the "SORT ... GET #" feature. */ spat = pattern->ptr; if (spat[0] == '#' && spat[1] == '\0') { + incrRefCount(subst); return subst; } @@ -6175,20 +6503,47 @@ static robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { return NULL; } + /* Find out if we're dealing with a hash dereference. */ + if ((f = strstr(p+1, "->")) != NULL) { + fieldlen = sdslen(spat)-(f-spat); + /* this also copies \0 character */ + memcpy(fieldname.buf,f+2,fieldlen-1); + fieldname.len = fieldlen-2; + } else { + fieldlen = 0; + } + prefixlen = p-spat; sublen = sdslen(ssub); - postfixlen = sdslen(spat)-(prefixlen+1); + postfixlen = sdslen(spat)-(prefixlen+1)-fieldlen; memcpy(keyname.buf,spat,prefixlen); memcpy(keyname.buf+prefixlen,ssub,sublen); memcpy(keyname.buf+prefixlen+sublen,p+1,postfixlen); keyname.buf[prefixlen+sublen+postfixlen] = '\0'; keyname.len = prefixlen+sublen+postfixlen; - - initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(long)*2)) decrRefCount(subst); - /* printf("lookup '%s' => %p\n", keyname.buf,de); */ - return lookupKeyRead(db,&keyobj); + /* Lookup substituted key */ + initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(long)*2)); + o = lookupKeyRead(db,&keyobj); + if (o == NULL) return NULL; + + if (fieldlen > 0) { + if (o->type != REDIS_HASH || fieldname.len < 1) return NULL; + + /* Retrieve value from hash by the field name. This operation + * already increases the refcount of the returned object. */ + initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(long)*2)); + o = hashGet(o, &fieldobj); + } else { + if (o->type != REDIS_STRING) return NULL; + + /* Every object that this function returns needs to have its refcount + * increased. sortCommand decreases it again. */ + incrRefCount(o); + } + + return o; } /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with @@ -6223,14 +6578,9 @@ static int sortCompare(const void *s1, const void *s2) { cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr); } } else { - /* Compare elements directly */ - robj *dec1, *dec2; - - dec1 = getDecodedObject(so1->obj); - dec2 = getDecodedObject(so2->obj); - cmp = strcoll(dec1->ptr,dec2->ptr); - decrRefCount(dec1); - decrRefCount(dec2); + /* Compare elements directly. Note that these objects already + * need to be non-encoded (see sortCommand). */ + cmp = strcoll(so1->obj->ptr,so2->obj->ptr); } } return server.sort_desc ? -cmp : cmp; @@ -6251,7 +6601,7 @@ static void sortCommand(redisClient *c) { /* Lookup the key to sort. It must be of the right types */ sortval = lookupKeyRead(c->db,c->argv[1]); if (sortval == NULL) { - addReply(c,shared.nullmultibulk); + addReply(c,shared.emptymultibulk); return; } if (sortval->type != REDIS_SET && sortval->type != REDIS_LIST && @@ -6357,38 +6707,36 @@ static void sortCommand(redisClient *c) { /* Now it's time to load the right scores in the sorting vector */ if (dontsort == 0) { for (j = 0; j < vectorlen; j++) { + robj *byval; if (sortby) { - robj *byval; - + /* lookup value to sort by */ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); - if (!byval || byval->type != REDIS_STRING) continue; - if (alpha) { - vector[j].u.cmpobj = getDecodedObject(byval); - } else { - if (byval->encoding == REDIS_ENCODING_RAW) { - vector[j].u.score = strtod(byval->ptr,NULL); - } else { - /* Don't need to decode the object if it's - * integer-encoded (the only encoding supported) so - * far. We can just cast it */ - if (byval->encoding == REDIS_ENCODING_INT) { - vector[j].u.score = (long)byval->ptr; - } else - redisAssert(1 != 1); - } - } + if (!byval) continue; } else { - if (!alpha) { - if (vector[j].obj->encoding == REDIS_ENCODING_RAW) - vector[j].u.score = strtod(vector[j].obj->ptr,NULL); - else { - if (vector[j].obj->encoding == REDIS_ENCODING_INT) - vector[j].u.score = (long) vector[j].obj->ptr; - else - redisAssert(1 != 1); - } + /* use object itself to sort by */ + byval = vector[j].obj; + } + + if (alpha) { + vector[j].u.cmpobj = getDecodedObject(byval); + } else { + if (byval->encoding == REDIS_ENCODING_RAW) { + vector[j].u.score = strtod(byval->ptr,NULL); + } else if (byval->encoding == REDIS_ENCODING_INT) { + /* Don't need to decode the object if it's + * integer-encoded (the only encoding supported) so + * far. We can just cast it */ + vector[j].u.score = (long)byval->ptr; + } else { + redisAssert(1 != 1); } } + + /* when the object was retrieved using lookupKeyByPattern, + * its refcount needs to be decreased. */ + if (sortby) { + decrRefCount(byval); + } } } @@ -6430,10 +6778,11 @@ static void sortCommand(redisClient *c) { vector[j].obj); if (sop->type == REDIS_SORT_GET) { - if (!val || val->type != REDIS_STRING) { + if (!val) { addReply(c,shared.nullbulk); } else { addReplyBulk(c,val); + decrRefCount(val); } } else { redisAssert(sop->type == REDIS_SORT_GET); /* always fails */ @@ -6460,11 +6809,14 @@ static void sortCommand(redisClient *c) { vector[j].obj); if (sop->type == REDIS_SORT_GET) { - if (!val || val->type != REDIS_STRING) { + if (!val) { listAddNodeTail(listPtr,createStringObject("",0)); } else { + /* We should do a incrRefCount on val because it is + * added to the list, but also a decrRefCount because + * it is returned by lookupKeyByPattern. This results + * in doing nothing at all. */ listAddNodeTail(listPtr,val); - incrRefCount(val); } } else { redisAssert(sop->type == REDIS_SORT_GET); /* always fails */ @@ -6485,7 +6837,7 @@ static void sortCommand(redisClient *c) { decrRefCount(sortval); listRelease(operations); for (j = 0; j < vectorlen; j++) { - if (sortby && alpha && vector[j].u.cmpobj) + if (alpha && vector[j].u.cmpobj) decrRefCount(vector[j].u.cmpobj); } zfree(vector); @@ -6540,8 +6892,11 @@ static sds genRedisInfoString(void) { "bgrewriteaof_in_progress:%d\r\n" "total_connections_received:%lld\r\n" "total_commands_processed:%lld\r\n" + "expired_keys:%lld\r\n" "hash_max_zipmap_entries:%ld\r\n" "hash_max_zipmap_value:%ld\r\n" + "pubsub_channels:%ld\r\n" + "pubsub_patterns:%u\r\n" "vm_enabled:%d\r\n" "role:%s\r\n" ,REDIS_VERSION, @@ -6561,8 +6916,11 @@ static sds genRedisInfoString(void) { server.bgrewritechildpid != -1, server.stat_numconnections, server.stat_numcommands, + server.stat_expiredkeys, server.hash_max_zipmap_entries, server.hash_max_zipmap_value, + dictSize(server.pubsub_channels), + listLength(server.pubsub_patterns), server.vm_enabled != 0, server.masterhost == NULL ? "master" : "slave" ); @@ -6684,6 +7042,7 @@ static int expireIfNeeded(redisDb *db, robj *key) { /* Delete the key */ dictDelete(db->expires,key); + server.stat_expiredkeys++; return dictDelete(db->dict,key) == DICT_OK; } @@ -6696,19 +7055,25 @@ static int deleteIfVolatile(redisDb *db, robj *key) { /* Delete the key */ server.dirty++; + server.stat_expiredkeys++; dictDelete(db->expires,key); return dictDelete(db->dict,key) == DICT_OK; } -static void expireGenericCommand(redisClient *c, robj *key, time_t seconds) { +static void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) { dictEntry *de; + time_t seconds; + + if (getLongFromObject(c, param, &seconds) != REDIS_OK) return; + + seconds -= offset; de = dictFind(c->db->dict,key); if (de == NULL) { addReply(c,shared.czero); return; } - if (seconds < 0) { + if (seconds <= 0) { if (deleteKey(c->db,key)) server.dirty++; addReply(c, shared.cone); return; @@ -6725,11 +7090,11 @@ static void expireGenericCommand(redisClient *c, robj *key, time_t seconds) { } static void expireCommand(redisClient *c) { - expireGenericCommand(c,c->argv[1],strtol(c->argv[2]->ptr,NULL,10)); + expireGenericCommand(c,c->argv[1],c->argv[2],0); } static void expireatCommand(redisClient *c) { - expireGenericCommand(c,c->argv[1],strtol(c->argv[2]->ptr,NULL,10)-time(NULL)); + expireGenericCommand(c,c->argv[1],c->argv[2],time(NULL)); } static void ttlCommand(redisClient *c) { @@ -6973,7 +7338,7 @@ static void blockingPopGenericCommand(redisClient *c, int where) { * non-blocking POP operation */ robj *argv[2], **orig_argv; int orig_argc; - + /* We need to alter the command arguments before to call * popGenericCommand() as the command takes a single key. */ orig_argv = c->argv; @@ -7210,7 +7575,7 @@ static void updateSlavesWaitingBgsave(int bgsaveerr) { slave->replstate = REDIS_REPL_WAIT_BGSAVE_END; } else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) { struct redis_stat buf; - + if (bgsaveerr != REDIS_OK) { freeClient(slave); redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error"); @@ -7608,14 +7973,9 @@ int loadAppendOnlyFile(char *filename) { redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr); exit(1); } - /* Try object sharing and encoding */ - if (server.shareobjects) { - int j; - for(j = 1; j < argc; j++) - argv[j] = tryObjectSharing(argv[j]); - } + /* Try object encoding */ if (cmd->flags & REDIS_CMD_BULK) - tryObjectEncoding(argv[argc-1]); + argv[argc-1] = tryObjectEncoding(argv[argc-1]); /* Run the command in the context of a fake client */ fakeClient->argc = argc; fakeClient->argv = argv; @@ -7872,7 +8232,7 @@ static int rewriteAppendOnlyFile(char *filename) { fflush(fp); fsync(fileno(fp)); fclose(fp); - + /* Use RENAME to make sure the DB file is changed atomically only * if the generate DB file is ok. */ if (rename(tmpfile,filename) == -1) { @@ -7931,6 +8291,7 @@ static int rewriteAppendOnlyFileBackground(void) { redisLog(REDIS_NOTICE, "Background append only file rewriting started by pid %d",childpid); server.bgrewritechildpid = childpid; + updateDictResizePolicy(); /* We set appendseldb to -1 in order to force the next call to the * feedAppendOnlyFile() to issue a SELECT command, so the differences * accumulated by the parent into server.bgrewritebuf will start @@ -7989,7 +8350,7 @@ static void aofRemoveTempFile(pid_t childpid) { static void expandVmSwapFilename(void) { char *p = strstr(server.vm_swap_file,"%p"); sds new; - + if (!p) return; new = sdsempty(); *p = '\0'; @@ -8114,7 +8475,7 @@ static int vmFreePage(off_t page) { } /* Find N contiguous free pages storing the first page of the cluster in *first. - * Returns REDIS_OK if it was able to find N contiguous pages, otherwise + * Returns REDIS_OK if it was able to find N contiguous pages, otherwise * REDIS_ERR is returned. * * This function uses a simple algorithm: we try to allocate @@ -8125,7 +8486,7 @@ static int vmFreePage(off_t page) { * we try to find less populated places doing a forward jump of * REDIS_VM_MAX_RANDOM_JUMP, then we start scanning again a few pages * without hurry, and then we jump again and so forth... - * + * * This function can be improved using a free list to avoid to guess * too much, since we could collect data about freed pages. * @@ -8498,7 +8859,10 @@ static void freeIOJob(iojob *j) { j->type == REDIS_IOJOB_DO_SWAP || j->type == REDIS_IOJOB_LOAD) && j->val != NULL) decrRefCount(j->val); - decrRefCount(j->key); + /* We don't decrRefCount the j->key field as we did't incremented + * the count creating IO Jobs. This is because the key field here is + * just used as an indentifier and if a key is removed the Job should + * never be touched again. */ zfree(j); } @@ -8671,7 +9035,7 @@ again: iojob *job = ln->value; if (job->canceled) continue; /* Skip this, already canceled. */ - if (compareStringObjects(job->key,o) == 0) { + if (job->key == o) { redisLog(REDIS_DEBUG,"*** CANCELED %p (%s) (type %d) (LIST ID %d)\n", (void*)job, (char*)o->ptr, job->type, i); /* Mark the pages as free since the swap didn't happened @@ -8776,7 +9140,7 @@ static void *IOThreadEntryPoint(void *arg) { listDelNode(server.io_processing,ln); listAddNodeTail(server.io_processed,j); unlockThreadedIO(); - + /* Signal the main thread there is new stuff to process */ assert(write(server.io_ready_pipe_write,"x",1) == 1); } @@ -8854,14 +9218,14 @@ static void queueIOJob(iojob *j) { static int vmSwapObjectThreaded(robj *key, robj *val, redisDb *db) { iojob *j; - + assert(key->storage == REDIS_VM_MEMORY); assert(key->refcount == 1); j = zmalloc(sizeof(*j)); j->type = REDIS_IOJOB_PREPARE_SWAP; j->db = db; - j->key = dupStringObject(key); + j->key = key; j->val = val; incrRefCount(val); j->canceled = 0; @@ -8898,7 +9262,7 @@ static int waitForSwappedKey(redisClient *c, robj *key) { vmCancelThreadedIOJob(o); return 0; } - + /* OK: the key is either swapped, or being loaded just now. */ /* Add the key to the list of keys this client is waiting for. @@ -8929,7 +9293,7 @@ static int waitForSwappedKey(redisClient *c, robj *key) { j = zmalloc(sizeof(*j)); j->type = REDIS_IOJOB_LOAD; j->db = c->db; - j->key = dupStringObject(key); + j->key = o; j->key->vtype = o->vtype; j->page = o->vm.page; j->val = NULL; @@ -9043,6 +9407,353 @@ static void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key) { } } +/* =========================== Remote Configuration ========================= */ + +static void configSetCommand(redisClient *c) { + robj *o = getDecodedObject(c->argv[3]); + if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) { + zfree(server.dbfilename); + server.dbfilename = zstrdup(o->ptr); + } else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) { + zfree(server.requirepass); + server.requirepass = zstrdup(o->ptr); + } else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) { + zfree(server.masterauth); + server.masterauth = zstrdup(o->ptr); + } else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) { + server.maxmemory = strtoll(o->ptr, NULL, 10); + } else { + addReplySds(c,sdscatprintf(sdsempty(), + "-ERR not supported CONFIG parameter %s\r\n", + (char*)c->argv[2]->ptr)); + decrRefCount(o); + return; + } + decrRefCount(o); + addReply(c,shared.ok); +} + +static void configGetCommand(redisClient *c) { + robj *o = getDecodedObject(c->argv[2]); + robj *lenobj = createObject(REDIS_STRING,NULL); + char *pattern = o->ptr; + int matches = 0; + + addReply(c,lenobj); + decrRefCount(lenobj); + + if (stringmatch(pattern,"dbfilename",0)) { + addReplyBulkCString(c,"dbfilename"); + addReplyBulkCString(c,server.dbfilename); + matches++; + } + if (stringmatch(pattern,"requirepass",0)) { + addReplyBulkCString(c,"requirepass"); + addReplyBulkCString(c,server.requirepass); + matches++; + } + if (stringmatch(pattern,"masterauth",0)) { + addReplyBulkCString(c,"masterauth"); + addReplyBulkCString(c,server.masterauth); + matches++; + } + if (stringmatch(pattern,"maxmemory",0)) { + char buf[128]; + + snprintf(buf,128,"%llu\n",server.maxmemory); + addReplyBulkCString(c,"maxmemory"); + addReplyBulkCString(c,buf); + matches++; + } + decrRefCount(o); + lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",matches*2); +} + +static void configCommand(redisClient *c) { + if (!strcasecmp(c->argv[1]->ptr,"set")) { + if (c->argc != 4) goto badarity; + configSetCommand(c); + } else if (!strcasecmp(c->argv[1]->ptr,"get")) { + if (c->argc != 3) goto badarity; + configGetCommand(c); + } else if (!strcasecmp(c->argv[1]->ptr,"resetstat")) { + if (c->argc != 2) goto badarity; + server.stat_numcommands = 0; + server.stat_numconnections = 0; + server.stat_expiredkeys = 0; + server.stat_starttime = time(NULL); + addReply(c,shared.ok); + } else { + addReplySds(c,sdscatprintf(sdsempty(), + "-ERR CONFIG subcommand must be one of GET, SET, RESETSTAT\r\n")); + } + return; + +badarity: + addReplySds(c,sdscatprintf(sdsempty(), + "-ERR Wrong number of arguments for CONFIG %s\r\n", + (char*) c->argv[1]->ptr)); +} + +/* =========================== Pubsub implementation ======================== */ + +static void freePubsubPattern(void *p) { + pubsubPattern *pat = p; + + decrRefCount(pat->pattern); + zfree(pat); +} + +static int listMatchPubsubPattern(void *a, void *b) { + pubsubPattern *pa = a, *pb = b; + + return (pa->client == pb->client) && + (compareStringObjects(pa->pattern,pb->pattern) == 0); +} + +/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or + * 0 if the client was already subscribed to that channel. */ +static int pubsubSubscribeChannel(redisClient *c, robj *channel) { + struct dictEntry *de; + list *clients = NULL; + int retval = 0; + + /* Add the channel to the client -> channels hash table */ + if (dictAdd(c->pubsub_channels,channel,NULL) == DICT_OK) { + retval = 1; + incrRefCount(channel); + /* Add the client to the channel -> list of clients hash table */ + de = dictFind(server.pubsub_channels,channel); + if (de == NULL) { + clients = listCreate(); + dictAdd(server.pubsub_channels,channel,clients); + incrRefCount(channel); + } else { + clients = dictGetEntryVal(de); + } + listAddNodeTail(clients,c); + } + /* Notify the client */ + addReply(c,shared.mbulk3); + addReply(c,shared.subscribebulk); + addReplyBulk(c,channel); + addReplyLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); + return retval; +} + +/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or + * 0 if the client was not subscribed to the specified channel. */ +static int pubsubUnsubscribeChannel(redisClient *c, robj *channel, int notify) { + struct dictEntry *de; + list *clients; + listNode *ln; + int retval = 0; + + /* Remove the channel from the client -> channels hash table */ + incrRefCount(channel); /* channel may be just a pointer to the same object + we have in the hash tables. Protect it... */ + if (dictDelete(c->pubsub_channels,channel) == DICT_OK) { + retval = 1; + /* Remove the client from the channel -> clients list hash table */ + de = dictFind(server.pubsub_channels,channel); + assert(de != NULL); + clients = dictGetEntryVal(de); + ln = listSearchKey(clients,c); + assert(ln != NULL); + listDelNode(clients,ln); + if (listLength(clients) == 0) { + /* Free the list and associated hash entry at all if this was + * the latest client, so that it will be possible to abuse + * Redis PUBSUB creating millions of channels. */ + dictDelete(server.pubsub_channels,channel); + } + } + /* Notify the client */ + if (notify) { + addReply(c,shared.mbulk3); + addReply(c,shared.unsubscribebulk); + addReplyBulk(c,channel); + addReplyLong(c,dictSize(c->pubsub_channels)+ + listLength(c->pubsub_patterns)); + + } + decrRefCount(channel); /* it is finally safe to release it */ + return retval; +} + +/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the clinet was already subscribed to that pattern. */ +static int pubsubSubscribePattern(redisClient *c, robj *pattern) { + int retval = 0; + + if (listSearchKey(c->pubsub_patterns,pattern) == NULL) { + retval = 1; + pubsubPattern *pat; + listAddNodeTail(c->pubsub_patterns,pattern); + incrRefCount(pattern); + pat = zmalloc(sizeof(*pat)); + pat->pattern = getDecodedObject(pattern); + pat->client = c; + listAddNodeTail(server.pubsub_patterns,pat); + } + /* Notify the client */ + addReply(c,shared.mbulk3); + addReply(c,shared.psubscribebulk); + addReplyBulk(c,pattern); + addReplyLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns)); + return retval; +} + +/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or + * 0 if the client was not subscribed to the specified channel. */ +static int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) { + listNode *ln; + pubsubPattern pat; + int retval = 0; + + incrRefCount(pattern); /* Protect the object. May be the same we remove */ + if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) { + retval = 1; + listDelNode(c->pubsub_patterns,ln); + pat.client = c; + pat.pattern = pattern; + ln = listSearchKey(server.pubsub_patterns,&pat); + listDelNode(server.pubsub_patterns,ln); + } + /* Notify the client */ + if (notify) { + addReply(c,shared.mbulk3); + addReply(c,shared.punsubscribebulk); + addReplyBulk(c,pattern); + addReplyLong(c,dictSize(c->pubsub_channels)+ + listLength(c->pubsub_patterns)); + } + decrRefCount(pattern); + return retval; +} + +/* Unsubscribe from all the channels. Return the number of channels the + * client was subscribed from. */ +static int pubsubUnsubscribeAllChannels(redisClient *c, int notify) { + dictIterator *di = dictGetIterator(c->pubsub_channels); + dictEntry *de; + int count = 0; + + while((de = dictNext(di)) != NULL) { + robj *channel = dictGetEntryKey(de); + + count += pubsubUnsubscribeChannel(c,channel,notify); + } + dictReleaseIterator(di); + return count; +} + +/* Unsubscribe from all the patterns. Return the number of patterns the + * client was subscribed from. */ +static int pubsubUnsubscribeAllPatterns(redisClient *c, int notify) { + listNode *ln; + listIter li; + int count = 0; + + listRewind(c->pubsub_patterns,&li); + while ((ln = listNext(&li)) != NULL) { + robj *pattern = ln->value; + + count += pubsubUnsubscribePattern(c,pattern,notify); + } + return count; +} + +/* Publish a message */ +static int pubsubPublishMessage(robj *channel, robj *message) { + int receivers = 0; + struct dictEntry *de; + listNode *ln; + listIter li; + + /* Send to clients listening for that channel */ + de = dictFind(server.pubsub_channels,channel); + if (de) { + list *list = dictGetEntryVal(de); + listNode *ln; + listIter li; + + listRewind(list,&li); + while ((ln = listNext(&li)) != NULL) { + redisClient *c = ln->value; + + addReply(c,shared.mbulk3); + addReply(c,shared.messagebulk); + addReplyBulk(c,channel); + addReplyBulk(c,message); + receivers++; + } + } + /* Send to clients listening to matching channels */ + if (listLength(server.pubsub_patterns)) { + listRewind(server.pubsub_patterns,&li); + channel = getDecodedObject(channel); + while ((ln = listNext(&li)) != NULL) { + pubsubPattern *pat = ln->value; + + if (stringmatchlen((char*)pat->pattern->ptr, + sdslen(pat->pattern->ptr), + (char*)channel->ptr, + sdslen(channel->ptr),0)) { + addReply(pat->client,shared.mbulk3); + addReply(pat->client,shared.messagebulk); + addReplyBulk(pat->client,channel); + addReplyBulk(pat->client,message); + receivers++; + } + } + decrRefCount(channel); + } + return receivers; +} + +static void subscribeCommand(redisClient *c) { + int j; + + for (j = 1; j < c->argc; j++) + pubsubSubscribeChannel(c,c->argv[j]); +} + +static void unsubscribeCommand(redisClient *c) { + if (c->argc == 1) { + pubsubUnsubscribeAllChannels(c,1); + return; + } else { + int j; + + for (j = 1; j < c->argc; j++) + pubsubUnsubscribeChannel(c,c->argv[j],1); + } +} + +static void psubscribeCommand(redisClient *c) { + int j; + + for (j = 1; j < c->argc; j++) + pubsubSubscribePattern(c,c->argv[j]); +} + +static void punsubscribeCommand(redisClient *c) { + if (c->argc == 1) { + pubsubUnsubscribeAllPatterns(c,1); + return; + } else { + int j; + + for (j = 1; j < c->argc; j++) + pubsubUnsubscribePattern(c,c->argv[j],1); + } +} + +static void publishCommand(redisClient *c) { + int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); + addReplyLong(c,receivers); +} + /* ================================= Debugging ============================== */ static void debugCommand(redisClient *c) { @@ -9101,6 +9812,9 @@ static void debugCommand(redisClient *c) { (void*)key, key->refcount, (unsigned long long) key->vm.page, (unsigned long long) key->vm.usedpages)); } + } else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) { + lookupKeyRead(c->db,c->argv[2]); + addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"swapout") && c->argc == 3) { dictEntry *de = dictFind(c->db->dict,c->argv[2]); robj *key, *val; @@ -9132,7 +9846,7 @@ static void debugCommand(redisClient *c) { } } else { addReplySds(c,sdsnew( - "-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT |SWAPOUT |RELOAD]\r\n")); + "-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT |SWAPIN |SWAPOUT |RELOAD]\r\n")); } } @@ -9200,6 +9914,7 @@ static void version() { static void usage() { fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf]\n"); + fprintf(stderr," ./redis-server - (read config from stdin)\n"); exit(1); } @@ -9260,7 +9975,7 @@ static void *getMcontextEip(ucontext_t *uc) { return (void*) uc->uc_mcontext->__ss.__rip; #else return (void*) uc->uc_mcontext->__ss.__eip; - #endif + #endif #elif defined(__i386__) || defined(__X86_64__) || defined(__x86_64__) return (void*) uc->uc_mcontext.gregs[REG_EIP]; /* Linux 32/64 bit */ #elif defined(__ia64__) /* Linux IA64 */ @@ -9285,7 +10000,7 @@ static void segvHandler(int sig, siginfo_t *info, void *secret) { redisLog(REDIS_WARNING, "%s",infostring); /* It's not safe to sdsfree() the returned string under memory * corruption conditions. Let it leak as we are going to abort */ - + trace_size = backtrace(trace, 100); /* overwrite sigaction with caller's address */ if (getMcontextEip(uc) != NULL) {