Commit 9e67df2a authored by Oran Agra's avatar Oran Agra
Browse files

rebase from unstable

parents f472bb10 bcb4d091
...@@ -34,10 +34,26 @@ ...@@ -34,10 +34,26 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
static struct { /*-----------------------------------------------------------------------------
const char *name; * Config file name-value maps.
const int value; *----------------------------------------------------------------------------*/
} validSyslogFacilities[] = {
typedef struct configEnum {
const char *name;
const int val;
} configEnum;
configEnum maxmemory_policy_enum[] = {
{"volatile-lru", REDIS_MAXMEMORY_VOLATILE_LRU},
{"volatile-random",REDIS_MAXMEMORY_VOLATILE_RANDOM},
{"volatile-ttl",REDIS_MAXMEMORY_VOLATILE_TTL},
{"allkeys-lru",REDIS_MAXMEMORY_ALLKEYS_LRU},
{"allkeys-random",REDIS_MAXMEMORY_ALLKEYS_RANDOM},
{"noeviction",REDIS_MAXMEMORY_NO_EVICTION},
{NULL, 0}
};
configEnum syslog_facility_enum[] = {
{"user", LOG_USER}, {"user", LOG_USER},
{"local0", LOG_LOCAL0}, {"local0", LOG_LOCAL0},
{"local1", LOG_LOCAL1}, {"local1", LOG_LOCAL1},
...@@ -50,6 +66,30 @@ static struct { ...@@ -50,6 +66,30 @@ static struct {
{NULL, 0} {NULL, 0}
}; };
configEnum loglevel_enum[] = {
{"debug", REDIS_DEBUG},
{"verbose", REDIS_VERBOSE},
{"notice", REDIS_NOTICE},
{"warning", REDIS_WARNING},
{NULL,0}
};
configEnum supervised_mode_enum[] = {
{"upstart", REDIS_SUPERVISED_UPSTART},
{"systemd", REDIS_SUPERVISED_SYSTEMD},
{"auto", REDIS_SUPERVISED_AUTODETECT},
{"no", REDIS_SUPERVISED_NONE},
{NULL, 0}
};
configEnum aof_fsync_enum[] = {
{"everysec", AOF_FSYNC_EVERYSEC},
{"always", AOF_FSYNC_ALWAYS},
{"no", AOF_FSYNC_NO},
{NULL, 0}
};
/* Output buffer limits presets. */
clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_TYPE_COUNT] = { clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_TYPE_COUNT] = {
{0, 0, 0}, /* normal */ {0, 0, 0}, /* normal */
{1024*1024*256, 1024*1024*64, 60}, /* slave */ {1024*1024*256, 1024*1024*64, 60}, /* slave */
...@@ -57,10 +97,42 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_TYPE_COUNT] = { ...@@ -57,10 +97,42 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_TYPE_COUNT] = {
}; };
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Config file parsing * Enum access functions
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
int supervisedToMode(const char *str); /* Get enum value from name. If there is no match INT_MIN is returned. */
int configEnumGetValue(configEnum *ce, char *name) {
while(ce->name != NULL) {
if (!strcasecmp(ce->name,name)) return ce->val;
ce++;
}
return INT_MIN;
}
/* Get enum name from value. If no match is found NULL is returned. */
const char *configEnumGetName(configEnum *ce, int val) {
while(ce->name != NULL) {
if (ce->val == val) return ce->name;
ce++;
}
return NULL;
}
/* Wrapper for configEnumGetName() returning "unknown" insetad of NULL if
* there is no match. */
const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
const char *name = configEnumGetName(ce,val);
return name ? name : "unknown";
}
/* Used for INFO generation. */
const char *maxmemoryToString(void) {
return configEnumGetNameOrUnknown(maxmemory_policy_enum,server.maxmemory);
}
/*-----------------------------------------------------------------------------
* Config file parsing
*----------------------------------------------------------------------------*/
int yesnotoi(char *s) { int yesnotoi(char *s) {
if (!strcasecmp(s,"yes")) return 1; if (!strcasecmp(s,"yes")) return 1;
...@@ -169,12 +241,10 @@ void loadServerConfigFromString(char *config) { ...@@ -169,12 +241,10 @@ void loadServerConfigFromString(char *config) {
exit(1); exit(1);
} }
} else if (!strcasecmp(argv[0],"loglevel") && argc == 2) { } else if (!strcasecmp(argv[0],"loglevel") && argc == 2) {
if (!strcasecmp(argv[1],"debug")) server.verbosity = REDIS_DEBUG; server.verbosity = configEnumGetValue(loglevel_enum,argv[1]);
else if (!strcasecmp(argv[1],"verbose")) server.verbosity = REDIS_VERBOSE; if (server.verbosity == INT_MIN) {
else if (!strcasecmp(argv[1],"notice")) server.verbosity = REDIS_NOTICE; err = "Invalid log level. "
else if (!strcasecmp(argv[1],"warning")) server.verbosity = REDIS_WARNING; "Must be one of debug, verbose, notice, warning";
else {
err = "Invalid log level. Must be one of debug, notice, warning";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) { } else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
...@@ -201,16 +271,9 @@ void loadServerConfigFromString(char *config) { ...@@ -201,16 +271,9 @@ void loadServerConfigFromString(char *config) {
if (server.syslog_ident) zfree(server.syslog_ident); if (server.syslog_ident) zfree(server.syslog_ident);
server.syslog_ident = zstrdup(argv[1]); server.syslog_ident = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) { } else if (!strcasecmp(argv[0],"syslog-facility") && argc == 2) {
int i; server.syslog_facility =
configEnumGetValue(syslog_facility_enum,argv[1]);
for (i = 0; validSyslogFacilities[i].name; i++) { if (server.syslog_facility == INT_MIN) {
if (!strcasecmp(validSyslogFacilities[i].name, argv[1])) {
server.syslog_facility = validSyslogFacilities[i].value;
break;
}
}
if (!validSyslogFacilities[i].name) {
err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7"; err = "Invalid log facility. Must be one of USER or between LOCAL0-LOCAL7";
goto loaderr; goto loaderr;
} }
...@@ -229,19 +292,9 @@ void loadServerConfigFromString(char *config) { ...@@ -229,19 +292,9 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) { } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) {
server.maxmemory = memtoll(argv[1],NULL); server.maxmemory = memtoll(argv[1],NULL);
} else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) { } else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) {
if (!strcasecmp(argv[1],"volatile-lru")) { server.maxmemory_policy =
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU; configEnumGetValue(maxmemory_policy_enum,argv[1]);
} else if (!strcasecmp(argv[1],"volatile-random")) { if (server.maxmemory_policy == INT_MIN) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
} else if (!strcasecmp(argv[1],"volatile-ttl")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
} else if (!strcasecmp(argv[1],"allkeys-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
} else if (!strcasecmp(argv[1],"allkeys-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
} else if (!strcasecmp(argv[1],"noeviction")) {
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
} else {
err = "Invalid maxmemory policy"; err = "Invalid maxmemory policy";
goto loaderr; goto loaderr;
} }
...@@ -345,13 +398,8 @@ void loadServerConfigFromString(char *config) { ...@@ -345,13 +398,8 @@ void loadServerConfigFromString(char *config) {
err = "argument must be 'yes' or 'no'"; goto loaderr; err = "argument must be 'yes' or 'no'"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) { } else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
if (!strcasecmp(argv[1],"no")) { server.aof_fsync = configEnumGetValue(aof_fsync_enum,argv[1]);
server.aof_fsync = AOF_FSYNC_NO; if (server.aof_fsync == INT_MIN) {
} else if (!strcasecmp(argv[1],"always")) {
server.aof_fsync = AOF_FSYNC_ALWAYS;
} else if (!strcasecmp(argv[1],"everysec")) {
server.aof_fsync = AOF_FSYNC_EVERYSEC;
} else {
err = "argument must be 'no', 'always' or 'everysec'"; err = "argument must be 'no', 'always' or 'everysec'";
goto loaderr; goto loaderr;
} }
...@@ -536,14 +584,14 @@ void loadServerConfigFromString(char *config) { ...@@ -536,14 +584,14 @@ void loadServerConfigFromString(char *config) {
} }
server.notify_keyspace_events = flags; server.notify_keyspace_events = flags;
} else if (!strcasecmp(argv[0],"supervised") && argc == 2) { } else if (!strcasecmp(argv[0],"supervised") && argc == 2) {
int mode = supervisedToMode(argv[1]); server.supervised_mode =
configEnumGetValue(supervised_mode_enum,argv[1]);
if (mode == -1) { if (server.supervised_mode == INT_MIN) {
err = "Invalid option for 'supervised'. " err = "Invalid option for 'supervised'. "
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'"; "Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
goto loaderr; goto loaderr;
} }
server.supervised_mode = mode;
} else if (!strcasecmp(argv[0],"sentinel")) { } else if (!strcasecmp(argv[0],"sentinel")) {
/* argc == 1 is handled by main() as we need to enter the sentinel /* argc == 1 is handled by main() as we need to enter the sentinel
* mode ASAP. */ * mode ASAP. */
...@@ -621,6 +669,36 @@ void loadServerConfig(char *filename, char *options) { ...@@ -621,6 +669,36 @@ void loadServerConfig(char *filename, char *options) {
* CONFIG SET implementation * CONFIG SET implementation
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
#define config_set_bool_field(_name,_var) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
int yn = yesnotoi(o->ptr); \
if (yn == -1) goto badfmt; \
_var = yn;
#define config_set_numerical_field(_name,_var,min,max) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt; \
if (min != LLONG_MIN && ll < min) goto badfmt; \
if (max != LLONG_MAX && ll > max) goto badfmt; \
_var = ll;
#define config_set_memory_field(_name,_var) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
ll = memtoll(o->ptr,&err); \
if (err || ll < 0) goto badfmt; \
_var = ll;
#define config_set_enum_field(_name,_var,_enumvar) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) { \
int enumval = configEnumGetValue(_enumvar,o->ptr); \
if (enumval == INT_MIN) goto badfmt; \
_var = enumval;
#define config_set_special_field(_name) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) {
#define config_set_else } else
void configSetCommand(redisClient *c) { void configSetCommand(redisClient *c) {
robj *o; robj *o;
long long ll; long long ll;
...@@ -629,31 +707,24 @@ void configSetCommand(redisClient *c) { ...@@ -629,31 +707,24 @@ void configSetCommand(redisClient *c) {
redisAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3])); redisAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
o = c->argv[3]; o = c->argv[3];
if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) { if (0) { /* this starts the config_set macros else-if chain. */
/* Special fields that can't be handled with general macros. */
config_set_special_field("dbfilename") {
if (!pathIsBaseName(o->ptr)) { if (!pathIsBaseName(o->ptr)) {
addReplyError(c, "dbfilename can't be a path, just a filename"); addReplyError(c, "dbfilename can't be a path, just a filename");
return; return;
} }
zfree(server.rdb_filename); zfree(server.rdb_filename);
server.rdb_filename = zstrdup(o->ptr); server.rdb_filename = zstrdup(o->ptr);
} else if (!strcasecmp(c->argv[2]->ptr,"requirepass")) { } config_set_special_field("requirepass") {
if (sdslen(o->ptr) > REDIS_AUTHPASS_MAX_LEN) goto badfmt; if (sdslen(o->ptr) > REDIS_AUTHPASS_MAX_LEN) goto badfmt;
zfree(server.requirepass); zfree(server.requirepass);
server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL; server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
} else if (!strcasecmp(c->argv[2]->ptr,"masterauth")) { } config_set_special_field("masterauth") {
zfree(server.masterauth); zfree(server.masterauth);
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL; server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory")) { } config_set_special_field("maxclients") {
ll = memtoll(o->ptr,&err);
if (err || ll < 0) goto badfmt;
server.maxmemory = ll;
if (server.maxmemory) {
if (server.maxmemory < zmalloc_used_memory()) {
redisLog(REDIS_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
}
freeMemoryIfNeeded();
}
} else if (!strcasecmp(c->argv[2]->ptr,"maxclients")) {
int orig_value = server.maxclients; int orig_value = server.maxclients;
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 1) goto badfmt; if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 1) goto badfmt;
...@@ -679,55 +750,7 @@ void configSetCommand(redisClient *c) { ...@@ -679,55 +750,7 @@ void configSetCommand(redisClient *c) {
} }
} }
} }
} else if (!strcasecmp(c->argv[2]->ptr,"hz")) { } config_set_special_field("appendonly") {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hz = ll;
if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-policy")) {
if (!strcasecmp(o->ptr,"volatile-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU;
} else if (!strcasecmp(o->ptr,"volatile-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_RANDOM;
} else if (!strcasecmp(o->ptr,"volatile-ttl")) {
server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_TTL;
} else if (!strcasecmp(o->ptr,"allkeys-lru")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_LRU;
} else if (!strcasecmp(o->ptr,"allkeys-random")) {
server.maxmemory_policy = REDIS_MAXMEMORY_ALLKEYS_RANDOM;
} else if (!strcasecmp(o->ptr,"noeviction")) {
server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
} else {
goto badfmt;
}
} else if (!strcasecmp(c->argv[2]->ptr,"maxmemory-samples")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll <= 0) goto badfmt;
server.maxmemory_samples = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0 || ll > LONG_MAX) goto badfmt;
server.maxidletime = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"tcp-keepalive")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
ll < 0 || ll > INT_MAX) goto badfmt;
server.tcpkeepalive = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"appendfsync")) {
if (!strcasecmp(o->ptr,"no")) {
server.aof_fsync = AOF_FSYNC_NO;
} else if (!strcasecmp(o->ptr,"everysec")) {
server.aof_fsync = AOF_FSYNC_EVERYSEC;
} else if (!strcasecmp(o->ptr,"always")) {
server.aof_fsync = AOF_FSYNC_ALWAYS;
} else {
goto badfmt;
}
} else if (!strcasecmp(c->argv[2]->ptr,"no-appendfsync-on-rewrite")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.aof_no_fsync_on_rewrite = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"appendonly")) {
int enable = yesnotoi(o->ptr); int enable = yesnotoi(o->ptr);
if (enable == -1) goto badfmt; if (enable == -1) goto badfmt;
...@@ -740,23 +763,7 @@ void configSetCommand(redisClient *c) { ...@@ -740,23 +763,7 @@ void configSetCommand(redisClient *c) {
return; return;
} }
} }
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-percentage")) { } config_set_special_field("save") {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.aof_rewrite_perc = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"auto-aof-rewrite-min-size")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.aof_rewrite_min_size = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"aof-rewrite-incremental-fsync")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.aof_rewrite_incremental_fsync = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"aof-load-truncated")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.aof_load_truncated = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"save")) {
int vlen, j; int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen); sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
...@@ -790,70 +797,12 @@ void configSetCommand(redisClient *c) { ...@@ -790,70 +797,12 @@ void configSetCommand(redisClient *c) {
appendServerSaveParams(seconds, changes); appendServerSaveParams(seconds, changes);
} }
sdsfreesplitres(v,vlen); sdsfreesplitres(v,vlen);
} else if (!strcasecmp(c->argv[2]->ptr,"slave-serve-stale-data")) { } config_set_special_field("dir") {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.repl_serve_stale_data = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"slave-read-only")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.repl_slave_ro = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"dir")) {
if (chdir((char*)o->ptr) == -1) { if (chdir((char*)o->ptr) == -1) {
addReplyErrorFormat(c,"Changing directory: %s", strerror(errno)); addReplyErrorFormat(c,"Changing directory: %s", strerror(errno));
return; return;
} }
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-entries")) { } config_set_special_field("client-output-buffer-limit") {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_ziplist_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"hash-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hash_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-max-ziplist-size")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_max_ziplist_size = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"list-compress-depth")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.list_compress_depth = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"set-max-intset-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.set_max_intset_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-entries")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.zset_max_ziplist_entries = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.zset_max_ziplist_value = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"hll-sparse-max-bytes")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.hll_sparse_max_bytes = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.lua_time_limit = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-log-slower-than")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR) goto badfmt;
server.slowlog_log_slower_than = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-max-len")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.slowlog_max_len = (unsigned)ll;
} else if (!strcasecmp(c->argv[2]->ptr,"latency-monitor-threshold")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.latency_monitor_threshold = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"loglevel")) {
if (!strcasecmp(o->ptr,"warning")) {
server.verbosity = REDIS_WARNING;
} else if (!strcasecmp(o->ptr,"notice")) {
server.verbosity = REDIS_NOTICE;
} else if (!strcasecmp(o->ptr,"verbose")) {
server.verbosity = REDIS_VERBOSE;
} else if (!strcasecmp(o->ptr,"debug")) {
server.verbosity = REDIS_DEBUG;
} else {
goto badfmt;
}
} else if (!strcasecmp(c->argv[2]->ptr,"client-output-buffer-limit")) {
int vlen, j; int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen); sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
...@@ -898,90 +847,137 @@ void configSetCommand(redisClient *c) { ...@@ -898,90 +847,137 @@ void configSetCommand(redisClient *c) {
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds; server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
} }
sdsfreesplitres(v,vlen); sdsfreesplitres(v,vlen);
} else if (!strcasecmp(c->argv[2]->ptr,"stop-writes-on-bgsave-error")) { } config_set_special_field("notify-keyspace-events") {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.stop_writes_on_bgsave_err = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-ping-slave-period")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
server.repl_ping_slave_period = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-timeout")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll <= 0) goto badfmt;
server.repl_timeout = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-size")) {
ll = memtoll(o->ptr,&err);
if (err || ll < 0) goto badfmt;
resizeReplicationBacklog(ll);
} else if (!strcasecmp(c->argv[2]->ptr,"repl-backlog-ttl")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
server.repl_backlog_time_limit = ll;
} else if (!strcasecmp(c->argv[2]->ptr,"watchdog-period")) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
if (ll)
enableWatchdog(ll);
else
disableWatchdog();
} else if (!strcasecmp(c->argv[2]->ptr,"rdbcompression")) {
int yn = yesnotoi(o->ptr);
if (yn == -1) goto badfmt;
server.rdb_compression = yn;
} else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
int flags = keyspaceEventsStringToFlags(o->ptr); int flags = keyspaceEventsStringToFlags(o->ptr);
if (flags == -1) goto badfmt; if (flags == -1) goto badfmt;
server.notify_keyspace_events = flags; server.notify_keyspace_events = flags;
} else if (!strcasecmp(c->argv[2]->ptr,"repl-disable-tcp-nodelay")) {
int yn = yesnotoi(o->ptr); /* Boolean fields.
* config_set_bool_field(name,var). */
if (yn == -1) goto badfmt; } config_set_bool_field(
server.repl_disable_tcp_nodelay = yn; "rdbcompression", server.rdb_compression) {
} else if (!strcasecmp(c->argv[2]->ptr,"repl-diskless-sync")) { } config_set_bool_field(
int yn = yesnotoi(o->ptr); "repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay) {
} config_set_bool_field(
if (yn == -1) goto badfmt; "repl-diskless-sync",server.repl_diskless_sync) {
server.repl_diskless_sync = yn; } config_set_bool_field(
} else if (!strcasecmp(c->argv[2]->ptr,"repl-diskless-sync-delay")) { "cluster-require-full-coverage",server.cluster_require_full_coverage) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || } config_set_bool_field(
ll < 0) goto badfmt; "aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
server.repl_diskless_sync_delay = ll; } config_set_bool_field(
} else if (!strcasecmp(c->argv[2]->ptr,"slave-priority")) { "aof-load-truncated",server.aof_load_truncated) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || } config_set_bool_field(
ll < 0) goto badfmt; "slave-serve-stale-data",server.repl_serve_stale_data) {
server.slave_priority = ll; } config_set_bool_field(
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-to-write")) { "slave-read-only",server.repl_slave_ro) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || } config_set_bool_field(
ll < 0) goto badfmt; "activerehashing",server.activerehashing) {
server.repl_min_slaves_to_write = ll; } config_set_bool_field(
"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
/* Numerical fields.
* config_set_numerical_field(name,var,min,max) */
} config_set_numerical_field(
"tcp-keepalive",server.tcpkeepalive,0,LLONG_MAX) {
} config_set_numerical_field(
"maxmemory-samples",server.maxmemory_samples,1,LLONG_MAX) {
} config_set_numerical_field(
"timeout",server.maxidletime,0,LONG_MAX) {
} config_set_numerical_field(
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,LLONG_MAX){
} config_set_numerical_field(
"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,0,LLONG_MAX) {
} config_set_numerical_field(
"hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LLONG_MAX) {
} config_set_numerical_field(
"hash-max-ziplist-value",server.hash_max_ziplist_value,0,LLONG_MAX) {
} config_set_numerical_field(
"list-max-ziplist-size",server.list_max_ziplist_size,0,LLONG_MAX) {
} config_set_numerical_field(
"list-compress-depth",server.list_compress_depth,0,LLONG_MAX) {
} config_set_numerical_field(
"set-max-intset-entries",server.set_max_intset_entries,0,LLONG_MAX) {
} config_set_numerical_field(
"zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LLONG_MAX) {
} config_set_numerical_field(
"zset-max-ziplist-value",server.zset_max_ziplist_value,0,LLONG_MAX) {
} config_set_numerical_field(
"hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LLONG_MAX) {
} config_set_numerical_field(
"lua-time-limit",server.lua_time_limit,0,LLONG_MAX) {
} config_set_numerical_field(
"slowlog-log-slower-than",server.slowlog_log_slower_than,0,LLONG_MAX) {
} config_set_numerical_field(
"slowlog-max-len",ll,0,LLONG_MAX) {
/* Cast to unsigned. */
server.slowlog_max_len = (unsigned)ll;
} config_set_numerical_field(
"latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){
} config_set_numerical_field(
"repl-ping-slave-period",server.repl_ping_slave_period,1,LLONG_MAX) {
} config_set_numerical_field(
"repl-timeout",server.repl_timeout,1,LLONG_MAX) {
} config_set_numerical_field(
"repl-backlog-ttl",server.repl_backlog_time_limit,0,LLONG_MAX) {
} config_set_numerical_field(
"repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,LLONG_MAX) {
} config_set_numerical_field(
"slave-priority",server.slave_priority,0,LLONG_MAX) {
} config_set_numerical_field(
"min-slaves-to-write",server.repl_min_slaves_to_write,0,LLONG_MAX) {
refreshGoodSlavesCount(); refreshGoodSlavesCount();
} else if (!strcasecmp(c->argv[2]->ptr,"min-slaves-max-lag")) { } config_set_numerical_field(
if (getLongLongFromObject(o,&ll) == REDIS_ERR || "min-slaves-max-lag",server.repl_min_slaves_max_lag,0,LLONG_MAX) {
ll < 0) goto badfmt;
server.repl_min_slaves_max_lag = ll;
refreshGoodSlavesCount(); refreshGoodSlavesCount();
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-require-full-coverage")) { } config_set_numerical_field(
int yn = yesnotoi(o->ptr); "cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
} config_set_numerical_field(
if (yn == -1) goto badfmt; "cluster-migration-barrier",server.cluster_migration_barrier,0,LLONG_MAX){
server.cluster_require_full_coverage = yn; } config_set_numerical_field(
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-node-timeout")) { "cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,LLONG_MAX) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || } config_set_numerical_field(
ll <= 0) goto badfmt; "hz",server.hz,0,LLONG_MAX) {
server.cluster_node_timeout = ll; /* Hz is more an hint from the user, so we accept values out of range
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-migration-barrier")) { * but cap them to reasonable values. */
if (getLongLongFromObject(o,&ll) == REDIS_ERR || if (server.hz < REDIS_MIN_HZ) server.hz = REDIS_MIN_HZ;
ll < 0) goto badfmt; if (server.hz > REDIS_MAX_HZ) server.hz = REDIS_MAX_HZ;
server.cluster_migration_barrier = ll; } config_set_numerical_field(
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-slave-validity-factor")) { "watchdog-period",ll,0,LLONG_MAX) {
if (getLongLongFromObject(o,&ll) == REDIS_ERR || if (ll)
ll < 0) goto badfmt; enableWatchdog(ll);
server.cluster_slave_validity_factor = ll; else
} else { disableWatchdog();
/* Memory fields.
* config_set_memory_field(name,var) */
} config_set_memory_field("maxmemory",server.maxmemory) {
if (server.maxmemory) {
if (server.maxmemory < zmalloc_used_memory()) {
redisLog(REDIS_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
}
freeMemoryIfNeeded();
}
} config_set_memory_field("repl-backlog-size",ll) {
resizeReplicationBacklog(ll);
/* Enumeration fields.
* config_set_enum_field(name,var,enum_var) */
} config_set_enum_field(
"loglevel",server.verbosity,loglevel_enum) {
} config_set_enum_field(
"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum) {
} config_set_enum_field(
"appendfsync",server.aof_fsync,aof_fsync_enum) {
/* Everyhing else is an error... */
} config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr); (char*)c->argv[2]->ptr);
return; return;
} }
/* On success we just return a generic OK for all the options. */
addReply(c,shared.ok); addReply(c,shared.ok);
return; return;
...@@ -1020,47 +1016,14 @@ badfmt: /* Bad format errors */ ...@@ -1020,47 +1016,14 @@ badfmt: /* Bad format errors */
} \ } \
} while(0); } while(0);
char *maxmemoryToString() { #define config_get_enum_field(_name,_var,_enumvar) do { \
char *s; if (stringmatch(pattern,_name,0)) { \
switch(server.maxmemory_policy) { addReplyBulkCString(c,_name); \
case REDIS_MAXMEMORY_VOLATILE_LRU: s = "volatile-lru"; break; addReplyBulkCString(c,configEnumGetNameOrUnknown(_enumvar,_var)); \
case REDIS_MAXMEMORY_VOLATILE_TTL: s = "volatile-ttl"; break; matches++; \
case REDIS_MAXMEMORY_VOLATILE_RANDOM: s = "volatile-random"; break; } \
case REDIS_MAXMEMORY_ALLKEYS_LRU: s = "allkeys-lru"; break; } while(0);
case REDIS_MAXMEMORY_ALLKEYS_RANDOM: s = "allkeys-random"; break;
case REDIS_MAXMEMORY_NO_EVICTION: s = "noeviction"; break;
default: s = "unknown"; break;
}
return s;
}
int supervisedToMode(const char *str) {
int mode;
if (!strcasecmp(str,"upstart")) {
mode = REDIS_SUPERVISED_UPSTART;
} else if (!strcasecmp(str,"systemd")) {
mode = REDIS_SUPERVISED_SYSTEMD;
} else if (!strcasecmp(str,"auto")) {
mode = REDIS_SUPERVISED_AUTODETECT;
} else if (!strcasecmp(str,"no")) {
mode = REDIS_SUPERVISED_NONE;
} else {
mode = -1;
}
return mode;
}
char *supervisedToString(void) {
char *s;
switch(server.supervised_mode) {
case REDIS_SUPERVISED_UPSTART: s = "upstart"; break;
case REDIS_SUPERVISED_SYSTEMD: s = "systemd"; break;
case REDIS_SUPERVISED_AUTODETECT: s = "auto"; break;
case REDIS_SUPERVISED_NONE: s = "no"; break;
default: s = "no"; break;
}
return s;
}
void configGetCommand(redisClient *c) { void configGetCommand(redisClient *c) {
robj *o = c->argv[2]; robj *o = c->argv[2];
void *replylen = addDeferredMultiBulkLength(c); void *replylen = addDeferredMultiBulkLength(c);
...@@ -1081,7 +1044,6 @@ void configGetCommand(redisClient *c) { ...@@ -1081,7 +1044,6 @@ void configGetCommand(redisClient *c) {
config_get_numerical_field("maxmemory",server.maxmemory); config_get_numerical_field("maxmemory",server.maxmemory);
config_get_numerical_field("maxmemory-samples",server.maxmemory_samples); config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
config_get_numerical_field("timeout",server.maxidletime); config_get_numerical_field("timeout",server.maxidletime);
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
config_get_numerical_field("auto-aof-rewrite-percentage", config_get_numerical_field("auto-aof-rewrite-percentage",
server.aof_rewrite_perc); server.aof_rewrite_perc);
config_get_numerical_field("auto-aof-rewrite-min-size", config_get_numerical_field("auto-aof-rewrite-min-size",
...@@ -1126,6 +1088,7 @@ void configGetCommand(redisClient *c) { ...@@ -1126,6 +1088,7 @@ void configGetCommand(redisClient *c) {
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier); config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor); config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay); config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
/* Bool (yes/no) values */ /* Bool (yes/no) values */
config_get_bool_field("cluster-require-full-coverage", config_get_bool_field("cluster-require-full-coverage",
...@@ -1151,6 +1114,18 @@ void configGetCommand(redisClient *c) { ...@@ -1151,6 +1114,18 @@ void configGetCommand(redisClient *c) {
config_get_bool_field("aof-load-truncated", config_get_bool_field("aof-load-truncated",
server.aof_load_truncated); server.aof_load_truncated);
/* Enum values */
config_get_enum_field("maxmemory-policy",
server.maxmemory_policy,maxmemory_policy_enum);
config_get_enum_field("loglevel",
server.verbosity,loglevel_enum);
config_get_enum_field("supervised",
server.supervised_mode,supervised_mode_enum);
config_get_enum_field("appendfsync",
server.aof_fsync,aof_fsync_enum);
config_get_enum_field("syslog-facility",
server.syslog_facility,syslog_facility_enum);
/* Everything we can't handle with macros follows. */ /* Everything we can't handle with macros follows. */
if (stringmatch(pattern,"appendonly",0)) { if (stringmatch(pattern,"appendonly",0)) {
...@@ -1168,24 +1143,6 @@ void configGetCommand(redisClient *c) { ...@@ -1168,24 +1143,6 @@ void configGetCommand(redisClient *c) {
addReplyBulkCString(c,buf); addReplyBulkCString(c,buf);
matches++; matches++;
} }
if (stringmatch(pattern,"maxmemory-policy",0)) {
addReplyBulkCString(c,"maxmemory-policy");
addReplyBulkCString(c,maxmemoryToString());
matches++;
}
if (stringmatch(pattern,"appendfsync",0)) {
char *policy;
switch(server.aof_fsync) {
case AOF_FSYNC_NO: policy = "no"; break;
case AOF_FSYNC_EVERYSEC: policy = "everysec"; break;
case AOF_FSYNC_ALWAYS: policy = "always"; break;
default: policy = "unknown"; break; /* too harmless to panic */
}
addReplyBulkCString(c,"appendfsync");
addReplyBulkCString(c,policy);
matches++;
}
if (stringmatch(pattern,"save",0)) { if (stringmatch(pattern,"save",0)) {
sds buf = sdsempty(); sds buf = sdsempty();
int j; int j;
...@@ -1202,25 +1159,6 @@ void configGetCommand(redisClient *c) { ...@@ -1202,25 +1159,6 @@ void configGetCommand(redisClient *c) {
sdsfree(buf); sdsfree(buf);
matches++; matches++;
} }
if (stringmatch(pattern,"loglevel",0)) {
char *s;
switch(server.verbosity) {
case REDIS_WARNING: s = "warning"; break;
case REDIS_VERBOSE: s = "verbose"; break;
case REDIS_NOTICE: s = "notice"; break;
case REDIS_DEBUG: s = "debug"; break;
default: s = "unknown"; break; /* too harmless to panic */
}
addReplyBulkCString(c,"loglevel");
addReplyBulkCString(c,s);
matches++;
}
if (stringmatch(pattern,"supervised",0)) {
addReplyBulkCString(c,"supervised");
addReplyBulkCString(c,supervisedToString());
matches++;
}
if (stringmatch(pattern,"client-output-buffer-limit",0)) { if (stringmatch(pattern,"client-output-buffer-limit",0)) {
sds buf = sdsempty(); sds buf = sdsempty();
int j; int j;
...@@ -1345,7 +1283,7 @@ void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds op ...@@ -1345,7 +1283,7 @@ void rewriteConfigAddLineNumberToOption(struct rewriteConfigState *state, sds op
* This is useful as only unused lines of processed options will be blanked * This is useful as only unused lines of processed options will be blanked
* in the config file, while options the rewrite process does not understand * in the config file, while options the rewrite process does not understand
* remain untouched. */ * remain untouched. */
void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, char *option) { void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *option) {
sds opt = sdsnew(option); sds opt = sdsnew(option);
if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt); if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt);
...@@ -1429,7 +1367,7 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) { ...@@ -1429,7 +1367,7 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
* *
* "line" is either used, or freed, so the caller does not need to free it * "line" is either used, or freed, so the caller does not need to free it
* in any way. */ * in any way. */
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force) { void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force) {
sds o = sdsnew(option); sds o = sdsnew(option);
list *l = dictFetchValue(state->option_to_line,o); list *l = dictFetchValue(state->option_to_line,o);
...@@ -1540,45 +1478,26 @@ void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, in ...@@ -1540,45 +1478,26 @@ void rewriteConfigOctalOption(struct rewriteConfigState *state, char *option, in
rewriteConfigRewriteLine(state,option,line,force); rewriteConfigRewriteLine(state,option,line,force);
} }
/* Rewrite an enumeration option, after the "value" every enum/value pair /* Rewrite an enumeration option. It takes as usually state and option name,
* is specified, terminated by NULL. After NULL the default value is * and in addition the enumeration array and the default value for the
* specified. See how the function is used for more information. */ * option. */
void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, ...) { void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int value, configEnum *ce, int defval) {
va_list ap;
char *enum_name, *matching_name = NULL;
int enum_val, def_val, force;
sds line; sds line;
const char *name = configEnumGetNameOrUnknown(ce,value);
int force = value != defval;
va_start(ap, value); line = sdscatprintf(sdsempty(),"%s %s",option,name);
while(1) {
enum_name = va_arg(ap,char*);
enum_val = va_arg(ap,int);
if (enum_name == NULL) {
def_val = enum_val;
break;
}
if (value == enum_val) matching_name = enum_name;
}
va_end(ap);
force = value != def_val;
line = sdscatprintf(sdsempty(),"%s %s",option,matching_name);
rewriteConfigRewriteLine(state,option,line,force); rewriteConfigRewriteLine(state,option,line,force);
} }
/* Rewrite the syslog-facility option. */ /* Rewrite the syslog-facility option. */
void rewriteConfigSyslogfacilityOption(struct rewriteConfigState *state) { void rewriteConfigSyslogfacilityOption(struct rewriteConfigState *state) {
int value = server.syslog_facility, j; int value = server.syslog_facility;
int force = value != LOG_LOCAL0; int force = value != LOG_LOCAL0;
char *name = NULL, *option = "syslog-facility"; const char *name = NULL, *option = "syslog-facility";
sds line; sds line;
for (j = 0; validSyslogFacilities[j].name; j++) { name = configEnumGetNameOrUnknown(syslog_facility_enum,value);
if (validSyslogFacilities[j].value == value) {
name = (char*) validSyslogFacilities[j].name;
break;
}
}
line = sdscatprintf(sdsempty(),"%s %s",option,name); line = sdscatprintf(sdsempty(),"%s %s",option,name);
rewriteConfigRewriteLine(state,option,line,force); rewriteConfigRewriteLine(state,option,line,force);
} }
...@@ -1839,12 +1758,7 @@ int rewriteConfig(char *path) { ...@@ -1839,12 +1758,7 @@ int rewriteConfig(char *path) {
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,REDIS_DEFAULT_UNIX_SOCKET_PERM); rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,REDIS_DEFAULT_UNIX_SOCKET_PERM);
rewriteConfigNumericalOption(state,"timeout",server.maxidletime,REDIS_MAXIDLETIME); rewriteConfigNumericalOption(state,"timeout",server.maxidletime,REDIS_MAXIDLETIME);
rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,REDIS_DEFAULT_TCP_KEEPALIVE); rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,REDIS_DEFAULT_TCP_KEEPALIVE);
rewriteConfigEnumOption(state,"loglevel",server.verbosity, rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,REDIS_DEFAULT_VERBOSITY);
"debug", REDIS_DEBUG,
"verbose", REDIS_VERBOSE,
"notice", REDIS_NOTICE,
"warning", REDIS_WARNING,
NULL, REDIS_DEFAULT_VERBOSITY);
rewriteConfigStringOption(state,"logfile",server.logfile,REDIS_DEFAULT_LOGFILE); rewriteConfigStringOption(state,"logfile",server.logfile,REDIS_DEFAULT_LOGFILE);
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,REDIS_DEFAULT_SYSLOG_ENABLED); rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,REDIS_DEFAULT_SYSLOG_ENABLED);
rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,REDIS_DEFAULT_SYSLOG_IDENT); rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,REDIS_DEFAULT_SYSLOG_IDENT);
...@@ -1873,22 +1787,11 @@ int rewriteConfig(char *path) { ...@@ -1873,22 +1787,11 @@ int rewriteConfig(char *path) {
rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL); rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS); rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS);
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY); rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY);
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy, rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum,REDIS_DEFAULT_MAXMEMORY_POLICY);
"volatile-lru", REDIS_MAXMEMORY_VOLATILE_LRU,
"allkeys-lru", REDIS_MAXMEMORY_ALLKEYS_LRU,
"volatile-random", REDIS_MAXMEMORY_VOLATILE_RANDOM,
"allkeys-random", REDIS_MAXMEMORY_ALLKEYS_RANDOM,
"volatile-ttl", REDIS_MAXMEMORY_VOLATILE_TTL,
"noeviction", REDIS_MAXMEMORY_NO_EVICTION,
NULL, REDIS_DEFAULT_MAXMEMORY_POLICY);
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES); rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES);
rewriteConfigYesNoOption(state,"appendonly",server.aof_state != REDIS_AOF_OFF,0); rewriteConfigYesNoOption(state,"appendonly",server.aof_state != REDIS_AOF_OFF,0);
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,REDIS_DEFAULT_AOF_FILENAME); rewriteConfigStringOption(state,"appendfilename",server.aof_filename,REDIS_DEFAULT_AOF_FILENAME);
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync, rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,REDIS_DEFAULT_AOF_FSYNC);
"everysec", AOF_FSYNC_EVERYSEC,
"always", AOF_FSYNC_ALWAYS,
"no", AOF_FSYNC_NO,
NULL, REDIS_DEFAULT_AOF_FSYNC);
rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE); rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,REDIS_AOF_REWRITE_PERC); rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,REDIS_AOF_REWRITE_PERC);
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,REDIS_AOF_REWRITE_MIN_SIZE); rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,REDIS_AOF_REWRITE_MIN_SIZE);
...@@ -1916,12 +1819,9 @@ int rewriteConfig(char *path) { ...@@ -1916,12 +1819,9 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ); rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC); rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,REDIS_DEFAULT_AOF_LOAD_TRUNCATED); rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,REDIS_DEFAULT_AOF_LOAD_TRUNCATED);
rewriteConfigEnumOption(state,"supervised",server.supervised_mode, rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,REDIS_SUPERVISED_NONE);
"upstart", REDIS_SUPERVISED_UPSTART,
"systemd", REDIS_SUPERVISED_SYSTEMD, /* Rewrite Sentinel config if in Sentinel mode. */
"auto", REDIS_SUPERVISED_AUTODETECT,
"no", REDIS_SUPERVISED_NONE,
NULL, REDIS_SUPERVISED_NONE);
if (server.sentinel_mode) rewriteConfigSentinelOption(state); if (server.sentinel_mode) rewriteConfigSentinelOption(state);
/* Step 3: remove all the orphaned lines in the old file, that is, lines /* Step 3: remove all the orphaned lines in the old file, that is, lines
......
...@@ -318,13 +318,17 @@ void delCommand(redisClient *c) { ...@@ -318,13 +318,17 @@ void delCommand(redisClient *c) {
addReplyLongLong(c,deleted); addReplyLongLong(c,deleted);
} }
/* EXISTS key1 key2 ... key_N.
* Return value is the number of keys existing. */
void existsCommand(redisClient *c) { void existsCommand(redisClient *c) {
expireIfNeeded(c->db,c->argv[1]); long long count = 0;
if (dbExists(c->db,c->argv[1])) { int j;
addReply(c, shared.cone);
} else { for (j = 1; j < c->argc; j++) {
addReply(c, shared.czero); expireIfNeeded(c->db,c->argv[j]);
if (dbExists(c->db,c->argv[j])) count++;
} }
addReplyLongLong(c,count);
} }
void selectCommand(redisClient *c) { void selectCommand(redisClient *c) {
......
...@@ -425,6 +425,27 @@ void debugCommand(redisClient *c) { ...@@ -425,6 +425,27 @@ void debugCommand(redisClient *c) {
sizes = sdscatprintf(sizes,"dictentry:%d ", (int)sizeof(dictEntry)); sizes = sdscatprintf(sizes,"dictentry:%d ", (int)sizeof(dictEntry));
sizes = sdscatprintf(sizes,"sdshdr:%d", (int)sizeof(struct sdshdr)); sizes = sdscatprintf(sizes,"sdshdr:%d", (int)sizeof(struct sdshdr));
addReplyBulkSds(c,sizes); addReplyBulkSds(c,sizes);
} else if (!strcasecmp(c->argv[1]->ptr,"htstats") && c->argc == 3) {
long dbid;
sds stats = sdsempty();
char buf[4096];
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != REDIS_OK)
return;
if (dbid < 0 || dbid >= server.dbnum) {
addReplyError(c,"Out of range database");
return;
}
stats = sdscatprintf(stats,"[Dictionary HT]\n");
dictGetStats(buf,sizeof(buf),server.db[dbid].dict);
stats = sdscat(stats,buf);
stats = sdscatprintf(stats,"[Expires HT]\n");
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
stats = sdscat(stats,buf);
addReplyBulkSds(c,stats);
} else if (!strcasecmp(c->argv[1]->ptr,"jemalloc") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"jemalloc") && c->argc == 3) {
#if defined(USE_JEMALLOC) #if defined(USE_JEMALLOC)
if (!strcasecmp(c->argv[2]->ptr, "info")) { if (!strcasecmp(c->argv[2]->ptr, "info")) {
......
...@@ -687,10 +687,10 @@ dictEntry *dictGetRandomKey(dict *d) ...@@ -687,10 +687,10 @@ dictEntry *dictGetRandomKey(dict *d)
* statistics. However the function is much faster than dictGetRandomKey() * statistics. However the function is much faster than dictGetRandomKey()
* at producing N elements. */ * at producing N elements. */
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) { unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
unsigned int j; /* internal hash table id, 0 or 1. */ unsigned long j; /* internal hash table id, 0 or 1. */
unsigned int tables; /* 1 or 2 tables? */ unsigned long tables; /* 1 or 2 tables? */
unsigned int stored = 0, maxsizemask; unsigned long stored = 0, maxsizemask;
unsigned int maxsteps; unsigned long maxsteps;
if (dictSize(d) < count) count = dictSize(d); if (dictSize(d) < count) count = dictSize(d);
maxsteps = count*10; maxsteps = count*10;
...@@ -709,14 +709,14 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) { ...@@ -709,14 +709,14 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
maxsizemask = d->ht[1].sizemask; maxsizemask = d->ht[1].sizemask;
/* Pick a random point inside the larger table. */ /* Pick a random point inside the larger table. */
unsigned int i = random() & maxsizemask; unsigned long i = random() & maxsizemask;
unsigned int emptylen = 0; /* Continuous empty entries so far. */ unsigned long emptylen = 0; /* Continuous empty entries so far. */
while(stored < count && maxsteps--) { while(stored < count && maxsteps--) {
for (j = 0; j < tables; j++) { for (j = 0; j < tables; j++) {
/* Invariant of the dict.c rehashing: up to the indexes already /* Invariant of the dict.c rehashing: up to the indexes already
* visited in ht[0] during the rehashing, there are no populated * visited in ht[0] during the rehashing, there are no populated
* buckets, so we can skip ht[0] for indexes between 0 and idx-1. */ * buckets, so we can skip ht[0] for indexes between 0 and idx-1. */
if (tables == 2 && j == 0 && i < d->rehashidx) { if (tables == 2 && j == 0 && i < (unsigned long) d->rehashidx) {
/* Moreover, if we are currently out of range in the second /* Moreover, if we are currently out of range in the second
* table, there will be no elements in both tables up to * table, there will be no elements in both tables up to
* the current rehashing index, so we jump if possible. * the current rehashing index, so we jump if possible.
...@@ -1002,24 +1002,21 @@ void dictDisableResize(void) { ...@@ -1002,24 +1002,21 @@ void dictDisableResize(void) {
dict_can_resize = 0; dict_can_resize = 0;
} }
#if 0 /* ------------------------------- Debugging ---------------------------------*/
/* The following is code that we don't use for Redis currently, but that is part
of the library. */
/* ----------------------- Debugging ------------------------*/
#define DICT_STATS_VECTLEN 50 #define DICT_STATS_VECTLEN 50
static void _dictPrintStatsHt(dictht *ht) { size_t _dictGetStatsHt(char *buf, size_t bufsize, dictht *ht, int tableid) {
unsigned long i, slots = 0, chainlen, maxchainlen = 0; unsigned long i, slots = 0, chainlen, maxchainlen = 0;
unsigned long totchainlen = 0; unsigned long totchainlen = 0;
unsigned long clvector[DICT_STATS_VECTLEN]; unsigned long clvector[DICT_STATS_VECTLEN];
size_t l = 0;
if (ht->used == 0) { if (ht->used == 0) {
printf("No stats available for empty dictionaries\n"); return snprintf(buf,bufsize,
return; "No stats available for empty dictionaries\n");
} }
/* Compute stats. */
for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0; for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0;
for (i = 0; i < ht->size; i++) { for (i = 0; i < ht->size; i++) {
dictEntry *he; dictEntry *he;
...@@ -1040,89 +1037,46 @@ static void _dictPrintStatsHt(dictht *ht) { ...@@ -1040,89 +1037,46 @@ static void _dictPrintStatsHt(dictht *ht) {
if (chainlen > maxchainlen) maxchainlen = chainlen; if (chainlen > maxchainlen) maxchainlen = chainlen;
totchainlen += chainlen; totchainlen += chainlen;
} }
printf("Hash table stats:\n");
printf(" table size: %ld\n", ht->size); /* Generate human readable stats. */
printf(" number of elements: %ld\n", ht->used); l += snprintf(buf+l,bufsize-l,
printf(" different slots: %ld\n", slots); "Hash table %d stats (%s):\n"
printf(" max chain length: %ld\n", maxchainlen); " table size: %ld\n"
printf(" avg chain length (counted): %.02f\n", (float)totchainlen/slots); " number of elements: %ld\n"
printf(" avg chain length (computed): %.02f\n", (float)ht->used/slots); " different slots: %ld\n"
printf(" Chain length distribution:\n"); " max chain length: %ld\n"
" avg chain length (counted): %.02f\n"
" avg chain length (computed): %.02f\n"
" Chain length distribution:\n",
tableid, (tableid == 0) ? "main hash table" : "rehashing target",
ht->size, ht->used, slots, maxchainlen,
(float)totchainlen/slots, (float)ht->used/slots);
for (i = 0; i < DICT_STATS_VECTLEN-1; i++) { for (i = 0; i < DICT_STATS_VECTLEN-1; i++) {
if (clvector[i] == 0) continue; if (clvector[i] == 0) continue;
printf(" %s%ld: %ld (%.02f%%)\n",(i == DICT_STATS_VECTLEN-1)?">= ":"", i, clvector[i], ((float)clvector[i]/ht->size)*100); if (l >= bufsize) break;
l += snprintf(buf+l,bufsize-l,
" %s%ld: %ld (%.02f%%)\n",
(i == DICT_STATS_VECTLEN-1)?">= ":"",
i, clvector[i], ((float)clvector[i]/ht->size)*100);
} }
}
void dictPrintStats(dict *d) {
_dictPrintStatsHt(&d->ht[0]);
if (dictIsRehashing(d)) {
printf("-- Rehashing into ht[1]:\n");
_dictPrintStatsHt(&d->ht[1]);
}
}
/* ----------------------- StringCopy Hash Table Type ------------------------*/
static unsigned int _dictStringCopyHTHashFunction(const void *key)
{
return dictGenHashFunction(key, strlen(key));
}
static void *_dictStringDup(void *privdata, const void *key)
{
int len = strlen(key);
char *copy = zmalloc(len+1);
DICT_NOTUSED(privdata);
memcpy(copy, key, len); /* Unlike snprintf(), teturn the number of characters actually written. */
copy[len] = '\0'; if (bufsize) buf[bufsize-1] = '\0';
return copy; return strlen(buf);
} }
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1, void dictGetStats(char *buf, size_t bufsize, dict *d) {
const void *key2) size_t l;
{ char *orig_buf = buf;
DICT_NOTUSED(privdata); size_t orig_bufsize = bufsize;
return strcmp(key1, key2) == 0; l = _dictGetStatsHt(buf,bufsize,&d->ht[0],0);
} buf += l;
bufsize -= l;
static void _dictStringDestructor(void *privdata, void *key) if (dictIsRehashing(d) && bufsize > 0) {
{ _dictGetStatsHt(buf,bufsize,&d->ht[1],1);
DICT_NOTUSED(privdata); }
/* Make sure there is a NULL term at the end. */
zfree(key); if (orig_bufsize) orig_buf[orig_bufsize-1] = '\0';
} }
dictType dictTypeHeapStringCopyKey = {
_dictStringCopyHTHashFunction, /* hash function */
_dictStringDup, /* key dup */
NULL, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */
_dictStringDestructor, /* key destructor */
NULL /* val destructor */
};
/* This is like StringCopy but does not auto-duplicate the key.
* It's used for intepreter's shared strings. */
dictType dictTypeHeapStrings = {
_dictStringCopyHTHashFunction, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */
_dictStringDestructor, /* key destructor */
NULL /* val destructor */
};
/* This is like StringCopy but also automatically handle dynamic
* allocated C strings as values. */
dictType dictTypeHeapStringCopyKeyValue = {
_dictStringCopyHTHashFunction, /* hash function */
_dictStringDup, /* key dup */
_dictStringDup, /* val dup */
_dictStringCopyHTKeyCompare, /* key compare */
_dictStringDestructor, /* key destructor */
_dictStringDestructor, /* val destructor */
};
#endif
...@@ -165,7 +165,7 @@ dictEntry *dictNext(dictIterator *iter); ...@@ -165,7 +165,7 @@ dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter); void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d); dictEntry *dictGetRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count); unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictPrintStats(dict *d); void dictGetStats(char *buf, size_t bufsize, dict *d);
unsigned int dictGenHashFunction(const void *key, int len); unsigned int dictGenHashFunction(const void *key, int len);
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len); unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *d, void(callback)(void*)); void dictEmpty(dict *d, void(callback)(void*));
......
/*
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
* Copyright (c) 2015, Salvatore Sanfilippo <antirez@gmail.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "geo.h"
#include "geohash_helper.h"
/* Things exported from t_zset.c only for geo.c, since it is the only other
* part of Redis that requires close zset introspection. */
unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
int zslValueLteMax(double value, zrangespec *spec);
/* ====================================================================
* This file implements the following commands:
*
* - geoadd - add coordinates for value to geoset
* - georadius - search radius by coordinates in geoset
* - georadiusbymember - search radius based on geoset member position
* ==================================================================== */
/* ====================================================================
* geoArray implementation
* ==================================================================== */
/* Create a new array of geoPoints. */
geoArray *geoArrayCreate(void) {
geoArray *ga = zmalloc(sizeof(*ga));
/* It gets allocated on first geoArrayAppend() call. */
ga->array = NULL;
ga->buckets = 0;
ga->used = 0;
return ga;
}
/* Add a new entry and return its pointer so that the caller can populate
* it with data. */
geoPoint *geoArrayAppend(geoArray *ga) {
if (ga->used == ga->buckets) {
ga->buckets = (ga->buckets == 0) ? 8 : ga->buckets*2;
ga->array = zrealloc(ga->array,sizeof(geoPoint)*ga->buckets);
}
geoPoint *gp = ga->array+ga->used;
ga->used++;
return gp;
}
/* Destroy a geoArray created with geoArrayCreate(). */
void geoArrayFree(geoArray *ga) {
size_t i;
for (i = 0; i < ga->used; i++) sdsfree(ga->array[i].member);
zfree(ga->array);
zfree(ga);
}
/* ====================================================================
* Helpers
* ==================================================================== */
int decodeGeohash(double bits, double *xy) {
GeoHashBits hash = { .bits = (uint64_t)bits, .step = GEO_STEP_MAX };
return geohashDecodeToLongLatWGS84(hash, xy);
}
/* Input Argument Helper */
/* Take a pointer to the latitude arg then use the next arg for longitude.
* On parse error REDIS_ERR is returned, otherwise REDIS_OK. */
int extractLongLatOrReply(redisClient *c, robj **argv,
double *xy) {
for (int i = 0; i < 2; i++) {
if (getDoubleFromObjectOrReply(c, argv[i], xy + i, NULL) !=
REDIS_OK) {
return REDIS_ERR;
}
if (xy[0] < GEO_LONG_MIN || xy[0] > GEO_LONG_MAX ||
xy[1] < GEO_LAT_MIN || xy[1] > GEO_LAT_MAX) {
addReplySds(c, sdscatprintf(sdsempty(),
"-ERR invalid longitude,latitude pair %f,%f\r\n",xy[0],xy[1]));
return REDIS_ERR;
}
}
return REDIS_OK;
}
/* Input Argument Helper */
/* Decode lat/long from a zset member's score.
* Returns REDIS_OK on successful decoding, otherwise REDIS_ERR is returned. */
int longLatFromMember(robj *zobj, robj *member, double *xy) {
double score = 0;
if (zsetScore(zobj, member, &score) == REDIS_ERR) return REDIS_ERR;
if (!decodeGeohash(score, xy)) return REDIS_ERR;
return REDIS_OK;
}
/* Check that the unit argument matches one of the known units, and returns
* the conversion factor to meters (you need to divide meters by the conversion
* factor to convert to the right unit).
*
* If the unit is not valid, an error is reported to the client, and a value
* less than zero is returned. */
double extractUnitOrReply(redisClient *c, robj *unit) {
char *u = unit->ptr;
if (!strcmp(u, "m")) {
return 1;
} else if (!strcmp(u, "km")) {
return 1000;
} else if (!strcmp(u, "ft")) {
return 0.3048;
} else if (!strcmp(u, "mi")) {
return 1609.34;
} else {
addReplyError(c,
"unsupported unit provided. please use m, km, ft, mi");
return -1;
}
}
/* Input Argument Helper.
* Extract the dinstance from the specified two arguments starting at 'argv'
* that shouldbe in the form: <number> <unit> and return the dinstance in the
* specified unit on success. *conversino is populated with the coefficient
* to use in order to convert meters to the unit.
*
* On error a value less than zero is returned. */
double extractDistanceOrReply(redisClient *c, robj **argv,
double *conversion) {
double distance;
if (getDoubleFromObjectOrReply(c, argv[0], &distance,
"need numeric radius") != REDIS_OK) {
return -1;
}
double to_meters = extractUnitOrReply(c,argv[1]);
if (to_meters < 0) return -1;
if (conversion) *conversion = to_meters;
return distance * to_meters;
}
/* The defailt addReplyDouble has too much accuracy. We use this
* for returning location distances. "5.2145 meters away" is nicer
* than "5.2144992818115 meters away." We provide 4 digits after the dot
* so that the returned value is decently accurate even when the unit is
* the kilometer. */
void addReplyDoubleDistance(redisClient *c, double d) {
char dbuf[128];
int dlen = snprintf(dbuf, sizeof(dbuf), "%.4f", d);
addReplyBulkCBuffer(c, dbuf, dlen);
}
/* Helper function for geoGetPointsInRange(): given a sorted set score
* representing a point, and another point (the center of our search) and
* a radius, appends this entry as a geoPoint into the specified geoArray
* only if the point is within the search area.
*
* returns REDIS_OK if the point is included, or REIDS_ERR if it is outside. */
int geoAppendIfWithinRadius(geoArray *ga, double lon, double lat, double radius, double score, sds member) {
double distance, xy[2];
if (!decodeGeohash(score,xy)) return REDIS_ERR; /* Can't decode. */
/* Note that geohashGetDistanceIfInRadiusWGS84() takes arguments in
* reverse order: longitude first, latitude later. */
if (!geohashGetDistanceIfInRadiusWGS84(lon,lat, xy[0], xy[1],
radius, &distance))
{
return REDIS_ERR;
}
/* Append the new element. */
geoPoint *gp = geoArrayAppend(ga);
gp->longitude = xy[0];
gp->latitude = xy[1];
gp->dist = distance;
gp->member = member;
gp->score = score;
return REDIS_OK;
}
/* Query a Redis sorted set to extract all the elements between 'min' and
* 'max', appending them into the array of geoPoint structures 'gparray'.
* The command returns the number of elements added to the array.
*
* Elements which are farest than 'radius' from the specified 'x' and 'y'
* coordinates are not included.
*
* The ability of this function to append to an existing set of points is
* important for good performances because querying by radius is performed
* using multiple queries to the sorted set, that we later need to sort
* via qsort. Similarly we need to be able to reject points outside the search
* radius area ASAP in order to allocate and process more points than needed. */
int geoGetPointsInRange(robj *zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {
/* minex 0 = include min in range; maxex 1 = exclude max in range */
/* That's: min <= val < max */
zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
size_t origincount = ga->used;
sds member;
if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *zl = zobj->ptr;
unsigned char *eptr, *sptr;
unsigned char *vstr = NULL;
unsigned int vlen = 0;
long long vlong = 0;
double score = 0;
if ((eptr = zzlFirstInRange(zl, &range)) == NULL) {
/* Nothing exists starting at our min. No results. */
return 0;
}
sptr = ziplistNext(zl, eptr);
while (eptr) {
score = zzlGetScore(sptr);
/* If we fell out of range, break. */
if (!zslValueLteMax(score, &range))
break;
/* We know the element exists. ziplistGet should always succeed */
ziplistGet(eptr, &vstr, &vlen, &vlong);
member = (vstr == NULL) ? sdsfromlonglong(vlong) :
sdsnewlen(vstr,vlen);
if (geoAppendIfWithinRadius(ga,lon,lat,radius,score,member)
== REDIS_ERR) sdsfree(member);
zzlNext(zl, &eptr, &sptr);
}
} else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
zset *zs = zobj->ptr;
zskiplist *zsl = zs->zsl;
zskiplistNode *ln;
if ((ln = zslFirstInRange(zsl, &range)) == NULL) {
/* Nothing exists starting at our min. No results. */
return 0;
}
while (ln) {
robj *o = ln->obj;
/* Abort when the node is no longer in range. */
if (!zslValueLteMax(ln->score, &range))
break;
member = (o->encoding == REDIS_ENCODING_INT) ?
sdsfromlonglong((long)o->ptr) :
sdsdup(o->ptr);
if (geoAppendIfWithinRadius(ga,lon,lat,radius,ln->score,member)
== REDIS_ERR) sdsfree(member);
ln = ln->level[0].forward;
}
}
return ga->used - origincount;
}
/* Compute the sorted set scores min (inclusive), max (exclusive) we should
* query in order to retrieve all the elements inside the specified area
* 'hash'. The two scores are returned by reference in *min and *max. */
void scoresOfGeoHashBox(GeoHashBits hash, GeoHashFix52Bits *min, GeoHashFix52Bits *max) {
/* We want to compute the sorted set scores that will include all the
* elements inside the specified Geohash 'hash', which has as many
* bits as specified by hash.step * 2.
*
* So if step is, for example, 3, and the hash value in binary
* is 101010, since our score is 52 bits we want every element which
* is in binary: 101010?????????????????????????????????????????????
* Where ? can be 0 or 1.
*
* To get the min score we just use the initial hash value left
* shifted enough to get the 52 bit value. Later we increment the
* 6 bit prefis (see the hash.bits++ statement), and get the new
* prefix: 101011, which we align again to 52 bits to get the maximum
* value (which is excluded from the search). So we get everything
* between the two following scores (represented in binary):
*
* 1010100000000000000000000000000000000000000000000000 (included)
* and
* 1010110000000000000000000000000000000000000000000000 (excluded).
*/
*min = geohashAlign52Bits(hash);
hash.bits++;
*max = geohashAlign52Bits(hash);
}
/* Obtain all members between the min/max of this geohash bounding box.
* Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
* Return the number of points added to the array. */
int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {
GeoHashFix52Bits min, max;
scoresOfGeoHashBox(hash,&min,&max);
return geoGetPointsInRange(zobj, min, max, lon, lat, radius, ga);
}
/* Search all eight neighbors + self geohash box */
int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
GeoHashBits neighbors[9];
unsigned int i, count = 0;
neighbors[0] = n.hash;
neighbors[1] = n.neighbors.north;
neighbors[2] = n.neighbors.south;
neighbors[3] = n.neighbors.east;
neighbors[4] = n.neighbors.west;
neighbors[5] = n.neighbors.north_east;
neighbors[6] = n.neighbors.north_west;
neighbors[7] = n.neighbors.south_east;
neighbors[8] = n.neighbors.south_west;
/* For each neighbor (*and* our own hashbox), get all the matching
* members and add them to the potential result list. */
for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) {
if (HASHISZERO(neighbors[i]))
continue;
count += membersOfGeoHashBox(zobj, neighbors[i], ga, lon, lat, radius);
}
return count;
}
/* Sort comparators for qsort() */
static int sort_gp_asc(const void *a, const void *b) {
const struct geoPoint *gpa = a, *gpb = b;
/* We can't do adist - bdist because they are doubles and
* the comparator returns an int. */
if (gpa->dist > gpb->dist)
return 1;
else if (gpa->dist == gpb->dist)
return 0;
else
return -1;
}
static int sort_gp_desc(const void *a, const void *b) {
return -sort_gp_asc(a, b);
}
/* ====================================================================
* Commands
* ==================================================================== */
/* GEOADD key long lat name [long2 lat2 name2 ... longN latN nameN] */
void geoaddCommand(redisClient *c) {
/* Check arguments number for sanity. */
if ((c->argc - 2) % 3 != 0) {
/* Need an odd number of arguments if we got this far... */
addReplyError(c, "syntax error. Try GEOADD key [x1] [y1] [name1] "
"[x2] [y2] [name2] ... ");
return;
}
int elements = (c->argc - 2) / 3;
int argc = 2+elements*2; /* ZADD key score ele ... */
robj **argv = zcalloc(argc*sizeof(robj*));
argv[0] = createRawStringObject("zadd",4);
argv[1] = c->argv[1]; /* key */
incrRefCount(argv[1]);
/* Create the argument vector to call ZADD in order to add all
* the score,value pairs to the requested zset, where score is actually
* an encoded version of lat,long. */
int i;
for (i = 0; i < elements; i++) {
double xy[2];
if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == REDIS_ERR) {
for (i = 0; i < argc; i++)
if (argv[i]) decrRefCount(argv[i]);
zfree(argv);
return;
}
/* Turn the coordinates into the score of the element. */
GeoHashBits hash;
geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
GeoHashFix52Bits bits = geohashAlign52Bits(hash);
robj *score = createObject(REDIS_STRING, sdsfromlonglong(bits));
robj *val = c->argv[2 + i * 3 + 2];
argv[2+i*2] = score;
argv[3+i*2] = val;
incrRefCount(val);
}
/* Finally call ZADD that will do the work for us. */
replaceClientCommandVector(c,argc,argv);
zaddCommand(c);
}
#define SORT_NONE 0
#define SORT_ASC 1
#define SORT_DESC 2
#define RADIUS_COORDS 1
#define RADIUS_MEMBER 2
/* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
* [COUNT count]
* GEORADIUSBYMEMBER key member radius unit ... options ... */
void georadiusGeneric(redisClient *c, int type) {
robj *key = c->argv[1];
/* Look up the requested zset */
robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
checkType(c, zobj, REDIS_ZSET)) {
return;
}
/* Find long/lat to use for radius search based on inquiry type */
int base_args;
double xy[2] = { 0 };
if (type == RADIUS_COORDS) {
base_args = 6;
if (extractLongLatOrReply(c, c->argv + 2, xy) == REDIS_ERR)
return;
} else if (type == RADIUS_MEMBER) {
base_args = 5;
robj *member = c->argv[2];
if (longLatFromMember(zobj, member, xy) == REDIS_ERR) {
addReplyError(c, "could not decode requested zset member");
return;
}
} else {
addReplyError(c, "unknown georadius search type");
return;
}
/* Extract radius and units from arguments */
double radius_meters = 0, conversion = 1;
if ((radius_meters = extractDistanceOrReply(c, c->argv + base_args - 2,
&conversion)) < 0) {
return;
}
/* Discover and populate all optional parameters. */
int withdist = 0, withhash = 0, withcoords = 0;
int sort = SORT_NONE;
long long count = 0;
if (c->argc > base_args) {
int remaining = c->argc - base_args;
for (int i = 0; i < remaining; i++) {
char *arg = c->argv[base_args + i]->ptr;
if (!strcasecmp(arg, "withdist")) {
withdist = 1;
} else if (!strcasecmp(arg, "withhash")) {
withhash = 1;
} else if (!strcasecmp(arg, "withcoord")) {
withcoords = 1;
} else if (!strcasecmp(arg, "asc")) {
sort = SORT_ASC;
} else if (!strcasecmp(arg, "desc")) {
sort = SORT_DESC;
} else if (!strcasecmp(arg, "count") && remaining > 0) {
if (getLongLongFromObjectOrReply(c, c->argv[base_args+i+1],
&count, NULL) != REDIS_OK) return;
if (count <= 0) {
addReplyError(c,"COUNT must be > 0");
return;
}
i++;
} else {
addReply(c, shared.syntaxerr);
return;
}
}
}
/* COUNT without ordering does not make much sense, force ASC
* ordering if COUNT was specified but no sorting was requested. */
if (count != 0 && sort == SORT_NONE) sort = SORT_ASC;
/* Get all neighbor geohash boxes for our radius search */
GeoHashRadius georadius =
geohashGetAreasByRadiusWGS84(xy[0], xy[1], radius_meters);
/* Search the zset for all matching points */
geoArray *ga = geoArrayCreate();
membersOfAllNeighbors(zobj, georadius, xy[0], xy[1], radius_meters, ga);
/* If no matching results, the user gets an empty reply. */
if (ga->used == 0) {
addReply(c, shared.emptymultibulk);
geoArrayFree(ga);
return;
}
long result_length = ga->used;
long option_length = 0;
/* Our options are self-contained nested multibulk replies, so we
* only need to track how many of those nested replies we return. */
if (withdist)
option_length++;
if (withcoords)
option_length++;
if (withhash)
option_length++;
/* The multibulk len we send is exactly result_length. The result is either
* all strings of just zset members *or* a nested multi-bulk reply
* containing the zset member string _and_ all the additional options the
* user enabled for this request. */
addReplyMultiBulkLen(c, (count == 0 || result_length < count) ?
result_length : count);
/* Process [optional] requested sorting */
if (sort == SORT_ASC) {
qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_asc);
} else if (sort == SORT_DESC) {
qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_desc);
}
/* Finally send results back to the caller */
int i;
for (i = 0; i < result_length; i++) {
geoPoint *gp = ga->array+i;
gp->dist /= conversion; /* Fix according to unit. */
/* If we have options in option_length, return each sub-result
* as a nested multi-bulk. Add 1 to account for result value itself. */
if (option_length)
addReplyMultiBulkLen(c, option_length + 1);
addReplyBulkSds(c,gp->member);
gp->member = NULL;
if (withdist)
addReplyDoubleDistance(c, gp->dist);
if (withhash)
addReplyLongLong(c, gp->score);
if (withcoords) {
addReplyMultiBulkLen(c, 2);
addReplyDouble(c, gp->longitude);
addReplyDouble(c, gp->latitude);
}
/* Stop if COUNT was specified and we already provided the
* specified number of elements. */
if (count != 0 && count == i+1) break;
}
geoArrayFree(ga);
}
/* GEORADIUS wrapper function. */
void georadiusCommand(redisClient *c) {
georadiusGeneric(c, RADIUS_COORDS);
}
/* GEORADIUSBYMEMBER wrapper function. */
void georadiusByMemberCommand(redisClient *c) {
georadiusGeneric(c, RADIUS_MEMBER);
}
/* GEOHASH key ele1 ele2 ... eleN
*
* Returns an array with an 11 characters geohash representation of the
* position of the specified elements. */
void geohashCommand(redisClient *c) {
char *geoalphabet= "0123456789bcdefghjkmnpqrstuvwxyz";
int j;
/* Look up the requested zset */
robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
== NULL || checkType(c, zobj, REDIS_ZSET)) return;
/* Geohash elements one after the other, using a null bulk reply for
* missing elements. */
addReplyMultiBulkLen(c,c->argc-2);
for (j = 2; j < c->argc; j++) {
double score;
if (zsetScore(zobj, c->argv[j], &score) == REDIS_ERR) {
addReply(c,shared.nullbulk);
} else {
/* The internal format we use for geocoding is a bit different
* than the standard, since we use as initial latitude range
* -85,85, while the normal geohashing algorithm uses -90,90.
* So we have to decode our position and re-encode using the
* standard ranges in order to output a valid geohash string. */
/* Decode... */
double xy[2];
if (!decodeGeohash(score,xy)) {
addReply(c,shared.nullbulk);
continue;
}
/* Re-encode */
GeoHashRange r[2];
GeoHashBits hash;
r[0].min = -180;
r[0].max = 180;
r[1].min = -90;
r[1].max = 90;
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
char buf[12];
int i;
for (i = 0; i < 11; i++) {
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
buf[i] = geoalphabet[idx];
}
buf[11] = '\0';
addReplyBulkCBuffer(c,buf,11);
}
}
}
/* GEOPOS key ele1 ele2 ... eleN
*
* Returns an array of two-items arrays representing the x,y position of each
* element specified in the arguments. For missing elements NULL is returned. */
void geoposCommand(redisClient *c) {
int j;
/* Look up the requested zset */
robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
== NULL || checkType(c, zobj, REDIS_ZSET)) return;
/* Report elements one after the other, using a null bulk reply for
* missing elements. */
addReplyMultiBulkLen(c,c->argc-2);
for (j = 2; j < c->argc; j++) {
double score;
if (zsetScore(zobj, c->argv[j], &score) == REDIS_ERR) {
addReply(c,shared.nullmultibulk);
} else {
/* Decode... */
double xy[2];
if (!decodeGeohash(score,xy)) {
addReply(c,shared.nullmultibulk);
continue;
}
addReplyMultiBulkLen(c,2);
addReplyDouble(c,xy[0]);
addReplyDouble(c,xy[1]);
}
}
}
/* GEODIST key ele1 ele2 [unit]
*
* Return the distance, in meters by default, otherwise accordig to "unit",
* between points ele1 and ele2. If one or more elements are missing NULL
* is returned. */
void geodistCommand(redisClient *c) {
double to_meter = 1;
/* Check if there is the unit to extract, otherwise assume meters. */
if (c->argc == 5) {
to_meter = extractUnitOrReply(c,c->argv[4]);
if (to_meter < 0) return;
} else if (c->argc > 5) {
addReply(c,shared.syntaxerr);
return;
}
/* Look up the requested zset */
robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptybulk))
== NULL || checkType(c, zobj, REDIS_ZSET)) return;
/* Get the scores. We need both otherwise NULL is returned. */
double score1, score2, xyxy[4];
if (zsetScore(zobj, c->argv[2], &score1) == REDIS_ERR ||
zsetScore(zobj, c->argv[3], &score2) == REDIS_ERR)
{
addReply(c,shared.nullbulk);
return;
}
/* Decode & compute the distance. */
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
addReply(c,shared.nullbulk);
else
addReplyDouble(c,
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
}
#ifndef __GEO_H__
#define __GEO_H__
#include "redis.h"
/* Structures used inside geo.c in order to represent points and array of
* points on the earth. */
typedef struct geoPoint {
double longitude;
double latitude;
double dist;
double score;
char *member;
} geoPoint;
typedef struct geoArray {
struct geoPoint *array;
size_t buckets;
size_t used;
} geoArray;
#endif
...@@ -248,7 +248,7 @@ sds createLatencyReport(void) { ...@@ -248,7 +248,7 @@ sds createLatencyReport(void) {
dictEntry *de; dictEntry *de;
int eventnum = 0; int eventnum = 0;
di = dictGetIterator(server.latency_events); di = dictGetSafeIterator(server.latency_events);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
char *event = dictGetKey(de); char *event = dictGetKey(de);
struct latencyTimeSeries *ts = dictGetVal(de); struct latencyTimeSeries *ts = dictGetVal(de);
......
...@@ -135,23 +135,49 @@ redisClient *createClient(int fd) { ...@@ -135,23 +135,49 @@ redisClient *createClient(int fd) {
* returns REDIS_OK, and make sure to install the write handler in our event * returns REDIS_OK, and make sure to install the write handler in our event
* loop so that when the socket is writable new data gets written. * loop so that when the socket is writable new data gets written.
* *
* If the client should not receive new data, because it is a fake client, * If the client should not receive new data, because it is a fake client
* a master, a slave not yet online, or because the setup of the write handler * (used to load AOF in memory), a master or because the setup of the write
* failed, the function returns REDIS_ERR. * handler failed, the function returns REDIS_ERR.
*
* The function may return REDIS_OK without actually installing the write
* event handler in the following cases:
*
* 1) The event handler should already be installed since the output buffer
* already contained something.
* 2) The client is a slave but not yet online, so we want to just accumulate
* writes in the buffer but not actually sending them yet.
* *
* Typically gets called every time a reply is built, before adding more * Typically gets called every time a reply is built, before adding more
* data to the clients output buffers. If the function returns REDIS_ERR no * data to the clients output buffers. If the function returns REDIS_ERR no
* data should be appended to the output buffers. */ * data should be appended to the output buffers. */
int prepareClientToWrite(redisClient *c) { int prepareClientToWrite(redisClient *c) {
/* If it's the Lua client we always return ok without installing any
* handler since there is no socket at all. */
if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK; if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK;
/* Masters don't receive replies, unless REDIS_MASTER_FORCE_REPLY flag
* is set. */
if ((c->flags & REDIS_MASTER) && if ((c->flags & REDIS_MASTER) &&
!(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR; !(c->flags & REDIS_MASTER_FORCE_REPLY)) return REDIS_ERR;
if (c->fd <= 0) return REDIS_ERR; /* Fake client */
if (c->fd <= 0) return REDIS_ERR; /* Fake client for AOF loading. */
/* Only install the handler if not already installed and, in case of
* slaves, if the client can actually receive writes. */
if (c->bufpos == 0 && listLength(c->reply) == 0 && if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE || (c->replstate == REDIS_REPL_NONE ||
c->replstate == REDIS_REPL_ONLINE) && !c->repl_put_online_on_ack && (c->replstate == REDIS_REPL_ONLINE && !c->repl_put_online_on_ack)))
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, {
sendReplyToClient, c) == AE_ERR) return REDIS_ERR; /* Try to install the write handler. */
if (aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
sendReplyToClient, c) == AE_ERR)
{
freeClientAsync(c);
return REDIS_ERR;
}
}
/* Authorize the caller to queue in the output buffer of this client. */
return REDIS_OK; return REDIS_OK;
} }
...@@ -175,7 +201,7 @@ robj *dupLastObjectIfNeeded(list *reply) { ...@@ -175,7 +201,7 @@ robj *dupLastObjectIfNeeded(list *reply) {
* Low level functions to add more data to output buffers. * Low level functions to add more data to output buffers.
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
int _addReplyToBuffer(redisClient *c, char *s, size_t len) { int _addReplyToBuffer(redisClient *c, const char *s, size_t len) {
size_t available = sizeof(c->buf)-c->bufpos; size_t available = sizeof(c->buf)-c->bufpos;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK; if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK;
...@@ -255,7 +281,7 @@ void _addReplySdsToList(redisClient *c, sds s) { ...@@ -255,7 +281,7 @@ void _addReplySdsToList(redisClient *c, sds s) {
asyncCloseClientOnOutputBufferLimitReached(c); asyncCloseClientOnOutputBufferLimitReached(c);
} }
void _addReplyStringToList(redisClient *c, char *s, size_t len) { void _addReplyStringToList(redisClient *c, const char *s, size_t len) {
robj *tail; robj *tail;
if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;
...@@ -341,19 +367,19 @@ void addReplySds(redisClient *c, sds s) { ...@@ -341,19 +367,19 @@ void addReplySds(redisClient *c, sds s) {
} }
} }
void addReplyString(redisClient *c, char *s, size_t len) { void addReplyString(redisClient *c, const char *s, size_t len) {
if (prepareClientToWrite(c) != REDIS_OK) return; if (prepareClientToWrite(c) != REDIS_OK) return;
if (_addReplyToBuffer(c,s,len) != REDIS_OK) if (_addReplyToBuffer(c,s,len) != REDIS_OK)
_addReplyStringToList(c,s,len); _addReplyStringToList(c,s,len);
} }
void addReplyErrorLength(redisClient *c, char *s, size_t len) { void addReplyErrorLength(redisClient *c, const char *s, size_t len) {
addReplyString(c,"-ERR ",5); addReplyString(c,"-ERR ",5);
addReplyString(c,s,len); addReplyString(c,s,len);
addReplyString(c,"\r\n",2); addReplyString(c,"\r\n",2);
} }
void addReplyError(redisClient *c, char *err) { void addReplyError(redisClient *c, const char *err) {
addReplyErrorLength(c,err,strlen(err)); addReplyErrorLength(c,err,strlen(err));
} }
...@@ -373,13 +399,13 @@ void addReplyErrorFormat(redisClient *c, const char *fmt, ...) { ...@@ -373,13 +399,13 @@ void addReplyErrorFormat(redisClient *c, const char *fmt, ...) {
sdsfree(s); sdsfree(s);
} }
void addReplyStatusLength(redisClient *c, char *s, size_t len) { void addReplyStatusLength(redisClient *c, const char *s, size_t len) {
addReplyString(c,"+",1); addReplyString(c,"+",1);
addReplyString(c,s,len); addReplyString(c,s,len);
addReplyString(c,"\r\n",2); addReplyString(c,"\r\n",2);
} }
void addReplyStatus(redisClient *c, char *status) { void addReplyStatus(redisClient *c, const char *status) {
addReplyStatusLength(c,status,strlen(status)); addReplyStatusLength(c,status,strlen(status));
} }
...@@ -454,10 +480,10 @@ void addReplyLongLongWithPrefix(redisClient *c, long long ll, char prefix) { ...@@ -454,10 +480,10 @@ void addReplyLongLongWithPrefix(redisClient *c, long long ll, char prefix) {
/* Things like $3\r\n or *2\r\n are emitted very often by the protocol /* Things like $3\r\n or *2\r\n are emitted very often by the protocol
* so we have a few shared objects to use if the integer is small * so we have a few shared objects to use if the integer is small
* like it is most of the times. */ * like it is most of the times. */
if (prefix == '*' && ll < REDIS_SHARED_BULKHDR_LEN) { if (prefix == '*' && ll < REDIS_SHARED_BULKHDR_LEN && ll >= 0) {
addReply(c,shared.mbulkhdr[ll]); addReply(c,shared.mbulkhdr[ll]);
return; return;
} else if (prefix == '$' && ll < REDIS_SHARED_BULKHDR_LEN) { } else if (prefix == '$' && ll < REDIS_SHARED_BULKHDR_LEN && ll >= 0) {
addReply(c,shared.bulkhdr[ll]); addReply(c,shared.bulkhdr[ll]);
return; return;
} }
...@@ -519,7 +545,7 @@ void addReplyBulk(redisClient *c, robj *obj) { ...@@ -519,7 +545,7 @@ void addReplyBulk(redisClient *c, robj *obj) {
} }
/* Add a C buffer as bulk reply */ /* Add a C buffer as bulk reply */
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) { void addReplyBulkCBuffer(redisClient *c, const void *p, size_t len) {
addReplyLongLongWithPrefix(c,len,'$'); addReplyLongLongWithPrefix(c,len,'$');
addReplyString(c,p,len); addReplyString(c,p,len);
addReply(c,shared.crlf); addReply(c,shared.crlf);
...@@ -534,7 +560,7 @@ void addReplyBulkSds(redisClient *c, sds s) { ...@@ -534,7 +560,7 @@ void addReplyBulkSds(redisClient *c, sds s) {
} }
/* Add a C nul term string as bulk reply */ /* Add a C nul term string as bulk reply */
void addReplyBulkCString(redisClient *c, char *s) { void addReplyBulkCString(redisClient *c, const char *s) {
if (s == NULL) { if (s == NULL) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
} else { } else {
...@@ -779,7 +805,7 @@ void freeClient(redisClient *c) { ...@@ -779,7 +805,7 @@ void freeClient(redisClient *c) {
* a context where calling freeClient() is not possible, because the client * a context where calling freeClient() is not possible, because the client
* should be valid for the continuation of the flow of the program. */ * should be valid for the continuation of the flow of the program. */
void freeClientAsync(redisClient *c) { void freeClientAsync(redisClient *c) {
if (c->flags & REDIS_CLOSE_ASAP) return; if (c->flags & REDIS_CLOSE_ASAP || c->flags & REDIS_LUA_CLIENT) return;
c->flags |= REDIS_CLOSE_ASAP; c->flags |= REDIS_CLOSE_ASAP;
listAddNodeTail(server.clients_to_close,c); listAddNodeTail(server.clients_to_close,c);
} }
...@@ -957,7 +983,7 @@ int processInlineBuffer(redisClient *c) { ...@@ -957,7 +983,7 @@ int processInlineBuffer(redisClient *c) {
/* Helper function. Trims query buffer to make the function that processes /* Helper function. Trims query buffer to make the function that processes
* multi bulk requests idempotent. */ * multi bulk requests idempotent. */
static void setProtocolError(redisClient *c, int pos) { static void setProtocolError(redisClient *c, int pos) {
if (server.verbosity >= REDIS_VERBOSE) { if (server.verbosity <= REDIS_VERBOSE) {
sds client = catClientInfoString(sdsempty(),c); sds client = catClientInfoString(sdsempty(),c);
redisLog(REDIS_VERBOSE, redisLog(REDIS_VERBOSE,
"Protocol error from client: %s", client); "Protocol error from client: %s", client);
...@@ -1501,6 +1527,16 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) { ...@@ -1501,6 +1527,16 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) {
va_end(ap); va_end(ap);
} }
/* Completely replace the client command vector with the provided one. */
void replaceClientCommandVector(redisClient *c, int argc, robj **argv) {
freeClientArgv(c);
zfree(c->argv);
c->argv = argv;
c->argc = argc;
c->cmd = lookupCommandOrOriginal(c->argv[0]->ptr);
redisAssertWithInfo(c,NULL,c->cmd != NULL);
}
/* Rewrite a single item in the command vector. /* Rewrite a single item in the command vector.
* The new val ref count is incremented, and the old decremented. */ * The new val ref count is incremented, and the old decremented. */
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) { void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) {
...@@ -1676,7 +1712,9 @@ void pauseClients(mstime_t end) { ...@@ -1676,7 +1712,9 @@ void pauseClients(mstime_t end) {
/* Return non-zero if clients are currently paused. As a side effect the /* Return non-zero if clients are currently paused. As a side effect the
* function checks if the pause time was reached and clear it. */ * function checks if the pause time was reached and clear it. */
int clientsArePaused(void) { int clientsArePaused(void) {
if (server.clients_paused && server.clients_pause_end_time < server.mstime) { if (server.clients_paused &&
server.clients_pause_end_time < server.mstime)
{
listNode *ln; listNode *ln;
listIter li; listIter li;
redisClient *c; redisClient *c;
...@@ -1689,7 +1727,10 @@ int clientsArePaused(void) { ...@@ -1689,7 +1727,10 @@ int clientsArePaused(void) {
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
c = listNodeValue(ln); c = listNodeValue(ln);
if (c->flags & REDIS_SLAVE) continue; /* Don't touch slaves and blocked clients. The latter pending
* requests be processed when unblocked. */
if (c->flags & (REDIS_SLAVE|REDIS_BLOCKED)) continue;
c->flags |= REDIS_UNBLOCKED;
listAddNodeTail(server.unblocked_clients,c); listAddNodeTail(server.unblocked_clients,c);
} }
} }
......
...@@ -50,14 +50,14 @@ robj *createObject(int type, void *ptr) { ...@@ -50,14 +50,14 @@ robj *createObject(int type, void *ptr) {
/* Create a string object with encoding REDIS_ENCODING_RAW, that is a plain /* Create a string object with encoding REDIS_ENCODING_RAW, that is a plain
* string object where o->ptr points to a proper sds string. */ * string object where o->ptr points to a proper sds string. */
robj *createRawStringObject(char *ptr, size_t len) { robj *createRawStringObject(const char *ptr, size_t len) {
return createObject(REDIS_STRING,sdsnewlen(ptr,len)); return createObject(REDIS_STRING,sdsnewlen(ptr,len));
} }
/* Create a string object with encoding REDIS_ENCODING_EMBSTR, that is /* Create a string object with encoding REDIS_ENCODING_EMBSTR, that is
* an object where the sds string is actually an unmodifiable string * an object where the sds string is actually an unmodifiable string
* allocated in the same chunk as the object itself. */ * allocated in the same chunk as the object itself. */
robj *createEmbeddedStringObject(char *ptr, size_t len) { robj *createEmbeddedStringObject(const char *ptr, size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1); robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
struct sdshdr *sh = (void*)(o+1); struct sdshdr *sh = (void*)(o+1);
...@@ -85,7 +85,7 @@ robj *createEmbeddedStringObject(char *ptr, size_t len) { ...@@ -85,7 +85,7 @@ robj *createEmbeddedStringObject(char *ptr, size_t len) {
* The current limit of 39 is chosen so that the biggest string object * The current limit of 39 is chosen so that the biggest string object
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */ * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39 #define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
robj *createStringObject(char *ptr, size_t len) { robj *createStringObject(const char *ptr, size_t len) {
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT) if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len); return createEmbeddedStringObject(ptr,len);
else else
......
...@@ -869,9 +869,9 @@ int rdbSave(char *filename) { ...@@ -869,9 +869,9 @@ int rdbSave(char *filename) {
return REDIS_OK; return REDIS_OK;
werr: werr:
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
fclose(fp); fclose(fp);
unlink(tmpfile); unlink(tmpfile);
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
return REDIS_ERR; return REDIS_ERR;
} }
......
...@@ -644,9 +644,9 @@ static int cliSendCommand(int argc, char **argv, int repeat) { ...@@ -644,9 +644,9 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
output_raw = 0; output_raw = 0;
if (!strcasecmp(command,"info") || if (!strcasecmp(command,"info") ||
(argc == 3 && !strcasecmp(command,"debug") && (argc >= 2 && !strcasecmp(command,"debug") &&
(!strcasecmp(argv[1],"jemalloc") && (!strcasecmp(argv[1],"jemalloc") ||
!strcasecmp(argv[2],"info"))) || !strcasecmp(argv[1],"htstats"))) ||
(argc == 2 && !strcasecmp(command,"cluster") && (argc == 2 && !strcasecmp(command,"cluster") &&
(!strcasecmp(argv[1],"nodes") || (!strcasecmp(argv[1],"nodes") ||
!strcasecmp(argv[1],"info"))) || !strcasecmp(argv[1],"info"))) ||
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/utsname.h> #include <sys/utsname.h>
#include <locale.h> #include <locale.h>
#include <sys/sysctl.h> #include <sys/socket.h>
/* Our shared "common" objects */ /* Our shared "common" objects */
...@@ -131,7 +131,7 @@ struct redisCommand redisCommandTable[] = { ...@@ -131,7 +131,7 @@ struct redisCommand redisCommandTable[] = {
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0}, {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
{"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0}, {"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0},
{"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0}, {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
{"exists",existsCommand,2,"rF",0,NULL,1,1,1,0,0}, {"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0}, {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
{"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0}, {"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
{"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0}, {"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
...@@ -281,9 +281,15 @@ struct redisCommand redisCommandTable[] = { ...@@ -281,9 +281,15 @@ struct redisCommand redisCommandTable[] = {
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0}, {"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
{"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0}, {"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0},
{"command",commandCommand,0,"rlt",0,NULL,0,0,0,0,0}, {"command",commandCommand,0,"rlt",0,NULL,0,0,0,0,0},
{"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0},
{"georadius",georadiusCommand,-6,"r",0,NULL,1,1,1,0,0},
{"georadiusbymember",georadiusByMemberCommand,-5,"r",0,NULL,1,1,1,0,0},
{"geohash",geohashCommand,-2,"r",0,NULL,1,1,1,0,0},
{"geopos",geoposCommand,-2,"r",0,NULL,1,1,1,0,0},
{"geodist",geodistCommand,-4,"r",0,NULL,1,1,1,0,0},
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0}, {"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0}, {"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,1,1,0,0}, {"pfcount",pfcountCommand,-2,"r",0,NULL,1,-1,1,0,0},
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0}, {"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0}, {"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},
{"latency",latencyCommand,-2,"arslt",0,NULL,0,0,0,0,0} {"latency",latencyCommand,-2,"arslt",0,NULL,0,0,0,0,0}
...@@ -905,9 +911,12 @@ long long getInstantaneousMetric(int metric) { ...@@ -905,9 +911,12 @@ long long getInstantaneousMetric(int metric) {
return sum / REDIS_METRIC_SAMPLES; return sum / REDIS_METRIC_SAMPLES;
} }
/* Check for timeouts. Returns non-zero if the client was terminated */ /* Check for timeouts. Returns non-zero if the client was terminated.
int clientsCronHandleTimeout(redisClient *c) { * The function gets the current time in milliseconds as argument since
time_t now = server.unixtime; * it gets called multiple times in a loop, so calling gettimeofday() for
* each iteration would be costly without any actual gain. */
int clientsCronHandleTimeout(redisClient *c, mstime_t now_ms) {
time_t now = now_ms/1000;
if (server.maxidletime && if (server.maxidletime &&
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
...@@ -923,11 +932,16 @@ int clientsCronHandleTimeout(redisClient *c) { ...@@ -923,11 +932,16 @@ int clientsCronHandleTimeout(redisClient *c) {
/* Blocked OPS timeout is handled with milliseconds resolution. /* Blocked OPS timeout is handled with milliseconds resolution.
* However note that the actual resolution is limited by * However note that the actual resolution is limited by
* server.hz. */ * server.hz. */
mstime_t now_ms = mstime();
if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) { if (c->bpop.timeout != 0 && c->bpop.timeout < now_ms) {
/* Handle blocking operation specific timeout. */
replyToBlockedClientTimedOut(c); replyToBlockedClientTimedOut(c);
unblockClient(c); unblockClient(c);
} else if (server.cluster_enabled) {
/* Cluster: handle unblock & redirect of clients blocked
* into keys no longer served by this server. */
if (clusterRedirectBlockedClientIfNeeded(c))
unblockClient(c);
} }
} }
return 0; return 0;
...@@ -959,17 +973,23 @@ int clientsCronResizeQueryBuffer(redisClient *c) { ...@@ -959,17 +973,23 @@ int clientsCronResizeQueryBuffer(redisClient *c) {
return 0; return 0;
} }
#define CLIENTS_CRON_MIN_ITERATIONS 5
void clientsCron(void) { void clientsCron(void) {
/* Make sure to process at least 1/(server.hz*10) of clients per call. /* Make sure to process at least numclients/server.hz of clients
* Since this function is called server.hz times per second we are sure that * per call. Since this function is called server.hz times per second
* in the worst case we process all the clients in 10 seconds. * we are sure that in the worst case we process all the clients in 1
* In normal conditions (a reasonable number of clients) we process * second. */
* all the clients in a shorter time. */
int numclients = listLength(server.clients); int numclients = listLength(server.clients);
int iterations = numclients/(server.hz*10); int iterations = numclients/server.hz;
mstime_t now = mstime();
/* Process at least a few clients while we are at it, even if we need
* to process less than CLIENTS_CRON_MIN_ITERATIONS to meet our contract
* of processing each client once per second. */
if (iterations < CLIENTS_CRON_MIN_ITERATIONS)
iterations = (numclients < CLIENTS_CRON_MIN_ITERATIONS) ?
numclients : CLIENTS_CRON_MIN_ITERATIONS;
if (iterations < 50)
iterations = (numclients < 50) ? numclients : 50;
while(listLength(server.clients) && iterations--) { while(listLength(server.clients) && iterations--) {
redisClient *c; redisClient *c;
listNode *head; listNode *head;
...@@ -983,7 +1003,7 @@ void clientsCron(void) { ...@@ -983,7 +1003,7 @@ void clientsCron(void) {
/* The following functions do different service checks on the client. /* The following functions do different service checks on the client.
* The protocol is that they return non-zero if the client was * The protocol is that they return non-zero if the client was
* terminated. */ * terminated. */
if (clientsCronHandleTimeout(c)) continue; if (clientsCronHandleTimeout(c,now)) continue;
if (clientsCronResizeQueryBuffer(c)) continue; if (clientsCronResizeQueryBuffer(c)) continue;
} }
} }
...@@ -1260,6 +1280,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -1260,6 +1280,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
void beforeSleep(struct aeEventLoop *eventLoop) { void beforeSleep(struct aeEventLoop *eventLoop) {
REDIS_NOTUSED(eventLoop); REDIS_NOTUSED(eventLoop);
/* Call the Redis Cluster before sleep function. Note that this function
* may change the state of Redis Cluster (from ok to fail or vice versa),
* so it's a good idea to call it before serving the unblocked clients
* later in this function. */
if (server.cluster_enabled) clusterBeforeSleep();
/* Run a fast expire cycle (the called function will return /* Run a fast expire cycle (the called function will return
* ASAP if a fast cycle is not needed). */ * ASAP if a fast cycle is not needed). */
if (server.active_expire_enabled && server.masterhost == NULL) if (server.active_expire_enabled && server.masterhost == NULL)
...@@ -1291,9 +1317,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) { ...@@ -1291,9 +1317,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
/* Write the AOF buffer on disk */ /* Write the AOF buffer on disk */
flushAppendOnlyFile(0); flushAppendOnlyFile(0);
/* Call the Redis Cluster before sleep function. */
if (server.cluster_enabled) clusterBeforeSleep();
} }
/* =========================== Server initialization ======================== */ /* =========================== Server initialization ======================== */
...@@ -1740,6 +1763,7 @@ void resetServerStats(void) { ...@@ -1740,6 +1763,7 @@ void resetServerStats(void) {
} }
server.stat_net_input_bytes = 0; server.stat_net_input_bytes = 0;
server.stat_net_output_bytes = 0; server.stat_net_output_bytes = 0;
server.aof_delayed_fsync = 0;
} }
void initServer(void) { void initServer(void) {
...@@ -1784,7 +1808,7 @@ void initServer(void) { ...@@ -1784,7 +1808,7 @@ void initServer(void) {
server.sofd = anetUnixServer(server.neterr,server.unixsocket, server.sofd = anetUnixServer(server.neterr,server.unixsocket,
server.unixsocketperm, server.tcp_backlog); server.unixsocketperm, server.tcp_backlog);
if (server.sofd == ANET_ERR) { if (server.sofd == ANET_ERR) {
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr); redisLog(REDIS_WARNING, "Opening Unix socket: %s", server.neterr);
exit(1); exit(1);
} }
anetNonBlock(NULL,server.sofd); anetNonBlock(NULL,server.sofd);
...@@ -2199,36 +2223,22 @@ int processCommand(redisClient *c) { ...@@ -2199,36 +2223,22 @@ int processCommand(redisClient *c) {
* 2) The command has no key arguments. */ * 2) The command has no key arguments. */
if (server.cluster_enabled && if (server.cluster_enabled &&
!(c->flags & REDIS_MASTER) && !(c->flags & REDIS_MASTER) &&
!(c->flags & REDIS_LUA_CLIENT &&
server.lua_caller->flags & REDIS_MASTER) &&
!(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0)) !(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0))
{ {
int hashslot; int hashslot;
if (server.cluster->state != REDIS_CLUSTER_OK) { if (server.cluster->state != REDIS_CLUSTER_OK) {
flagTransaction(c); flagTransaction(c);
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down. Use CLUSTER INFO for more information\r\n")); clusterRedirectClient(c,NULL,0,REDIS_CLUSTER_REDIR_DOWN_STATE);
return REDIS_OK; return REDIS_OK;
} else { } else {
int error_code; int error_code;
clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code); clusterNode *n = getNodeByQuery(c,c->cmd,c->argv,c->argc,&hashslot,&error_code);
if (n == NULL) { if (n == NULL || n != server.cluster->myself) {
flagTransaction(c);
if (error_code == REDIS_CLUSTER_REDIR_CROSS_SLOT) {
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
} else if (error_code == REDIS_CLUSTER_REDIR_UNSTABLE) {
/* The request spawns mutliple keys in the same slot,
* but the slot is not "stable" currently as there is
* a migration or import in progress. */
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
} else {
redisPanic("getNodeByQuery() unknown error.");
}
return REDIS_OK;
} else if (n != server.cluster->myself) {
flagTransaction(c); flagTransaction(c);
addReplySds(c,sdscatprintf(sdsempty(), clusterRedirectClient(c,n,hashslot,error_code);
"-%s %d %s:%d\r\n",
(error_code == REDIS_CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
hashslot,n->ip,n->port));
return REDIS_OK; return REDIS_OK;
} }
} }
...@@ -2745,7 +2755,7 @@ sds genRedisInfoString(char *section) { ...@@ -2745,7 +2755,7 @@ sds genRedisInfoString(char *section) {
char maxmemory_hmem[64]; char maxmemory_hmem[64];
size_t zmalloc_used = zmalloc_used_memory(); size_t zmalloc_used = zmalloc_used_memory();
size_t total_system_mem = server.system_memory_size; size_t total_system_mem = server.system_memory_size;
char *evict_policy = maxmemoryToString(); const char *evict_policy = maxmemoryToString();
long long memory_lua = (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024; long long memory_lua = (long long)lua_gc(server.lua,LUA_GCCOUNT,0)*1024;
/* Peak memory is updated from time to time by serverCron() so it /* Peak memory is updated from time to time by serverCron() so it
......
...@@ -1053,14 +1053,14 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); ...@@ -1053,14 +1053,14 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
void addReplyBulk(redisClient *c, robj *obj); void addReplyBulk(redisClient *c, robj *obj);
void addReplyBulkCString(redisClient *c, char *s); void addReplyBulkCString(redisClient *c, const char *s);
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len); void addReplyBulkCBuffer(redisClient *c, const void *p, size_t len);
void addReplyBulkLongLong(redisClient *c, long long ll); void addReplyBulkLongLong(redisClient *c, long long ll);
void addReply(redisClient *c, robj *obj); void addReply(redisClient *c, robj *obj);
void addReplySds(redisClient *c, sds s); void addReplySds(redisClient *c, sds s);
void addReplyBulkSds(redisClient *c, sds s); void addReplyBulkSds(redisClient *c, sds s);
void addReplyError(redisClient *c, char *err); void addReplyError(redisClient *c, const char *err);
void addReplyStatus(redisClient *c, char *status); void addReplyStatus(redisClient *c, const char *status);
void addReplyDouble(redisClient *c, double d); void addReplyDouble(redisClient *c, double d);
void addReplyLongLong(redisClient *c, long long ll); void addReplyLongLong(redisClient *c, long long ll);
void addReplyMultiBulkLen(redisClient *c, long length); void addReplyMultiBulkLen(redisClient *c, long length);
...@@ -1074,6 +1074,7 @@ sds catClientInfoString(sds s, redisClient *client); ...@@ -1074,6 +1074,7 @@ sds catClientInfoString(sds s, redisClient *client);
sds getAllClientsInfoString(void); sds getAllClientsInfoString(void);
void rewriteClientCommandVector(redisClient *c, int argc, ...); void rewriteClientCommandVector(redisClient *c, int argc, ...);
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval); void rewriteClientCommandArgument(redisClient *c, int i, robj *newval);
void replaceClientCommandVector(redisClient *c, int argc, robj **argv);
unsigned long getClientOutputBufferMemoryUsage(redisClient *c); unsigned long getClientOutputBufferMemoryUsage(redisClient *c);
void freeClientsInAsyncFreeQueue(void); void freeClientsInAsyncFreeQueue(void);
void asyncCloseClientOnOutputBufferLimitReached(redisClient *c); void asyncCloseClientOnOutputBufferLimitReached(redisClient *c);
...@@ -1136,9 +1137,9 @@ void freeSetObject(robj *o); ...@@ -1136,9 +1137,9 @@ void freeSetObject(robj *o);
void freeZsetObject(robj *o); void freeZsetObject(robj *o);
void freeHashObject(robj *o); void freeHashObject(robj *o);
robj *createObject(int type, void *ptr); robj *createObject(int type, void *ptr);
robj *createStringObject(char *ptr, size_t len); robj *createStringObject(const char *ptr, size_t len);
robj *createRawStringObject(char *ptr, size_t len); robj *createRawStringObject(const char *ptr, size_t len);
robj *createEmbeddedStringObject(char *ptr, size_t len); robj *createEmbeddedStringObject(const char *ptr, size_t len);
robj *dupStringObject(robj *o); robj *dupStringObject(robj *o);
int isObjectRepresentableAsLongLong(robj *o, long long *llongval); int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
robj *tryObjectEncoding(robj *o); robj *tryObjectEncoding(robj *o);
...@@ -1241,6 +1242,7 @@ void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr); ...@@ -1241,6 +1242,7 @@ void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr); void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
unsigned int zsetLength(robj *zobj); unsigned int zsetLength(robj *zobj);
void zsetConvert(robj *zobj, int encoding); void zsetConvert(robj *zobj, int encoding);
int zsetScore(robj *zobj, robj *member, double *score);
unsigned long zslGetRank(zskiplist *zsl, double score, robj *o); unsigned long zslGetRank(zskiplist *zsl, double score, robj *o);
/* Core functions */ /* Core functions */
...@@ -1275,7 +1277,7 @@ void closeListeningSockets(int unlink_unix_socket); ...@@ -1275,7 +1277,7 @@ void closeListeningSockets(int unlink_unix_socket);
void updateCachedTime(void); void updateCachedTime(void);
void resetServerStats(void); void resetServerStats(void);
unsigned int getLRUClock(void); unsigned int getLRUClock(void);
char *maxmemoryToString(void); const char *maxmemoryToString(void);
/* Set data type */ /* Set data type */
robj *setTypeCreate(robj *value); robj *setTypeCreate(robj *value);
...@@ -1328,7 +1330,7 @@ void loadServerConfig(char *filename, char *options); ...@@ -1328,7 +1330,7 @@ void loadServerConfig(char *filename, char *options);
void appendServerSaveParams(time_t seconds, int changes); void appendServerSaveParams(time_t seconds, int changes);
void resetServerSaveParams(void); void resetServerSaveParams(void);
struct rewriteConfigState; /* Forward declaration to export API. */ struct rewriteConfigState; /* Forward declaration to export API. */
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force); void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force);
int rewriteConfig(char *path); int rewriteConfig(char *path);
/* db.c -- Keyspace access API */ /* db.c -- Keyspace access API */
...@@ -1396,6 +1398,7 @@ void blockClient(redisClient *c, int btype); ...@@ -1396,6 +1398,7 @@ void blockClient(redisClient *c, int btype);
void unblockClient(redisClient *c); void unblockClient(redisClient *c);
void replyToBlockedClientTimedOut(redisClient *c); void replyToBlockedClientTimedOut(redisClient *c);
int getTimeoutFromObjectOrReply(redisClient *c, robj *object, mstime_t *timeout, int unit); int getTimeoutFromObjectOrReply(redisClient *c, robj *object, mstime_t *timeout, int unit);
void disconnectAllBlockedClients(void);
/* Git SHA1 */ /* Git SHA1 */
char *redisGitSHA1(void); char *redisGitSHA1(void);
...@@ -1556,6 +1559,14 @@ void bitcountCommand(redisClient *c); ...@@ -1556,6 +1559,14 @@ void bitcountCommand(redisClient *c);
void bitposCommand(redisClient *c); void bitposCommand(redisClient *c);
void replconfCommand(redisClient *c); void replconfCommand(redisClient *c);
void waitCommand(redisClient *c); void waitCommand(redisClient *c);
void geoencodeCommand(redisClient *c);
void geodecodeCommand(redisClient *c);
void georadiusByMemberCommand(redisClient *c);
void georadiusCommand(redisClient *c);
void geoaddCommand(redisClient *c);
void geohashCommand(redisClient *c);
void geoposCommand(redisClient *c);
void geodistCommand(redisClient *c);
void pfselftestCommand(redisClient *c); void pfselftestCommand(redisClient *c);
void pfaddCommand(redisClient *c); void pfaddCommand(redisClient *c);
void pfcountCommand(redisClient *c); void pfcountCommand(redisClient *c);
......
...@@ -652,7 +652,8 @@ void replconfCommand(redisClient *c) { ...@@ -652,7 +652,8 @@ void replconfCommand(redisClient *c) {
* *
* It does a few things: * It does a few things:
* *
* 1) Put the slave in ONLINE state. * 1) Put the slave in ONLINE state (useless when the function is called
* because state is already ONLINE but repl_put_online_on_ack is true).
* 2) Make sure the writable event is re-installed, since calling the SYNC * 2) Make sure the writable event is re-installed, since calling the SYNC
* command disables it, so that we can accumulate output buffer without * command disables it, so that we can accumulate output buffer without
* sending it to the slave. * sending it to the slave.
...@@ -660,7 +661,7 @@ void replconfCommand(redisClient *c) { ...@@ -660,7 +661,7 @@ void replconfCommand(redisClient *c) {
void putSlaveOnline(redisClient *slave) { void putSlaveOnline(redisClient *slave) {
slave->replstate = REDIS_REPL_ONLINE; slave->replstate = REDIS_REPL_ONLINE;
slave->repl_put_online_on_ack = 0; slave->repl_put_online_on_ack = 0;
slave->repl_ack_time = server.unixtime; slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
sendReplyToClient, slave) == AE_ERR) { sendReplyToClient, slave) == AE_ERR) {
redisLog(REDIS_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno)); redisLog(REDIS_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno));
...@@ -773,7 +774,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) { ...@@ -773,7 +774,7 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
* is technically online now. */ * is technically online now. */
slave->replstate = REDIS_REPL_ONLINE; slave->replstate = REDIS_REPL_ONLINE;
slave->repl_put_online_on_ack = 1; slave->repl_put_online_on_ack = 1;
slave->repl_ack_time = server.unixtime; slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */
} else { } else {
if (bgsaveerr != REDIS_OK) { if (bgsaveerr != REDIS_OK) {
freeClient(slave); freeClient(slave);
...@@ -1443,7 +1444,7 @@ error: ...@@ -1443,7 +1444,7 @@ error:
int connectWithMaster(void) { int connectWithMaster(void) {
int fd; int fd;
fd = anetTcpNonBlockBindConnect(NULL, fd = anetTcpNonBlockBestEffortBindConnect(NULL,
server.masterhost,server.masterport,REDIS_BIND_ADDR); server.masterhost,server.masterport,REDIS_BIND_ADDR);
if (fd == -1) { if (fd == -1) {
redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s", redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
...@@ -1505,6 +1506,7 @@ void replicationSetMaster(char *ip, int port) { ...@@ -1505,6 +1506,7 @@ void replicationSetMaster(char *ip, int port) {
server.masterhost = sdsnew(ip); server.masterhost = sdsnew(ip);
server.masterport = port; server.masterport = port;
if (server.master) freeClient(server.master); if (server.master) freeClient(server.master);
disconnectAllBlockedClients(); /* Clients blocked in master, now slave. */
disconnectSlaves(); /* Force our slaves to resync with us as well. */ disconnectSlaves(); /* Force our slaves to resync with us as well. */
replicationDiscardCachedMaster(); /* Don't try a PSYNC. */ replicationDiscardCachedMaster(); /* Don't try a PSYNC. */
freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */ freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */
......
...@@ -357,8 +357,9 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) { ...@@ -357,8 +357,9 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
if (cmd->flags & REDIS_CMD_WRITE) server.lua_write_dirty = 1; if (cmd->flags & REDIS_CMD_WRITE) server.lua_write_dirty = 1;
/* If this is a Redis Cluster node, we need to make sure Lua is not /* If this is a Redis Cluster node, we need to make sure Lua is not
* trying to access non-local keys. */ * trying to access non-local keys, with the exception of commands
if (server.cluster_enabled) { * received from our master. */
if (server.cluster_enabled && !(server.lua_caller->flags & REDIS_MASTER)) {
/* Duplicate relevant flags in the lua client. */ /* Duplicate relevant flags in the lua client. */
c->flags &= ~(REDIS_READONLY|REDIS_ASKING); c->flags &= ~(REDIS_READONLY|REDIS_ASKING);
c->flags |= server.lua_caller->flags & (REDIS_READONLY|REDIS_ASKING); c->flags |= server.lua_caller->flags & (REDIS_READONLY|REDIS_ASKING);
...@@ -611,11 +612,12 @@ void scriptingEnableGlobalsProtection(lua_State *lua) { ...@@ -611,11 +612,12 @@ void scriptingEnableGlobalsProtection(lua_State *lua) {
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html. /* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
* Modified to be adapted to Redis. */ * Modified to be adapted to Redis. */
s[j++]="local dbg=debug\n";
s[j++]="local mt = {}\n"; s[j++]="local mt = {}\n";
s[j++]="setmetatable(_G, mt)\n"; s[j++]="setmetatable(_G, mt)\n";
s[j++]="mt.__newindex = function (t, n, v)\n"; s[j++]="mt.__newindex = function (t, n, v)\n";
s[j++]=" if debug.getinfo(2) then\n"; s[j++]=" if dbg.getinfo(2) then\n";
s[j++]=" local w = debug.getinfo(2, \"S\").what\n"; s[j++]=" local w = dbg.getinfo(2, \"S\").what\n";
s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n"; s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n";
s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n"; s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
s[j++]=" end\n"; s[j++]=" end\n";
...@@ -623,11 +625,12 @@ void scriptingEnableGlobalsProtection(lua_State *lua) { ...@@ -623,11 +625,12 @@ void scriptingEnableGlobalsProtection(lua_State *lua) {
s[j++]=" rawset(t, n, v)\n"; s[j++]=" rawset(t, n, v)\n";
s[j++]="end\n"; s[j++]="end\n";
s[j++]="mt.__index = function (t, n)\n"; s[j++]="mt.__index = function (t, n)\n";
s[j++]=" if debug.getinfo(2) and debug.getinfo(2, \"S\").what ~= \"C\" then\n"; s[j++]=" if dbg.getinfo(2) and dbg.getinfo(2, \"S\").what ~= \"C\" then\n";
s[j++]=" error(\"Script attempted to access unexisting global variable '\"..tostring(n)..\"'\", 2)\n"; s[j++]=" error(\"Script attempted to access unexisting global variable '\"..tostring(n)..\"'\", 2)\n";
s[j++]=" end\n"; s[j++]=" end\n";
s[j++]=" return rawget(t, n)\n"; s[j++]=" return rawget(t, n)\n";
s[j++]="end\n"; s[j++]="end\n";
s[j++]="debug = nil\n";
s[j++]=NULL; s[j++]=NULL;
for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j])); for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
...@@ -731,10 +734,11 @@ void scriptingInit(void) { ...@@ -731,10 +734,11 @@ void scriptingInit(void) {
* information about the caller, that's what makes sense from the point * information about the caller, that's what makes sense from the point
* of view of the user debugging a script. */ * of view of the user debugging a script. */
{ {
char *errh_func = "function __redis__err__handler(err)\n" char *errh_func = "local dbg = debug\n"
" local i = debug.getinfo(2,'nSl')\n" "function __redis__err__handler(err)\n"
" local i = dbg.getinfo(2,'nSl')\n"
" if i and i.what == 'C' then\n" " if i and i.what == 'C' then\n"
" i = debug.getinfo(3,'nSl')\n" " i = dbg.getinfo(3,'nSl')\n"
" end\n" " end\n"
" if i then\n" " if i then\n"
" return i.source .. ':' .. i.currentline .. ': ' .. err\n" " return i.source .. ':' .. i.currentline .. ': ' .. err\n"
...@@ -876,7 +880,7 @@ void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) { ...@@ -876,7 +880,7 @@ void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
} }
/* Define a lua function with the specified function name and body. /* Define a lua function with the specified function name and body.
* The function name musts be a 2 characters long string, since all the * The function name musts be a 42 characters long string, since all the
* functions we defined in the Lua context are in the form: * functions we defined in the Lua context are in the form:
* *
* f_<hex sha1 sum> * f_<hex sha1 sum>
......
...@@ -71,7 +71,7 @@ sds sdsempty(void) { ...@@ -71,7 +71,7 @@ sds sdsempty(void) {
return sdsnewlen("",0); return sdsnewlen("",0);
} }
/* Create a new sds string starting from a null termined C string. */ /* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) { sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init); size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen); return sdsnewlen(init, initlen);
...@@ -557,7 +557,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -557,7 +557,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
* Example: * Example:
* *
* s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); * s = sdsnew("AA...AA.a.aa.aHelloWorld :::");
* s = sdstrim(s,"A. :"); * s = sdstrim(s,"Aa. :");
* printf("%s\n", s); * printf("%s\n", s);
* *
* Output will be just "Hello World". * Output will be just "Hello World".
...@@ -1098,6 +1098,7 @@ int sdsTest(int argc, char *argv[]) { ...@@ -1098,6 +1098,7 @@ int sdsTest(int argc, char *argv[]) {
unsigned int oldfree; unsigned int oldfree;
sdsfree(x); sdsfree(x);
sdsfree(y);
x = sdsnew("0"); x = sdsnew("0");
sh = (void*) (x-(sizeof(struct sdshdr))); sh = (void*) (x-(sizeof(struct sdshdr)));
test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0); test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0);
...@@ -1110,6 +1111,8 @@ int sdsTest(int argc, char *argv[]) { ...@@ -1110,6 +1111,8 @@ int sdsTest(int argc, char *argv[]) {
test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1'); test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1');
test_cond("sdsIncrLen() -- len", sh->len == 2); test_cond("sdsIncrLen() -- len", sh->len == 2);
test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); test_cond("sdsIncrLen() -- free", sh->free == oldfree-1);
sdsfree(x);
} }
} }
test_report() test_report()
......
...@@ -54,19 +54,18 @@ typedef struct sentinelAddr { ...@@ -54,19 +54,18 @@ typedef struct sentinelAddr {
#define SRI_MASTER (1<<0) #define SRI_MASTER (1<<0)
#define SRI_SLAVE (1<<1) #define SRI_SLAVE (1<<1)
#define SRI_SENTINEL (1<<2) #define SRI_SENTINEL (1<<2)
#define SRI_DISCONNECTED (1<<3) #define SRI_S_DOWN (1<<3) /* Subjectively down (no quorum). */
#define SRI_S_DOWN (1<<4) /* Subjectively down (no quorum). */ #define SRI_O_DOWN (1<<4) /* Objectively down (confirmed by others). */
#define SRI_O_DOWN (1<<5) /* Objectively down (confirmed by others). */ #define SRI_MASTER_DOWN (1<<5) /* A Sentinel with this flag set thinks that
#define SRI_MASTER_DOWN (1<<6) /* A Sentinel with this flag set thinks that
its master is down. */ its master is down. */
#define SRI_FAILOVER_IN_PROGRESS (1<<7) /* Failover is in progress for #define SRI_FAILOVER_IN_PROGRESS (1<<6) /* Failover is in progress for
this master. */ this master. */
#define SRI_PROMOTED (1<<8) /* Slave selected for promotion. */ #define SRI_PROMOTED (1<<7) /* Slave selected for promotion. */
#define SRI_RECONF_SENT (1<<9) /* SLAVEOF <newmaster> sent. */ #define SRI_RECONF_SENT (1<<8) /* SLAVEOF <newmaster> sent. */
#define SRI_RECONF_INPROG (1<<10) /* Slave synchronization in progress. */ #define SRI_RECONF_INPROG (1<<9) /* Slave synchronization in progress. */
#define SRI_RECONF_DONE (1<<11) /* Slave synchronized with new master. */ #define SRI_RECONF_DONE (1<<10) /* Slave synchronized with new master. */
#define SRI_FORCE_FAILOVER (1<<12) /* Force failover with master up. */ #define SRI_FORCE_FAILOVER (1<<11) /* Force failover with master up. */
#define SRI_SCRIPT_KILL_SENT (1<<13) /* SCRIPT KILL already sent on -BUSY */ #define SRI_SCRIPT_KILL_SENT (1<<12) /* SCRIPT KILL already sent on -BUSY */
/* Note: times are in milliseconds. */ /* Note: times are in milliseconds. */
#define SENTINEL_INFO_PERIOD 10000 #define SENTINEL_INFO_PERIOD 10000
...@@ -115,27 +114,59 @@ typedef struct sentinelAddr { ...@@ -115,27 +114,59 @@ typedef struct sentinelAddr {
#define SENTINEL_SCRIPT_MAX_RETRY 10 #define SENTINEL_SCRIPT_MAX_RETRY 10
#define SENTINEL_SCRIPT_RETRY_DELAY 30000 /* 30 seconds between retries. */ #define SENTINEL_SCRIPT_RETRY_DELAY 30000 /* 30 seconds between retries. */
typedef struct sentinelRedisInstance { /* SENTINEL SIMULATE-FAILURE command flags. */
int flags; /* See SRI_... defines */ #define SENTINEL_SIMFAILURE_NONE 0
char *name; /* Master name from the point of view of this sentinel. */ #define SENTINEL_SIMFAILURE_CRASH_AFTER_ELECTION (1<<0)
char *runid; /* run ID of this instance. */ #define SENTINEL_SIMFAILURE_CRASH_AFTER_PROMOTION (1<<1)
uint64_t config_epoch; /* Configuration epoch. */
sentinelAddr *addr; /* Master host. */ /* The link to a sentinelRedisInstance. When we have the same set of Sentinels
* monitoring many masters, we have different instances representing the
* same Sentinels, one per master, and we need to share the hiredis connections
* among them. Oherwise if 5 Sentinels are monitoring 100 masters we create
* 500 outgoing connections instead of 5.
*
* So this structure represents a reference counted link in terms of the two
* hiredis connections for commands and Pub/Sub, and the fields needed for
* failure detection, since the ping/pong time are now local to the link: if
* the link is available, the instance is avaialbe. This way we don't just
* have 5 connections instead of 500, we also send 5 pings instead of 500.
*
* Links are shared only for Sentinels: master and slave instances have
* a link with refcount = 1, always. */
typedef struct instanceLink {
int refcount; /* Number of sentinelRedisInstance owners. */
int disconnected; /* Non-zero if we need to reconnect cc or pc. */
int pending_commands; /* Number of commands sent waiting for a reply. */
redisAsyncContext *cc; /* Hiredis context for commands. */ redisAsyncContext *cc; /* Hiredis context for commands. */
redisAsyncContext *pc; /* Hiredis context for Pub / Sub. */ redisAsyncContext *pc; /* Hiredis context for Pub / Sub. */
int pending_commands; /* Number of commands sent waiting for a reply. */
mstime_t cc_conn_time; /* cc connection time. */ mstime_t cc_conn_time; /* cc connection time. */
mstime_t pc_conn_time; /* pc connection time. */ mstime_t pc_conn_time; /* pc connection time. */
mstime_t pc_last_activity; /* Last time we received any message. */ mstime_t pc_last_activity; /* Last time we received any message. */
mstime_t last_avail_time; /* Last time the instance replied to ping with mstime_t last_avail_time; /* Last time the instance replied to ping with
a reply we consider valid. */ a reply we consider valid. */
mstime_t last_ping_time; /* Last time a pending ping was sent in the mstime_t act_ping_time; /* Time at which the last pending ping (no pong
context of the current command connection received after it) was sent. This field is
with the instance. 0 if still not sent or set to 0 when a pong is received, and set again
if pong already received. */ to the current time if the value is 0 and a new
ping is sent. */
mstime_t last_ping_time; /* Time at which we sent the last ping. This is
only used to avoid sending too many pings
during failure. Idle time is computed using
the act_ping_time field. */
mstime_t last_pong_time; /* Last time the instance replied to ping, mstime_t last_pong_time; /* Last time the instance replied to ping,
whatever the reply was. That's used to check whatever the reply was. That's used to check
if the link is idle and must be reconnected. */ if the link is idle and must be reconnected. */
mstime_t last_reconn_time; /* Last reconnection attempt performed when
the link was down. */
} instanceLink;
typedef struct sentinelRedisInstance {
int flags; /* See SRI_... defines */
char *name; /* Master name from the point of view of this sentinel. */
char *runid; /* Run ID of this instance, or unique ID if is a Sentinel.*/
uint64_t config_epoch; /* Configuration epoch. */
sentinelAddr *addr; /* Master host. */
instanceLink *link; /* Link to the instance, may be shared for Sentinels. */
mstime_t last_pub_time; /* Last time we sent hello via Pub/Sub. */ mstime_t last_pub_time; /* Last time we sent hello via Pub/Sub. */
mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time
we received a hello from this Sentinel we received a hello from this Sentinel
...@@ -195,19 +226,21 @@ typedef struct sentinelRedisInstance { ...@@ -195,19 +226,21 @@ typedef struct sentinelRedisInstance {
/* Main state. */ /* Main state. */
struct sentinelState { struct sentinelState {
uint64_t current_epoch; /* Current epoch. */ char myid[REDIS_RUN_ID_SIZE+1]; /* This sentinel ID. */
uint64_t current_epoch; /* Current epoch. */
dict *masters; /* Dictionary of master sentinelRedisInstances. dict *masters; /* Dictionary of master sentinelRedisInstances.
Key is the instance name, value is the Key is the instance name, value is the
sentinelRedisInstance structure pointer. */ sentinelRedisInstance structure pointer. */
int tilt; /* Are we in TILT mode? */ int tilt; /* Are we in TILT mode? */
int running_scripts; /* Number of scripts in execution right now. */ int running_scripts; /* Number of scripts in execution right now. */
mstime_t tilt_start_time; /* When TITL started. */ mstime_t tilt_start_time; /* When TITL started. */
mstime_t previous_time; /* Last time we ran the time handler. */ mstime_t previous_time; /* Last time we ran the time handler. */
list *scripts_queue; /* Queue of user scripts to execute. */ list *scripts_queue; /* Queue of user scripts to execute. */
char *announce_ip; /* IP addr that is gossiped to other sentinels if char *announce_ip; /* IP addr that is gossiped to other sentinels if
not NULL. */ not NULL. */
int announce_port; /* Port that is gossiped to other sentinels if int announce_port; /* Port that is gossiped to other sentinels if
non zero. */ non zero. */
unsigned long simfailure_flags; /* Failures simulation. */
} sentinel; } sentinel;
/* A script execution job. */ /* A script execution job. */
...@@ -327,8 +360,7 @@ sentinelRedisInstance *sentinelGetMasterByName(char *name); ...@@ -327,8 +360,7 @@ sentinelRedisInstance *sentinelGetMasterByName(char *name);
char *sentinelGetSubjectiveLeader(sentinelRedisInstance *master); char *sentinelGetSubjectiveLeader(sentinelRedisInstance *master);
char *sentinelGetObjectiveLeader(sentinelRedisInstance *master); char *sentinelGetObjectiveLeader(sentinelRedisInstance *master);
int yesnotoi(char *s); int yesnotoi(char *s);
void sentinelDisconnectInstanceFromContext(const redisAsyncContext *c); void instanceLinkConnectionError(const redisAsyncContext *c);
void sentinelKillLink(sentinelRedisInstance *ri, redisAsyncContext *c);
const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri); const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri);
void sentinelAbortFailover(sentinelRedisInstance *ri); void sentinelAbortFailover(sentinelRedisInstance *ri);
void sentinelEvent(int level, char *type, sentinelRedisInstance *ri, const char *fmt, ...); void sentinelEvent(int level, char *type, sentinelRedisInstance *ri, const char *fmt, ...);
...@@ -342,6 +374,8 @@ void sentinelFlushConfig(void); ...@@ -342,6 +374,8 @@ void sentinelFlushConfig(void);
void sentinelGenerateInitialMonitorEvents(void); void sentinelGenerateInitialMonitorEvents(void);
int sentinelSendPing(sentinelRedisInstance *ri); int sentinelSendPing(sentinelRedisInstance *ri);
int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master); int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master);
sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, char *ip, int port, char *runid);
void sentinelSimFailureCrash(void);
/* ========================= Dictionary types =============================== */ /* ========================= Dictionary types =============================== */
...@@ -398,6 +432,7 @@ struct redisCommand sentinelcmds[] = { ...@@ -398,6 +432,7 @@ struct redisCommand sentinelcmds[] = {
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0}, {"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}, {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0}, {"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0} {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
}; };
...@@ -432,12 +467,14 @@ void initSentinel(void) { ...@@ -432,12 +467,14 @@ void initSentinel(void) {
sentinel.scripts_queue = listCreate(); sentinel.scripts_queue = listCreate();
sentinel.announce_ip = NULL; sentinel.announce_ip = NULL;
sentinel.announce_port = 0; sentinel.announce_port = 0;
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
memset(sentinel.myid,0,sizeof(sentinel.myid));
} }
/* This function gets called when the server is in Sentinel mode, started, /* This function gets called when the server is in Sentinel mode, started,
* loaded the configuration, and is ready for normal operations. */ * loaded the configuration, and is ready for normal operations. */
void sentinelIsRunning(void) { void sentinelIsRunning(void) {
redisLog(REDIS_WARNING,"Sentinel runid is %s", server.runid); int j;
if (server.configfile == NULL) { if (server.configfile == NULL) {
redisLog(REDIS_WARNING, redisLog(REDIS_WARNING,
...@@ -450,6 +487,21 @@ void sentinelIsRunning(void) { ...@@ -450,6 +487,21 @@ void sentinelIsRunning(void) {
exit(1); exit(1);
} }
/* If this Sentinel has yet no ID set in the configuration file, we
* pick a random one and persist the config on disk. From now on this
* will be this Sentinel ID across restarts. */
for (j = 0; j < REDIS_RUN_ID_SIZE; j++)
if (sentinel.myid[j] != 0) break;
if (j == REDIS_RUN_ID_SIZE) {
/* Pick ID and presist the config. */
getRandomHexChars(sentinel.myid,REDIS_RUN_ID_SIZE);
sentinelFlushConfig();
}
/* Log its ID to make debugging of issues simpler. */
redisLog(REDIS_WARNING,"Sentinel ID is %s", sentinel.myid);
/* We want to generate a +monitor event for every configured master /* We want to generate a +monitor event for every configured master
* at startup. */ * at startup. */
sentinelGenerateInitialMonitorEvents(); sentinelGenerateInitialMonitorEvents();
...@@ -871,6 +923,194 @@ void sentinelCallClientReconfScript(sentinelRedisInstance *master, int role, cha ...@@ -871,6 +923,194 @@ void sentinelCallClientReconfScript(sentinelRedisInstance *master, int role, cha
state, from->ip, fromport, to->ip, toport, NULL); state, from->ip, fromport, to->ip, toport, NULL);
} }
/* =============================== instanceLink ============================= */
/* Create a not yet connected link object. */
instanceLink *createInstanceLink(void) {
instanceLink *link = zmalloc(sizeof(*link));
link->refcount = 1;
link->disconnected = 1;
link->pending_commands = 0;
link->cc = NULL;
link->pc = NULL;
link->cc_conn_time = 0;
link->pc_conn_time = 0;
link->last_reconn_time = 0;
link->pc_last_activity = 0;
/* We set the act_ping_time to "now" even if we actually don't have yet
* a connection with the node, nor we sent a ping.
* This is useful to detect a timeout in case we'll not be able to connect
* with the node at all. */
link->act_ping_time = mstime();
link->last_ping_time = 0;
link->last_avail_time = mstime();
link->last_pong_time = mstime();
return link;
}
/* Disconnect an hiredis connection in the context of an instance link. */
void instanceLinkCloseConnection(instanceLink *link, redisAsyncContext *c) {
if (c == NULL) return;
if (link->cc == c) {
link->cc = NULL;
link->pending_commands = 0;
}
if (link->pc == c) link->pc = NULL;
c->data = NULL;
link->disconnected = 1;
redisAsyncFree(c);
}
/* Decrement the refcount of a link object, if it drops to zero, actually
* free it and return NULL. Otherwise don't do anything and return the pointer
* to the object.
*
* If we are not going to free the link and ri is not NULL, we rebind all the
* pending requests in link->cc (hiredis connection for commands) to a
* callback that will just ignore them. This is useful to avoid processing
* replies for an instance that no longer exists. */
instanceLink *releaseInstanceLink(instanceLink *link, sentinelRedisInstance *ri)
{
redisAssert(link->refcount > 0);
link->refcount--;
if (link->refcount != 0) {
if (ri && ri->link->cc) {
/* This instance may have pending callbacks in the hiredis async
* context, having as 'privdata' the instance that we are going to
* free. Let's rewrite the callback list, directly exploiting
* hiredis internal data structures, in order to bind them with
* a callback that will ignore the reply at all. */
redisCallback *cb;
redisCallbackList *callbacks = &link->cc->replies;
cb = callbacks->head;
while(cb) {
if (cb->privdata == ri) {
cb->fn = sentinelDiscardReplyCallback;
cb->privdata = NULL; /* Not strictly needed. */
}
cb = cb->next;
}
}
return link; /* Other active users. */
}
instanceLinkCloseConnection(link,link->cc);
instanceLinkCloseConnection(link,link->pc);
zfree(link);
return NULL;
}
/* This function will attempt to share the instance link we already have
* for the same Sentinel in the context of a different master, with the
* instance we are passing as argument.
*
* This way multiple Sentinel objects that refer all to the same physical
* Sentinel instance but in the context of different masters will use
* a single connection, will send a single PING per second for failure
* detection and so forth.
*
* Return REDIS_OK if a matching Sentinel was found in the context of a
* different master and sharing was performed. Otherwise REDIS_ERR
* is returned. */
int sentinelTryConnectionSharing(sentinelRedisInstance *ri) {
redisAssert(ri->flags & SRI_SENTINEL);
dictIterator *di;
dictEntry *de;
if (ri->runid == NULL) return REDIS_ERR; /* No way to identify it. */
if (ri->link->refcount > 1) return REDIS_ERR; /* Already shared. */
di = dictGetIterator(sentinel.masters);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *master = dictGetVal(de), *match;
/* We want to share with the same physical Sentinel referenced
* in other masters, so skip our master. */
if (master == ri->master) continue;
match = getSentinelRedisInstanceByAddrAndRunID(master->sentinels,
NULL,0,ri->runid);
if (match == NULL) continue; /* No match. */
if (match == ri) continue; /* Should never happen but... safer. */
/* We identified a matching Sentinel, great! Let's free our link
* and use the one of the matching Sentinel. */
releaseInstanceLink(ri->link,NULL);
ri->link = match->link;
match->link->refcount++;
return REDIS_OK;
}
dictReleaseIterator(di);
return REDIS_ERR;
}
/* When we detect a Sentinel to switch address (reporting a different IP/port
* pair in Hello messages), let's update all the matching Sentinels in the
* context of other masters as well and disconnect the links, so that everybody
* will be updated.
*
* Return the number of updated Sentinel addresses. */
int sentinelUpdateSentinelAddressInAllMasters(sentinelRedisInstance *ri) {
redisAssert(ri->flags & SRI_SENTINEL);
dictIterator *di;
dictEntry *de;
int reconfigured = 0;
di = dictGetIterator(sentinel.masters);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *master = dictGetVal(de), *match;
match = getSentinelRedisInstanceByAddrAndRunID(master->sentinels,
NULL,0,ri->runid);
if (match->link->disconnected == 0) {
instanceLinkCloseConnection(match->link,match->link->cc);
instanceLinkCloseConnection(match->link,match->link->pc);
}
if (match == ri) continue; /* Address already updated for it. */
/* Update the address of the matching Sentinel by copying the address
* of the Sentinel object that received the address update. */
releaseSentinelAddr(match->addr);
match->addr = dupSentinelAddr(ri->addr);
reconfigured++;
}
dictReleaseIterator(di);
if (reconfigured)
sentinelEvent(REDIS_NOTICE,"+sentinel-address-update", ri,
"%@ %d additional matching instances", reconfigured);
return reconfigured;
}
/* This function is called when an hiredis connection reported an error.
* We set it to NULL and mark the link as disconnected so that it will be
* reconnected again.
*
* Note: we don't free the hiredis context as hiredis will do it for us
* for async connections. */
void instanceLinkConnectionError(const redisAsyncContext *c) {
instanceLink *link = c->data;
int pubsub;
if (!link) return;
pubsub = (link->pc == c);
if (pubsub)
link->pc = NULL;
else
link->cc = NULL;
link->disconnected = 1;
}
/* Hiredis connection established / disconnected callbacks. We need them
* just to cleanup our link state. */
void sentinelLinkEstablishedCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) instanceLinkConnectionError(c);
}
void sentinelDisconnectCallback(const redisAsyncContext *c, int status) {
REDIS_NOTUSED(status);
instanceLinkConnectionError(c);
}
/* ========================== sentinelRedisInstance ========================= */ /* ========================== sentinelRedisInstance ========================= */
/* Create a redis instance, the following fields must be populated by the /* Create a redis instance, the following fields must be populated by the
...@@ -893,6 +1133,7 @@ void sentinelCallClientReconfScript(sentinelRedisInstance *master, int role, cha ...@@ -893,6 +1133,7 @@ void sentinelCallClientReconfScript(sentinelRedisInstance *master, int role, cha
* *
* The function may also fail and return NULL with errno set to EBUSY if * The function may also fail and return NULL with errno set to EBUSY if
* a master or slave with the same name already exists. */ * a master or slave with the same name already exists. */
sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *hostname, int port, int quorum, sentinelRedisInstance *master) { sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *hostname, int port, int quorum, sentinelRedisInstance *master) {
sentinelRedisInstance *ri; sentinelRedisInstance *ri;
sentinelAddr *addr; sentinelAddr *addr;
...@@ -921,6 +1162,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char * ...@@ -921,6 +1162,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
else if (flags & SRI_SENTINEL) table = master->sentinels; else if (flags & SRI_SENTINEL) table = master->sentinels;
sdsname = sdsnew(name); sdsname = sdsnew(name);
if (dictFind(table,sdsname)) { if (dictFind(table,sdsname)) {
releaseSentinelAddr(addr);
sdsfree(sdsname); sdsfree(sdsname);
errno = EBUSY; errno = EBUSY;
return NULL; return NULL;
...@@ -930,24 +1172,12 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char * ...@@ -930,24 +1172,12 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
ri = zmalloc(sizeof(*ri)); ri = zmalloc(sizeof(*ri));
/* Note that all the instances are started in the disconnected state, /* Note that all the instances are started in the disconnected state,
* the event loop will take care of connecting them. */ * the event loop will take care of connecting them. */
ri->flags = flags | SRI_DISCONNECTED; ri->flags = flags;
ri->name = sdsname; ri->name = sdsname;
ri->runid = NULL; ri->runid = NULL;
ri->config_epoch = 0; ri->config_epoch = 0;
ri->addr = addr; ri->addr = addr;
ri->cc = NULL; ri->link = createInstanceLink();
ri->pc = NULL;
ri->pending_commands = 0;
ri->cc_conn_time = 0;
ri->pc_conn_time = 0;
ri->pc_last_activity = 0;
/* We set the last_ping_time to "now" even if we actually don't have yet
* a connection with the node, nor we sent a ping.
* This is useful to detect a timeout in case we'll not be able to connect
* with the node at all. */
ri->last_ping_time = mstime();
ri->last_avail_time = mstime();
ri->last_pong_time = mstime();
ri->last_pub_time = mstime(); ri->last_pub_time = mstime();
ri->last_hello_time = mstime(); ri->last_hello_time = mstime();
ri->last_master_down_reply_time = mstime(); ri->last_master_down_reply_time = mstime();
...@@ -1003,9 +1233,8 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) { ...@@ -1003,9 +1233,8 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
dictRelease(ri->sentinels); dictRelease(ri->sentinels);
dictRelease(ri->slaves); dictRelease(ri->slaves);
/* Release hiredis connections. */ /* Disconnect the instance. */
if (ri->cc) sentinelKillLink(ri,ri->cc); releaseInstanceLink(ri->link,ri);
if (ri->pc) sentinelKillLink(ri,ri->pc);
/* Free other resources. */ /* Free other resources. */
sdsfree(ri->name); sdsfree(ri->name);
...@@ -1049,35 +1278,29 @@ const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri) { ...@@ -1049,35 +1278,29 @@ const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri) {
else return "unknown"; else return "unknown";
} }
/* This function removes all the instances found in the dictionary of /* This function remove the Sentinel with the specified ID from the
* sentinels in the specified 'master', having either: * specified master.
*
* 1) The same ip/port as specified.
* 2) The same runid.
* *
* "1" and "2" don't need to verify at the same time, just one is enough. * If "runid" is NULL the function returns ASAP.
* If "runid" is NULL it is not checked.
* Similarly if "ip" is NULL it is not checked.
* *
* This function is useful because every time we add a new Sentinel into * This function is useful because on Sentinels address switch, we want to
* a master's Sentinels dictionary, we want to be very sure about not * remove our old entry and add a new one for the same ID but with the new
* having duplicated instances for any reason. This is important because * address.
* other sentinels are needed to reach ODOWN quorum, and later to get
* voted for a given configuration epoch in order to perform the failover.
* *
* The function returns the number of Sentinels removed. */ * The function returns 1 if the matching Sentinel was removed, otherwise
int removeMatchingSentinelsFromMaster(sentinelRedisInstance *master, char *ip, int port, char *runid) { * 0 if there was no Sentinel with this ID. */
int removeMatchingSentinelFromMaster(sentinelRedisInstance *master, char *runid) {
dictIterator *di; dictIterator *di;
dictEntry *de; dictEntry *de;
int removed = 0; int removed = 0;
if (runid == NULL) return 0;
di = dictGetSafeIterator(master->sentinels); di = dictGetSafeIterator(master->sentinels);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de); sentinelRedisInstance *ri = dictGetVal(de);
if ((ri->runid && runid && strcmp(ri->runid,runid) == 0) || if (ri->runid && strcmp(ri->runid,runid) == 0) {
(ip && strcmp(ri->addr->ip,ip) == 0 && port == ri->addr->port))
{
dictDelete(master->sentinels,ri->name); dictDelete(master->sentinels,ri->name);
removed++; removed++;
} }
...@@ -1156,7 +1379,9 @@ void sentinelDelFlagsToDictOfRedisInstances(dict *instances, int flags) { ...@@ -1156,7 +1379,9 @@ void sentinelDelFlagsToDictOfRedisInstances(dict *instances, int flags) {
* 1) Remove all slaves. * 1) Remove all slaves.
* 2) Remove all sentinels. * 2) Remove all sentinels.
* 3) Remove most of the flags resulting from runtime operations. * 3) Remove most of the flags resulting from runtime operations.
* 4) Reset timers to their default value. * 4) Reset timers to their default value. For example after a reset it will be
* possible to failover again the same master ASAP, without waiting the
* failover timeout delay.
* 5) In the process of doing this undo the failover if in progress. * 5) In the process of doing this undo the failover if in progress.
* 6) Disconnect the connections with the master (will reconnect automatically). * 6) Disconnect the connections with the master (will reconnect automatically).
*/ */
...@@ -1170,24 +1395,25 @@ void sentinelResetMaster(sentinelRedisInstance *ri, int flags) { ...@@ -1170,24 +1395,25 @@ void sentinelResetMaster(sentinelRedisInstance *ri, int flags) {
dictRelease(ri->sentinels); dictRelease(ri->sentinels);
ri->sentinels = dictCreate(&instancesDictType,NULL); ri->sentinels = dictCreate(&instancesDictType,NULL);
} }
if (ri->cc) sentinelKillLink(ri,ri->cc); instanceLinkCloseConnection(ri->link,ri->link->cc);
if (ri->pc) sentinelKillLink(ri,ri->pc); instanceLinkCloseConnection(ri->link,ri->link->pc);
ri->flags &= SRI_MASTER|SRI_DISCONNECTED; ri->flags &= SRI_MASTER;
if (ri->leader) { if (ri->leader) {
sdsfree(ri->leader); sdsfree(ri->leader);
ri->leader = NULL; ri->leader = NULL;
} }
ri->failover_state = SENTINEL_FAILOVER_STATE_NONE; ri->failover_state = SENTINEL_FAILOVER_STATE_NONE;
ri->failover_state_change_time = 0; ri->failover_state_change_time = 0;
ri->failover_start_time = 0; ri->failover_start_time = 0; /* We can failover again ASAP. */
ri->promoted_slave = NULL; ri->promoted_slave = NULL;
sdsfree(ri->runid); sdsfree(ri->runid);
sdsfree(ri->slave_master_host); sdsfree(ri->slave_master_host);
ri->runid = NULL; ri->runid = NULL;
ri->slave_master_host = NULL; ri->slave_master_host = NULL;
ri->last_ping_time = mstime(); ri->link->act_ping_time = mstime();
ri->last_avail_time = mstime(); ri->link->last_ping_time = 0;
ri->last_pong_time = mstime(); ri->link->last_avail_time = mstime();
ri->link->last_pong_time = mstime();
ri->role_reported_time = mstime(); ri->role_reported_time = mstime();
ri->role_reported = SRI_MASTER; ri->role_reported = SRI_MASTER;
if (flags & SENTINEL_GENERATE_EVENT) if (flags & SENTINEL_GENERATE_EVENT)
...@@ -1269,10 +1495,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip, ...@@ -1269,10 +1495,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip,
slave = createSentinelRedisInstance(NULL,SRI_SLAVE,slaves[j]->ip, slave = createSentinelRedisInstance(NULL,SRI_SLAVE,slaves[j]->ip,
slaves[j]->port, master->quorum, master); slaves[j]->port, master->quorum, master);
releaseSentinelAddr(slaves[j]); releaseSentinelAddr(slaves[j]);
if (slave) { if (slave) sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
sentinelFlushConfig();
}
} }
zfree(slaves); zfree(slaves);
...@@ -1330,6 +1553,13 @@ void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) { ...@@ -1330,6 +1553,13 @@ void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) {
} }
} }
char *sentinelGetInstanceTypeString(sentinelRedisInstance *ri) {
if (ri->flags & SRI_MASTER) return "master";
else if (ri->flags & SRI_SLAVE) return "slave";
else if (ri->flags & SRI_SENTINEL) return "sentinel";
else return "unknown";
}
/* ============================ Config handling ============================= */ /* ============================ Config handling ============================= */
char *sentinelHandleConfiguration(char **argv, int argc) { char *sentinelHandleConfiguration(char **argv, int argc) {
sentinelRedisInstance *ri; sentinelRedisInstance *ri;
...@@ -1393,6 +1623,10 @@ char *sentinelHandleConfiguration(char **argv, int argc) { ...@@ -1393,6 +1623,10 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
unsigned long long current_epoch = strtoull(argv[1],NULL,10); unsigned long long current_epoch = strtoull(argv[1],NULL,10);
if (current_epoch > sentinel.current_epoch) if (current_epoch > sentinel.current_epoch)
sentinel.current_epoch = current_epoch; sentinel.current_epoch = current_epoch;
} else if (!strcasecmp(argv[0],"myid") && argc == 2) {
if (strlen(argv[1]) != REDIS_RUN_ID_SIZE)
return "Malformed Sentinel id in myid option.";
memcpy(sentinel.myid,argv[1],REDIS_RUN_ID_SIZE);
} else if (!strcasecmp(argv[0],"config-epoch") && argc == 3) { } else if (!strcasecmp(argv[0],"config-epoch") && argc == 3) {
/* config-epoch <name> <epoch> */ /* config-epoch <name> <epoch> */
ri = sentinelGetMasterByName(argv[1]); ri = sentinelGetMasterByName(argv[1]);
...@@ -1431,7 +1665,10 @@ char *sentinelHandleConfiguration(char **argv, int argc) { ...@@ -1431,7 +1665,10 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
{ {
return "Wrong hostname or port for sentinel."; return "Wrong hostname or port for sentinel.";
} }
if (argc == 5) si->runid = sdsnew(argv[4]); if (argc == 5) {
si->runid = sdsnew(argv[4]);
sentinelTryConnectionSharing(si);
}
} else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) { } else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) {
/* announce-ip <ip-address> */ /* announce-ip <ip-address> */
if (strlen(argv[1])) if (strlen(argv[1]))
...@@ -1455,6 +1692,10 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) { ...@@ -1455,6 +1692,10 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
dictEntry *de; dictEntry *de;
sds line; sds line;
/* sentinel unique ID. */
line = sdscatprintf(sdsempty(), "sentinel myid %s", sentinel.myid);
rewriteConfigRewriteLine(state,"sentinel",line,1);
/* For every master emit a "sentinel monitor" config entry. */ /* For every master emit a "sentinel monitor" config entry. */
di = dictGetIterator(sentinel.masters); di = dictGetIterator(sentinel.masters);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
...@@ -1546,7 +1787,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) { ...@@ -1546,7 +1787,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
slave_addr = master->addr; slave_addr = master->addr;
line = sdscatprintf(sdsempty(), line = sdscatprintf(sdsempty(),
"sentinel known-slave %s %s %d", "sentinel known-slave %s %s %d",
master->name, ri->addr->ip, ri->addr->port); master->name, slave_addr->ip, slave_addr->port);
rewriteConfigRewriteLine(state,"sentinel",line,1); rewriteConfigRewriteLine(state,"sentinel",line,1);
} }
dictReleaseIterator(di2); dictReleaseIterator(di2);
...@@ -1616,57 +1857,6 @@ werr: ...@@ -1616,57 +1857,6 @@ werr:
/* ====================== hiredis connection handling ======================= */ /* ====================== hiredis connection handling ======================= */
/* Completely disconnect a hiredis link from an instance. */
void sentinelKillLink(sentinelRedisInstance *ri, redisAsyncContext *c) {
if (ri->cc == c) {
ri->cc = NULL;
ri->pending_commands = 0;
}
if (ri->pc == c) ri->pc = NULL;
c->data = NULL;
ri->flags |= SRI_DISCONNECTED;
redisAsyncFree(c);
}
/* This function takes a hiredis context that is in an error condition
* and make sure to mark the instance as disconnected performing the
* cleanup needed.
*
* Note: we don't free the hiredis context as hiredis will do it for us
* for async connections. */
void sentinelDisconnectInstanceFromContext(const redisAsyncContext *c) {
sentinelRedisInstance *ri = c->data;
int pubsub;
if (ri == NULL) return; /* The instance no longer exists. */
pubsub = (ri->pc == c);
sentinelEvent(REDIS_DEBUG, pubsub ? "-pubsub-link" : "-cmd-link", ri,
"%@ #%s", c->errstr);
if (pubsub)
ri->pc = NULL;
else
ri->cc = NULL;
ri->flags |= SRI_DISCONNECTED;
}
void sentinelLinkEstablishedCallback(const redisAsyncContext *c, int status) {
if (status != REDIS_OK) {
sentinelDisconnectInstanceFromContext(c);
} else {
sentinelRedisInstance *ri = c->data;
int pubsub = (ri->pc == c);
sentinelEvent(REDIS_DEBUG, pubsub ? "+pubsub-link" : "+cmd-link", ri,
"%@");
}
}
void sentinelDisconnectCallback(const redisAsyncContext *c, int status) {
REDIS_NOTUSED(status);
sentinelDisconnectInstanceFromContext(c);
}
/* Send the AUTH command with the specified master password if needed. /* Send the AUTH command with the specified master password if needed.
* Note that for slaves the password set for the master is used. * Note that for slaves the password set for the master is used.
* *
...@@ -1678,8 +1868,8 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) { ...@@ -1678,8 +1868,8 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
ri->master->auth_pass; ri->master->auth_pass;
if (auth_pass) { if (auth_pass) {
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, NULL, "AUTH %s", if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "AUTH %s",
auth_pass) == REDIS_OK) ri->pending_commands++; auth_pass) == REDIS_OK) ri->link->pending_commands++;
} }
} }
...@@ -1692,77 +1882,82 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) { ...@@ -1692,77 +1882,82 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char *type) { void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char *type) {
char name[64]; char name[64];
snprintf(name,sizeof(name),"sentinel-%.8s-%s",server.runid,type); snprintf(name,sizeof(name),"sentinel-%.8s-%s",sentinel.myid,type);
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, NULL, if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri,
"CLIENT SETNAME %s", name) == REDIS_OK) "CLIENT SETNAME %s", name) == REDIS_OK)
{ {
ri->pending_commands++; ri->link->pending_commands++;
} }
} }
/* Create the async connections for the specified instance if the instance /* Create the async connections for the instance link if the link
* is disconnected. Note that the SRI_DISCONNECTED flag is set even if just * is disconnected. Note that link->disconnected is true even if just
* one of the two links (commands and pub/sub) is missing. */ * one of the two links (commands and pub/sub) is missing. */
void sentinelReconnectInstance(sentinelRedisInstance *ri) { void sentinelReconnectInstance(sentinelRedisInstance *ri) {
if (!(ri->flags & SRI_DISCONNECTED)) return; if (ri->link->disconnected == 0) return;
instanceLink *link = ri->link;
mstime_t now = mstime();
if (now - ri->link->last_reconn_time < SENTINEL_PING_PERIOD) return;
ri->link->last_reconn_time = now;
/* Commands connection. */ /* Commands connection. */
if (ri->cc == NULL) { if (link->cc == NULL) {
ri->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,REDIS_BIND_ADDR); link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,REDIS_BIND_ADDR);
if (ri->cc->err) { if (link->cc->err) {
sentinelEvent(REDIS_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s", sentinelEvent(REDIS_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
ri->cc->errstr); link->cc->errstr);
sentinelKillLink(ri,ri->cc); instanceLinkCloseConnection(link,link->cc);
} else { } else {
ri->cc_conn_time = mstime(); link->cc_conn_time = mstime();
ri->cc->data = ri; link->cc->data = link;
redisAeAttach(server.el,ri->cc); redisAeAttach(server.el,link->cc);
redisAsyncSetConnectCallback(ri->cc, redisAsyncSetConnectCallback(link->cc,
sentinelLinkEstablishedCallback); sentinelLinkEstablishedCallback);
redisAsyncSetDisconnectCallback(ri->cc, redisAsyncSetDisconnectCallback(link->cc,
sentinelDisconnectCallback); sentinelDisconnectCallback);
sentinelSendAuthIfNeeded(ri,ri->cc); sentinelSendAuthIfNeeded(ri,link->cc);
sentinelSetClientName(ri,ri->cc,"cmd"); sentinelSetClientName(ri,link->cc,"cmd");
/* Send a PING ASAP when reconnecting. */ /* Send a PING ASAP when reconnecting. */
sentinelSendPing(ri); sentinelSendPing(ri);
} }
} }
/* Pub / Sub */ /* Pub / Sub */
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && ri->pc == NULL) { if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
ri->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,REDIS_BIND_ADDR); link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,REDIS_BIND_ADDR);
if (ri->pc->err) { if (link->pc->err) {
sentinelEvent(REDIS_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s", sentinelEvent(REDIS_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
ri->pc->errstr); link->pc->errstr);
sentinelKillLink(ri,ri->pc); instanceLinkCloseConnection(link,link->pc);
} else { } else {
int retval; int retval;
ri->pc_conn_time = mstime(); link->pc_conn_time = mstime();
ri->pc->data = ri; link->pc->data = link;
redisAeAttach(server.el,ri->pc); redisAeAttach(server.el,link->pc);
redisAsyncSetConnectCallback(ri->pc, redisAsyncSetConnectCallback(link->pc,
sentinelLinkEstablishedCallback); sentinelLinkEstablishedCallback);
redisAsyncSetDisconnectCallback(ri->pc, redisAsyncSetDisconnectCallback(link->pc,
sentinelDisconnectCallback); sentinelDisconnectCallback);
sentinelSendAuthIfNeeded(ri,ri->pc); sentinelSendAuthIfNeeded(ri,link->pc);
sentinelSetClientName(ri,ri->pc,"pubsub"); sentinelSetClientName(ri,link->pc,"pubsub");
/* Now we subscribe to the Sentinels "Hello" channel. */ /* Now we subscribe to the Sentinels "Hello" channel. */
retval = redisAsyncCommand(ri->pc, retval = redisAsyncCommand(link->pc,
sentinelReceiveHelloMessages, NULL, "SUBSCRIBE %s", sentinelReceiveHelloMessages, ri, "SUBSCRIBE %s",
SENTINEL_HELLO_CHANNEL); SENTINEL_HELLO_CHANNEL);
if (retval != REDIS_OK) { if (retval != REDIS_OK) {
/* If we can't subscribe, the Pub/Sub connection is useless /* If we can't subscribe, the Pub/Sub connection is useless
* and we can simply disconnect it and try again. */ * and we can simply disconnect it and try again. */
sentinelKillLink(ri,ri->pc); instanceLinkCloseConnection(link,link->pc);
return; return;
} }
} }
} }
/* Clear the DISCONNECTED flags only if we have both the connections /* Clear the disconnected status only if we have both the connections
* (or just the commands connection if this is a sentinel instance). */ * (or just the commands connection if this is a sentinel instance). */
if (ri->cc && (ri->flags & SRI_SENTINEL || ri->pc)) if (link->cc && (ri->flags & SRI_SENTINEL || link->pc))
ri->flags &= ~SRI_DISCONNECTED; link->disconnected = 0;
} }
/* ======================== Redis instances pinging ======================== */ /* ======================== Redis instances pinging ======================== */
...@@ -1848,6 +2043,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) { ...@@ -1848,6 +2043,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
atoi(port), ri->quorum, ri)) != NULL) atoi(port), ri->quorum, ri)) != NULL)
{ {
sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@"); sentinelEvent(REDIS_NOTICE,"+slave",slave,"%@");
sentinelFlushConfig();
} }
} }
} }
...@@ -1954,6 +2150,9 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) { ...@@ -1954,6 +2150,9 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
ri->master->failover_state_change_time = mstime(); ri->master->failover_state_change_time = mstime();
sentinelFlushConfig(); sentinelFlushConfig();
sentinelEvent(REDIS_WARNING,"+promoted-slave",ri,"%@"); sentinelEvent(REDIS_WARNING,"+promoted-slave",ri,"%@");
if (sentinel.simfailure_flags &
SENTINEL_SIMFAILURE_CRASH_AFTER_PROMOTION)
sentinelSimFailureCrash();
sentinelEvent(REDIS_WARNING,"+failover-state-reconf-slaves", sentinelEvent(REDIS_WARNING,"+failover-state-reconf-slaves",
ri->master,"%@"); ri->master,"%@");
sentinelCallClientReconfScript(ri->master,SENTINEL_LEADER, sentinelCallClientReconfScript(ri->master,SENTINEL_LEADER,
...@@ -2030,36 +2229,35 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) { ...@@ -2030,36 +2229,35 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
} }
void sentinelInfoReplyCallback(redisAsyncContext *c, void *reply, void *privdata) { void sentinelInfoReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
sentinelRedisInstance *ri = c->data; sentinelRedisInstance *ri = privdata;
instanceLink *link = c->data;
redisReply *r; redisReply *r;
REDIS_NOTUSED(privdata);
if (ri) ri->pending_commands--; if (!reply || !link) return;
if (!reply || !ri) return; link->pending_commands--;
r = reply; r = reply;
if (r->type == REDIS_REPLY_STRING) { if (r->type == REDIS_REPLY_STRING)
sentinelRefreshInstanceInfo(ri,r->str); sentinelRefreshInstanceInfo(ri,r->str);
}
} }
/* Just discard the reply. We use this when we are not monitoring the return /* Just discard the reply. We use this when we are not monitoring the return
* value of the command but its effects directly. */ * value of the command but its effects directly. */
void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privdata) { void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
sentinelRedisInstance *ri = c->data; instanceLink *link = c->data;
REDIS_NOTUSED(reply); REDIS_NOTUSED(reply);
REDIS_NOTUSED(privdata); REDIS_NOTUSED(privdata);
if (ri) ri->pending_commands--; if (link) link->pending_commands--;
} }
void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata) { void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
sentinelRedisInstance *ri = c->data; sentinelRedisInstance *ri = privdata;
instanceLink *link = c->data;
redisReply *r; redisReply *r;
REDIS_NOTUSED(privdata);
if (ri) ri->pending_commands--; if (!reply || !link) return;
if (!reply || !ri) return; link->pending_commands--;
r = reply; r = reply;
if (r->type == REDIS_REPLY_STATUS || if (r->type == REDIS_REPLY_STATUS ||
...@@ -2070,8 +2268,8 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata ...@@ -2070,8 +2268,8 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
strncmp(r->str,"LOADING",7) == 0 || strncmp(r->str,"LOADING",7) == 0 ||
strncmp(r->str,"MASTERDOWN",10) == 0) strncmp(r->str,"MASTERDOWN",10) == 0)
{ {
ri->last_avail_time = mstime(); link->last_avail_time = mstime();
ri->last_ping_time = 0; /* Flag the pong as received. */ link->act_ping_time = 0; /* Flag the pong as received. */
} else { } else {
/* Send a SCRIPT KILL command if the instance appears to be /* Send a SCRIPT KILL command if the instance appears to be
* down because of a busy script. */ * down because of a busy script. */
...@@ -2079,26 +2277,26 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata ...@@ -2079,26 +2277,26 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
(ri->flags & SRI_S_DOWN) && (ri->flags & SRI_S_DOWN) &&
!(ri->flags & SRI_SCRIPT_KILL_SENT)) !(ri->flags & SRI_SCRIPT_KILL_SENT))
{ {
if (redisAsyncCommand(ri->cc, if (redisAsyncCommand(ri->link->cc,
sentinelDiscardReplyCallback, NULL, sentinelDiscardReplyCallback, ri,
"SCRIPT KILL") == REDIS_OK) "SCRIPT KILL") == REDIS_OK)
ri->pending_commands++; ri->link->pending_commands++;
ri->flags |= SRI_SCRIPT_KILL_SENT; ri->flags |= SRI_SCRIPT_KILL_SENT;
} }
} }
} }
ri->last_pong_time = mstime(); link->last_pong_time = mstime();
} }
/* This is called when we get the reply about the PUBLISH command we send /* This is called when we get the reply about the PUBLISH command we send
* to the master to advertise this sentinel. */ * to the master to advertise this sentinel. */
void sentinelPublishReplyCallback(redisAsyncContext *c, void *reply, void *privdata) { void sentinelPublishReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
sentinelRedisInstance *ri = c->data; sentinelRedisInstance *ri = privdata;
instanceLink *link = c->data;
redisReply *r; redisReply *r;
REDIS_NOTUSED(privdata);
if (ri) ri->pending_commands--; if (!reply || !link) return;
if (!reply || !ri) return; link->pending_commands--;
r = reply; r = reply;
/* Only update pub_time if we actually published our message. Otherwise /* Only update pub_time if we actually published our message. Otherwise
...@@ -2136,25 +2334,25 @@ void sentinelProcessHelloMessage(char *hello, int hello_len) { ...@@ -2136,25 +2334,25 @@ void sentinelProcessHelloMessage(char *hello, int hello_len) {
if (!si) { if (!si) {
/* If not, remove all the sentinels that have the same runid /* If not, remove all the sentinels that have the same runid
* OR the same ip/port, because it's either a restart or a * because there was an address change, and add the same Sentinel
* network topology change. */ * with the new address back. */
removed = removeMatchingSentinelsFromMaster(master,token[0],port, removed = removeMatchingSentinelFromMaster(master,token[2]);
token[2]);
if (removed) { if (removed) {
sentinelEvent(REDIS_NOTICE,"-dup-sentinel",master, sentinelEvent(REDIS_NOTICE,"+sentinel-address-switch",master,
"%@ #duplicate of %s:%d or %s", "%@ ip %s port %d for %s", token[0],port,token[2]);
token[0],port,token[2]);
} }
/* Add the new sentinel. */ /* Add the new sentinel. */
si = createSentinelRedisInstance(NULL,SRI_SENTINEL, si = createSentinelRedisInstance(NULL,SRI_SENTINEL,
token[0],port,master->quorum,master); token[0],port,master->quorum,master);
if (si) { if (si) {
sentinelEvent(REDIS_NOTICE,"+sentinel",si,"%@"); if (!removed) sentinelEvent(REDIS_NOTICE,"+sentinel",si,"%@");
/* The runid is NULL after a new instance creation and /* The runid is NULL after a new instance creation and
* for Sentinels we don't have a later chance to fill it, * for Sentinels we don't have a later chance to fill it,
* so do it now. */ * so do it now. */
si->runid = sdsnew(token[2]); si->runid = sdsnew(token[2]);
sentinelTryConnectionSharing(si);
if (removed) sentinelUpdateSentinelAddressInAllMasters(si);
sentinelFlushConfig(); sentinelFlushConfig();
} }
} }
...@@ -2203,9 +2401,9 @@ cleanup: ...@@ -2203,9 +2401,9 @@ cleanup:
/* This is our Pub/Sub callback for the Hello channel. It's useful in order /* This is our Pub/Sub callback for the Hello channel. It's useful in order
* to discover other sentinels attached at the same master. */ * to discover other sentinels attached at the same master. */
void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privdata) { void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privdata) {
sentinelRedisInstance *ri = c->data; sentinelRedisInstance *ri = privdata;
redisReply *r; redisReply *r;
REDIS_NOTUSED(privdata); REDIS_NOTUSED(c);
if (!reply || !ri) return; if (!reply || !ri) return;
r = reply; r = reply;
...@@ -2213,7 +2411,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd ...@@ -2213,7 +2411,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
/* Update the last activity in the pubsub channel. Note that since we /* Update the last activity in the pubsub channel. Note that since we
* receive our messages as well this timestamp can be used to detect * receive our messages as well this timestamp can be used to detect
* if the link is probably disconnected even if it seems otherwise. */ * if the link is probably disconnected even if it seems otherwise. */
ri->pc_last_activity = mstime(); ri->link->pc_last_activity = mstime();
/* Sanity check in the reply we expect, so that the code that follows /* Sanity check in the reply we expect, so that the code that follows
* can avoid to check for details. */ * can avoid to check for details. */
...@@ -2225,7 +2423,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd ...@@ -2225,7 +2423,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
strcmp(r->element[0]->str,"message") != 0) return; strcmp(r->element[0]->str,"message") != 0) return;
/* We are not interested in meeting ourselves */ /* We are not interested in meeting ourselves */
if (strstr(r->element[2]->str,server.runid) != NULL) return; if (strstr(r->element[2]->str,sentinel.myid) != NULL) return;
sentinelProcessHelloMessage(r->element[2]->str, r->element[2]->len); sentinelProcessHelloMessage(r->element[2]->str, r->element[2]->len);
} }
...@@ -2250,14 +2448,14 @@ int sentinelSendHello(sentinelRedisInstance *ri) { ...@@ -2250,14 +2448,14 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master; sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master;
sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master); sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master);
if (ri->flags & SRI_DISCONNECTED) return REDIS_ERR; if (ri->link->disconnected) return REDIS_ERR;
/* Use the specified announce address if specified, otherwise try to /* Use the specified announce address if specified, otherwise try to
* obtain our own IP address. */ * obtain our own IP address. */
if (sentinel.announce_ip) { if (sentinel.announce_ip) {
announce_ip = sentinel.announce_ip; announce_ip = sentinel.announce_ip;
} else { } else {
if (anetSockName(ri->cc->c.fd,ip,sizeof(ip),NULL) == -1) if (anetSockName(ri->link->cc->c.fd,ip,sizeof(ip),NULL) == -1)
return REDIS_ERR; return REDIS_ERR;
announce_ip = ip; announce_ip = ip;
} }
...@@ -2268,16 +2466,16 @@ int sentinelSendHello(sentinelRedisInstance *ri) { ...@@ -2268,16 +2466,16 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
snprintf(payload,sizeof(payload), snprintf(payload,sizeof(payload),
"%s,%d,%s,%llu," /* Info about this sentinel. */ "%s,%d,%s,%llu," /* Info about this sentinel. */
"%s,%s,%d,%llu", /* Info about current master. */ "%s,%s,%d,%llu", /* Info about current master. */
announce_ip, announce_port, server.runid, announce_ip, announce_port, sentinel.myid,
(unsigned long long) sentinel.current_epoch, (unsigned long long) sentinel.current_epoch,
/* --- */ /* --- */
master->name,master_addr->ip,master_addr->port, master->name,master_addr->ip,master_addr->port,
(unsigned long long) master->config_epoch); (unsigned long long) master->config_epoch);
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelPublishReplyCallback, NULL, "PUBLISH %s %s", sentinelPublishReplyCallback, ri, "PUBLISH %s %s",
SENTINEL_HELLO_CHANNEL,payload); SENTINEL_HELLO_CHANNEL,payload);
if (retval != REDIS_OK) return REDIS_ERR; if (retval != REDIS_OK) return REDIS_ERR;
ri->pending_commands++; ri->link->pending_commands++;
return REDIS_OK; return REDIS_OK;
} }
...@@ -2313,20 +2511,22 @@ int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master) { ...@@ -2313,20 +2511,22 @@ int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master) {
return REDIS_OK; return REDIS_OK;
} }
/* Send a PING to the specified instance and refresh the last_ping_time /* Send a PING to the specified instance and refresh the act_ping_time
* if it is zero (that is, if we received a pong for the previous ping). * if it is zero (that is, if we received a pong for the previous ping).
* *
* On error zero is returned, and we can't consider the PING command * On error zero is returned, and we can't consider the PING command
* queued in the connection. */ * queued in the connection. */
int sentinelSendPing(sentinelRedisInstance *ri) { int sentinelSendPing(sentinelRedisInstance *ri) {
int retval = redisAsyncCommand(ri->cc, int retval = redisAsyncCommand(ri->link->cc,
sentinelPingReplyCallback, NULL, "PING"); sentinelPingReplyCallback, ri, "PING");
if (retval == REDIS_OK) { if (retval == REDIS_OK) {
ri->pending_commands++; ri->link->pending_commands++;
/* We update the ping time only if we received the pong for ri->link->last_ping_time = mstime();
* the previous ping, otherwise we are technically waiting /* We update the active ping time only if we received the pong for
* since the first ping that did not received a reply. */ * the previous ping, otherwise we are technically waiting since the
if (ri->last_ping_time == 0) ri->last_ping_time = mstime(); * first ping that did not received a reply. */
if (ri->link->act_ping_time == 0)
ri->link->act_ping_time = ri->link->last_ping_time;
return 1; return 1;
} else { } else {
return 0; return 0;
...@@ -2342,7 +2542,7 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) { ...@@ -2342,7 +2542,7 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
/* Return ASAP if we have already a PING or INFO already pending, or /* Return ASAP if we have already a PING or INFO already pending, or
* in the case the instance is not properly connected. */ * in the case the instance is not properly connected. */
if (ri->flags & SRI_DISCONNECTED) return; if (ri->link->disconnected) return;
/* For INFO, PING, PUBLISH that are not critical commands to send we /* For INFO, PING, PUBLISH that are not critical commands to send we
* also have a limit of SENTINEL_MAX_PENDING_COMMANDS. We don't * also have a limit of SENTINEL_MAX_PENDING_COMMANDS. We don't
...@@ -2350,7 +2550,8 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) { ...@@ -2350,7 +2550,8 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
* properly (note that anyway there is a redundant protection about this, * properly (note that anyway there is a redundant protection about this,
* that is, the link will be disconnected and reconnected if a long * that is, the link will be disconnected and reconnected if a long
* timeout condition is detected. */ * timeout condition is detected. */
if (ri->pending_commands >= SENTINEL_MAX_PENDING_COMMANDS) return; if (ri->link->pending_commands >=
SENTINEL_MAX_PENDING_COMMANDS * ri->link->refcount) return;
/* If this is a slave of a master in O_DOWN condition we start sending /* If this is a slave of a master in O_DOWN condition we start sending
* it INFO every second, instead of the usual SENTINEL_INFO_PERIOD * it INFO every second, instead of the usual SENTINEL_INFO_PERIOD
...@@ -2374,10 +2575,11 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) { ...@@ -2374,10 +2575,11 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
(now - ri->info_refresh) > info_period)) (now - ri->info_refresh) > info_period))
{ {
/* Send INFO to masters and slaves, not sentinels. */ /* Send INFO to masters and slaves, not sentinels. */
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelInfoReplyCallback, NULL, "INFO"); sentinelInfoReplyCallback, ri, "INFO");
if (retval == REDIS_OK) ri->pending_commands++; if (retval == REDIS_OK) ri->link->pending_commands++;
} else if ((now - ri->last_pong_time) > ping_period) { } else if ((now - ri->link->last_pong_time) > ping_period &&
(now - ri->link->last_ping_time) > ping_period/2) {
/* Send PING to all the three kinds of instances. */ /* Send PING to all the three kinds of instances. */
sentinelSendPing(ri); sentinelSendPing(ri);
} else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) { } else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
...@@ -2431,7 +2633,7 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) { ...@@ -2431,7 +2633,7 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
if (ri->flags & SRI_MASTER) flags = sdscat(flags,"master,"); if (ri->flags & SRI_MASTER) flags = sdscat(flags,"master,");
if (ri->flags & SRI_SLAVE) flags = sdscat(flags,"slave,"); if (ri->flags & SRI_SLAVE) flags = sdscat(flags,"slave,");
if (ri->flags & SRI_SENTINEL) flags = sdscat(flags,"sentinel,"); if (ri->flags & SRI_SENTINEL) flags = sdscat(flags,"sentinel,");
if (ri->flags & SRI_DISCONNECTED) flags = sdscat(flags,"disconnected,"); if (ri->link->disconnected) flags = sdscat(flags,"disconnected,");
if (ri->flags & SRI_MASTER_DOWN) flags = sdscat(flags,"master_down,"); if (ri->flags & SRI_MASTER_DOWN) flags = sdscat(flags,"master_down,");
if (ri->flags & SRI_FAILOVER_IN_PROGRESS) if (ri->flags & SRI_FAILOVER_IN_PROGRESS)
flags = sdscat(flags,"failover_in_progress,"); flags = sdscat(flags,"failover_in_progress,");
...@@ -2445,8 +2647,12 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) { ...@@ -2445,8 +2647,12 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
sdsfree(flags); sdsfree(flags);
fields++; fields++;
addReplyBulkCString(c,"pending-commands"); addReplyBulkCString(c,"link-pending-commands");
addReplyBulkLongLong(c,ri->pending_commands); addReplyBulkLongLong(c,ri->link->pending_commands);
fields++;
addReplyBulkCString(c,"link-refcount");
addReplyBulkLongLong(c,ri->link->refcount);
fields++; fields++;
if (ri->flags & SRI_FAILOVER_IN_PROGRESS) { if (ri->flags & SRI_FAILOVER_IN_PROGRESS) {
...@@ -2457,15 +2663,15 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) { ...@@ -2457,15 +2663,15 @@ void addReplySentinelRedisInstance(redisClient *c, sentinelRedisInstance *ri) {
addReplyBulkCString(c,"last-ping-sent"); addReplyBulkCString(c,"last-ping-sent");
addReplyBulkLongLong(c, addReplyBulkLongLong(c,
ri->last_ping_time ? (mstime() - ri->last_ping_time) : 0); ri->link->act_ping_time ? (mstime() - ri->link->act_ping_time) : 0);
fields++; fields++;
addReplyBulkCString(c,"last-ok-ping-reply"); addReplyBulkCString(c,"last-ok-ping-reply");
addReplyBulkLongLong(c,mstime() - ri->last_avail_time); addReplyBulkLongLong(c,mstime() - ri->link->last_avail_time);
fields++; fields++;
addReplyBulkCString(c,"last-ping-reply"); addReplyBulkCString(c,"last-ping-reply");
addReplyBulkLongLong(c,mstime() - ri->last_pong_time); addReplyBulkLongLong(c,mstime() - ri->link->last_pong_time);
fields++; fields++;
if (ri->flags & SRI_S_DOWN) { if (ri->flags & SRI_S_DOWN) {
...@@ -2619,6 +2825,31 @@ sentinelRedisInstance *sentinelGetMasterByNameOrReplyError(redisClient *c, ...@@ -2619,6 +2825,31 @@ sentinelRedisInstance *sentinelGetMasterByNameOrReplyError(redisClient *c,
return ri; return ri;
} }
#define SENTINEL_ISQR_OK 0
#define SENTINEL_ISQR_NOQUORUM (1<<0)
#define SENTINEL_ISQR_NOAUTH (1<<1)
int sentinelIsQuorumReachable(sentinelRedisInstance *master, int *usableptr) {
dictIterator *di;
dictEntry *de;
int usable = 1; /* Number of usable Sentinels. Init to 1 to count myself. */
int result = SENTINEL_ISQR_OK;
int voters = dictSize(master->sentinels)+1; /* Known Sentinels + myself. */
di = dictGetIterator(master->sentinels);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
if (ri->flags & (SRI_S_DOWN|SRI_O_DOWN)) continue;
usable++;
}
dictReleaseIterator(di);
if (usable < (int)master->quorum) result |= SENTINEL_ISQR_NOQUORUM;
if (usable < voters/2+1) result |= SENTINEL_ISQR_NOAUTH;
if (usableptr) *usableptr = usable;
return result;
}
void sentinelCommand(redisClient *c) { void sentinelCommand(redisClient *c) {
if (!strcasecmp(c->argv[1]->ptr,"masters")) { if (!strcasecmp(c->argv[1]->ptr,"masters")) {
/* SENTINEL MASTERS */ /* SENTINEL MASTERS */
...@@ -2649,7 +2880,23 @@ void sentinelCommand(redisClient *c) { ...@@ -2649,7 +2880,23 @@ void sentinelCommand(redisClient *c) {
return; return;
addReplyDictOfRedisInstances(c,ri->sentinels); addReplyDictOfRedisInstances(c,ri->sentinels);
} else if (!strcasecmp(c->argv[1]->ptr,"is-master-down-by-addr")) { } else if (!strcasecmp(c->argv[1]->ptr,"is-master-down-by-addr")) {
/* SENTINEL IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid>*/ /* SENTINEL IS-MASTER-DOWN-BY-ADDR <ip> <port> <current-epoch> <runid>
*
* Arguments:
*
* ip and port are the ip and port of the master we want to be
* checked by Sentinel. Note that the command will not check by
* name but just by master, in theory different Sentinels may monitor
* differnet masters with the same name.
*
* current-epoch is needed in order to understand if we are allowed
* to vote for a failover leader or not. Each Sentinel can vote just
* one time per epoch.
*
* runid is "*" if we are not seeking for a vote from the Sentinel
* in order to elect the failover leader. Otherwise it is set to the
* runid we want the Sentinel to vote if it did not already voted.
*/
sentinelRedisInstance *ri; sentinelRedisInstance *ri;
long long req_epoch; long long req_epoch;
uint64_t leader_epoch = 0; uint64_t leader_epoch = 0;
...@@ -2775,6 +3022,10 @@ void sentinelCommand(redisClient *c) { ...@@ -2775,6 +3022,10 @@ void sentinelCommand(redisClient *c) {
sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum); sentinelEvent(REDIS_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
addReply(c,shared.ok); addReply(c,shared.ok);
} }
} else if (!strcasecmp(c->argv[1]->ptr,"flushconfig")) {
sentinelFlushConfig();
addReply(c,shared.ok);
return;
} else if (!strcasecmp(c->argv[1]->ptr,"remove")) { } else if (!strcasecmp(c->argv[1]->ptr,"remove")) {
/* SENTINEL REMOVE <name> */ /* SENTINEL REMOVE <name> */
sentinelRedisInstance *ri; sentinelRedisInstance *ri;
...@@ -2785,10 +3036,37 @@ void sentinelCommand(redisClient *c) { ...@@ -2785,10 +3036,37 @@ void sentinelCommand(redisClient *c) {
dictDelete(sentinel.masters,c->argv[2]->ptr); dictDelete(sentinel.masters,c->argv[2]->ptr);
sentinelFlushConfig(); sentinelFlushConfig();
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"ckquorum")) {
/* SENTINEL CKQUORUM <name> */
sentinelRedisInstance *ri;
int usable;
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
== NULL) return;
int result = sentinelIsQuorumReachable(ri,&usable);
if (result == SENTINEL_ISQR_OK) {
addReplySds(c, sdscatfmt(sdsempty(),
"+OK %i usable Sentinels. Quorum and failover authorization "
"can be reached\r\n",usable));
} else {
sds e = sdscatfmt(sdsempty(),
"-NOQUORUM %i usable Sentinels. ",usable);
if (result & SENTINEL_ISQR_NOQUORUM)
e = sdscat(e,"Not enough available Sentinels to reach the"
" specified quorum for this master");
if (result & SENTINEL_ISQR_NOAUTH) {
if (result & SENTINEL_ISQR_NOQUORUM) e = sdscat(e,". ");
e = sdscat(e, "Not enough available Sentinels to reach the"
" majority and authorize a failover");
}
e = sdscat(e,"\r\n");
addReplySds(c,e);
}
} else if (!strcasecmp(c->argv[1]->ptr,"set")) { } else if (!strcasecmp(c->argv[1]->ptr,"set")) {
if (c->argc < 3 || c->argc % 2 == 0) goto numargserr; if (c->argc < 3 || c->argc % 2 == 0) goto numargserr;
sentinelSetCommand(c); sentinelSetCommand(c);
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) { } else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
/* SENTINEL INFO-CACHE <name> */
if (c->argc < 2) goto numargserr; if (c->argc < 2) goto numargserr;
mstime_t now = mstime(); mstime_t now = mstime();
...@@ -2849,6 +3127,33 @@ void sentinelCommand(redisClient *c) { ...@@ -2849,6 +3127,33 @@ void sentinelCommand(redisClient *c) {
} }
dictReleaseIterator(di); dictReleaseIterator(di);
if (masters_local != sentinel.masters) dictRelease(masters_local); if (masters_local != sentinel.masters) dictRelease(masters_local);
} else if (!strcasecmp(c->argv[1]->ptr,"simulate-failure")) {
/* SENTINEL SIMULATE-FAILURE <flag> <flag> ... <flag> */
int j;
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
for (j = 2; j < c->argc; j++) {
if (!strcasecmp(c->argv[j]->ptr,"crash-after-election")) {
sentinel.simfailure_flags |=
SENTINEL_SIMFAILURE_CRASH_AFTER_ELECTION;
redisLog(REDIS_WARNING,"Failure simulation: this Sentinel "
"will crash after being successfully elected as failover "
"leader");
} else if (!strcasecmp(c->argv[j]->ptr,"crash-after-promotion")) {
sentinel.simfailure_flags |=
SENTINEL_SIMFAILURE_CRASH_AFTER_PROMOTION;
redisLog(REDIS_WARNING,"Failure simulation: this Sentinel "
"will crash after promoting the selected slave to master");
} else if (!strcasecmp(c->argv[j]->ptr,"help")) {
addReplyMultiBulkLen(c,2);
addReplyBulkCString(c,"crash-after-election");
addReplyBulkCString(c,"crash-after-promotion");
} else {
addReplyError(c,"Unknown failure simulation specified");
return;
}
}
addReply(c,shared.ok);
} else { } else {
addReplyErrorFormat(c,"Unknown sentinel subcommand '%s'", addReplyErrorFormat(c,"Unknown sentinel subcommand '%s'",
(char*)c->argv[1]->ptr); (char*)c->argv[1]->ptr);
...@@ -2896,11 +3201,13 @@ void sentinelInfoCommand(redisClient *c) { ...@@ -2896,11 +3201,13 @@ void sentinelInfoCommand(redisClient *c) {
"sentinel_masters:%lu\r\n" "sentinel_masters:%lu\r\n"
"sentinel_tilt:%d\r\n" "sentinel_tilt:%d\r\n"
"sentinel_running_scripts:%d\r\n" "sentinel_running_scripts:%d\r\n"
"sentinel_scripts_queue_length:%ld\r\n", "sentinel_scripts_queue_length:%ld\r\n"
"sentinel_simulate_failure_flags:%lu\r\n",
dictSize(sentinel.masters), dictSize(sentinel.masters),
sentinel.tilt, sentinel.tilt,
sentinel.running_scripts, sentinel.running_scripts,
listLength(sentinel.scripts_queue)); listLength(sentinel.scripts_queue),
sentinel.simfailure_flags);
di = dictGetIterator(sentinel.masters); di = dictGetIterator(sentinel.masters);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
...@@ -3051,8 +3358,8 @@ void sentinelPublishCommand(redisClient *c) { ...@@ -3051,8 +3358,8 @@ void sentinelPublishCommand(redisClient *c) {
void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) { void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
mstime_t elapsed = 0; mstime_t elapsed = 0;
if (ri->last_ping_time) if (ri->link->act_ping_time)
elapsed = mstime() - ri->last_ping_time; elapsed = mstime() - ri->link->act_ping_time;
/* Check if we are in need for a reconnection of one of the /* Check if we are in need for a reconnection of one of the
* links, because we are detecting low activity. * links, because we are detecting low activity.
...@@ -3060,15 +3367,16 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) { ...@@ -3060,15 +3367,16 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
* 1) Check if the command link seems connected, was connected not less * 1) Check if the command link seems connected, was connected not less
* than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have a * than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have a
* pending ping for more than half the timeout. */ * pending ping for more than half the timeout. */
if (ri->cc && if (ri->link->cc &&
(mstime() - ri->cc_conn_time) > SENTINEL_MIN_LINK_RECONNECT_PERIOD && (mstime() - ri->link->cc_conn_time) >
ri->last_ping_time != 0 && /* Ther is a pending ping... */ SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
ri->link->act_ping_time != 0 && /* Ther is a pending ping... */
/* The pending ping is delayed, and we did not received /* The pending ping is delayed, and we did not received
* error replies as well. */ * error replies as well. */
(mstime() - ri->last_ping_time) > (ri->down_after_period/2) && (mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
(mstime() - ri->last_pong_time) > (ri->down_after_period/2)) (mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
{ {
sentinelKillLink(ri,ri->cc); instanceLinkCloseConnection(ri->link,ri->link->cc);
} }
/* 2) Check if the pubsub link seems connected, was connected not less /* 2) Check if the pubsub link seems connected, was connected not less
...@@ -3076,11 +3384,12 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) { ...@@ -3076,11 +3384,12 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
* activity in the Pub/Sub channel for more than * activity in the Pub/Sub channel for more than
* SENTINEL_PUBLISH_PERIOD * 3. * SENTINEL_PUBLISH_PERIOD * 3.
*/ */
if (ri->pc && if (ri->link->pc &&
(mstime() - ri->pc_conn_time) > SENTINEL_MIN_LINK_RECONNECT_PERIOD && (mstime() - ri->link->pc_conn_time) >
(mstime() - ri->pc_last_activity) > (SENTINEL_PUBLISH_PERIOD*3)) SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
(mstime() - ri->link->pc_last_activity) > (SENTINEL_PUBLISH_PERIOD*3))
{ {
sentinelKillLink(ri,ri->pc); instanceLinkCloseConnection(ri->link,ri->link->pc);
} }
/* Update the SDOWN flag. We believe the instance is SDOWN if: /* Update the SDOWN flag. We believe the instance is SDOWN if:
...@@ -3154,12 +3463,12 @@ void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) { ...@@ -3154,12 +3463,12 @@ void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
/* Receive the SENTINEL is-master-down-by-addr reply, see the /* Receive the SENTINEL is-master-down-by-addr reply, see the
* sentinelAskMasterStateToOtherSentinels() function for more information. */ * sentinelAskMasterStateToOtherSentinels() function for more information. */
void sentinelReceiveIsMasterDownReply(redisAsyncContext *c, void *reply, void *privdata) { void sentinelReceiveIsMasterDownReply(redisAsyncContext *c, void *reply, void *privdata) {
sentinelRedisInstance *ri = c->data; sentinelRedisInstance *ri = privdata;
instanceLink *link = c->data;
redisReply *r; redisReply *r;
REDIS_NOTUSED(privdata);
if (ri) ri->pending_commands--; if (!reply || !link) return;
if (!reply || !ri) return; link->pending_commands--;
r = reply; r = reply;
/* Ignore every error or unexpected reply. /* Ignore every error or unexpected reply.
...@@ -3220,27 +3529,34 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f ...@@ -3220,27 +3529,34 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
* 2) Sentinel is connected. * 2) Sentinel is connected.
* 3) We did not received the info within SENTINEL_ASK_PERIOD ms. */ * 3) We did not received the info within SENTINEL_ASK_PERIOD ms. */
if ((master->flags & SRI_S_DOWN) == 0) continue; if ((master->flags & SRI_S_DOWN) == 0) continue;
if (ri->flags & SRI_DISCONNECTED) continue; if (ri->link->disconnected) continue;
if (!(flags & SENTINEL_ASK_FORCED) && if (!(flags & SENTINEL_ASK_FORCED) &&
mstime() - ri->last_master_down_reply_time < SENTINEL_ASK_PERIOD) mstime() - ri->last_master_down_reply_time < SENTINEL_ASK_PERIOD)
continue; continue;
/* Ask */ /* Ask */
ll2string(port,sizeof(port),master->addr->port); ll2string(port,sizeof(port),master->addr->port);
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelReceiveIsMasterDownReply, NULL, sentinelReceiveIsMasterDownReply, ri,
"SENTINEL is-master-down-by-addr %s %s %llu %s", "SENTINEL is-master-down-by-addr %s %s %llu %s",
master->addr->ip, port, master->addr->ip, port,
sentinel.current_epoch, sentinel.current_epoch,
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ? (master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
server.runid : "*"); sentinel.myid : "*");
if (retval == REDIS_OK) ri->pending_commands++; if (retval == REDIS_OK) ri->link->pending_commands++;
} }
dictReleaseIterator(di); dictReleaseIterator(di);
} }
/* =============================== FAILOVER ================================= */ /* =============================== FAILOVER ================================= */
/* Crash because of user request via SENTINEL simulate-failure command. */
void sentinelSimFailureCrash(void) {
redisLog(REDIS_WARNING,
"Sentinel CRASH because of SENTINEL simulate-failure");
exit(99);
}
/* Vote for the sentinel with 'req_runid' or return the old vote if already /* Vote for the sentinel with 'req_runid' or return the old vote if already
* voted for the specifed 'req_epoch' or one greater. * voted for the specifed 'req_epoch' or one greater.
* *
...@@ -3265,7 +3581,7 @@ char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char ...@@ -3265,7 +3581,7 @@ char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char
/* If we did not voted for ourselves, set the master failover start /* If we did not voted for ourselves, set the master failover start
* time to now, in order to force a delay before we can start a * time to now, in order to force a delay before we can start a
* failover for the same master. */ * failover for the same master. */
if (strcasecmp(master->leader,server.runid)) if (strcasecmp(master->leader,sentinel.myid))
master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC; master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
} }
...@@ -3346,7 +3662,7 @@ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) { ...@@ -3346,7 +3662,7 @@ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
if (winner) if (winner)
myvote = sentinelVoteLeader(master,epoch,winner,&leader_epoch); myvote = sentinelVoteLeader(master,epoch,winner,&leader_epoch);
else else
myvote = sentinelVoteLeader(master,epoch,server.runid,&leader_epoch); myvote = sentinelVoteLeader(master,epoch,sentinel.myid,&leader_epoch);
if (myvote && leader_epoch == epoch) { if (myvote && leader_epoch == epoch) {
uint64_t votes = sentinelLeaderIncr(counters,myvote); uint64_t votes = sentinelLeaderIncr(counters,myvote);
...@@ -3400,35 +3716,35 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) { ...@@ -3400,35 +3716,35 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
* *
* Note that we don't check the replies returned by commands, since we * Note that we don't check the replies returned by commands, since we
* will observe instead the effects in the next INFO output. */ * will observe instead the effects in the next INFO output. */
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelDiscardReplyCallback, NULL, "MULTI"); sentinelDiscardReplyCallback, ri, "MULTI");
if (retval == REDIS_ERR) return retval; if (retval == REDIS_ERR) return retval;
ri->pending_commands++; ri->link->pending_commands++;
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelDiscardReplyCallback, NULL, "SLAVEOF %s %s", host, portstr); sentinelDiscardReplyCallback, ri, "SLAVEOF %s %s", host, portstr);
if (retval == REDIS_ERR) return retval; if (retval == REDIS_ERR) return retval;
ri->pending_commands++; ri->link->pending_commands++;
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelDiscardReplyCallback, NULL, "CONFIG REWRITE"); sentinelDiscardReplyCallback, ri, "CONFIG REWRITE");
if (retval == REDIS_ERR) return retval; if (retval == REDIS_ERR) return retval;
ri->pending_commands++; ri->link->pending_commands++;
/* CLIENT KILL TYPE <type> is only supported starting from Redis 2.8.12, /* CLIENT KILL TYPE <type> is only supported starting from Redis 2.8.12,
* however sending it to an instance not understanding this command is not * however sending it to an instance not understanding this command is not
* an issue because CLIENT is variadic command, so Redis will not * an issue because CLIENT is variadic command, so Redis will not
* recognized as a syntax error, and the transaction will not fail (but * recognized as a syntax error, and the transaction will not fail (but
* only the unsupported command will fail). */ * only the unsupported command will fail). */
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelDiscardReplyCallback, NULL, "CLIENT KILL TYPE normal"); sentinelDiscardReplyCallback, ri, "CLIENT KILL TYPE normal");
if (retval == REDIS_ERR) return retval; if (retval == REDIS_ERR) return retval;
ri->pending_commands++; ri->link->pending_commands++;
retval = redisAsyncCommand(ri->cc, retval = redisAsyncCommand(ri->link->cc,
sentinelDiscardReplyCallback, NULL, "EXEC"); sentinelDiscardReplyCallback, ri, "EXEC");
if (retval == REDIS_ERR) return retval; if (retval == REDIS_ERR) return retval;
ri->pending_commands++; ri->link->pending_commands++;
return REDIS_OK; return REDIS_OK;
} }
...@@ -3566,8 +3882,9 @@ sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master) { ...@@ -3566,8 +3882,9 @@ sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master) {
sentinelRedisInstance *slave = dictGetVal(de); sentinelRedisInstance *slave = dictGetVal(de);
mstime_t info_validity_time; mstime_t info_validity_time;
if (slave->flags & (SRI_S_DOWN|SRI_O_DOWN|SRI_DISCONNECTED)) continue; if (slave->flags & (SRI_S_DOWN|SRI_O_DOWN)) continue;
if (mstime() - slave->last_avail_time > SENTINEL_PING_PERIOD*5) continue; if (slave->link->disconnected) continue;
if (mstime() - slave->link->last_avail_time > SENTINEL_PING_PERIOD*5) continue;
if (slave->slave_priority == 0) continue; if (slave->slave_priority == 0) continue;
/* If the master is in SDOWN state we get INFO for slaves every second. /* If the master is in SDOWN state we get INFO for slaves every second.
...@@ -3598,7 +3915,7 @@ void sentinelFailoverWaitStart(sentinelRedisInstance *ri) { ...@@ -3598,7 +3915,7 @@ void sentinelFailoverWaitStart(sentinelRedisInstance *ri) {
/* Check if we are the leader for the failover epoch. */ /* Check if we are the leader for the failover epoch. */
leader = sentinelGetLeader(ri, ri->failover_epoch); leader = sentinelGetLeader(ri, ri->failover_epoch);
isleader = leader && strcasecmp(leader,server.runid) == 0; isleader = leader && strcasecmp(leader,sentinel.myid) == 0;
sdsfree(leader); sdsfree(leader);
/* If I'm not the leader, and it is not a forced failover via /* If I'm not the leader, and it is not a forced failover via
...@@ -3618,6 +3935,8 @@ void sentinelFailoverWaitStart(sentinelRedisInstance *ri) { ...@@ -3618,6 +3935,8 @@ void sentinelFailoverWaitStart(sentinelRedisInstance *ri) {
return; return;
} }
sentinelEvent(REDIS_WARNING,"+elected-leader",ri,"%@"); sentinelEvent(REDIS_WARNING,"+elected-leader",ri,"%@");
if (sentinel.simfailure_flags & SENTINEL_SIMFAILURE_CRASH_AFTER_ELECTION)
sentinelSimFailureCrash();
ri->failover_state = SENTINEL_FAILOVER_STATE_SELECT_SLAVE; ri->failover_state = SENTINEL_FAILOVER_STATE_SELECT_SLAVE;
ri->failover_state_change_time = mstime(); ri->failover_state_change_time = mstime();
sentinelEvent(REDIS_WARNING,"+failover-state-select-slave",ri,"%@"); sentinelEvent(REDIS_WARNING,"+failover-state-select-slave",ri,"%@");
...@@ -3648,7 +3967,7 @@ void sentinelFailoverSendSlaveOfNoOne(sentinelRedisInstance *ri) { ...@@ -3648,7 +3967,7 @@ void sentinelFailoverSendSlaveOfNoOne(sentinelRedisInstance *ri) {
/* We can't send the command to the promoted slave if it is now /* We can't send the command to the promoted slave if it is now
* disconnected. Retry again and again with this state until the timeout * disconnected. Retry again and again with this state until the timeout
* is reached, then abort the failover. */ * is reached, then abort the failover. */
if (ri->promoted_slave->flags & SRI_DISCONNECTED) { if (ri->link->disconnected) {
if (mstime() - ri->failover_state_change_time > ri->failover_timeout) { if (mstime() - ri->failover_state_change_time > ri->failover_timeout) {
sentinelEvent(REDIS_WARNING,"-failover-abort-slave-timeout",ri,"%@"); sentinelEvent(REDIS_WARNING,"-failover-abort-slave-timeout",ri,"%@");
sentinelAbortFailover(ri); sentinelAbortFailover(ri);
...@@ -3727,8 +4046,8 @@ void sentinelFailoverDetectEnd(sentinelRedisInstance *master) { ...@@ -3727,8 +4046,8 @@ void sentinelFailoverDetectEnd(sentinelRedisInstance *master) {
sentinelRedisInstance *slave = dictGetVal(de); sentinelRedisInstance *slave = dictGetVal(de);
int retval; int retval;
if (slave->flags & if (slave->flags & (SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
(SRI_RECONF_DONE|SRI_RECONF_SENT|SRI_DISCONNECTED)) continue; if (slave->link->disconnected) continue;
retval = sentinelSendSlaveOf(slave, retval = sentinelSendSlaveOf(slave,
master->promoted_slave->addr->ip, master->promoted_slave->addr->ip,
...@@ -3783,8 +4102,8 @@ void sentinelFailoverReconfNextSlave(sentinelRedisInstance *master) { ...@@ -3783,8 +4102,8 @@ void sentinelFailoverReconfNextSlave(sentinelRedisInstance *master) {
/* Nothing to do for instances that are disconnected or already /* Nothing to do for instances that are disconnected or already
* in RECONF_SENT state. */ * in RECONF_SENT state. */
if (slave->flags & (SRI_DISCONNECTED|SRI_RECONF_SENT|SRI_RECONF_INPROG)) if (slave->flags & (SRI_RECONF_SENT|SRI_RECONF_INPROG)) continue;
continue; if (slave->link->disconnected) continue;
/* Send SLAVEOF <new master>. */ /* Send SLAVEOF <new master>. */
retval = sentinelSendSlaveOf(slave, retval = sentinelSendSlaveOf(slave,
......
...@@ -23,7 +23,7 @@ A million repetitions of "a" ...@@ -23,7 +23,7 @@ A million repetitions of "a"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/types.h> /* for u_int*_t */ #include <stdint.h>
#include "solarisfixes.h" #include "solarisfixes.h"
#include "sha1.h" #include "sha1.h"
#include "config.h" #include "config.h"
...@@ -53,12 +53,12 @@ A million repetitions of "a" ...@@ -53,12 +53,12 @@ A million repetitions of "a"
/* Hash a single 512-bit block. This is the core of the algorithm. */ /* Hash a single 512-bit block. This is the core of the algorithm. */
void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]) void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
{ {
u_int32_t a, b, c, d, e; uint32_t a, b, c, d, e;
typedef union { typedef union {
unsigned char c[64]; unsigned char c[64];
u_int32_t l[16]; uint32_t l[16];
} CHAR64LONG16; } CHAR64LONG16;
#ifdef SHA1HANDSOFF #ifdef SHA1HANDSOFF
CHAR64LONG16 block[1]; /* use array to appear as a pointer */ CHAR64LONG16 block[1]; /* use array to appear as a pointer */
...@@ -128,9 +128,9 @@ void SHA1Init(SHA1_CTX* context) ...@@ -128,9 +128,9 @@ void SHA1Init(SHA1_CTX* context)
/* Run your data through this. */ /* Run your data through this. */
void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len) void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
{ {
u_int32_t i, j; uint32_t i, j;
j = context->count[0]; j = context->count[0];
if ((context->count[0] += len << 3) < j) if ((context->count[0] += len << 3) < j)
...@@ -168,7 +168,7 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context) ...@@ -168,7 +168,7 @@ void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
for (i = 0; i < 2; i++) for (i = 0; i < 2; i++)
{ {
u_int32_t t = context->count[i]; uint32_t t = context->count[i];
int j; int j;
for (j = 0; j < 4; t >>= 8, j++) for (j = 0; j < 4; t >>= 8, j++)
......
...@@ -8,14 +8,14 @@ By Steve Reid <steve@edmweb.com> ...@@ -8,14 +8,14 @@ By Steve Reid <steve@edmweb.com>
*/ */
typedef struct { typedef struct {
u_int32_t state[5]; uint32_t state[5];
u_int32_t count[2]; uint32_t count[2];
unsigned char buffer[64]; unsigned char buffer[64];
} SHA1_CTX; } SHA1_CTX;
void SHA1Transform(u_int32_t state[5], const unsigned char buffer[64]); void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
void SHA1Init(SHA1_CTX* context); void SHA1Init(SHA1_CTX* context);
void SHA1Update(SHA1_CTX* context, const unsigned char* data, u_int32_t len); void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
void SHA1Final(unsigned char digest[20], SHA1_CTX* context); void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
#ifdef REDIS_TEST #ifdef REDIS_TEST
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment