Commit aaada3f9 authored by Pieter Noordhuis's avatar Pieter Noordhuis
Browse files

Merge branch 'master' into intset-split

Conflicts:
	src/Makefile
	src/t_set.c
parents 2767f1c0 cbce5171
This is a stable release, for beta testing make sure to download the latest source code from Git:
git clone git://github.com/antirez/redis.git
It's also possibe to download the latest source code as a tarball:
http://github.com/antirez/redis/tree/master
(use the download button)
To compile Redis, do the following:
cd src; make
The compilation will produce a redis-server binary.
Copy this file where you want.
Run the server using the following command line:
/path/to/redis-server
This will start a Redis server with the default configuration.
Otherwise if you want to provide your configuration use:
/path/to/redis-server /path/to/redis.conf
You can find an example redis.conf file in the root directory
of this source distribution.
# Top level makefile, the real shit is at src/Makefile
TARGETS=32bit noopt test
all:
cd src && $(MAKE) $@
install: dummy
cd src && $(MAKE) $@
$(TARGETS) clean:
cd src && $(MAKE) $@
dummy:
...@@ -6,6 +6,8 @@ VERSION 2.2 TODO (Optimizations and latency) ...@@ -6,6 +6,8 @@ VERSION 2.2 TODO (Optimizations and latency)
* Support for syslog(3). * Support for syslog(3).
* Change the implementation of ZCOUNT to use the augmented skiplist in order to be much faster. * Change the implementation of ZCOUNT to use the augmented skiplist in order to be much faster.
* Add an explicit test for MULTI/EXEC reloaded in the AOF.
* Command table -> hash table, with support for command renaming
VM TODO VM TODO
======= =======
...@@ -56,3 +58,9 @@ KNOWN BUGS ...@@ -56,3 +58,9 @@ KNOWN BUGS
========== ==========
* LRANGE and other commands are using 32 bit integers for ranges, and overflows are not detected. So LRANGE mylist 0 23498204823094823904823904 will have random effects. * LRANGE and other commands are using 32 bit integers for ranges, and overflows are not detected. So LRANGE mylist 0 23498204823094823904823904 will have random effects.
REDIS CLI TODO
==============
* Computer parsable output generation
* Memoize return values so that they can be used later as arguments, like $1
...@@ -15,6 +15,10 @@ endif ...@@ -15,6 +15,10 @@ endif
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF) CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb DEBUG?= -g -rdynamic -ggdb
INSTALL_TOP= /usr/local
INSTALL_BIN= $(INSTALL_TOP)/bin
INSTALL= cp -p
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
...@@ -110,3 +114,10 @@ noopt: ...@@ -110,3 +114,10 @@ noopt:
32bitgprof: 32bitgprof:
make PROF="-pg" ARCH="-arch i386" make PROF="-pg" ARCH="-arch i386"
install: all
$(INSTALL) $(PRGNAME) $(INSTALL_BIN)
$(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
$(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
$(INSTALL) $(CHECKDUMPPRGNAME) $(INSTALL_BIN)
$(INSTALL) $(CHECKAOFPRGNAME) $(INSTALL_BIN)
...@@ -194,6 +194,7 @@ struct redisClient *createFakeClient(void) { ...@@ -194,6 +194,7 @@ struct redisClient *createFakeClient(void) {
* so that Redis will not try to send replies to this client. */ * so that Redis will not try to send replies to this client. */
c->replstate = REDIS_REPL_WAIT_BGSAVE_START; c->replstate = REDIS_REPL_WAIT_BGSAVE_START;
c->reply = listCreate(); c->reply = listCreate();
c->watched_keys = listCreate();
listSetFreeMethod(c->reply,decrRefCount); listSetFreeMethod(c->reply,decrRefCount);
listSetDupMethod(c->reply,dupClientReplyValue); listSetDupMethod(c->reply,dupClientReplyValue);
initClientMultiState(c); initClientMultiState(c);
...@@ -203,6 +204,7 @@ struct redisClient *createFakeClient(void) { ...@@ -203,6 +204,7 @@ struct redisClient *createFakeClient(void) {
void freeFakeClient(struct redisClient *c) { void freeFakeClient(struct redisClient *c) {
sdsfree(c->querybuf); sdsfree(c->querybuf);
listRelease(c->reply); listRelease(c->reply);
listRelease(c->watched_keys);
freeClientMultiState(c); freeClientMultiState(c);
zfree(c); zfree(c);
} }
......
...@@ -45,8 +45,7 @@ robj *lookupKeyRead(redisDb *db, robj *key) { ...@@ -45,8 +45,7 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
} }
robj *lookupKeyWrite(redisDb *db, robj *key) { robj *lookupKeyWrite(redisDb *db, robj *key) {
deleteIfVolatile(db,key); expireIfNeeded(db,key);
touchWatchedKey(db,key);
return lookupKey(db,key); return lookupKey(db,key);
} }
...@@ -322,7 +321,6 @@ void renameGenericCommand(redisClient *c, int nx) { ...@@ -322,7 +321,6 @@ void renameGenericCommand(redisClient *c, int nx) {
return; return;
incrRefCount(o); incrRefCount(o);
deleteIfVolatile(c->db,c->argv[2]);
if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) { if (dbAdd(c->db,c->argv[2],o) == REDIS_ERR) {
if (nx) { if (nx) {
decrRefCount(o); decrRefCount(o);
...@@ -332,6 +330,7 @@ void renameGenericCommand(redisClient *c, int nx) { ...@@ -332,6 +330,7 @@ void renameGenericCommand(redisClient *c, int nx) {
dbReplace(c->db,c->argv[2],o); dbReplace(c->db,c->argv[2],o);
} }
dbDelete(c->db,c->argv[1]); dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[2]); touchWatchedKey(c->db,c->argv[2]);
server.dirty++; server.dirty++;
addReply(c,nx ? shared.cone : shared.ok); addReply(c,nx ? shared.cone : shared.ok);
...@@ -375,7 +374,6 @@ void moveCommand(redisClient *c) { ...@@ -375,7 +374,6 @@ void moveCommand(redisClient *c) {
} }
/* Try to add the element to the target DB */ /* Try to add the element to the target DB */
deleteIfVolatile(dst,c->argv[1]);
if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) { if (dbAdd(dst,c->argv[1],o) == REDIS_ERR) {
addReply(c,shared.czero); addReply(c,shared.czero);
return; return;
...@@ -396,23 +394,16 @@ int removeExpire(redisDb *db, robj *key) { ...@@ -396,23 +394,16 @@ int removeExpire(redisDb *db, robj *key) {
/* An expire may only be removed if there is a corresponding entry in the /* An expire may only be removed if there is a corresponding entry in the
* main dict. Otherwise, the key will never be freed. */ * main dict. Otherwise, the key will never be freed. */
redisAssert(dictFind(db->dict,key->ptr) != NULL); redisAssert(dictFind(db->dict,key->ptr) != NULL);
if (dictDelete(db->expires,key->ptr) == DICT_OK) { return dictDelete(db->expires,key->ptr) == DICT_OK;
return 1;
} else {
return 0;
}
} }
int setExpire(redisDb *db, robj *key, time_t when) { void setExpire(redisDb *db, robj *key, time_t when) {
dictEntry *de; dictEntry *de;
/* Reuse the sds from the main dict in the expire dict */ /* Reuse the sds from the main dict in the expire dict */
redisAssert((de = dictFind(db->dict,key->ptr)) != NULL); de = dictFind(db->dict,key->ptr);
if (dictAdd(db->expires,dictGetEntryKey(de),(void*)when) == DICT_ERR) { redisAssert(de != NULL);
return 0; dictReplace(db->expires,dictGetEntryKey(de),(void*)when);
} else {
return 1;
}
} }
/* Return the expire time of the specified key, or -1 if no expire /* Return the expire time of the specified key, or -1 if no expire
...@@ -430,8 +421,46 @@ time_t getExpire(redisDb *db, robj *key) { ...@@ -430,8 +421,46 @@ time_t getExpire(redisDb *db, robj *key) {
return (time_t) dictGetEntryVal(de); return (time_t) dictGetEntryVal(de);
} }
/* Propagate expires into slaves and the AOF file.
* When a key expires in the master, a DEL operation for this key is sent
* to all the slaves and the AOF file if enabled.
*
* This way the key expiry is centralized in one place, and since both
* AOF and the master->slave link guarantee operation ordering, everything
* will be consistent even if we allow write operations against expiring
* keys. */
void propagateExpire(redisDb *db, robj *key) {
struct redisCommand *cmd;
robj *argv[2];
cmd = lookupCommand("del");
argv[0] = createStringObject("DEL",3);
argv[1] = key;
incrRefCount(key);
if (server.appendonly)
feedAppendOnlyFile(cmd,db->id,argv,2);
if (listLength(server.slaves))
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}
int expireIfNeeded(redisDb *db, robj *key) { int expireIfNeeded(redisDb *db, robj *key) {
time_t when = getExpire(db,key); time_t when = getExpire(db,key);
/* If we are running in the context of a slave, return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
if (server.masterhost != NULL) {
return time(NULL) > when;
}
if (when < 0) return 0; if (when < 0) return 0;
/* Return when this key has not expired */ /* Return when this key has not expired */
...@@ -440,15 +469,7 @@ int expireIfNeeded(redisDb *db, robj *key) { ...@@ -440,15 +469,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
/* Delete the key */ /* Delete the key */
server.stat_expiredkeys++; server.stat_expiredkeys++;
server.dirty++; server.dirty++;
return dbDelete(db,key); propagateExpire(db,key);
}
int deleteIfVolatile(redisDb *db, robj *key) {
if (getExpire(db,key) < 0) return 0;
/* Delete the key */
server.stat_expiredkeys++;
server.dirty++;
return dbDelete(db,key); return dbDelete(db,key);
} }
...@@ -472,15 +493,14 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) { ...@@ -472,15 +493,14 @@ void expireGenericCommand(redisClient *c, robj *key, robj *param, long offset) {
if (seconds <= 0) { if (seconds <= 0) {
if (dbDelete(c->db,key)) server.dirty++; if (dbDelete(c->db,key)) server.dirty++;
addReply(c, shared.cone); addReply(c, shared.cone);
touchWatchedKey(c->db,key);
return; return;
} else { } else {
time_t when = time(NULL)+seconds; time_t when = time(NULL)+seconds;
if (setExpire(c->db,key,when)) { setExpire(c->db,key,when);
addReply(c,shared.cone); addReply(c,shared.cone);
server.dirty++; touchWatchedKey(c->db,key);
} else { server.dirty++;
addReply(c,shared.czero);
}
return; return;
} }
} }
...@@ -505,4 +525,18 @@ void ttlCommand(redisClient *c) { ...@@ -505,4 +525,18 @@ void ttlCommand(redisClient *c) {
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",ttl)); addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",ttl));
} }
void persistCommand(redisClient *c) {
dictEntry *de;
de = dictFind(c->db->dict,c->argv[1]->ptr);
if (de == NULL) {
addReply(c,shared.czero);
} else {
if (removeExpire(c->db,c->argv[1])) {
addReply(c,shared.cone);
server.dirty++;
} else {
addReply(c,shared.czero);
}
}
}
...@@ -52,33 +52,6 @@ ...@@ -52,33 +52,6 @@
* around when there is a child performing saving operations. */ * around when there is a child performing saving operations. */
static int dict_can_resize = 1; static int dict_can_resize = 1;
/* ---------------------------- Utility funcitons --------------------------- */
static void _dictPanic(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "\nDICT LIBRARY PANIC: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n\n");
va_end(ap);
}
/* ------------------------- Heap Management Wrappers------------------------ */
static void *_dictAlloc(size_t size)
{
void *p = zmalloc(size);
if (p == NULL)
_dictPanic("Out of memory");
return p;
}
static void _dictFree(void *ptr) {
zfree(ptr);
}
/* -------------------------- private prototypes ---------------------------- */ /* -------------------------- private prototypes ---------------------------- */
static int _dictExpandIfNeeded(dict *ht); static int _dictExpandIfNeeded(dict *ht);
...@@ -132,7 +105,7 @@ static void _dictReset(dictht *ht) ...@@ -132,7 +105,7 @@ static void _dictReset(dictht *ht)
dict *dictCreate(dictType *type, dict *dictCreate(dictType *type,
void *privDataPtr) void *privDataPtr)
{ {
dict *d = _dictAlloc(sizeof(*d)); dict *d = zmalloc(sizeof(*d));
_dictInit(d,type,privDataPtr); _dictInit(d,type,privDataPtr);
return d; return d;
...@@ -175,14 +148,12 @@ int dictExpand(dict *d, unsigned long size) ...@@ -175,14 +148,12 @@ int dictExpand(dict *d, unsigned long size)
if (dictIsRehashing(d) || d->ht[0].used > size) if (dictIsRehashing(d) || d->ht[0].used > size)
return DICT_ERR; return DICT_ERR;
/* Allocate the new hashtable and initialize all pointers to NULL */
n.size = realsize; n.size = realsize;
n.sizemask = realsize-1; n.sizemask = realsize-1;
n.table = _dictAlloc(realsize*sizeof(dictEntry*)); n.table = zcalloc(realsize*sizeof(dictEntry*));
n.used = 0; n.used = 0;
/* Initialize all the pointers to NULL */
memset(n.table, 0, realsize*sizeof(dictEntry*));
/* Is this the first initialization? If so it's not really a rehashing /* Is this the first initialization? If so it's not really a rehashing
* we just set the first hash table so that it can accept keys. */ * we just set the first hash table so that it can accept keys. */
if (d->ht[0].table == NULL) { if (d->ht[0].table == NULL) {
...@@ -208,7 +179,7 @@ int dictRehash(dict *d, int n) { ...@@ -208,7 +179,7 @@ int dictRehash(dict *d, int n) {
/* Check if we already rehashed the whole table... */ /* Check if we already rehashed the whole table... */
if (d->ht[0].used == 0) { if (d->ht[0].used == 0) {
_dictFree(d->ht[0].table); zfree(d->ht[0].table);
d->ht[0] = d->ht[1]; d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]); _dictReset(&d->ht[1]);
d->rehashidx = -1; d->rehashidx = -1;
...@@ -285,7 +256,7 @@ int dictAdd(dict *d, void *key, void *val) ...@@ -285,7 +256,7 @@ int dictAdd(dict *d, void *key, void *val)
/* Allocates the memory and stores key */ /* Allocates the memory and stores key */
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0]; ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
entry = _dictAlloc(sizeof(*entry)); entry = zmalloc(sizeof(*entry));
entry->next = ht->table[index]; entry->next = ht->table[index];
ht->table[index] = entry; ht->table[index] = entry;
ht->used++; ht->used++;
...@@ -348,7 +319,7 @@ static int dictGenericDelete(dict *d, const void *key, int nofree) ...@@ -348,7 +319,7 @@ static int dictGenericDelete(dict *d, const void *key, int nofree)
dictFreeEntryKey(d, he); dictFreeEntryKey(d, he);
dictFreeEntryVal(d, he); dictFreeEntryVal(d, he);
} }
_dictFree(he); zfree(he);
d->ht[table].used--; d->ht[table].used--;
return DICT_OK; return DICT_OK;
} }
...@@ -382,13 +353,13 @@ int _dictClear(dict *d, dictht *ht) ...@@ -382,13 +353,13 @@ int _dictClear(dict *d, dictht *ht)
nextHe = he->next; nextHe = he->next;
dictFreeEntryKey(d, he); dictFreeEntryKey(d, he);
dictFreeEntryVal(d, he); dictFreeEntryVal(d, he);
_dictFree(he); zfree(he);
ht->used--; ht->used--;
he = nextHe; he = nextHe;
} }
} }
/* Free the table and the allocated cache structure */ /* Free the table and the allocated cache structure */
_dictFree(ht->table); zfree(ht->table);
/* Re-initialize the table */ /* Re-initialize the table */
_dictReset(ht); _dictReset(ht);
return DICT_OK; /* never fails */ return DICT_OK; /* never fails */
...@@ -399,7 +370,7 @@ void dictRelease(dict *d) ...@@ -399,7 +370,7 @@ void dictRelease(dict *d)
{ {
_dictClear(d,&d->ht[0]); _dictClear(d,&d->ht[0]);
_dictClear(d,&d->ht[1]); _dictClear(d,&d->ht[1]);
_dictFree(d); zfree(d);
} }
dictEntry *dictFind(dict *d, const void *key) dictEntry *dictFind(dict *d, const void *key)
...@@ -432,7 +403,7 @@ void *dictFetchValue(dict *d, const void *key) { ...@@ -432,7 +403,7 @@ void *dictFetchValue(dict *d, const void *key) {
dictIterator *dictGetIterator(dict *d) dictIterator *dictGetIterator(dict *d)
{ {
dictIterator *iter = _dictAlloc(sizeof(*iter)); dictIterator *iter = zmalloc(sizeof(*iter));
iter->d = d; iter->d = d;
iter->table = 0; iter->table = 0;
...@@ -475,7 +446,7 @@ dictEntry *dictNext(dictIterator *iter) ...@@ -475,7 +446,7 @@ dictEntry *dictNext(dictIterator *iter)
void dictReleaseIterator(dictIterator *iter) void dictReleaseIterator(dictIterator *iter)
{ {
if (!(iter->index == -1 && iter->table == 0)) iter->d->iterators--; if (!(iter->index == -1 && iter->table == 0)) iter->d->iterators--;
_dictFree(iter); zfree(iter);
} }
/* Return a random entry from the hash table. Useful to /* Return a random entry from the hash table. Useful to
...@@ -644,6 +615,12 @@ void dictDisableResize(void) { ...@@ -644,6 +615,12 @@ void dictDisableResize(void) {
dict_can_resize = 0; dict_can_resize = 0;
} }
#if 0
/* The following are just example hash table types implementations.
* Not useful for Redis so they are commented out.
*/
/* ----------------------- StringCopy Hash Table Type ------------------------*/ /* ----------------------- StringCopy Hash Table Type ------------------------*/
static unsigned int _dictStringCopyHTHashFunction(const void *key) static unsigned int _dictStringCopyHTHashFunction(const void *key)
...@@ -651,10 +628,10 @@ static unsigned int _dictStringCopyHTHashFunction(const void *key) ...@@ -651,10 +628,10 @@ static unsigned int _dictStringCopyHTHashFunction(const void *key)
return dictGenHashFunction(key, strlen(key)); return dictGenHashFunction(key, strlen(key));
} }
static void *_dictStringCopyHTKeyDup(void *privdata, const void *key) static void *_dictStringDup(void *privdata, const void *key)
{ {
int len = strlen(key); int len = strlen(key);
char *copy = _dictAlloc(len+1); char *copy = zmalloc(len+1);
DICT_NOTUSED(privdata); DICT_NOTUSED(privdata);
memcpy(copy, key, len); memcpy(copy, key, len);
...@@ -662,17 +639,6 @@ static void *_dictStringCopyHTKeyDup(void *privdata, const void *key) ...@@ -662,17 +639,6 @@ static void *_dictStringCopyHTKeyDup(void *privdata, const void *key)
return copy; return copy;
} }
static void *_dictStringKeyValCopyHTValDup(void *privdata, const void *val)
{
int len = strlen(val);
char *copy = _dictAlloc(len+1);
DICT_NOTUSED(privdata);
memcpy(copy, val, len);
copy[len] = '\0';
return copy;
}
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1, static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
const void *key2) const void *key2)
{ {
...@@ -681,47 +647,41 @@ static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1, ...@@ -681,47 +647,41 @@ static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
return strcmp(key1, key2) == 0; return strcmp(key1, key2) == 0;
} }
static void _dictStringCopyHTKeyDestructor(void *privdata, void *key) static void _dictStringDestructor(void *privdata, void *key)
{
DICT_NOTUSED(privdata);
_dictFree((void*)key); /* ATTENTION: const cast */
}
static void _dictStringKeyValCopyHTValDestructor(void *privdata, void *val)
{ {
DICT_NOTUSED(privdata); DICT_NOTUSED(privdata);
_dictFree((void*)val); /* ATTENTION: const cast */ zfree(key);
} }
dictType dictTypeHeapStringCopyKey = { dictType dictTypeHeapStringCopyKey = {
_dictStringCopyHTHashFunction, /* hash function */ _dictStringCopyHTHashFunction, /* hash function */
_dictStringCopyHTKeyDup, /* key dup */ _dictStringDup, /* key dup */
NULL, /* val dup */ NULL, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */ _dictStringCopyHTKeyCompare, /* key compare */
_dictStringCopyHTKeyDestructor, /* key destructor */ _dictStringDestructor, /* key destructor */
NULL /* val destructor */ NULL /* val destructor */
}; };
/* This is like StringCopy but does not auto-duplicate the key. /* This is like StringCopy but does not auto-duplicate the key.
* It's used for intepreter's shared strings. */ * It's used for intepreter's shared strings. */
dictType dictTypeHeapStrings = { dictType dictTypeHeapStrings = {
_dictStringCopyHTHashFunction, /* hash function */ _dictStringCopyHTHashFunction, /* hash function */
NULL, /* key dup */ NULL, /* key dup */
NULL, /* val dup */ NULL, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */ _dictStringCopyHTKeyCompare, /* key compare */
_dictStringCopyHTKeyDestructor, /* key destructor */ _dictStringDestructor, /* key destructor */
NULL /* val destructor */ NULL /* val destructor */
}; };
/* This is like StringCopy but also automatically handle dynamic /* This is like StringCopy but also automatically handle dynamic
* allocated C strings as values. */ * allocated C strings as values. */
dictType dictTypeHeapStringCopyKeyValue = { dictType dictTypeHeapStringCopyKeyValue = {
_dictStringCopyHTHashFunction, /* hash function */ _dictStringCopyHTHashFunction, /* hash function */
_dictStringCopyHTKeyDup, /* key dup */ _dictStringDup, /* key dup */
_dictStringKeyValCopyHTValDup, /* val dup */ _dictStringDup, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */ _dictStringCopyHTKeyCompare, /* key compare */
_dictStringCopyHTKeyDestructor, /* key destructor */ _dictStringDestructor, /* key destructor */
_dictStringKeyValCopyHTValDestructor, /* val destructor */ _dictStringDestructor, /* val destructor */
}; };
#endif
...@@ -70,6 +70,7 @@ ...@@ -70,6 +70,7 @@
*/ */
#include "fmacros.h" #include "fmacros.h"
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h> #include <stdlib.h>
...@@ -81,13 +82,14 @@ ...@@ -81,13 +82,14 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096 #define LINENOISE_MAX_LINE 4096
static char *unsupported_term[] = {"dumb","cons25",NULL}; static char *unsupported_term[] = {"dumb","cons25",NULL};
static struct termios orig_termios; /* in order to restore at exit */ static struct termios orig_termios; /* in order to restore at exit */
static int rawmode = 0; /* for atexit() function to check if restore is needed*/ static int rawmode = 0; /* for atexit() function to check if restore is needed*/
static int atexit_registered = 0; /* register atexit just 1 time */ static int atexit_registered = 0; /* register atexit just 1 time */
static int history_max_len = 100; static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
static int history_len = 0; static int history_len = 0;
char **history = NULL; char **history = NULL;
...@@ -219,11 +221,10 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) ...@@ -219,11 +221,10 @@ static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt)
if (nread <= 0) return len; if (nread <= 0) return len;
switch(c) { switch(c) {
case 13: /* enter */ case 13: /* enter */
history_len--;
return len;
case 4: /* ctrl-d */ case 4: /* ctrl-d */
history_len--; history_len--;
return (len == 0) ? -1 : (int)len; free(history[history_len]);
return (len == 0 && c == 4) ? -1 : (int)len;
case 3: /* ctrl-c */ case 3: /* ctrl-c */
errno = EAGAIN; errno = EAGAIN;
return -1; return -1;
...@@ -396,7 +397,7 @@ int linenoiseHistoryAdd(const char *line) { ...@@ -396,7 +397,7 @@ int linenoiseHistoryAdd(const char *line) {
char *linecopy; char *linecopy;
if (history_max_len == 0) return 0; if (history_max_len == 0) return 0;
if (history == 0) { if (history == NULL) {
history = malloc(sizeof(char*)*history_max_len); history = malloc(sizeof(char*)*history_max_len);
if (history == NULL) return 0; if (history == NULL) return 0;
memset(history,0,(sizeof(char*)*history_max_len)); memset(history,0,(sizeof(char*)*history_max_len));
...@@ -404,6 +405,7 @@ int linenoiseHistoryAdd(const char *line) { ...@@ -404,6 +405,7 @@ int linenoiseHistoryAdd(const char *line) {
linecopy = strdup(line); linecopy = strdup(line);
if (!linecopy) return 0; if (!linecopy) return 0;
if (history_len == history_max_len) { if (history_len == history_max_len) {
free(history[0]);
memmove(history,history+1,sizeof(char*)*(history_max_len-1)); memmove(history,history+1,sizeof(char*)*(history_max_len-1));
history_len--; history_len--;
} }
...@@ -431,3 +433,39 @@ int linenoiseHistorySetMaxLen(int len) { ...@@ -431,3 +433,39 @@ int linenoiseHistorySetMaxLen(int len) {
history_len = history_max_len; history_len = history_max_len;
return 1; return 1;
} }
/* Save the history in the specified file. On success 0 is returned
* otherwise -1 is returned. */
int linenoiseHistorySave(char *filename) {
FILE *fp = fopen(filename,"w");
int j;
if (fp == NULL) return -1;
for (j = 0; j < history_len; j++)
fprintf(fp,"%s\n",history[j]);
fclose(fp);
return 0;
}
/* Load the history from the specified file. If the file does not exist
* zero is returned and no operation is performed.
*
* If the file exists and the operation succeeded 0 is returned, otherwise
* on error -1 is returned. */
int linenoiseHistoryLoad(char *filename) {
FILE *fp = fopen(filename,"r");
char buf[LINENOISE_MAX_LINE];
if (fp == NULL) return -1;
while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
char *p;
p = strchr(buf,'\r');
if (!p) p = strchr(buf,'\n');
if (p) *p = '\0';
linenoiseHistoryAdd(buf);
}
fclose(fp);
return 0;
}
...@@ -35,7 +35,9 @@ ...@@ -35,7 +35,9 @@
#define __LINENOISE_H #define __LINENOISE_H
char *linenoise(const char *prompt); char *linenoise(const char *prompt);
int linenoiseHistoryAdd(char *line); int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len); int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(char *filename);
int linenoiseHistoryLoad(char *filename);
#endif /* __LINENOISE_H */ #endif /* __LINENOISE_H */
...@@ -235,19 +235,24 @@ void freeClient(redisClient *c) { ...@@ -235,19 +235,24 @@ void freeClient(redisClient *c) {
ln = listSearchKey(server.clients,c); ln = listSearchKey(server.clients,c);
redisAssert(ln != NULL); redisAssert(ln != NULL);
listDelNode(server.clients,ln); listDelNode(server.clients,ln);
/* Remove from the list of clients that are now ready to be restarted /* Remove from the list of clients waiting for swapped keys, or ready
* after waiting for swapped keys */ * to be restarted, but not yet woken up again. */
if (c->flags & REDIS_IO_WAIT && listLength(c->io_keys) == 0) { if (c->flags & REDIS_IO_WAIT) {
ln = listSearchKey(server.io_ready_clients,c); redisAssert(server.vm_enabled);
if (ln) { if (listLength(c->io_keys) == 0) {
ln = listSearchKey(server.io_ready_clients,c);
/* When this client is waiting to be woken up (REDIS_IO_WAIT),
* it should be present in the list io_ready_clients */
redisAssert(ln != NULL);
listDelNode(server.io_ready_clients,ln); listDelNode(server.io_ready_clients,ln);
server.vm_blocked_clients--; } else {
while (listLength(c->io_keys)) {
ln = listFirst(c->io_keys);
dontWaitForSwappedKey(c,ln->value);
}
} }
} server.vm_blocked_clients--;
/* Remove from the list of clients waiting for swapped keys */
while (server.vm_enabled && listLength(c->io_keys)) {
ln = listFirst(c->io_keys);
dontWaitForSwappedKey(c,ln->value);
} }
listRelease(c->io_keys); listRelease(c->io_keys);
/* Master/slave cleanup */ /* Master/slave cleanup */
......
#include "redis.h" #include "redis.h"
#include <pthread.h> #include <pthread.h>
#include <math.h>
robj *createObject(int type, void *ptr) { robj *createObject(int type, void *ptr) {
robj *o; robj *o;
...@@ -11,8 +12,7 @@ robj *createObject(int type, void *ptr) { ...@@ -11,8 +12,7 @@ robj *createObject(int type, void *ptr) {
listDelNode(server.objfreelist,head); listDelNode(server.objfreelist,head);
if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex); if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex);
} else { } else {
if (server.vm_enabled) if (server.vm_enabled) pthread_mutex_unlock(&server.obj_freelist_mutex);
pthread_mutex_unlock(&server.obj_freelist_mutex);
o = zmalloc(sizeof(*o)); o = zmalloc(sizeof(*o));
} }
o->type = type; o->type = type;
...@@ -36,7 +36,8 @@ robj *createStringObject(char *ptr, size_t len) { ...@@ -36,7 +36,8 @@ robj *createStringObject(char *ptr, size_t len) {
robj *createStringObjectFromLongLong(long long value) { robj *createStringObjectFromLongLong(long long value) {
robj *o; robj *o;
if (value >= 0 && value < REDIS_SHARED_INTEGERS) { if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
pthread_equal(pthread_self(),server.mainthread)) {
incrRefCount(shared.integers[value]); incrRefCount(shared.integers[value]);
o = shared.integers[value]; o = shared.integers[value];
} else { } else {
...@@ -197,6 +198,7 @@ void decrRefCount(void *obj) { ...@@ -197,6 +198,7 @@ void decrRefCount(void *obj) {
case REDIS_HASH: freeHashObject(o); break; case REDIS_HASH: freeHashObject(o); break;
default: redisPanic("Unknown object type"); break; default: redisPanic("Unknown object type"); break;
} }
o->ptr = NULL; /* defensive programming. We'll see NULL in traces. */
if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex); if (server.vm_enabled) pthread_mutex_lock(&server.obj_freelist_mutex);
if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX || if (listLength(server.objfreelist) > REDIS_OBJFREELIST_MAX ||
!listAddNodeHead(server.objfreelist,o)) !listAddNodeHead(server.objfreelist,o))
...@@ -232,8 +234,15 @@ robj *tryObjectEncoding(robj *o) { ...@@ -232,8 +234,15 @@ robj *tryObjectEncoding(robj *o) {
/* Check if we can represent this string as a long integer */ /* Check if we can represent this string as a long integer */
if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o; if (isStringRepresentableAsLong(s,&value) == REDIS_ERR) return o;
/* Ok, this object can be encoded */ /* Ok, this object can be encoded...
if (value >= 0 && value < REDIS_SHARED_INTEGERS) { *
* Can I use a shared object? Only if the object is inside a given
* range and if this is the main thread, since when VM is enabled we
* have the constraint that I/O thread should only handle non-shared
* objects, in order to avoid race conditions (we don't have per-object
* locking). */
if (value >= 0 && value < REDIS_SHARED_INTEGERS &&
pthread_equal(pthread_self(),server.mainthread)) {
decrRefCount(o); decrRefCount(o);
incrRefCount(shared.integers[value]); incrRefCount(shared.integers[value]);
return shared.integers[value]; return shared.integers[value];
...@@ -329,7 +338,7 @@ int getDoubleFromObject(robj *o, double *target) { ...@@ -329,7 +338,7 @@ int getDoubleFromObject(robj *o, double *target) {
redisAssert(o->type == REDIS_STRING); redisAssert(o->type == REDIS_STRING);
if (o->encoding == REDIS_ENCODING_RAW) { if (o->encoding == REDIS_ENCODING_RAW) {
value = strtod(o->ptr, &eptr); value = strtod(o->ptr, &eptr);
if (eptr[0] != '\0') return REDIS_ERR; if (eptr[0] != '\0' || isnan(value)) return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) { } else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr; value = (long)o->ptr;
} else { } else {
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
*/ */
#include "fmacros.h" #include "fmacros.h"
#include "version.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
...@@ -60,6 +61,7 @@ static struct config { ...@@ -60,6 +61,7 @@ static struct config {
int pubsub_mode; int pubsub_mode;
int raw_output; int raw_output;
char *auth; char *auth;
char *historyfile;
} config; } config;
static int cliReadReply(int fd); static int cliReadReply(int fd);
...@@ -315,6 +317,9 @@ static int parseOptions(int argc, char **argv) { ...@@ -315,6 +317,9 @@ static int parseOptions(int argc, char **argv) {
config.interactive = 1; config.interactive = 1;
} else if (!strcmp(argv[i],"-c")) { } else if (!strcmp(argv[i],"-c")) {
config.argn_from_stdin = 1; config.argn_from_stdin = 1;
} else if (!strcmp(argv[i],"-v")) {
printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION);
exit(0);
} else { } else {
break; break;
} }
...@@ -340,7 +345,7 @@ static sds readArgFromStdin(void) { ...@@ -340,7 +345,7 @@ static sds readArgFromStdin(void) {
} }
static void usage() { static void usage() {
fprintf(stderr, "usage: redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] [-i] cmd arg1 arg2 arg3 ... argN\n"); fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n"); fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n"); fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n"); fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
...@@ -361,80 +366,17 @@ static char **convertToSds(int count, char** args) { ...@@ -361,80 +366,17 @@ static char **convertToSds(int count, char** args) {
return sds; return sds;
} }
static char **splitArguments(char *line, int *argc) {
char *p = line;
char *current = NULL;
char **vector = NULL;
*argc = 0;
while(1) {
/* skip blanks */
while(*p && isspace(*p)) p++;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
int done = 0;
if (current == NULL) current = sdsempty();
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1)) {
char c;
p++;
switch(*p) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'b': c = '\b'; break;
case 'a': c = '\a'; break;
default: c = *p; break;
}
current = sdscatlen(current,&c,1);
} else if (*p == '"') {
done = 1;
} else {
current = sdscatlen(current,p,1);
}
} else {
switch(*p) {
case ' ':
case '\n':
case '\r':
case '\t':
case '\0':
done=1;
break;
case '"':
inq=1;
break;
default:
current = sdscatlen(current,p,1);
break;
}
}
if (*p) p++;
}
/* add the token to the vector */
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
vector[*argc] = current;
(*argc)++;
current = NULL;
} else {
return vector;
}
}
}
#define LINE_BUFLEN 4096 #define LINE_BUFLEN 4096
static void repl() { static void repl() {
int argc, j; int argc, j;
char *line, **argv; char *line;
sds *argv;
while((line = linenoise("redis> ")) != NULL) { while((line = linenoise("redis> ")) != NULL) {
if (line[0] != '\0') { if (line[0] != '\0') {
argv = splitArguments(line,&argc); argv = sdssplitargs(line,&argc);
linenoiseHistoryAdd(line); linenoiseHistoryAdd(line);
if (config.historyfile) linenoiseHistorySave(config.historyfile);
if (argc > 0) { if (argc > 0) {
if (strcasecmp(argv[0],"quit") == 0 || if (strcasecmp(argv[0],"quit") == 0 ||
strcasecmp(argv[0],"exit") == 0) strcasecmp(argv[0],"exit") == 0)
...@@ -468,6 +410,13 @@ int main(int argc, char **argv) { ...@@ -468,6 +410,13 @@ int main(int argc, char **argv) {
config.pubsub_mode = 0; config.pubsub_mode = 0;
config.raw_output = 0; config.raw_output = 0;
config.auth = NULL; config.auth = NULL;
config.historyfile = NULL;
if (getenv("HOME") != NULL) {
config.historyfile = malloc(256);
snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
linenoiseHistoryLoad(config.historyfile);
}
firstarg = parseOptions(argc,argv); firstarg = parseOptions(argc,argv);
argc -= firstarg; argc -= firstarg;
......
...@@ -74,6 +74,7 @@ struct redisCommand readonlyCommandTable[] = { ...@@ -74,6 +74,7 @@ struct redisCommand readonlyCommandTable[] = {
{"setex",setexCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, {"setex",setexCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0},
{"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, {"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1},
{"substr",substrCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, {"substr",substrCommand,4,REDIS_CMD_INLINE,NULL,1,1,1},
{"strlen",strlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, {"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0},
{"exists",existsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, {"exists",existsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1},
...@@ -169,6 +170,7 @@ struct redisCommand readonlyCommandTable[] = { ...@@ -169,6 +170,7 @@ struct redisCommand readonlyCommandTable[] = {
{"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, {"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
{"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, {"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0},
{"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, {"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"persist",persistCommand,2,REDIS_CMD_INLINE,NULL,1,1,1},
{"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, {"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0},
{"debug",debugCommand,-2,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}, {"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0},
...@@ -186,23 +188,22 @@ struct redisCommand readonlyCommandTable[] = { ...@@ -186,23 +188,22 @@ struct redisCommand readonlyCommandTable[] = {
void redisLog(int level, const char *fmt, ...) { void redisLog(int level, const char *fmt, ...) {
va_list ap; va_list ap;
FILE *fp; FILE *fp;
char *c = ".-*#";
char buf[64];
time_t now;
if (level < server.verbosity) return;
fp = (server.logfile == NULL) ? stdout : fopen(server.logfile,"a"); fp = (server.logfile == NULL) ? stdout : fopen(server.logfile,"a");
if (!fp) return; if (!fp) return;
va_start(ap, fmt); va_start(ap, fmt);
if (level >= server.verbosity) { now = time(NULL);
char *c = ".-*#"; strftime(buf,64,"%d %b %H:%M:%S",localtime(&now));
char buf[64]; fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
time_t now; vfprintf(fp, fmt, ap);
fprintf(fp,"\n");
now = time(NULL); fflush(fp);
strftime(buf,64,"%d %b %H:%M:%S",localtime(&now));
fprintf(fp,"[%d] %s %c ",(int)getpid(),buf,c[level]);
vfprintf(fp, fmt, ap);
fprintf(fp,"\n");
fflush(fp);
}
va_end(ap); va_end(ap);
if (server.logfile) fclose(fp); if (server.logfile) fclose(fp);
...@@ -435,6 +436,48 @@ void updateDictResizePolicy(void) { ...@@ -435,6 +436,48 @@ void updateDictResizePolicy(void) {
/* ======================= Cron: called every 100 ms ======================== */ /* ======================= Cron: called every 100 ms ======================== */
/* Try to expire a few timed out keys. The algorithm used is adaptive and
* will use few CPU cycles if there are few expiring keys, otherwise
* it will get more aggressive to avoid that too much memory is used by
* keys that can be removed from the keyspace. */
void activeExpireCycle(void) {
int j;
for (j = 0; j < server.dbnum; j++) {
int expired;
redisDb *db = server.db+j;
/* Continue to expire if at the end of the cycle more than 25%
* of the keys were expired. */
do {
long num = dictSize(db->expires);
time_t now = time(NULL);
expired = 0;
if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
num = REDIS_EXPIRELOOKUPS_PER_CRON;
while (num--) {
dictEntry *de;
time_t t;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
t = (time_t) dictGetEntryVal(de);
if (now > t) {
sds key = dictGetEntryKey(de);
robj *keyobj = createStringObject(key,sdslen(key));
propagateExpire(db,keyobj);
dbDelete(db,keyobj);
decrRefCount(keyobj);
expired++;
server.stat_expiredkeys++;
}
}
} while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
}
}
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
int j, loops = server.cronloops++; int j, loops = server.cronloops++;
REDIS_NOTUSED(eventLoop); REDIS_NOTUSED(eventLoop);
...@@ -533,41 +576,10 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -533,41 +576,10 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
} }
} }
/* Try to expire a few timed out keys. The algorithm used is adaptive and /* Expire a few keys per cycle, only if this is a master.
* will use few CPU cycles if there are few expiring keys, otherwise * On slaves we wait for DEL operations synthesized by the master
* it will get more aggressive to avoid that too much memory is used by * in order to guarantee a strict consistency. */
* keys that can be removed from the keyspace. */ if (server.masterhost == NULL) activeExpireCycle();
for (j = 0; j < server.dbnum; j++) {
int expired;
redisDb *db = server.db+j;
/* Continue to expire if at the end of the cycle more than 25%
* of the keys were expired. */
do {
long num = dictSize(db->expires);
time_t now = time(NULL);
expired = 0;
if (num > REDIS_EXPIRELOOKUPS_PER_CRON)
num = REDIS_EXPIRELOOKUPS_PER_CRON;
while (num--) {
dictEntry *de;
time_t t;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
t = (time_t) dictGetEntryVal(de);
if (now > t) {
sds key = dictGetEntryKey(de);
robj *keyobj = createStringObject(key,sdslen(key));
dbDelete(db,keyobj);
decrRefCount(keyobj);
expired++;
server.stat_expiredkeys++;
}
}
} while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);
}
/* Swap a few keys on disk if we are over the memory limit and VM /* Swap a few keys on disk if we are over the memory limit and VM
* is enbled. Try to free objects from the free list first. */ * is enbled. Try to free objects from the free list first. */
...@@ -761,6 +773,7 @@ void initServer() { ...@@ -761,6 +773,7 @@ void initServer() {
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
setupSigSegvAction(); setupSigSegvAction();
server.mainthread = pthread_self();
server.devnull = fopen("/dev/null","w"); server.devnull = fopen("/dev/null","w");
if (server.devnull == NULL) { if (server.devnull == NULL) {
redisLog(REDIS_WARNING, "Can't open /dev/null: %s", server.neterr); redisLog(REDIS_WARNING, "Can't open /dev/null: %s", server.neterr);
...@@ -827,7 +840,7 @@ int qsortRedisCommands(const void *r1, const void *r2) { ...@@ -827,7 +840,7 @@ int qsortRedisCommands(const void *r1, const void *r2) {
void sortCommandTable() { void sortCommandTable() {
/* Copy and sort the read-only version of the command table */ /* Copy and sort the read-only version of the command table */
commandTable = (struct redisCommand*)malloc(sizeof(readonlyCommandTable)); commandTable = (struct redisCommand*)zmalloc(sizeof(readonlyCommandTable));
memcpy(commandTable,readonlyCommandTable,sizeof(readonlyCommandTable)); memcpy(commandTable,readonlyCommandTable,sizeof(readonlyCommandTable));
qsort(commandTable, qsort(commandTable,
sizeof(readonlyCommandTable)/sizeof(struct redisCommand), sizeof(readonlyCommandTable)/sizeof(struct redisCommand),
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <pthread.h>
#include "ae.h" /* Event driven programming library */ #include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */ #include "sds.h" /* Dynamic safe strings */
...@@ -329,6 +330,7 @@ struct sharedObjectsStruct { ...@@ -329,6 +330,7 @@ struct sharedObjectsStruct {
/* Global server state structure */ /* Global server state structure */
struct redisServer { struct redisServer {
pthread_t mainthread;
int port; int port;
int fd; int fd;
redisDb *db; redisDb *db;
...@@ -775,10 +777,10 @@ void resetServerSaveParams(); ...@@ -775,10 +777,10 @@ void resetServerSaveParams();
/* db.c -- Keyspace access API */ /* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key); int removeExpire(redisDb *db, robj *key);
void propagateExpire(redisDb *db, robj *key);
int expireIfNeeded(redisDb *db, robj *key); int expireIfNeeded(redisDb *db, robj *key);
int deleteIfVolatile(redisDb *db, robj *key);
time_t getExpire(redisDb *db, robj *key); time_t getExpire(redisDb *db, robj *key);
int setExpire(redisDb *db, robj *key, time_t when); void setExpire(redisDb *db, robj *key, time_t when);
robj *lookupKey(redisDb *db, robj *key); robj *lookupKey(redisDb *db, robj *key);
robj *lookupKeyRead(redisDb *db, robj *key); robj *lookupKeyRead(redisDb *db, robj *key);
robj *lookupKeyWrite(redisDb *db, robj *key); robj *lookupKeyWrite(redisDb *db, robj *key);
...@@ -861,6 +863,7 @@ void expireCommand(redisClient *c); ...@@ -861,6 +863,7 @@ void expireCommand(redisClient *c);
void expireatCommand(redisClient *c); void expireatCommand(redisClient *c);
void getsetCommand(redisClient *c); void getsetCommand(redisClient *c);
void ttlCommand(redisClient *c); void ttlCommand(redisClient *c);
void persistCommand(redisClient *c);
void slaveofCommand(redisClient *c); void slaveofCommand(redisClient *c);
void debugCommand(redisClient *c); void debugCommand(redisClient *c);
void msetCommand(redisClient *c); void msetCommand(redisClient *c);
...@@ -882,6 +885,7 @@ void blpopCommand(redisClient *c); ...@@ -882,6 +885,7 @@ void blpopCommand(redisClient *c);
void brpopCommand(redisClient *c); void brpopCommand(redisClient *c);
void appendCommand(redisClient *c); void appendCommand(redisClient *c);
void substrCommand(redisClient *c); void substrCommand(redisClient *c);
void strlenCommand(redisClient *c);
void zrankCommand(redisClient *c); void zrankCommand(redisClient *c);
void zrevrankCommand(redisClient *c); void zrevrankCommand(redisClient *c);
void hsetCommand(redisClient *c); void hsetCommand(redisClient *c);
...@@ -908,4 +912,11 @@ void publishCommand(redisClient *c); ...@@ -908,4 +912,11 @@ void publishCommand(redisClient *c);
void watchCommand(redisClient *c); void watchCommand(redisClient *c);
void unwatchCommand(redisClient *c); void unwatchCommand(redisClient *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
void free(void *ptr) __attribute__ ((deprecated));
void *malloc(size_t size) __attribute__ ((deprecated));
void *realloc(void *ptr, size_t size) __attribute__ ((deprecated));
#endif
#endif #endif
...@@ -382,3 +382,80 @@ sds sdscatrepr(sds s, char *p, size_t len) { ...@@ -382,3 +382,80 @@ sds sdscatrepr(sds s, char *p, size_t len) {
} }
return sdscatlen(s,"\"",1); return sdscatlen(s,"\"",1);
} }
/* Split a line into arguments, where every argument can be in the
* following programming-language REPL-alike form:
*
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
*
* The number of arguments is stored into *argc, and an array
* of sds is returned. The caller should sdsfree() all the returned
* strings and finally zfree() the array itself.
*
* Note that sdscatrepr() is able to convert back a string into
* a quoted string in the same format sdssplitargs() is able to parse.
*/
sds *sdssplitargs(char *line, int *argc) {
char *p = line;
char *current = NULL;
char **vector = NULL;
*argc = 0;
while(1) {
/* skip blanks */
while(*p && isspace(*p)) p++;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
int done = 0;
if (current == NULL) current = sdsempty();
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1)) {
char c;
p++;
switch(*p) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'b': c = '\b'; break;
case 'a': c = '\a'; break;
default: c = *p; break;
}
current = sdscatlen(current,&c,1);
} else if (*p == '"') {
done = 1;
} else {
current = sdscatlen(current,p,1);
}
} else {
switch(*p) {
case ' ':
case '\n':
case '\r':
case '\t':
case '\0':
done=1;
break;
case '"':
inq=1;
break;
default:
current = sdscatlen(current,p,1);
break;
}
}
if (*p) p++;
}
/* add the token to the vector */
vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
vector[*argc] = current;
(*argc)++;
current = NULL;
} else {
return vector;
}
}
}
...@@ -70,5 +70,6 @@ void sdstolower(sds s); ...@@ -70,5 +70,6 @@ void sdstolower(sds s);
void sdstoupper(sds s); void sdstoupper(sds s);
sds sdsfromlonglong(long long value); sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, char *p, size_t len); sds sdscatrepr(sds s, char *p, size_t len);
sds *sdssplitargs(char *line, int *argc);
#endif #endif
...@@ -364,6 +364,7 @@ void sortCommand(redisClient *c) { ...@@ -364,6 +364,7 @@ void sortCommand(redisClient *c) {
* SORT result is empty a new key is set and maybe the old content * SORT result is empty a new key is set and maybe the old content
* replaced. */ * replaced. */
server.dirty += 1+outputlen; server.dirty += 1+outputlen;
touchWatchedKey(c->db,storekey);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",outputlen)); addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",outputlen));
} }
......
...@@ -224,6 +224,7 @@ void hsetCommand(redisClient *c) { ...@@ -224,6 +224,7 @@ void hsetCommand(redisClient *c) {
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
update = hashTypeSet(o,c->argv[2],c->argv[3]); update = hashTypeSet(o,c->argv[2],c->argv[3]);
addReply(c, update ? shared.czero : shared.cone); addReply(c, update ? shared.czero : shared.cone);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
...@@ -238,6 +239,7 @@ void hsetnxCommand(redisClient *c) { ...@@ -238,6 +239,7 @@ void hsetnxCommand(redisClient *c) {
hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
hashTypeSet(o,c->argv[2],c->argv[3]); hashTypeSet(o,c->argv[2],c->argv[3]);
addReply(c, shared.cone); addReply(c, shared.cone);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
} }
...@@ -258,6 +260,7 @@ void hmsetCommand(redisClient *c) { ...@@ -258,6 +260,7 @@ void hmsetCommand(redisClient *c) {
hashTypeSet(o,c->argv[i],c->argv[i+1]); hashTypeSet(o,c->argv[i],c->argv[i+1]);
} }
addReply(c, shared.ok); addReply(c, shared.ok);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
...@@ -284,6 +287,7 @@ void hincrbyCommand(redisClient *c) { ...@@ -284,6 +287,7 @@ void hincrbyCommand(redisClient *c) {
hashTypeSet(o,c->argv[2],new); hashTypeSet(o,c->argv[2],new);
decrRefCount(new); decrRefCount(new);
addReplyLongLong(c,value); addReplyLongLong(c,value);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
...@@ -330,6 +334,7 @@ void hdelCommand(redisClient *c) { ...@@ -330,6 +334,7 @@ void hdelCommand(redisClient *c) {
if (hashTypeDelete(o,c->argv[2])) { if (hashTypeDelete(o,c->argv[2])) {
if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
addReply(c,shared.cone); addReply(c,shared.cone);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} else { } else {
addReply(c,shared.czero); addReply(c,shared.czero);
......
...@@ -273,12 +273,14 @@ void pushGenericCommand(redisClient *c, int where) { ...@@ -273,12 +273,14 @@ void pushGenericCommand(redisClient *c, int where) {
return; return;
} }
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
touchWatchedKey(c->db,c->argv[1]);
addReply(c,shared.cone); addReply(c,shared.cone);
return; return;
} }
} }
listTypePush(lobj,c->argv[2],where); listTypePush(lobj,c->argv[2],where);
addReplyLongLong(c,listTypeLength(lobj)); addReplyLongLong(c,listTypeLength(lobj));
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
...@@ -327,6 +329,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { ...@@ -327,6 +329,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
if (subject->encoding == REDIS_ENCODING_ZIPLIST && if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) > server.list_max_ziplist_entries) ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST); listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} else { } else {
/* Notify client of a failed insert */ /* Notify client of a failed insert */
...@@ -335,6 +338,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { ...@@ -335,6 +338,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
} }
} else { } else {
listTypePush(subject,val,where); listTypePush(subject,val,where);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
...@@ -419,6 +423,7 @@ void lsetCommand(redisClient *c) { ...@@ -419,6 +423,7 @@ void lsetCommand(redisClient *c) {
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr)); o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
decrRefCount(value); decrRefCount(value);
addReply(c,shared.ok); addReply(c,shared.ok);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
...@@ -430,6 +435,7 @@ void lsetCommand(redisClient *c) { ...@@ -430,6 +435,7 @@ void lsetCommand(redisClient *c) {
listNodeValue(ln) = value; listNodeValue(ln) = value;
incrRefCount(value); incrRefCount(value);
addReply(c,shared.ok); addReply(c,shared.ok);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
} else { } else {
...@@ -448,6 +454,7 @@ void popGenericCommand(redisClient *c, int where) { ...@@ -448,6 +454,7 @@ void popGenericCommand(redisClient *c, int where) {
addReplyBulk(c,value); addReplyBulk(c,value);
decrRefCount(value); decrRefCount(value);
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
} }
...@@ -476,11 +483,10 @@ void lrangeCommand(redisClient *c) { ...@@ -476,11 +483,10 @@ void lrangeCommand(redisClient *c) {
if (start < 0) start = llen+start; if (start < 0) start = llen+start;
if (end < 0) end = llen+end; if (end < 0) end = llen+end;
if (start < 0) start = 0; if (start < 0) start = 0;
if (end < 0) end = 0;
/* indexes sanity checks */ /* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */
if (start > end || start >= llen) { if (start > end || start >= llen) {
/* Out of range start or start > end result in empty list */
addReply(c,shared.emptymultibulk); addReply(c,shared.emptymultibulk);
return; return;
} }
...@@ -516,9 +522,9 @@ void ltrimCommand(redisClient *c) { ...@@ -516,9 +522,9 @@ void ltrimCommand(redisClient *c) {
if (start < 0) start = llen+start; if (start < 0) start = llen+start;
if (end < 0) end = llen+end; if (end < 0) end = llen+end;
if (start < 0) start = 0; if (start < 0) start = 0;
if (end < 0) end = 0;
/* indexes sanity checks */ /* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */
if (start > end || start >= llen) { if (start > end || start >= llen) {
/* Out of range start or start > end result in empty list */ /* Out of range start or start > end result in empty list */
ltrim = llen; ltrim = llen;
...@@ -547,6 +553,7 @@ void ltrimCommand(redisClient *c) { ...@@ -547,6 +553,7 @@ void ltrimCommand(redisClient *c) {
redisPanic("Unknown list encoding"); redisPanic("Unknown list encoding");
} }
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
addReply(c,shared.ok); addReply(c,shared.ok);
} }
...@@ -588,6 +595,7 @@ void lremCommand(redisClient *c) { ...@@ -588,6 +595,7 @@ void lremCommand(redisClient *c) {
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]); if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed)); addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed));
if (removed) touchWatchedKey(c->db,c->argv[1]);
} }
/* This is the semantic of this command: /* This is the semantic of this command:
...@@ -636,6 +644,7 @@ void rpoplpushcommand(redisClient *c) { ...@@ -636,6 +644,7 @@ void rpoplpushcommand(redisClient *c) {
/* Delete the source list when it is empty */ /* Delete the source list when it is empty */
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]); if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++; server.dirty++;
} }
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment