all: redis-server redis-benchmark redis-cli redis-check-dump redis-check-aof
+
# Deps (use make dep to generate this)
adlist.o: adlist.c adlist.h zmalloc.h
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
ae_kqueue.o: ae_kqueue.c
ae_select.o: ae_select.c
anet.o: anet.c fmacros.h anet.h
+aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+config.o: config.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+debug.o: debug.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h sha1.h
dict.o: dict.c fmacros.h dict.h zmalloc.h
+intset.o: intset.c intset.h zmalloc.h
linenoise.o: linenoise.c fmacros.h
lzf_c.o: lzf_c.c lzfP.h
lzf_d.o: lzf_d.c lzfP.h
+multi.o: multi.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+networking.o: networking.c redis.h fmacros.h config.h ae.h sds.h dict.h \
+ adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+object.o: object.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
pqsort.o: pqsort.c
+pubsub.o: pubsub.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+rdb.o: rdb.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h lzf.h
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
zmalloc.h
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
redis-check-dump.o: redis-check-dump.c lzf.h
-redis-cli.o: redis-cli.c fmacros.h anet.h sds.h adlist.h zmalloc.h \
- linenoise.h
-redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \
- adlist.h zmalloc.h lzf.h pqsort.h zipmap.h ziplist.h sha1.h
+redis-cli.o: redis-cli.c fmacros.h version.h anet.h sds.h adlist.h \
+ zmalloc.h linenoise.h
+redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
release.o: release.c release.h
+replication.o: replication.c redis.h fmacros.h config.h ae.h sds.h dict.h \
+ adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
sds.o: sds.c sds.h zmalloc.h
sha1.o: sha1.c sha1.h
+sort.o: sort.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h pqsort.h
+t_hash.o: t_hash.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_list.o: t_list.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_set.o: t_set.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_string.o: t_string.c redis.h fmacros.h config.h ae.h sds.h dict.h \
+ adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_zset.o: t_zset.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+util.o: util.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+vm.o: vm.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
ziplist.o: ziplist.c zmalloc.h ziplist.h
zipmap.o: zipmap.c zmalloc.h
-intset.o: intset.c zmalloc.h
zmalloc.o: zmalloc.c config.h
redis-server: $(OBJ)
/* Emit the SADDs needed to rebuild the set */
if (o->encoding == REDIS_ENCODING_INTSET) {
int ii = 0;
- long long llval;
+ int64_t llval;
while(intsetGet(o->ptr,ii++,&llval)) {
if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
if (fwriteBulkObject(fp,&key) == 0) goto werr;
dictIterator *di;
dictEntry *de;
sds pattern = c->argv[1]->ptr;
- int plen = sdslen(pattern);
+ int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0;
robj *lenobj = createObject(REDIS_STRING,NULL);
di = dictGetIterator(c->db->dict);
addReply(c,lenobj);
decrRefCount(lenobj);
+ allkeys = (pattern[0] == '*' && pattern[1] == '\0');
while((de = dictNext(di)) != NULL) {
sds key = dictGetEntryKey(de);
robj *keyobj;
- if ((pattern[0] == '*' && pattern[1] == '\0') ||
- stringmatchlen(pattern,plen,key,sdslen(key),0)) {
+ if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key));
if (expireIfNeeded(c->db,keyobj) == 0) {
addReplyBulk(c,keyobj);
dictReleaseIterator(di);
} else if (o->encoding == REDIS_ENCODING_INTSET) {
intset *is = o->ptr;
- long long llval;
+ int64_t llval;
int i = 0;
if (rdbSaveLen(fp,intsetLen(is)) == -1) return -1;
if (server.bgsavechildpid != -1) return REDIS_ERR;
if (server.vm_enabled) waitEmptyIOJobsQueue();
+ server.dirty_before_bgsave = server.dirty;
if ((childpid = fork()) == 0) {
/* Child */
if (server.vm_enabled) vmReopenSwapFile();
if (o->encoding == REDIS_ENCODING_INTSET) {
/* Fetch integer value from element */
- if (getLongLongFromObject(ele,&llval) == REDIS_OK) {
+ if (isObjectRepresentableAsLongLong(ele,&llval) == REDIS_OK) {
o->ptr = intsetAdd(o->ptr,llval,NULL);
} else {
setTypeConvert(o,REDIS_ENCODING_HT);
if (!bysignal && exitcode == 0) {
redisLog(REDIS_NOTICE,
"Background saving terminated with success");
- server.dirty = 0;
+ server.dirty = server.dirty - server.dirty_before_bgsave;
server.lastsave = time(NULL);
} else if (!bysignal && exitcode != 0) {
redisLog(REDIS_WARNING, "Background saving error");
/* data type to hold offset in file and size */
typedef struct {
void *data;
- unsigned long size;
- unsigned long offset;
+ uint64_t size;
+ uint64_t offset;
} pos;
static unsigned char level = 0;
/* Hold a stack of errors */
typedef struct {
char error[16][1024];
- unsigned long offset[16];
- unsigned int level;
+ uint64_t offset[16];
+ uint32_t level;
} errors_t;
static errors_t errors;
printf("%s %s %s\n", head, body, tail);
}
-void printValid(int ops, int bytes) {
+void printValid(uint64_t ops, uint64_t bytes) {
char body[80];
- sprintf(body, "Processed %d valid opcodes (in %d bytes)", ops, bytes);
+ sprintf(body, "Processed %llu valid opcodes (in %llu bytes)", ops, bytes);
printCentered(4, 80, body);
}
-void printSkipped(int bytes, int offset) {
+void printSkipped(uint64_t bytes, uint64_t offset) {
char body[80];
- sprintf(body, "Skipped %d bytes (resuming at 0x%08x)", bytes, offset);
+ sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)", bytes, offset);
printCentered(4, 80, body);
}
/* display error stack */
for (i = 0; i < errors.level; i++) {
- printf("0x%08lx - %s\n", errors.offset[i], errors.error[i]);
+ printf("0x%08llx - %s\n", errors.offset[i], errors.error[i]);
}
}
void process() {
- int i, num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
+ uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
entry entry;
processHeader();
num_valid_bytes = 0;
/* search for next valid entry */
- unsigned long offset = positions[0].offset + 1;
+ uint64_t offset = positions[0].offset + 1;
+ int i = 0;
+
while (!entry.success && offset < positions[0].size) {
positions[1].offset = offset;
}
/* print summary on errors */
- if (num_errors > 0) {
+ if (num_errors) {
printf("\n");
- printf("Total unprocessable opcodes: %d\n", num_errors);
+ printf("Total unprocessable opcodes: %llu\n", num_errors);
}
}
}
int fd;
- unsigned long size;
+ size_t size;
struct stat stat;
void *data;
ssize_t ret;
ret = read(fd,&c,1);
- if (ret == -1) {
+ if (ret <= 0) {
sdsfree(line);
return NULL;
} else if ((ret == 0) || (c == '\n')) {
return 0;
}
+static void showInteractiveHelp(void) {
+ printf(
+ "\n"
+ "Welcome to redis-cli " REDIS_VERSION "!\n"
+ "Just type any valid Redis command to see a pretty printed output.\n"
+ "\n"
+ "It is possible to quote strings, like in:\n"
+ " set \"my key\" \"some string \\xff\\n\"\n"
+ "\n"
+ "You can find a list of valid Redis commands at\n"
+ " http://code.google.com/p/redis/wiki/CommandReference\n"
+ "\n"
+ "Note: redis-cli supports line editing, use up/down arrows for history."
+ "\n\n");
+}
+
static int cliSendCommand(int argc, char **argv, int repeat) {
char *command = argv[0];
int fd, j, retval = 0;
sds cmd;
config.raw_output = !strcasecmp(command,"info");
+ if (!strcasecmp(command,"help")) {
+ showInteractiveHelp();
+ return 0;
+ }
if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
if (!strcasecmp(command,"subscribe") ||
while(repeat--) {
anetWrite(fd,cmd,sdslen(cmd));
while (config.monitor_mode) {
- cliReadSingleLineReply(fd,0);
+ if (cliReadSingleLineReply(fd,0)) exit(1);
+ printf("\n");
}
if (config.pubsub_mode) {
if (config.auth != NULL) {
char *authargv[2];
+ int dbnum = config.dbnum;
+ /* We need to save the real configured database number and set it to
+ * zero here, otherwise cliSendCommand() will try to perform the
+ * SELECT command before the authentication, and it will fail. */
+ config.dbnum = 0;
authargv[0] = "AUTH";
authargv[1] = config.auth;
cliSendCommand(2, convertToSds(2, authargv), 1);
+ config.dbnum = dbnum; /* restore the right DB number */
}
/* Start interactive mode when no command is provided */
if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue;
for (j = 0; j < server.dbnum; j++) {
int minttl = -1;
- robj *minkey = NULL;
+ sds minkey = NULL;
+ robj *keyobj = NULL;
struct dictEntry *de;
if (dictSize(server.db[j].expires)) {
minttl = t;
}
}
- dbDelete(server.db+j,minkey);
+ keyobj = createStringObject(minkey,sdslen(minkey));
+ dbDelete(server.db+j,keyobj);
+ decrRefCount(keyobj);
}
}
if (!freed) return; /* nothing to free... */
int fd;
redisDb *db;
long long dirty; /* changes to DB from the last save */
+ long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
list *clients;
list *slaves, *monitors;
char neterr[ANET_ERR_LEN];
long long memtoll(const char *p, int *err);
int ll2string(char *s, size_t len, long long value);
int isStringRepresentableAsLong(sds s, long *longval);
+int isStringRepresentableAsLongLong(sds s, long long *longval);
+int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
/* Configuration */
void loadServerConfig(char *filename);
while(size) {
if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) {
nread = read(fd,ptr,size);
- if (nread == -1) return -1;
+ if (nread <= 0) return -1;
ptr += nread;
size -= nread;
totread += nread;
}
err:
- while(*argc--)
+ while((*argc)--)
sdsfree(vector[*argc]);
zfree(vector);
if (current) sdsfree(current);
}
}
+ /* If we are inside a MULTI/EXEC and the list is empty the only thing
+ * we can do is treating it as a timeout (even with timeout 0). */
+ if (c->flags & REDIS_MULTI) {
+ addReply(c,shared.nullmultibulk);
+ return;
+ }
+
/* If the list is empty or the key does not exists we must block */
timeout = lltimeout;
if (timeout > 0) timeout += time(NULL);
* an integer-encodable value, an intset will be returned. Otherwise a regular
* hash table. */
robj *setTypeCreate(robj *value) {
- if (getLongLongFromObject(value,NULL) == REDIS_OK)
+ if (isObjectRepresentableAsLongLong(value,NULL) == REDIS_OK)
return createIntsetObject();
return createSetObject();
}
return 1;
}
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
- if (getLongLongFromObject(value,&llval) == REDIS_OK) {
+ if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
uint8_t success = 0;
subject->ptr = intsetAdd(subject->ptr,llval,&success);
if (success) {
return 1;
}
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
- if (getLongLongFromObject(value,&llval) == REDIS_OK) {
+ if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
uint8_t success;
subject->ptr = intsetRemove(subject->ptr,llval,&success);
if (success) return 1;
if (subject->encoding == REDIS_ENCODING_HT) {
return dictFind((dict*)subject->ptr,value) != NULL;
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
- if (getLongLongFromObject(value,&llval) == REDIS_OK) {
+ if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
return intsetFind((intset*)subject->ptr,llval);
}
} else {
incrRefCount(ret);
}
} else if (si->encoding == REDIS_ENCODING_INTSET) {
- long long llval;
+ int64_t llval;
if (intsetGet(si->subject->ptr,si->ii++,&llval))
ret = createStringObjectFromLongLong(llval);
}
return l;
}
-/* Check if the nul-terminated string 's' can be represented by a long
+/* Check if the sds string 's' can be represented by a long long
* (that is, is a number that fits into long without any other space or
- * character before or after the digits).
+ * character before or after the digits, so that converting this number
+ * back to a string will result in the same bytes as the original string).
*
- * If so, the function returns REDIS_OK and *longval is set to the value
+ * If so, the function returns REDIS_OK and *llongval is set to the value
* of the number. Otherwise REDIS_ERR is returned */
-int isStringRepresentableAsLong(sds s, long *longval) {
+int isStringRepresentableAsLongLong(sds s, long long *llongval) {
char buf[32], *endptr;
- long value;
+ long long value;
int slen;
- value = strtol(s, &endptr, 10);
+ value = strtoll(s, &endptr, 10);
if (endptr[0] != '\0') return REDIS_ERR;
slen = ll2string(buf,32,value);
/* If the number converted back into a string is not identical
* then it's not possible to encode the string as integer */
if (sdslen(s) != (unsigned)slen || memcmp(buf,s,slen)) return REDIS_ERR;
- if (longval) *longval = value;
+ if (llongval) *llongval = value;
+ return REDIS_OK;
+}
+
+int isStringRepresentableAsLong(sds s, long *longval) {
+ long long ll;
+
+ if (isStringRepresentableAsLongLong(s,&ll) == REDIS_ERR) return REDIS_ERR;
+ if (ll < LONG_MIN || ll > LONG_MAX) return REDIS_ERR;
+ *longval = (long)ll;
return REDIS_OK;
}
+
+int isObjectRepresentableAsLongLong(robj *o, long long *llongval) {
+ redisAssert(o->type == REDIS_STRING);
+ if (o->encoding == REDIS_ENCODING_INT) {
+ if (llongval) *llongval = (long) o->ptr;
+ return REDIS_OK;
+ } else {
+ return isStringRepresentableAsLongLong(o->ptr,llongval);
+ }
+}
-#define REDIS_VERSION "2.1.2"
+#define REDIS_VERSION "2.1.3"
/* Every time a thread finished a Job, it writes a byte into the write side
* of an unix pipe in order to "awake" the main thread, and this function
- * is called. */
+ * is called.
+ *
+ * Note that this is called both by the event loop, when a I/O thread
+ * sends a byte in the notification pipe, and is also directly called from
+ * waitEmptyIOJobsQueue().
+ *
+ * In the latter case we don't want to swap more, so we use the
+ * "privdata" argument setting it to a not NULL value to signal this
+ * condition. */
void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
int mask)
{
REDIS_NOTUSED(mask);
REDIS_NOTUSED(privdata);
+ if (privdata != NULL) trytoswap = 0; /* check the comments above... */
+
/* For every byte we read in the read side of the pipe, there is one
* I/O job completed to process. */
while((retval = read(fd,buf,1)) == 1) {
io_processed_len = listLength(server.io_processed);
unlockThreadedIO();
if (io_processed_len) {
- vmThreadedIOCompletedJob(NULL,server.io_ready_pipe_read,NULL,0);
+ vmThreadedIOCompletedJob(NULL,server.io_ready_pipe_read,
+ (void*)0xdeadbeef,0);
usleep(1000); /* 1 millisecond */
} else {
usleep(10000); /* 10 milliseconds */
}
close $fd
} e]} {
- puts "Can't PING server at $host:$port... $e"
+ puts -nonewline "."
+ } else {
+ puts -nonewline "ok"
}
return $retval
}
if {$::valgrind} {
exec valgrind src/redis-server $config_file > $stdout 2> $stderr &
- after 2000
} else {
exec src/redis-server $config_file > $stdout 2> $stderr &
- after 500
}
# check that the server actually started
- if {$code ne "undefined" && ![ping_server $::host $::port]} {
+ # ugly but tries to be as fast as possible...
+ set retrynum 20
+ set serverisup 0
+
+ puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
+ after 10
+ if {$code ne "undefined"} {
+ while {[incr retrynum -1]} {
+ catch {
+ if {[ping_server $::host $::port]} {
+ set serverisup 1
+ }
+ }
+ if {$serverisup} break
+ after 50
+ }
+ } else {
+ set serverisup 1
+ }
+ puts {}
+
+ if {!$serverisup} {
error_and_quit $config_file [exec cat $stderr]
}
set ::host 127.0.0.1
set ::port 16379
-set ::traceleaks 1
+set ::traceleaks 0
set ::valgrind 0
set ::denytags {}
set ::allowtags {}
execute_tests "unit/cas"
execute_tests "integration/replication"
execute_tests "integration/aof"
- execute_tests "integration/redis-cli"
+# execute_tests "integration/redis-cli"
execute_tests "unit/pubsub"
# run tests with VM enabled
-start_server {} {
+start_server {tags {"other"}} {
test {SAVE - make sure there are all the types as values} {
# Wait for a background saving in progress to terminate
waitForBgsave r
-start_server {} {
+start_server {tags {"protocol"}} {
test {Handle an empty query well} {
set fd [r channel]
puts -nonewline $fd "\r\n"
foreach {num cmd enc title} {
16 lpush ziplist "Ziplist"
- 64 lpush linkedlist "Linked list"
+ 1000 lpush linkedlist "Linked list"
+ 10000 lpush linkedlist "Big Linked list"
16 sadd intset "Intset"
- 64 sadd hashtable "Hash table"
+ 1000 sadd hashtable "Hash table"
+ 10000 sadd hashtable "Big Hash table"
} {
set result [create_random_dataset $num $cmd]
assert_encoding $enc tosort
}
}
+ test {BLPOP inside a transaction} {
+ r del xlist
+ r lpush xlist foo
+ r lpush xlist bar
+ r multi
+ r blpop xlist 0
+ r blpop xlist 0
+ r blpop xlist 0
+ r exec
+ } {{xlist bar} {xlist foo} {}}
+
test {LPUSHX, RPUSHX - generic} {
r del xlist
assert_equal 0 [r lpushx xlist a]
}
r sadd set5 0
- # it is possible that a hashtable encoded only contains integers,
- # because it is converted from an intset to a hashtable when a
- # non-integer element is added and then removed.
+ # To make sure the sets are encoded as the type we are testing -- also
+ # when the VM is enabled and the values may be swapped in and out
+ # while the tests are running -- an extra element is added to every
+ # set that determines its encoding.
+ set large 200
if {$type eq "hashtable"} {
- for {set i 1} {$i <= 5} {incr i} {
- r sadd [format "set%d" $i] foo
- r srem [format "set%d" $i] foo
- }
+ set large foo
+ }
+
+ for {set i 1} {$i <= 5} {incr i} {
+ r sadd [format "set%d" $i] $large
}
test "Generated sets must be encoded as $type" {
}
test "SINTER with two sets - $type" {
- assert_equal {195 196 197 198 199} [lsort [r sinter set1 set2]]
+ assert_equal [list 195 196 197 198 199 $large] [lsort [r sinter set1 set2]]
}
test "SINTERSTORE with two sets - $type" {
r sinterstore setres set1 set2
- assert_encoding intset setres
- assert_equal {195 196 197 198 199} [lsort [r smembers setres]]
+ assert_encoding $type setres
+ assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]]
}
test "SINTERSTORE with two sets, after a DEBUG RELOAD - $type" {
r debug reload
r sinterstore setres set1 set2
- assert_encoding intset setres
- assert_equal {195 196 197 198 199} [lsort [r smembers setres]]
+ assert_encoding $type setres
+ assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]]
}
test "SUNION with two sets - $type" {
test "SUNIONSTORE with two sets - $type" {
r sunionstore setres set1 set2
- assert_encoding intset setres
+ assert_encoding $type setres
set expected [lsort -uniq "[r smembers set1] [r smembers set2]"]
assert_equal $expected [lsort [r smembers setres]]
}
test "SINTER against three sets - $type" {
- assert_equal {195 199} [lsort [r sinter set1 set2 set3]]
+ assert_equal [list 195 199 $large] [lsort [r sinter set1 set2 set3]]
}
test "SINTERSTORE with three sets - $type" {
r sinterstore setres set1 set2 set3
- assert_equal {195 199} [r smembers setres]
+ assert_equal [list 195 199 $large] [lsort [r smembers setres]]
}
test "SUNION with non existing keys - $type" {
test "SDIFFSTORE with three sets - $type" {
r sdiffstore setres set1 set4 set5
- assert_encoding intset setres
+ # The type is determined by type of the first key to diff against.
+ # See the implementation for more information.
+ assert_encoding $type setres
assert_equal {1 2 3 4} [lsort [r smembers setres]]
}
}