Compare commits

...
44 Commits
Author SHA1 Message Date
antirez a2d68e6524 Redis 2.2.11 2011-06-22 22:37:21 +02:00
antirez 25e805b417 Use a safe iterator while saving the DB, since the getExpire() function will access the iterating dictionary. 2011-06-17 15:41:10 +02:00
antirez 4ec8b732b7 Redis 2.2.10 2011-06-14 17:32:49 +02:00
antirez 046b4f66ef redis-cli updated to the same version as the 2.4 branch 2011-06-14 17:28:01 +02:00
antirez 5d91926304 Fix for issue 566, fixed in a more long term way into the 2.4 branch 2011-06-14 17:19:25 +02:00
Pieter Noordhuis 5afce9b6ea SETNX regression tests. 2011-06-14 17:13:08 +02:00
antirez 0df2e5b9d5 Fixed init script bug, thanks to Henrik Westphal 2011-06-14 17:12:14 +02:00
antirez 0b4eb5fd21 Better init script 2011-06-14 13:49:02 +02:00
Pieter Noordhuis b13216a8c7 Work with make v3.80 2011-06-13 09:42:03 +02:00
antirez 25a585ad42 Changelog typo fixed 2011-06-09 22:03:18 +02:00
antirez 7d8c021a9a Redis 2.2.9 2011-06-09 19:08:22 +02:00
antirez be87e76906 Merge remote branch 'origin/2.2' into 2.2 2011-06-09 19:04:48 +02:00
antirez 09d9879963 AOF log rewriting in slave performed after a successful sych with the master was misplaced. Now it happens at the right time. 2011-06-09 19:04:31 +02:00
antirez dfc74051ce test for the BRPOPLPUSH issue 561 related issues 2011-05-30 17:16:33 +02:00
antirez 196fc32b77 use the new rewriteClientCommandVector() function for SPOP -> SREM replication translation as well. 2011-05-27 15:39:31 +02:00
antirez b190b0c98f modified the BRPOPLPUSH target field cleanup strategy to fix it the proper way. 2011-05-27 15:01:20 +02:00
antirez d7061f8137 Removed a leak in the BRPOPLPUSH unrelated to issue 561 2011-05-27 14:09:50 +02:00
antirez 20867e8009 Fix for issue 516, rewriting the command vector to correctly repliate BRPOPLPUSH. Still to test everything, especially edge cases 2011-05-26 13:49:03 +02:00
antirez 10e987c5fd Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-05-25 12:29:23 +02:00
antirez cc958ccf42 Fix for Pub/Sub system, introduced in Redis 2.2.6 with the new copy-on-write safe iterator semantics. In the hope this is the last bug I introduced this way. 2011-05-25 12:29:14 +02:00
antirez ec279203df Redis 2.2.8 2011-05-24 16:34:53 +02:00
antirez 480a2e73cf Comment typo fixed 2011-05-24 10:43:31 +02:00
antirez f447a7ebb4 ZUNIONSTORE new iterator API fix backported into 2.2 2011-05-21 20:34:59 +02:00
antirez 748a2da3e8 Print version info before running the test 2011-05-20 11:44:09 +02:00
antirez b02e81be07 Fixed SINTER[STORE] problem related to the new copy on write safe iterator 2011-05-15 12:15:54 +02:00
antirez 430719ca53 Fixed bug introduced with the copy-on-write friendly iteartor 2011-05-13 12:53:07 +02:00
antirez cc8a0f898b Fix for DEBUG DIGEST, key may expire on lookup 2011-05-13 11:42:02 +02:00
antirez cd128d2882 replication with expire test modified to produce no or less false failures 2011-05-12 20:15:13 +02:00
antirez c717adbc2e regression for zinterstore bug 2011-05-12 20:14:15 +02:00
Pieter Noordhuis 1ffa5d73ed Convert intset encoded set before Z*STORE 2011-05-12 16:18:43 +02:00
antirez a7fa2baf06 Redis 2.2.7 2011-05-11 10:40:24 +02:00
antirez f7aef5241b removed assert causing an illegal memory access. This was responsible of crashes during BLPOP and other list blocking operations. 2011-05-11 09:49:23 +02:00
antirez c86a4f9102 use safe Dict iteration in DEBUG DIGEST 2011-05-10 11:04:30 +02:00
antirez 2e638590ad Introduced a safe iterator interface that can be used to iterate while accessing the dictionary at the same time. Now the default interface is consireded unsafe and should be used only with dictNext() 2011-05-10 11:04:03 +02:00
antirez 3554f09ddc Merge branch '2.2' of github.com:antirez/redis into 2.2 2011-05-07 12:10:47 +02:00
antirez ccab83e729 when Redis fails accepting a new connection reports the error at WARNING and not VERBOSE error level. Thanks to offby1 for proposing this in the Redis mailing list. #backport-candidate 2011-05-07 12:10:39 +02:00
antirez 97ddfbbfc3 Redis version 2.2.6 2011-05-05 15:56:47 +02:00
Pieter Noordhuis 50e50d6a25 Fix ziplist regression and update stresser 2011-05-05 15:17:49 +02:00
antirez 9d665825d9 Fixed a bug with replication where SLAVEOF NO ONE caused a slave to close the connection with its slaves 2011-04-29 14:18:33 +02:00
antirez 19b55f8ebc Redis 2.2.5 2011-04-22 16:25:13 +02:00
Pieter Noordhuis bf3692797d Use correct argc/argv for cleanup when loading AOF 2011-04-22 09:41:07 +02:00
Pieter Noordhuis da06854477 Reformat AOF tests 2011-04-22 09:40:10 +02:00
antirez 38de9362b2 Revert "tests for variadic list push"
This reverts commit 1b905277bb.
Moving all the new stuff in 2.4 branch.
2011-04-15 17:41:32 +02:00
antirez 7600994937 Revert "variadic LPUSH/RPUSH backported to 2.2"
This reverts commit c138dc7da4.
Moving all the new stuff in 2.4 branch.
2011-04-15 17:41:07 +02:00
28 changed files with 491 additions and 201 deletions
+65
View File
@@ -12,6 +12,71 @@ for 2.0.
CHANGELOG
---------
What's new in Redis 2.2.11
==========================
* Solved a never reported but possibly critical bug in the AOF and RDB
persistence, introduced with the new version of the iterator: In very rare
circumstances the AOF (after rerwite) or the rdb file may contain the same
key more than one time.
What's new in Redis 2.2.10
==========================
* Fix for issue 566. It was possible for SETNX to behave in abnormal ways.
* Work with make v3.80
* redis-cli updated to the same version as the 2.4 branch. Now the
reconnection handling is better. You can prefix commands with a number
to execute the command the specified number of times. When using -r
in non interactive mode it is possible to set a delay between commands.
* Better init script.
What's new in Redis 2.2.9
=========================
Redis 2.2.9 is an important bugfix release:
* A Slave used to rewrite the AOF log after a sync with the master, but the
rewrite was performed at the wrong time, causing inconsistencies in the
AOF file generated.
* [LR]LPUSH replication in presence of BRPOPLPUSH was broken. Fixed.
* Memory leak in BRPOPLPUSH fixed.
* Pub/Sub bug resulting in random crashes fixed.
What's new in Redis 2.2.8
=========================
* A new form of dict.c (hash table implementation) iterator that performs less
copy-on-write of pages, introduced in Redis 2.2.7, caused ZINTERSTORE,
ZUNIONSTORE, SINTER, SINTERSTORE commands to behave in the wrong way.
This bug is now fixed.
* Print version info before running the test with 'make test'. This is mainly
useful for the Continuous Integration system we run.
* Fix for DEBUG DIGEST, key may expire on lookup, producing the wrong result.
* Replication with expire test modified to produce no or less false failures.
* Fixed Z*STORE when dealing with intsets, regression test added.
What's new in Redis 2.2.7
=========================
* Fixed bug #543-2 (the issue was reopened with a completely different report)
that caused Redis to randomly crash on list push performed against lists
with other clients blocked with BLPOP (or variants).
What's new in Redis 2.2.6
=========================
* Fixed bug #543. If you saw Redis instances crashing on List operations
(only happening with a non-default max entry size ziplist setting in
redis.conf) it was almost certainly this problem.
* Fixed a bug with replication where SLAVEOF NO ONE caused a slave to close the
connection with all its slaves.
What's new in Redis 2.2.5
=========================
* Fixed a crash occurring when loading an AOF containing the SPOP command.
What's new in Redis 2.2.4
=========================
+4 -1
View File
@@ -15,7 +15,8 @@ ifeq ($(uname_S),SunOS)
DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ}
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
else ifeq ($(uname_S),Darwin)
else
ifeq ($(uname_S),Darwin)
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
CCLINK?=-lm -pthread
LDFLAGS?=-L. -Wl,-rpath,.
@@ -33,6 +34,8 @@ else
STLIBNAME?=libhiredis.a
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
endif
endif
CCOPT= $(CFLAGS) $(CCLINK)
DEBUG?= -g -ggdb
+6 -4
View File
@@ -285,9 +285,11 @@ int loadAppendOnlyFile(char *filename) {
/* The fake client should not have a reply */
redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
/* Clean up, ready for the next command */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
zfree(argv);
/* Clean up. Command code may have changed argv/argc so we use the
* argv/argc of the client instead of the local variables. */
for (j = 0; j < fakeClient->argc; j++)
decrRefCount(fakeClient->argv[j]);
zfree(fakeClient->argv);
/* Handle swapping while loading big datasets when VM is on */
force_swapout = 0;
@@ -346,7 +348,7 @@ int rewriteAppendOnlyFile(char *filename) {
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetIterator(d);
di = dictGetSafeIterator(d);
if (!di) {
fclose(fp);
return REDIS_ERR;
+6 -1
View File
@@ -80,7 +80,7 @@ void computeDatasetDigest(unsigned char *final) {
redisDb *db = server.db+j;
if (dictSize(db->dict) == 0) continue;
di = dictGetIterator(db->dict);
di = dictGetSafeIterator(db->dict);
/* hash the DB id, so the same dataset moved in a different
* DB will lead to a different digest */
@@ -101,6 +101,11 @@ void computeDatasetDigest(unsigned char *final) {
/* Make sure the key is loaded if VM is active */
o = lookupKeyRead(db,keyobj);
if (o == NULL) {
/* Key expired on lookup? Try the next one. */
decrRefCount(keyobj);
continue;
}
aux = htonl(o->type);
mixDigest(digest,&aux,sizeof(aux));
+15 -5
View File
@@ -244,9 +244,9 @@ int dictRehashMilliseconds(dict *d, int ms) {
}
/* This function performs just a step of rehashing, and only if there are
* not iterators bound to our hash table. When we have iterators in the middle
* of a rehashing we can't mess with the two hash tables otherwise some element
* can be missed or duplicated.
* no safe iterators bound to our hash table. When we have iterators in the
* middle of a rehashing we can't mess with the two hash tables otherwise
* some element can be missed or duplicated.
*
* This function is called by common lookup or update operations in the
* dictionary so that the hash table automatically migrates from H1 to H2
@@ -423,17 +423,26 @@ dictIterator *dictGetIterator(dict *d)
iter->d = d;
iter->table = 0;
iter->index = -1;
iter->safe = 0;
iter->entry = NULL;
iter->nextEntry = NULL;
return iter;
}
dictIterator *dictGetSafeIterator(dict *d) {
dictIterator *i = dictGetIterator(d);
i->safe = 1;
return i;
}
dictEntry *dictNext(dictIterator *iter)
{
while (1) {
if (iter->entry == NULL) {
dictht *ht = &iter->d->ht[iter->table];
if (iter->index == -1 && iter->table == 0) iter->d->iterators++;
if (iter->safe && iter->index == -1 && iter->table == 0)
iter->d->iterators++;
iter->index++;
if (iter->index >= (signed) ht->size) {
if (dictIsRehashing(iter->d) && iter->table == 0) {
@@ -460,7 +469,8 @@ dictEntry *dictNext(dictIterator *iter)
void dictReleaseIterator(dictIterator *iter)
{
if (!(iter->index == -1 && iter->table == 0)) iter->d->iterators--;
if (iter->safe && !(iter->index == -1 && iter->table == 0))
iter->d->iterators--;
zfree(iter);
}
+6 -2
View File
@@ -74,10 +74,13 @@ typedef struct dict {
int iterators; /* number of iterators currently running */
} dict;
/* If safe is set to 1 this is a safe iteartor, that means, you can call
* dictAdd, dictFind, and other functions against the dictionary even while
* iterating. Otherwise it is a non safe iterator, and only dictNext()
* should be called while iterating. */
typedef struct dictIterator {
dict *d;
int table;
int index;
int table, index, safe;
dictEntry *entry, *nextEntry;
} dictIterator;
@@ -132,6 +135,7 @@ dictEntry * dictFind(dict *d, const void *key);
void *dictFetchValue(dict *d, const void *key);
int dictResize(dict *d);
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
+37 -6
View File
@@ -416,7 +416,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
cfd = anetTcpAccept(server.neterr, fd, cip, &cport);
if (cfd == AE_ERR) {
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
@@ -431,7 +431,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
cfd = anetUnixAccept(server.neterr, fd);
if (cfd == AE_ERR) {
redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr);
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
return;
}
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
@@ -523,10 +523,16 @@ void freeClient(redisClient *c) {
* close the connection with all our slaves if we have any, so
* when we'll resync with the master the other slaves will sync again
* with us as well. Note that also when the slave is not connected
* to the master it will keep refusing connections by other slaves. */
while (listLength(server.slaves)) {
ln = listFirst(server.slaves);
freeClient((redisClient*)ln->value);
* to the master it will keep refusing connections by other slaves.
*
* We do this only if server.masterhost != NULL. If it is NULL this
* means the user called SLAVEOF NO ONE and we are freeing our
* link with the master, so no need to close link with slaves. */
if (server.masterhost != NULL) {
while (listLength(server.slaves)) {
ln = listFirst(server.slaves);
freeClient((redisClient*)ln->value);
}
}
}
/* Release memory */
@@ -866,3 +872,28 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
*biggest_input_buffer = bib;
}
void rewriteClientCommandVector(redisClient *c, int argc, ...) {
va_list ap;
int j;
robj **argv; /* The new argument vector */
argv = zmalloc(sizeof(robj*)*argc);
va_start(ap,argc);
for (j = 0; j < argc; j++) {
robj *a;
a = va_arg(ap, robj*);
argv[j] = a;
incrRefCount(a);
}
/* We free the objects in the original vector at the end, so we are
* sure that if the same objects are reused in the new vector the
* refcount gets incremented before it gets decremented. */
for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]);
zfree(c->argv);
/* Replace argv and argc with our new versions. */
c->argv = argv;
c->argc = argc;
va_end(ap);
}
+1 -1
View File
@@ -141,7 +141,7 @@ int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
/* Unsubscribe from all the channels. Return the number of channels the
* client was subscribed from. */
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
dictIterator *di = dictGetIterator(c->pubsub_channels);
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
dictEntry *de;
int count = 0;
+1 -1
View File
@@ -427,7 +427,7 @@ int rdbSave(char *filename) {
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetIterator(d);
di = dictGetSafeIterator(d);
if (!di) {
fclose(fp);
return REDIS_ERR;
+75 -35
View File
@@ -55,6 +55,7 @@ static struct config {
int hostport;
char *hostsocket;
long repeat;
long interval;
int dbnum;
int interactive;
int shutdown;
@@ -62,7 +63,6 @@ static struct config {
int pubsub_mode;
int stdinarg; /* get last arg from stdin. (-x option) */
char *auth;
char *historyfile;
int raw_output; /* output mode per command */
sds mb_delim;
char prompt[32];
@@ -88,9 +88,11 @@ static long long mstime(void) {
static void cliRefreshPrompt(void) {
if (config.dbnum == 0)
snprintf(config.prompt,sizeof(config.prompt),"redis> ");
snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d> ",
config.hostip, config.hostport);
else
snprintf(config.prompt,sizeof(config.prompt),"redis:%d> ",config.dbnum);
snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d[%d]> ",
config.hostip, config.hostport, config.dbnum);
}
/*------------------------------------------------------------------------------
@@ -315,10 +317,9 @@ static int cliConnect(int force) {
return REDIS_OK;
}
static void cliPrintContextErrorAndExit() {
static void cliPrintContextError() {
if (context == NULL) return;
fprintf(stderr,"Error: %s\n",context->errstr);
exit(1);
}
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
@@ -395,15 +396,18 @@ static sds cliFormatReplyRaw(redisReply *r) {
switch (r->type) {
case REDIS_REPLY_NIL:
/* Nothing... */
break;
break;
case REDIS_REPLY_ERROR:
out = sdscatlen(out,r->str,r->len);
out = sdscatlen(out,"\n",1);
break;
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
out = sdscatlen(out,r->str,r->len);
break;
break;
case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"%lld",r->integer);
break;
break;
case REDIS_REPLY_ARRAY:
for (i = 0; i < r->elements; i++) {
if (i > 0) out = sdscat(out,config.mb_delim);
@@ -411,7 +415,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
out = sdscatlen(out,tmp,sdslen(tmp));
sdsfree(tmp);
}
break;
break;
default:
fprintf(stderr,"Unknown reply type: %d\n", r->type);
exit(1);
@@ -434,7 +438,8 @@ static int cliReadReply(int output_raw_strings) {
if (context->err == REDIS_ERR_EOF)
return REDIS_ERR;
}
cliPrintContextErrorAndExit();
cliPrintContextError();
exit(1);
return REDIS_ERR; /* avoid compiler warning */
}
@@ -460,12 +465,17 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
size_t *argvlen;
int j, output_raw;
if (context == NULL) {
printf("Not connected, please use: connect <host> <port>\n");
return REDIS_OK;
if (context == NULL) return REDIS_ERR;
output_raw = 0;
if (!strcasecmp(command,"info") ||
(argc == 2 && !strcasecmp(command,"client") &&
!strcasecmp(argv[1],"list")))
{
output_raw = 1;
}
output_raw = !strcasecmp(command,"info");
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
cliOutputHelp(--argc, ++argv);
return REDIS_OK;
@@ -505,6 +515,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
cliRefreshPrompt();
}
}
if (config.interval) usleep(config.interval);
fflush(stdout); /* Make it grep friendly */
}
free(argvlen);
@@ -540,6 +552,10 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"-r") && !lastarg) {
config.repeat = strtoll(argv[i+1],NULL,10);
i++;
} else if (!strcmp(argv[i],"-i") && !lastarg) {
double seconds = atof(argv[i+1]);
config.interval = seconds*1000000;
i++;
} else if (!strcmp(argv[i],"-n") && !lastarg) {
config.dbnum = atoi(argv[i+1]);
i++;
@@ -592,6 +608,8 @@ static void usage() {
" -s <socket> Server socket (overrides hostname and port)\n"
" -a <password> Password to use when connecting to the server\n"
" -r <repeat> Execute specified command N times\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
" It is possible to specify sub-second times like -i 0.1.\n"
" -n <db> Database number\n"
" -x Read last argument from STDIN\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n"
@@ -603,6 +621,7 @@ static void usage() {
" cat /etc/passwd | redis-cli -x set mypasswd\n"
" redis-cli get mypasswd\n"
" redis-cli -r 100 lpush mylist x\n"
" redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
"\n"
"When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands.\n"
@@ -625,19 +644,32 @@ static char **convertToSds(int count, char** args) {
#define LINE_BUFLEN 4096
static void repl() {
int argc, j;
sds historyfile = NULL;
int history = 0;
char *line;
int argc;
sds *argv;
config.interactive = 1;
linenoiseSetCompletionCallback(completionCallback);
/* Only use history when stdin is a tty. */
if (isatty(fileno(stdin))) {
history = 1;
if (getenv("HOME") != NULL) {
historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
linenoiseHistoryLoad(historyfile);
}
}
cliRefreshPrompt();
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
if (line[0] != '\0') {
argv = sdssplitargs(line,&argc);
linenoiseHistoryAdd(line);
if (config.historyfile) linenoiseHistorySave(config.historyfile);
if (history) linenoiseHistoryAdd(line);
if (historyfile) linenoiseHistorySave(historyfile);
if (argv == NULL) {
printf("Invalid argument(s)\n");
continue;
@@ -655,14 +687,25 @@ static void repl() {
linenoiseClearScreen();
} else {
long long start_time = mstime(), elapsed;
int repeat, skipargs = 0;
if (cliSendCommand(argc,argv,1) != REDIS_OK) {
repeat = atoi(argv[0]);
if (repeat) {
skipargs = 1;
} else {
repeat = 1;
}
if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
!= REDIS_OK)
{
cliConnect(1);
/* If we still cannot send the command,
* print error and abort. */
if (cliSendCommand(argc,argv,1) != REDIS_OK)
cliPrintContextErrorAndExit();
/* If we still cannot send the command print error.
* We'll try to reconnect the next time. */
if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
!= REDIS_OK)
cliPrintContextError();
}
elapsed = mstime()-start_time;
if (elapsed >= 500) {
@@ -671,8 +714,7 @@ static void repl() {
}
}
/* Free the argument vector */
for (j = 0; j < argc; j++)
sdsfree(argv[j]);
while(argc--) sdsfree(argv[argc]);
zfree(argv);
}
/* linenoise() returns malloc-ed lines like readline() */
@@ -701,6 +743,7 @@ int main(int argc, char **argv) {
config.hostport = 6379;
config.hostsocket = NULL;
config.repeat = 1;
config.interval = 0;
config.dbnum = 0;
config.interactive = 0;
config.shutdown = 0;
@@ -708,26 +751,23 @@ int main(int argc, char **argv) {
config.pubsub_mode = 0;
config.stdinarg = 0;
config.auth = NULL;
config.historyfile = NULL;
config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL);
config.mb_delim = sdsnew("\n");
cliInitHelp();
if (getenv("HOME") != NULL) {
config.historyfile = malloc(256);
snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
linenoiseHistoryLoad(config.historyfile);
}
firstarg = parseOptions(argc,argv);
argc -= firstarg;
argv += firstarg;
/* Try to connect */
if (cliConnect(0) != REDIS_OK) exit(1);
/* Start interactive mode when no command is provided */
if (argc == 0) repl();
if (argc == 0) {
/* Note that in repl mode we don't abort on connection error.
* A new attempt will be performed for every command send. */
cliConnect(0);
repl();
}
/* Otherwise, we have some arguments to execute */
if (cliConnect(0) != REDIS_OK) exit(1);
return noninteractive(argc,convertToSds(argc,argv));
}
+2 -2
View File
@@ -85,8 +85,8 @@ struct redisCommand readonlyCommandTable[] = {
{"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"mget",mgetCommand,-2,0,NULL,1,-1,1},
{"rpush",rpushCommand,-3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"lpush",lpushCommand,-3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"rpush",rpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"lpush",lpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"rpushx",rpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"lpushx",lpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
{"linsert",linsertCommand,5,REDIS_CMD_DENYOOM,NULL,1,1,1},
+1
View File
@@ -661,6 +661,7 @@ void addReplyMultiBulkLen(redisClient *c, long length);
void *dupClientReplyValue(void *o);
void getClientsMaxBuffers(unsigned long *longest_output_list,
unsigned long *biggest_input_buffer);
void rewriteClientCommandVector(redisClient *c, int argc, ...);
#ifdef __GNUC__
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
+2 -1
View File
@@ -390,6 +390,8 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
server.master->authenticated = 1;
server.replstate = REDIS_REPL_CONNECTED;
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
/* Rewrite the AOF file now that the dataset changed. */
if (server.appendonly) rewriteAppendOnlyFileBackground();
}
}
@@ -519,7 +521,6 @@ void replicationCron(void) {
redisLog(REDIS_NOTICE,"Connecting to MASTER...");
if (syncWithMaster() == REDIS_OK) {
redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started: SYNC sent");
if (server.appendonly) rewriteAppendOnlyFileBackground();
}
}
+1
View File
@@ -363,6 +363,7 @@ void sortCommand(redisClient *c) {
}
}
}
lookupKeyWrite(c->db,storekey); /* Force expire of old key if needed. */
dbReplace(c->db,storekey,sobj);
/* Note: we add 1 because the DB is dirty anyway since even if the
* SORT result is empty a new key is set and maybe the old content
+57 -36
View File
@@ -259,35 +259,30 @@ void listTypeConvert(robj *subject, int enc) {
*----------------------------------------------------------------------------*/
void pushGenericCommand(redisClient *c, int where) {
int j, addlen = 0, pushed = 0;
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
int may_have_waiting_clients = (lobj == NULL);
if (lobj && lobj->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr);
return;
}
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
if (may_have_waiting_clients) {
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[j])) {
addlen++;
continue;
} else {
may_have_waiting_clients = 0;
}
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (lobj == NULL) {
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
addReply(c,shared.cone);
return;
}
if (!lobj) {
lobj = createZiplistObject();
dbAdd(c->db,c->argv[1],lobj);
lobj = createZiplistObject();
dbAdd(c->db,c->argv[1],lobj);
} else {
if (lobj->type != REDIS_LIST) {
addReply(c,shared.wrongtypeerr);
return;
}
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
touchWatchedKey(c->db,c->argv[1]);
addReply(c,shared.cone);
return;
}
listTypePush(lobj,c->argv[j],where);
pushed++;
}
addReplyLongLong(c,addlen + (lobj ? listTypeLength(lobj) : 0));
if (pushed) touchWatchedKey(c->db,c->argv[1]);
server.dirty += pushed;
listTypePush(lobj,c->argv[2],where);
addReplyLongLong(c,listTypeLength(lobj));
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
void lpushCommand(redisClient *c) {
@@ -640,7 +635,9 @@ void lremCommand(redisClient *c) {
* as well. This command was originally proposed by Ezra Zygmuntowicz.
*/
void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey, robj *dstobj, robj *value) {
robj *aux;
if (!handleClientsWaitingListPush(c,dstkey,value)) {
/* Create the list if the key does not exist */
if (!dstobj) {
@@ -648,9 +645,25 @@ void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value
dbAdd(c->db,dstkey,dstobj);
} else {
touchWatchedKey(c->db,dstkey);
server.dirty++;
}
listTypePush(dstobj,value,REDIS_HEAD);
/* If we are pushing as a result of LPUSH against a key
* watched by BLPOPLPUSH, we need to rewrite the command vector.
* But if this is called directly by RPOPLPUSH (either directly
* or via a BRPOPLPUSH where the popped list exists)
* we should replicate the BRPOPLPUSH command itself. */
if (c != origclient) {
aux = createStringObject("LPUSH",5);
rewriteClientCommandVector(origclient,3,aux,dstkey,value);
decrRefCount(aux);
} else {
/* Make sure to always use RPOPLPUSH in the replication / AOF,
* even if the original command was BRPOPLPUSH. */
aux = createStringObject("RPOPLPUSH",9);
rewriteClientCommandVector(origclient,3,aux,c->argv[1],c->argv[2]);
decrRefCount(aux);
}
server.dirty++;
}
/* Always send the pushed value to the client. */
@@ -666,16 +679,22 @@ void rpoplpushCommand(redisClient *c) {
addReply(c,shared.nullbulk);
} else {
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
robj *touchedkey = c->argv[1];
if (dobj && checkType(c,dobj,REDIS_LIST)) return;
value = listTypePop(sobj,REDIS_TAIL);
rpoplpushHandlePush(c,c->argv[2],dobj,value);
/* We saved touched key, and protect it, since rpoplpushHandlePush
* may change the client command argument vector. */
incrRefCount(touchedkey);
rpoplpushHandlePush(c,c,c->argv[2],dobj,value);
/* listTypePop returns an object with its refcount incremented */
decrRefCount(value);
/* Delete the source list when it is empty */
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
touchWatchedKey(c->db,touchedkey);
decrRefCount(touchedkey);
server.dirty++;
}
}
@@ -777,6 +796,7 @@ void unblockClientWaitingData(redisClient *c) {
/* Cleanup the client structure */
zfree(c->bpop.keys);
c->bpop.keys = NULL;
if (c->bpop.target) decrRefCount(c->bpop.target);
c->bpop.target = NULL;
c->flags &= ~REDIS_BLOCKED;
c->flags |= REDIS_UNBLOCKED;
@@ -820,26 +840,27 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
receiver = ln->value;
dstkey = receiver->bpop.target;
/* Protect receiver->bpop.target, that will be freed by
* the next unblockClientWaitingData() call. */
if (dstkey) incrRefCount(dstkey);
/* This should remove the first element of the "clients" list. */
unblockClientWaitingData(receiver);
redisAssert(ln != listFirst(clients));
if (dstkey == NULL) {
/* BRPOP/BLPOP */
addReplyMultiBulkLen(receiver,2);
addReplyBulk(receiver,key);
addReplyBulk(receiver,ele);
return 1;
return 1; /* Serve just the first client as in B[RL]POP semantics */
} else {
/* BRPOPLPUSH, note that receiver->db is always equal to c->db. */
dstobj = lookupKeyWrite(receiver->db,dstkey);
if (dstobj && checkType(receiver,dstobj,REDIS_LIST)) {
decrRefCount(dstkey);
} else {
rpoplpushHandlePush(receiver,dstkey,dstobj,ele);
if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) {
rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele);
decrRefCount(dstkey);
return 1;
}
decrRefCount(dstkey);
}
}
+7 -11
View File
@@ -325,7 +325,7 @@ void scardCommand(redisClient *c) {
}
void spopCommand(redisClient *c) {
robj *set, *ele;
robj *set, *ele, *aux;
int64_t llele;
int encoding;
@@ -341,16 +341,11 @@ void spopCommand(redisClient *c) {
setTypeRemove(set,ele);
}
/* Change argv to replicate as SREM */
c->argc = 3;
c->argv = zrealloc(c->argv,sizeof(robj*)*(c->argc));
/* Overwrite SREM with SPOP (same length) */
redisAssert(sdslen(c->argv[0]->ptr) == 4);
memcpy(c->argv[0]->ptr, "SREM", 4);
/* Popped element already has incremented refcount */
c->argv[2] = ele;
/* Replicate/AOF this command as an SREM operation */
aux = createStringObject("SREM",4);
rewriteClientCommandVector(c,3,aux,c->argv[1],ele);
decrRefCount(ele);
decrRefCount(aux);
addReplyBulk(c,ele);
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
@@ -433,6 +428,7 @@ void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum,
si = setTypeInitIterator(sets[0]);
while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
for (j = 1; j < setnum; j++) {
if (sets[j] == sets[0]) continue;
if (encoding == REDIS_ENCODING_INTSET) {
/* intset with intset is simple... and fast */
if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
+1
View File
@@ -25,6 +25,7 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
}
}
lookupKeyWrite(c->db,key); /* Force expire of old key if needed */
retval = dbAdd(c->db,key,val);
if (retval == REDIS_ERR) {
if (!nx) {
+28 -4
View File
@@ -615,6 +615,10 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
if (obj->type == REDIS_ZSET) {
src[i].dict = ((zset*)obj->ptr)->dict;
} else if (obj->type == REDIS_SET) {
if (obj->encoding == REDIS_ENCODING_INTSET)
setTypeConvert(obj, REDIS_ENCODING_HT);
redisAssert(obj->encoding == REDIS_ENCODING_HT);
src[i].dict = (obj->ptr);
} else {
zfree(src);
@@ -682,7 +686,18 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
score = src[0].weight * zunionInterDictValue(de);
for (j = 1; j < setnum; j++) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
dictEntry *other;
/* If it's the same dictionary don't lookup as we are not
* in the context of a safe iterator. It's the same
* dictionary so we are sure the element is inside.
* This happens on SINTERSTORE dest 2 mykey mykey. */
if (src[j].dict == src[0].dict) {
other = de;
} else {
other = dictFind(src[j].dict,dictGetEntryKey(de));
}
if (other) {
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(&score,value,aggregate);
@@ -720,10 +735,19 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
/* because the zsets are sorted by size, its only possible
* for sets at larger indices to hold this entry */
for (j = (i+1); j < setnum; j++) {
dictEntry *other = dictFind(src[j].dict,dictGetEntryKey(de));
if (other) {
value = src[j].weight * zunionInterDictValue(other);
/* It is not safe to access the zset we are
* iterating, so explicitly check for equal object. */
if (src[j].dict == src[i].dict) {
value = src[i].weight * zunionInterDictValue(de);
zunionInterAggregate(&score,value,aggregate);
} else {
dictEntry *other;
other = dictFind(src[j].dict,dictGetEntryKey(de));
if (other) {
value = src[j].weight * zunionInterDictValue(other);
zunionInterAggregate(&score,value,aggregate);
}
}
}
+1 -1
View File
@@ -1 +1 @@
#define REDIS_VERSION "2.2.4"
#define REDIS_VERSION "2.2.11"
+34 -31
View File
@@ -398,12 +398,17 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
offset = p-zl;
extra = rawlensize-next.prevrawlensize;
zl = ziplistResize(zl,curlen+extra);
ZIPLIST_TAIL_OFFSET(zl) += extra;
p = zl+offset;
/* Move the tail to the back. */
/* Current pointer and offset for next element. */
np = p+rawlen;
noffset = np-zl;
/* Update tail offset when next element is not the tail element. */
if ((zl+ZIPLIST_TAIL_OFFSET(zl)) != np)
ZIPLIST_TAIL_OFFSET(zl) += extra;
/* Move the tail to the back. */
memmove(np+rawlensize,
np+next.prevrawlensize,
curlen-noffset-next.prevrawlensize-1);
@@ -877,7 +882,7 @@ void pop(unsigned char *zl, int where) {
}
}
void randstring(char *target, unsigned int min, unsigned int max) {
int randstring(char *target, unsigned int min, unsigned int max) {
int p, len = min+rand()%(max-min+1);
int minval, maxval;
switch(rand() % 3) {
@@ -899,10 +904,9 @@ void randstring(char *target, unsigned int min, unsigned int max) {
while(p < len)
target[p++] = minval+rand()%(maxval-minval+1);
return;
return len;
}
int main(int argc, char **argv) {
unsigned char *zl, *p;
unsigned char *entry;
@@ -1235,6 +1239,7 @@ int main(int argc, char **argv) {
int i,j,len,where;
unsigned char *p;
char buf[1024];
int buflen;
list *ref;
listNode *refnode;
@@ -1243,10 +1248,6 @@ int main(int argc, char **argv) {
unsigned int slen;
long long sval;
/* In the regression for the cascade bug, it was triggered
* with a random seed of 2. */
srand(2);
for (i = 0; i < 20000; i++) {
zl = ziplistNew();
ref = listCreate();
@@ -1256,31 +1257,32 @@ int main(int argc, char **argv) {
/* Create lists */
for (j = 0; j < len; j++) {
where = (rand() & 1) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
switch(rand() % 4) {
case 0:
sprintf(buf,"%lld",(0LL + rand()) >> 20);
break;
case 1:
sprintf(buf,"%lld",(0LL + rand()));
break;
case 2:
sprintf(buf,"%lld",(0LL + rand()) << 20);
break;
case 3:
randstring(buf,0,256);
break;
default:
assert(NULL);
if (rand() % 2) {
buflen = randstring(buf,1,sizeof(buf)-1);
} else {
switch(rand() % 3) {
case 0:
buflen = sprintf(buf,"%lld",(0LL + rand()) >> 20);
break;
case 1:
buflen = sprintf(buf,"%lld",(0LL + rand()));
break;
case 2:
buflen = sprintf(buf,"%lld",(0LL + rand()) << 20);
break;
default:
assert(NULL);
}
}
/* Add to ziplist */
zl = ziplistPush(zl, (unsigned char*)buf, strlen(buf), where);
zl = ziplistPush(zl, (unsigned char*)buf, buflen, where);
/* Add to reference list */
if (where == ZIPLIST_HEAD) {
listAddNodeHead(ref,sdsnew(buf));
listAddNodeHead(ref,sdsnewlen(buf, buflen));
} else if (where == ZIPLIST_TAIL) {
listAddNodeTail(ref,sdsnew(buf));
listAddNodeTail(ref,sdsnewlen(buf, buflen));
} else {
assert(NULL);
}
@@ -1295,12 +1297,13 @@ int main(int argc, char **argv) {
assert(ziplistGet(p,&sstr,&slen,&sval));
if (sstr == NULL) {
sprintf(buf,"%lld",sval);
buflen = sprintf(buf,"%lld",sval);
} else {
memcpy(buf,sstr,slen);
buf[slen] = '\0';
buflen = slen;
memcpy(buf,sstr,buflen);
buf[buflen] = '\0';
}
assert(strcmp(buf,listNodeValue(refnode)) == 0);
assert(memcmp(buf,listNodeValue(refnode),buflen) == 0);
}
zfree(zl);
listRelease(ref);
+47 -25
View File
@@ -31,13 +31,14 @@ tags {"aof"} {
}
start_server_aof [list dir $server_path] {
test {Unfinished MULTI: Server should not have been started} {
is_alive $srv
} {0}
test "Unfinished MULTI: Server should not have been started" {
assert_equal 0 [is_alive $srv]
}
test {Unfinished MULTI: Server should have logged an error} {
exec cat [dict get $srv stdout] | tail -n1
} {*Unexpected end of file reading the append only file*}
test "Unfinished MULTI: Server should have logged an error" {
set result [exec cat [dict get $srv stdout] | tail -n1]
assert_match "*Unexpected end of file reading the append only file*" $result
}
}
## Test that the server exits when the AOF contains a short read
@@ -47,36 +48,57 @@ tags {"aof"} {
}
start_server_aof [list dir $server_path] {
test {Short read: Server should not have been started} {
is_alive $srv
} {0}
test "Short read: Server should not have been started" {
assert_equal 0 [is_alive $srv]
}
test {Short read: Server should have logged an error} {
exec cat [dict get $srv stdout] | tail -n1
} {*Bad file format reading the append only file*}
test "Short read: Server should have logged an error" {
set result [exec cat [dict get $srv stdout] | tail -n1]
assert_match "*Bad file format reading the append only file*" $result
}
}
## Test that redis-check-aof indeed sees this AOF is not valid
test {Short read: Utility should confirm the AOF is not valid} {
test "Short read: Utility should confirm the AOF is not valid" {
catch {
exec src/redis-check-aof $aof_path
} str
set _ $str
} {*not valid*}
} result
assert_match "*not valid*" $result
}
test {Short read: Utility should be able to fix the AOF} {
exec echo y | src/redis-check-aof --fix $aof_path
} {*Successfully truncated AOF*}
test "Short read: Utility should be able to fix the AOF" {
set result [exec echo y | src/redis-check-aof --fix $aof_path]
assert_match "*Successfully truncated AOF*" $result
}
## Test that the server can be started using the truncated AOF
start_server_aof [list dir $server_path] {
test {Fixed AOF: Server should have been started} {
is_alive $srv
} {1}
test "Fixed AOF: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
test {Fixed AOF: Keyspace should contain values that were parsable} {
test "Fixed AOF: Keyspace should contain values that were parsable" {
set client [redis [dict get $srv host] [dict get $srv port]]
list [$client get foo] [$client get bar]
} {hello {}}
assert_equal "hello" [$client get foo]
assert_equal "" [$client get bar]
}
}
## Test that SPOP (that modifies the client its argc/argv) is correctly free'd
create_aof {
append_to_aof [formatCommand sadd set foo]
append_to_aof [formatCommand sadd set bar]
append_to_aof [formatCommand spop set]
}
start_server_aof [list dir $server_path] {
test "AOF+SPOP: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]]
assert_equal 1 [$client scard set]
}
}
}
+20
View File
@@ -6,6 +6,24 @@ start_server {tags {"repl"}} {
s -1 role
} {slave}
test {BRPOPLPUSH replication, when blocking against empty list} {
set rd [redis_deferring_client]
$rd brpoplpush a b 5
r lpush a foo
after 1000
assert_equal [r debug digest] [r -1 debug digest]
}
test {BRPOPLPUSH replication, list exists} {
set rd [redis_deferring_client]
r lpush c 1
r lpush c 2
r lpush c 3
$rd brpoplpush c d 5
after 1000
assert_equal [r debug digest] [r -1 debug digest]
}
test {MASTER and SLAVE dataset should be identical after complex ops} {
createComplexDataset r 10000
after 500
@@ -27,6 +45,8 @@ start_server {tags {"repl"}} {
test {MASTER and SLAVE consistency with expire} {
createComplexDataset r 50000 useexpire
after 4000 ;# Make sure everything expired before taking the digest
r keys * ;# Force DEL syntesizing to slave
after 1000 ;# Wait another second. Now everything should be fine.
if {[r debug digest] ne [r -1 debug digest]} {
set csv1 [csvdump r]
set csv2 [csvdump {r -1}]
+1
View File
@@ -108,6 +108,7 @@ proc cleanup {} {
}
proc execute_everything {} {
execute_tests "unit/printver"
execute_tests "unit/auth"
execute_tests "unit/protocol"
execute_tests "unit/basic"
+38 -11
View File
@@ -138,21 +138,48 @@ start_server {tags {"basic"}} {
r decrby novar 17179869185
} {-1}
test {SETNX target key missing} {
r setnx novar2 foobared
r get novar2
} {foobared}
test "SETNX target key missing" {
r del novar
assert_equal 1 [r setnx novar foobared]
assert_equal "foobared" [r get novar]
}
test {SETNX target key exists} {
r setnx novar2 blabla
r get novar2
} {foobared}
test "SETNX target key exists" {
r set novar foobared
assert_equal 0 [r setnx novar blabla]
assert_equal "foobared" [r get novar]
}
test {SETNX against volatile key} {
test "SETNX against not-expired volatile key" {
r set x 10
r expire x 10000
list [r setnx x 20] [r get x]
} {0 10}
assert_equal 0 [r setnx x 20]
assert_equal 10 [r get x]
}
test "SETNX against expired volatile key" {
# Make it very unlikely for the key this test uses to be expired by the
# active expiry cycle. This is tightly coupled to the implementation of
# active expiry and dbAdd() but currently the only way to test that
# SETNX expires a key when it should have been.
for {set x 0} {$x < 9999} {incr x} {
r setex key-$x 3600 value
}
# This will be one of 10000 expiring keys. A cycle is executed every
# 100ms, sampling 10 keys for being expired or not. This key will be
# expired for at most 1s when we wait 2s, resulting in a total sample
# of 100 keys. The probability of the success of this test being a
# false positive is therefore approx. 1%.
r set x 10
r expire x 1
# Wait for the key to expire
after 2000
assert_equal 1 [r setnx x 20]
assert_equal 20 [r get x]
}
test {EXISTS} {
set res {}
+6
View File
@@ -0,0 +1,6 @@
start_server {} {
set i [r info]
regexp {redis_version:(.*?)\r\n} $i - version
regexp {redis_git_sha1:(.*?)\r\n} $i - sha1
puts "Testing Redis version $version ($sha1)"
}
+8 -16
View File
@@ -55,13 +55,6 @@ start_server {
assert_equal $largevalue(linkedlist) [r lindex mylist2 2]
}
test {Variadic RPUSH/LPUSH} {
r del mylist
assert_equal 4 [r lpush mylist a b c d]
assert_equal 8 [r rpush mylist 0 1 2 3]
assert_equal {d c b a 0 1 2 3} [r lrange mylist 0 -1]
}
test {DEL a list - ziplist} {
assert_equal 1 [r del myziplist2]
assert_equal 0 [r exists myziplist2]
@@ -149,15 +142,6 @@ start_server {
}
}
test "BLPOP with variadic LPUSH" {
set rd [redis_deferring_client]
r del blist target
$rd blpop blist 0
assert_equal 2 [r lpush blist foo bar]
assert_equal {blist foo} [$rd read]
assert_equal bar [lindex [r lrange blist 0 -1] 0]
}
test "BRPOPLPUSH with zero timeout should block indefinitely" {
set rd [redis_deferring_client]
r del blist target
@@ -278,6 +262,14 @@ start_server {
r exec
} {foo bar {} {} {bar foo}}
test {BRPOPLPUSH timeout} {
set rd [redis_deferring_client]
$rd brpoplpush foo_list bar_list 1
after 2000
$rd read
} {}
foreach {pop} {BLPOP BRPOP} {
test "$pop: with single empty list argument" {
set rd [redis_deferring_client]
+7
View File
@@ -484,6 +484,13 @@ start_server {tags {"zset"}} {
test {ZINTERSTORE with AGGREGATE MAX} {
list [r zinterstore zsetc 2 zseta zsetb aggregate max] [r zrange zsetc 0 -1 withscores]
} {2 {b 2 c 3}}
test {ZINTERSTORE regression with two sets, intset+hashtable} {
r del seta setb setc
r sadd set1 a
r sadd set2 10
r zinterstore set3 2 set1 set2
} {0}
foreach cmd {ZUNIONSTORE ZINTERSTORE} {
test "$cmd with +inf/-inf scores" {
+14 -7
View File
@@ -1,7 +1,11 @@
#!/bin/sh
#
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
REDISPORT=6379
EXEC=/usr/local/bin/redis-server
CLIEXEC=/usr/local/bin/redis-cli
PIDFILE=/var/run/redis_${REDISPORT}.pid
CONF="/etc/redis/${REDISPORT}.conf"
@@ -10,21 +14,21 @@ case "$1" in
start)
if [ -f $PIDFILE ]
then
echo -n "$PIDFILE exists, process is already running or crashed\n"
echo "$PIDFILE exists, process is already running or crashed"
else
echo -n "Starting Redis server...\n"
echo "Starting Redis server..."
$EXEC $CONF
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo -n "$PIDFILE does not exist, process is not running\n"
echo "$PIDFILE does not exist, process is not running"
else
PID=$(cat $PIDFILE)
echo -n "Stopping ...\n"
echo -n "SHUTDOWN\r\n" | nc localhost $REDISPORT &
while [ -x /proc/${PIDFILE} ]
PID=$(cat $PIDFILE)
echo "Stopping ..."
$CLIEXEC -p $REDISPORT shutdown
while [ -x /proc/${PID} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
@@ -32,4 +36,7 @@ case "$1" in
echo "Redis stopped"
fi
;;
*)
echo "Please use start or stop as first argument"
;;
esac