Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7da5980848 | ||
|
|
062f60a36e | ||
|
|
4243483d3e | ||
|
|
d785413d86 | ||
|
|
e8a1a169dd | ||
|
|
dc0f13774b | ||
|
|
47911b8630 | ||
|
|
7ff729e192 | ||
|
|
02b9a72bae | ||
|
|
1862239136 | ||
|
|
f8791a158b | ||
|
|
26319d08b5 | ||
|
|
fc78d978de | ||
|
|
6cb7860658 | ||
|
|
611dcb56ee | ||
|
|
889b017403 | ||
|
|
d1369c3d9f | ||
|
|
6818905406 | ||
|
|
ef1cf15c14 | ||
|
|
dee8d84270 | ||
|
|
5576a28977 | ||
|
|
4f8b18f3dd | ||
|
|
c64255990e | ||
|
|
ba3c5494d8 | ||
|
|
684b61505a | ||
|
|
189e865c51 | ||
|
|
b9f8c2a5b0 | ||
|
|
18d16f8592 | ||
|
|
3a00520e5f | ||
|
|
f93d9929d8 |
@@ -14,6 +14,22 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[ Redis 2.6.12 ]
|
||||
|
||||
UPGRADE URGENCY: MODERATE, nothing very critical but a few non trivial bugs.
|
||||
|
||||
* [BUGFIX] redis-cli --bigkeys: don't crash with empty DB.
|
||||
* [BUGFIX] stop-writes-on-bgsave-error now works in redis.conf
|
||||
* [BUGFIX] Don't crash at startup if RDB is there but can't be opened.
|
||||
* [BUGFIX] Initial value for master_link_down_since_seconds is now huge.
|
||||
* [BUGFIX] Allow SELECT while loading the DB.
|
||||
* [BUGFIX] Don't replicate/AOF an empty MULTI/EXEC if the transaction
|
||||
is empty or containing just read-only commands.
|
||||
* [BUGFIX] EXPIRE should not be able to resurrect keys (see issue #1026).
|
||||
* [IMPROVED] Extended SET back ported from Redis 2.8 / unstable
|
||||
See http://redis.io/commands/set for more information.
|
||||
* [IMPROVED] Test suite improved.
|
||||
|
||||
--[ Redis 2.6.11 ]
|
||||
|
||||
UPGRADE URGENCY: LOW, however updating is encouraged if you have many instances
|
||||
@@ -23,6 +39,8 @@ UPGRADE URGENCY: LOW, however updating is encouraged if you have many instances
|
||||
* [BUGFIX] redis-cli: use keepalive socket option for improved reliability.
|
||||
* [BUGFIX] Allow AUTH while loading the DB in memory.
|
||||
* [BUGFIX] Don't segfault on unbalanced quotes while parsing config file.
|
||||
* [BUGFIX] Don't segfault if command gets propagated to AOF / replication
|
||||
link as another command name that was renamed in redis.conf
|
||||
* [IMPROVED] serverCron() frequency is now a runtime parameter (was REDIS_HZ).
|
||||
* [IMPROVED] Use a lot less CPU when idle, even with many configured DBs.
|
||||
|
||||
|
||||
+1
-1
@@ -789,7 +789,7 @@ void configGetCommand(redisClient *c) {
|
||||
/* String values */
|
||||
config_get_string_field("dbfilename",server.rdb_filename);
|
||||
config_get_string_field("requirepass",server.requirepass);
|
||||
config_get_string_field("masterauth",server.requirepass);
|
||||
config_get_string_field("masterauth",server.masterauth);
|
||||
config_get_string_field("bind",server.bindaddr);
|
||||
config_get_string_field("unixsocket",server.unixsocket);
|
||||
config_get_string_field("logfile",server.logfile);
|
||||
|
||||
@@ -540,7 +540,6 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
* unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for
|
||||
* the argv[2] parameter. The basetime is always specified in milliseconds. */
|
||||
void expireGenericCommand(redisClient *c, long long basetime, int unit) {
|
||||
dictEntry *de;
|
||||
robj *key = c->argv[1], *param = c->argv[2];
|
||||
long long when; /* unix time in milliseconds when the key will expire. */
|
||||
|
||||
@@ -550,11 +549,12 @@ void expireGenericCommand(redisClient *c, long long basetime, int unit) {
|
||||
if (unit == UNIT_SECONDS) when *= 1000;
|
||||
when += basetime;
|
||||
|
||||
de = dictFind(c->db->dict,key->ptr);
|
||||
if (de == NULL) {
|
||||
/* No key, return zero. */
|
||||
if (lookupKeyRead(c->db,key) == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a slave instance.
|
||||
|
||||
+7
-2
@@ -330,9 +330,14 @@ void debugCommand(redisClient *c) {
|
||||
|
||||
usleep(utime);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set-active-expire") &&
|
||||
c->argc == 3)
|
||||
{
|
||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
|
||||
addReplyErrorFormat(c, "Unknown DEBUG subcommand or wrong number of arguments for '%s'",
|
||||
(char*)c->argv[1]->ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-22
@@ -103,13 +103,11 @@ void discardCommand(redisClient *c) {
|
||||
|
||||
/* Send a MULTI command to all the slaves and AOF file. Check the execCommand
|
||||
* implementation for more information. */
|
||||
void execCommandReplicateMulti(redisClient *c) {
|
||||
void execCommandPropagateMulti(redisClient *c) {
|
||||
robj *multistring = createStringObject("MULTI",5);
|
||||
|
||||
if (server.aof_state != REDIS_AOF_OFF)
|
||||
feedAppendOnlyFile(server.multiCommand,c->db->id,&multistring,1);
|
||||
if (listLength(server.slaves))
|
||||
replicationFeedSlaves(server.slaves,c->db->id,&multistring,1);
|
||||
propagate(server.multiCommand,c->db->id,&multistring,1,
|
||||
REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL);
|
||||
decrRefCount(multistring);
|
||||
}
|
||||
|
||||
@@ -118,6 +116,7 @@ void execCommand(redisClient *c) {
|
||||
robj **orig_argv;
|
||||
int orig_argc;
|
||||
struct redisCommand *orig_cmd;
|
||||
int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */
|
||||
|
||||
if (!(c->flags & REDIS_MULTI)) {
|
||||
addReplyError(c,"EXEC without MULTI");
|
||||
@@ -133,19 +132,10 @@ void execCommand(redisClient *c) {
|
||||
if (c->flags & (REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC)) {
|
||||
addReply(c, c->flags & REDIS_DIRTY_EXEC ? shared.execaborterr :
|
||||
shared.nullmultibulk);
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);
|
||||
unwatchAllKeys(c);
|
||||
discardTransaction(c);
|
||||
goto handle_monitor;
|
||||
}
|
||||
|
||||
/* Replicate a MULTI request now that we are sure the block is executed.
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
execCommandReplicateMulti(c);
|
||||
|
||||
/* Exec all the queued commands */
|
||||
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
|
||||
orig_argv = c->argv;
|
||||
@@ -156,6 +146,16 @@ void execCommand(redisClient *c) {
|
||||
c->argc = c->mstate.commands[j].argc;
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
c->cmd = c->mstate.commands[j].cmd;
|
||||
|
||||
/* Propagate a MULTI request once we encounter the first write op.
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
if (!must_propagate && !(c->cmd->flags & REDIS_CMD_READONLY)) {
|
||||
execCommandPropagateMulti(c);
|
||||
must_propagate = 1;
|
||||
}
|
||||
|
||||
call(c,REDIS_CALL_FULL);
|
||||
|
||||
/* Commands may alter argc/argv, restore mstate. */
|
||||
@@ -166,13 +166,10 @@ void execCommand(redisClient *c) {
|
||||
c->argv = orig_argv;
|
||||
c->argc = orig_argc;
|
||||
c->cmd = orig_cmd;
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);
|
||||
/* Make sure the EXEC command is always replicated / AOF, since we
|
||||
* always send the MULTI command (we can't know beforehand if the
|
||||
* next operations will contain at least a modification to the DB). */
|
||||
server.dirty++;
|
||||
discardTransaction(c);
|
||||
/* Make sure the EXEC command will be propagated as well if MULTI
|
||||
* was already propagated. */
|
||||
if (must_propagate) server.dirty++;
|
||||
|
||||
handle_monitor:
|
||||
/* Send EXEC to clients waiting data from MONITOR. We do it here
|
||||
|
||||
@@ -1067,11 +1067,8 @@ int rdbLoad(char *filename) {
|
||||
FILE *fp;
|
||||
rio rdb;
|
||||
|
||||
fp = fopen(filename,"r");
|
||||
if (!fp) {
|
||||
errno = ENOENT;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
|
||||
|
||||
rioInitWithFile(&rdb,fp);
|
||||
if (server.rdb_checksum)
|
||||
rdb.update_cksum = rioGenericUpdateChecksum;
|
||||
|
||||
+184
@@ -42,6 +42,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "sds.h"
|
||||
@@ -76,6 +77,7 @@ static struct config {
|
||||
int slave_mode;
|
||||
int pipe_mode;
|
||||
int getrdb_mode;
|
||||
int stat_mode;
|
||||
char *rdb_filename;
|
||||
int bigkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
@@ -630,6 +632,36 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Send the INFO command, reconnecting the link if needed. */
|
||||
static redisReply *reconnectingInfo(void) {
|
||||
redisContext *c = context;
|
||||
redisReply *reply = NULL;
|
||||
int tries = 0;
|
||||
|
||||
assert(!c->err);
|
||||
while(reply == NULL) {
|
||||
while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
|
||||
printf("Reconnecting (%d)...\r", ++tries);
|
||||
fflush(stdout);
|
||||
|
||||
redisFree(c);
|
||||
c = redisConnect(config.hostip,config.hostport);
|
||||
usleep(1000000);
|
||||
}
|
||||
|
||||
reply = redisCommand(c,"INFO");
|
||||
if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
|
||||
fprintf(stderr, "Error: %s\n", c->errstr);
|
||||
exit(1);
|
||||
} else if (tries > 0) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
context = c;
|
||||
return reply;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
* User interface
|
||||
*--------------------------------------------------------------------------- */
|
||||
@@ -670,6 +702,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.latency_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--slave")) {
|
||||
config.slave_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--stat")) {
|
||||
config.stat_mode = 1;
|
||||
} else if (!strcmp(argv[i],"--rdb") && !lastarg) {
|
||||
config.getrdb_mode = 1;
|
||||
config.rdb_filename = argv[++i];
|
||||
@@ -1205,7 +1239,11 @@ static void findBigKeys(void) {
|
||||
fprintf(stderr, "RANDOMKEY error: %s\n",
|
||||
reply1->str);
|
||||
exit(1);
|
||||
} else if (reply1->type == REDIS_REPLY_NIL) {
|
||||
fprintf(stderr, "It looks like the database is empty!\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Get the key type */
|
||||
reply2 = redisCommand(context,"TYPE %s",reply1->str);
|
||||
assert(reply2 && reply2->type == REDIS_REPLY_STATUS);
|
||||
@@ -1261,6 +1299,145 @@ static void findBigKeys(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the specified INFO field from the INFO command output "info".
|
||||
* A new buffer is allocated for the result, that needs to be free'd.
|
||||
* If the field is not found NULL is returned. */
|
||||
static char *getInfoField(char *info, char *field) {
|
||||
char *p = strstr(info,field);
|
||||
char *n1, *n2;
|
||||
char *result;
|
||||
|
||||
if (!p) return NULL;
|
||||
p += strlen(field)+1;
|
||||
n1 = strchr(p,'\r');
|
||||
n2 = strchr(p,',');
|
||||
if (n2 && n2 < n1) n1 = n2;
|
||||
result = malloc(sizeof(char)*(n1-p)+1);
|
||||
memcpy(result,p,(n1-p));
|
||||
result[n1-p] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Like the above function but automatically convert the result into
|
||||
* a long. On error (missing field) LONG_MIN is returned. */
|
||||
static long getLongInfoField(char *info, char *field) {
|
||||
char *value = getInfoField(info,field);
|
||||
long l;
|
||||
|
||||
if (!value) return LONG_MIN;
|
||||
l = strtol(value,NULL,10);
|
||||
free(value);
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Convert number of bytes into a human readable string of the form:
|
||||
* 100B, 2G, 100M, 4K, and so forth. */
|
||||
void bytesToHuman(char *s, long long n) {
|
||||
double d;
|
||||
|
||||
if (n < 0) {
|
||||
*s = '-';
|
||||
s++;
|
||||
n = -n;
|
||||
}
|
||||
if (n < 1024) {
|
||||
/* Bytes */
|
||||
sprintf(s,"%lluB",n);
|
||||
return;
|
||||
} else if (n < (1024*1024)) {
|
||||
d = (double)n/(1024);
|
||||
sprintf(s,"%.2fK",d);
|
||||
} else if (n < (1024LL*1024*1024)) {
|
||||
d = (double)n/(1024*1024);
|
||||
sprintf(s,"%.2fM",d);
|
||||
} else if (n < (1024LL*1024*1024*1024)) {
|
||||
d = (double)n/(1024LL*1024*1024);
|
||||
sprintf(s,"%.2fG",d);
|
||||
}
|
||||
}
|
||||
|
||||
static void statMode() {
|
||||
redisReply *reply;
|
||||
long aux, requests = 0;
|
||||
int i = 0;
|
||||
|
||||
while(1) {
|
||||
char buf[64];
|
||||
int j;
|
||||
|
||||
reply = reconnectingInfo();
|
||||
if (reply->type == REDIS_REPLY_ERROR) {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((i++ % 20) == 0) {
|
||||
printf(
|
||||
"------- data ------ --------------------- load -------------------- - child -\n"
|
||||
"keys mem clients blocked requests connections \n");
|
||||
}
|
||||
|
||||
/* Keys */
|
||||
aux = 0;
|
||||
for (j = 0; j < 20; j++) {
|
||||
long k;
|
||||
|
||||
sprintf(buf,"db%d:keys",j);
|
||||
k = getLongInfoField(reply->str,buf);
|
||||
if (k == LONG_MIN) continue;
|
||||
aux += k;
|
||||
}
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf("%-11s",buf);
|
||||
|
||||
/* Used memory */
|
||||
aux = getLongInfoField(reply->str,"used_memory");
|
||||
bytesToHuman(buf,aux);
|
||||
printf("%-8s",buf);
|
||||
|
||||
/* Clients */
|
||||
aux = getLongInfoField(reply->str,"connected_clients");
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf(" %-8s",buf);
|
||||
|
||||
/* Blocked (BLPOPPING) Clients */
|
||||
aux = getLongInfoField(reply->str,"blocked_clients");
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf("%-8s",buf);
|
||||
|
||||
/* Requets */
|
||||
aux = getLongInfoField(reply->str,"total_commands_processed");
|
||||
sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
|
||||
printf("%-19s",buf);
|
||||
requests = aux;
|
||||
|
||||
/* Connections */
|
||||
aux = getLongInfoField(reply->str,"total_connections_received");
|
||||
sprintf(buf,"%ld",aux);
|
||||
printf(" %-12s",buf);
|
||||
|
||||
/* Children */
|
||||
aux = getLongInfoField(reply->str,"bgsave_in_progress");
|
||||
aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
|
||||
switch(aux) {
|
||||
case 0: break;
|
||||
case 1:
|
||||
printf("SAVE");
|
||||
break;
|
||||
case 2:
|
||||
printf("AOF");
|
||||
break;
|
||||
case 3:
|
||||
printf("SAVE+AOF");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
freeReplyObject(reply);
|
||||
usleep(config.interval);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int firstarg;
|
||||
|
||||
@@ -1325,6 +1502,13 @@ int main(int argc, char **argv) {
|
||||
findBigKeys();
|
||||
}
|
||||
|
||||
/* Stat mode */
|
||||
if (config.stat_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
if (config.interval == 0) config.interval = 1000000;
|
||||
statMode();
|
||||
}
|
||||
|
||||
/* Start interactive mode when no command is provided */
|
||||
if (argc == 0 && !config.eval) {
|
||||
/* Note that in repl mode we don't abort on connection error.
|
||||
|
||||
+9
-7
@@ -114,7 +114,7 @@ struct redisCommand *commandTable;
|
||||
*/
|
||||
struct redisCommand redisCommandTable[] = {
|
||||
{"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"setex",setexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
@@ -197,7 +197,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
|
||||
{"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
|
||||
{"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"select",selectCommand,2,"r",0,NULL,0,0,0,0,0},
|
||||
{"select",selectCommand,2,"rl",0,NULL,0,0,0,0,0},
|
||||
{"move",moveCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"rename",renameCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
|
||||
{"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
|
||||
@@ -236,7 +236,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"unsubscribe",unsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"punsubscribe",punsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"publish",publishCommand,3,"pflt",0,NULL,0,0,0,0,0},
|
||||
{"publish",publishCommand,3,"pfltr",0,NULL,0,0,0,0,0},
|
||||
{"watch",watchCommand,-2,"rs",0,noPreloadGetKeys,1,-1,1,0,0},
|
||||
{"unwatch",unwatchCommand,1,"rs",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,4,"awm",0,NULL,1,1,1,0,0},
|
||||
@@ -825,7 +825,8 @@ void clientsCron(void) {
|
||||
void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (server.masterhost == NULL) activeExpireCycle();
|
||||
if (server.active_expire_enabled && server.masterhost == NULL)
|
||||
activeExpireCycle();
|
||||
|
||||
/* Perform hash tables rehashing if needed, but only if there are no
|
||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||
@@ -1156,6 +1157,7 @@ void initServerConfig() {
|
||||
server.verbosity = REDIS_NOTICE;
|
||||
server.maxidletime = REDIS_MAXIDLETIME;
|
||||
server.tcpkeepalive = 0;
|
||||
server.active_expire_enabled = 1;
|
||||
server.client_max_querybuf_len = REDIS_MAX_QUERYBUF_LEN;
|
||||
server.saveparams = NULL;
|
||||
server.loading = 0;
|
||||
@@ -1185,6 +1187,7 @@ void initServerConfig() {
|
||||
server.requirepass = NULL;
|
||||
server.rdb_compression = 1;
|
||||
server.rdb_checksum = 1;
|
||||
server.stop_writes_on_bgsave_err = 1;
|
||||
server.activerehashing = 1;
|
||||
server.maxclients = REDIS_MAX_CLIENTS;
|
||||
server.bpop_blocked_clients = 0;
|
||||
@@ -1221,7 +1224,7 @@ void initServerConfig() {
|
||||
server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT;
|
||||
server.repl_serve_stale_data = 1;
|
||||
server.repl_slave_ro = 1;
|
||||
server.repl_down_since = time(NULL);
|
||||
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
|
||||
server.repl_disable_tcp_nodelay = 0;
|
||||
server.slave_priority = REDIS_DEFAULT_SLAVE_PRIORITY;
|
||||
|
||||
@@ -1392,7 +1395,6 @@ void initServer() {
|
||||
server.ops_sec_last_sample_ops = 0;
|
||||
server.unixtime = time(NULL);
|
||||
server.lastbgsave_status = REDIS_OK;
|
||||
server.stop_writes_on_bgsave_err = 1;
|
||||
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
|
||||
redisPanic("create time event failed");
|
||||
exit(1);
|
||||
@@ -2603,7 +2605,7 @@ void loadDataFromDisk(void) {
|
||||
redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",
|
||||
(float)(ustime()-start)/1000000);
|
||||
} else if (errno != ENOENT) {
|
||||
redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting.");
|
||||
redisLog(REDIS_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +557,7 @@ struct redisServer {
|
||||
int verbosity; /* Loglevel in redis.conf */
|
||||
int maxidletime; /* Client timeout in seconds */
|
||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||
size_t client_max_querybuf_len; /* Limit for client query buffer length */
|
||||
int dbnum; /* Total number of configured DBs */
|
||||
int daemonize; /* True if running as a daemon */
|
||||
|
||||
+62
-8
@@ -42,7 +42,27 @@ static int checkStringLength(redisClient *c, long long size) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expire, int unit) {
|
||||
/* The setGenericCommand() function implements the SET operation with different
|
||||
* options and variants. This function is called in order to implement the
|
||||
* following commands: SET, SETEX, PSETEX, SETNX.
|
||||
*
|
||||
* 'flags' changes the behavior of the command (NX or XX, see belove).
|
||||
*
|
||||
* 'expire' represents an expire to set in form of a Redis object as passed
|
||||
* by the user. It is interpreted according to the specified 'unit'.
|
||||
*
|
||||
* 'ok_reply' and 'abort_reply' is what the function will reply to the client
|
||||
* if the operation is performed, or when it is not because of NX or
|
||||
* XX flags.
|
||||
*
|
||||
* If ok_reply is NULL "+OK" is used.
|
||||
* If abort_reply is NULL, "$-1" is used. */
|
||||
|
||||
#define REDIS_SET_NO_FLAGS 0
|
||||
#define REDIS_SET_NX (1<<0) /* Set if key not exists. */
|
||||
#define REDIS_SET_XX (1<<1) /* Set if key exists. */
|
||||
|
||||
void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
|
||||
long long milliseconds = 0; /* initialized to avoid any harmness warning */
|
||||
|
||||
if (expire) {
|
||||
@@ -55,34 +75,68 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
|
||||
if (unit == UNIT_SECONDS) milliseconds *= 1000;
|
||||
}
|
||||
|
||||
if (nx && lookupKeyWrite(c->db,key) != NULL) {
|
||||
addReply(c,shared.czero);
|
||||
if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
|
||||
(flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL))
|
||||
{
|
||||
addReply(c, abort_reply ? abort_reply : shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
server.dirty++;
|
||||
if (expire) setExpire(c->db,key,mstime()+milliseconds);
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
addReply(c, ok_reply ? ok_reply : shared.ok);
|
||||
}
|
||||
|
||||
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
|
||||
void setCommand(redisClient *c) {
|
||||
int j;
|
||||
robj *expire = NULL;
|
||||
int unit = UNIT_SECONDS;
|
||||
int flags = REDIS_SET_NO_FLAGS;
|
||||
|
||||
for (j = 3; j < c->argc; j++) {
|
||||
char *a = c->argv[j]->ptr;
|
||||
robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
|
||||
|
||||
if ((a[0] == 'n' || a[0] == 'N') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
|
||||
flags |= REDIS_SET_NX;
|
||||
} else if ((a[0] == 'x' || a[0] == 'X') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
|
||||
flags |= REDIS_SET_XX;
|
||||
} else if ((a[0] == 'e' || a[0] == 'E') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
|
||||
unit = UNIT_SECONDS;
|
||||
expire = next;
|
||||
j++;
|
||||
} else if ((a[0] == 'p' || a[0] == 'P') &&
|
||||
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
|
||||
unit = UNIT_MILLISECONDS;
|
||||
expire = next;
|
||||
j++;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setGenericCommand(c,0,c->argv[1],c->argv[2],NULL,0);
|
||||
setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
|
||||
}
|
||||
|
||||
void setnxCommand(redisClient *c) {
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setGenericCommand(c,1,c->argv[1],c->argv[2],NULL,0);
|
||||
setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
|
||||
}
|
||||
|
||||
void setexCommand(redisClient *c) {
|
||||
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
||||
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS);
|
||||
setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
|
||||
}
|
||||
|
||||
void psetexCommand(redisClient *c) {
|
||||
c->argv[3] = tryObjectEncoding(c->argv[3]);
|
||||
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS);
|
||||
setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
|
||||
}
|
||||
|
||||
int getGenericCommand(redisClient *c) {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.6.11"
|
||||
#define REDIS_VERSION "2.6.12"
|
||||
|
||||
@@ -23,3 +23,61 @@ start_server [list overrides [list "dir" $server_path "dbfilename" "encodings.rd
|
||||
}
|
||||
}
|
||||
|
||||
set server_path [tmpdir "server.rdb-startup-test"]
|
||||
|
||||
start_server [list overrides [list "dir" $server_path]] {
|
||||
test {Server started empty with non-existing RDB file} {
|
||||
r debug digest
|
||||
} {0000000000000000000000000000000000000000}
|
||||
# Save an RDB file, needed for the next test.
|
||||
r save
|
||||
}
|
||||
|
||||
start_server [list overrides [list "dir" $server_path]] {
|
||||
test {Server started empty with empty RDB file} {
|
||||
r debug digest
|
||||
} {0000000000000000000000000000000000000000}
|
||||
}
|
||||
|
||||
# Helper function to start a server and kill it, just to check the error
|
||||
# logged.
|
||||
set defaults {}
|
||||
proc start_server_and_kill_it {overrides code} {
|
||||
upvar defaults defaults srv srv server_path server_path
|
||||
set config [concat $defaults $overrides]
|
||||
set srv [start_server [list overrides $config]]
|
||||
uplevel 1 $code
|
||||
kill_server $srv
|
||||
}
|
||||
|
||||
# Make the RDB file unreadable
|
||||
file attributes [file join $server_path dump.rdb] -permissions 0222
|
||||
|
||||
# Now make sure the server aborted with an error
|
||||
start_server_and_kill_it [list "dir" $server_path] {
|
||||
wait_for_condition 50 100 {
|
||||
[string match {*Fatal error loading*} \
|
||||
[exec tail -n1 < [dict get $srv stdout]]]
|
||||
} else {
|
||||
fail "Server started even if RDB was unreadable!"
|
||||
}
|
||||
}
|
||||
|
||||
# Fix permissions of the RDB file, but corrupt its CRC64 checksum.
|
||||
file attributes [file join $server_path dump.rdb] -permissions 0666
|
||||
set filesize [file size [file join $server_path dump.rdb]]
|
||||
set fd [open [file join $server_path dump.rdb] r+]
|
||||
fconfigure $fd -translation binary
|
||||
seek $fd -8 end
|
||||
puts -nonewline $fd "foobar00"; # Corrupt the checksum
|
||||
close $fd
|
||||
|
||||
# Now make sure the server aborted with an error
|
||||
start_server_and_kill_it [list "dir" $server_path] {
|
||||
wait_for_condition 50 100 {
|
||||
[string match {*RDB checksum*} \
|
||||
[exec tail -n1 < [dict get $srv stdout]]]
|
||||
} else {
|
||||
fail "Server started even if RDB was corrupted!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +408,65 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
}
|
||||
}
|
||||
|
||||
proc attach_to_replication_stream {} {
|
||||
set s [socket [srv 0 "host"] [srv 0 "port"]]
|
||||
fconfigure $s -translation binary
|
||||
puts -nonewline $s "SYNC\r\n"
|
||||
flush $s
|
||||
|
||||
# Get the count
|
||||
set count [gets $s]
|
||||
set prefix [string range $count 0 0]
|
||||
if {$prefix ne {$}} {
|
||||
error "attach_to_replication_stream error. Received '$count' as count."
|
||||
}
|
||||
set count [string range $count 1 end]
|
||||
|
||||
# Consume the bulk payload
|
||||
while {$count} {
|
||||
set buf [read $s $count]
|
||||
set count [expr {$count-[string length $buf]}]
|
||||
}
|
||||
return $s
|
||||
}
|
||||
|
||||
proc read_from_replication_stream {s} {
|
||||
fconfigure $s -blocking 0
|
||||
set attempt 0
|
||||
while {[gets $s count] == -1} {
|
||||
if {[incr attempt] == 10} return ""
|
||||
after 100
|
||||
}
|
||||
fconfigure $s -blocking 1
|
||||
|
||||
# Workaround for Redis 2.6, not always using the new protocol in the
|
||||
# replication channel (this was fixed in >= 2.8).
|
||||
if {[string tolower [lindex [split $count] 0]] eq {select}} {
|
||||
return $count
|
||||
}
|
||||
|
||||
# Return a list of arguments for the command.
|
||||
set count [string range $count 1 end]
|
||||
set res {}
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
read $s 1
|
||||
set arg [::redis::redis_bulk_read $s]
|
||||
if {$j == 0} {set arg [string tolower $arg]}
|
||||
lappend res $arg
|
||||
}
|
||||
return $res
|
||||
}
|
||||
|
||||
proc assert_replication_stream {s patterns} {
|
||||
for {set j 0} {$j < [llength $patterns]} {incr j} {
|
||||
assert_match [lindex $patterns $j] [read_from_replication_stream $s]
|
||||
}
|
||||
}
|
||||
|
||||
proc close_replication_stream {s} {
|
||||
close $s
|
||||
}
|
||||
|
||||
# With the parallel test running multiple Redis instances at the same time
|
||||
# we need a fast enough computer, otherwise a lot of tests may generate
|
||||
# false positives.
|
||||
|
||||
@@ -712,4 +712,46 @@ start_server {tags {"basic"}} {
|
||||
assert_equal [string range $bin $_start $_end] [r getrange bin $start $end]
|
||||
}
|
||||
}
|
||||
|
||||
test {Extended SET can detect syntax errors} {
|
||||
set e {}
|
||||
catch {r set foo bar non-existing-option} e
|
||||
set e
|
||||
} {*syntax*}
|
||||
|
||||
test {Extended SET NX option} {
|
||||
r del foo
|
||||
set v1 [r set foo 1 nx]
|
||||
set v2 [r set foo 2 nx]
|
||||
list $v1 $v2 [r get foo]
|
||||
} {OK {} 1}
|
||||
|
||||
test {Extended SET XX option} {
|
||||
r del foo
|
||||
set v1 [r set foo 1 xx]
|
||||
r set foo bar
|
||||
set v2 [r set foo 2 xx]
|
||||
list $v1 $v2 [r get foo]
|
||||
} {{} OK 2}
|
||||
|
||||
test {Extended SET EX option} {
|
||||
r del foo
|
||||
r set foo bar ex 10
|
||||
set ttl [r ttl foo]
|
||||
assert {$ttl <= 10 && $ttl > 5}
|
||||
}
|
||||
|
||||
test {Extended SET PX option} {
|
||||
r del foo
|
||||
r set foo bar px 10000
|
||||
set ttl [r ttl foo]
|
||||
assert {$ttl <= 10 && $ttl > 5}
|
||||
}
|
||||
|
||||
test {Extended SET using multiple options at once} {
|
||||
r set foo val
|
||||
assert {[r set foo bar xx px 10000] eq {OK}}
|
||||
set ttl [r ttl foo]
|
||||
assert {$ttl <= 10 && $ttl > 5}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,34 @@ start_server {tags {"expire"}} {
|
||||
list $size1 $size2
|
||||
} {3 0}
|
||||
|
||||
test {Redis should lazy expire keys} {
|
||||
r flushdb
|
||||
r debug set-active-expire 0
|
||||
r psetex key1 500 a
|
||||
r psetex key2 500 a
|
||||
r psetex key3 500 a
|
||||
set size1 [r dbsize]
|
||||
# Redis expires random keys ten times every second so we are
|
||||
# fairly sure that all the three keys should be evicted after
|
||||
# one second.
|
||||
after 1000
|
||||
set size2 [r dbsize]
|
||||
r mget key1 key2 key3
|
||||
set size3 [r dbsize]
|
||||
r debug set-active-expire 1
|
||||
list $size1 $size2 $size3
|
||||
} {3 3 0}
|
||||
|
||||
test {EXPIRE should not resurrect keys (issue #1026)} {
|
||||
r debug set-active-expire 0
|
||||
r set foo bar
|
||||
r pexpire foo 500
|
||||
after 1000
|
||||
r expire foo 10
|
||||
r debug set-active-expire 1
|
||||
r exists foo
|
||||
} {0}
|
||||
|
||||
test {5 keys in, 5 keys out} {
|
||||
r flushdb
|
||||
r set a c
|
||||
|
||||
+58
-1
@@ -205,7 +205,10 @@ start_server {tags {"multi"}} {
|
||||
r select 5
|
||||
r multi
|
||||
r ping
|
||||
r exec
|
||||
set res [r exec]
|
||||
# Restore original DB
|
||||
r select 9
|
||||
set res
|
||||
} {PONG}
|
||||
|
||||
test {WATCH will consider touched keys target of EXPIRE} {
|
||||
@@ -249,4 +252,58 @@ start_server {tags {"multi"}} {
|
||||
r incr x
|
||||
r exec
|
||||
} {11}
|
||||
|
||||
test {MULTI / EXEC is propagated correctly (single write command)} {
|
||||
set repl [attach_to_replication_stream]
|
||||
r multi
|
||||
r set foo bar
|
||||
r exec
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{multi}
|
||||
{set foo bar}
|
||||
{exec}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
}
|
||||
|
||||
test {MULTI / EXEC is propagated correctly (empty transaction)} {
|
||||
set repl [attach_to_replication_stream]
|
||||
r multi
|
||||
r exec
|
||||
r set foo bar
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{set foo bar}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
}
|
||||
|
||||
test {MULTI / EXEC is propagated correctly (read-only commands)} {
|
||||
r set foo value1
|
||||
set repl [attach_to_replication_stream]
|
||||
r multi
|
||||
r get foo
|
||||
r exec
|
||||
r set foo value2
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{set foo value2}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
}
|
||||
|
||||
test {MULTI / EXEC is propagated correctly (write command, no effect)} {
|
||||
r del bar foo bar
|
||||
set repl [attach_to_replication_stream]
|
||||
r multi
|
||||
r del foo
|
||||
r exec
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{multi}
|
||||
{exec}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ start_server {tags {"obuf-limits"}} {
|
||||
if {![regexp {omem=([0-9]+)} $c - omem]} break
|
||||
if {$omem > 200000} break
|
||||
}
|
||||
assert {$omem >= 99000 && $omem < 200000}
|
||||
assert {$omem >= 90000 && $omem < 200000}
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user