diff --git a/00-RELEASENOTES b/00-RELEASENOTES
index 26d8cdf9c6405a8584e6bfaa2a0d65bdaf33c9d5..a41effad4793db452f0a8c3af0b8b9d9b58c3b36 100644
--- a/00-RELEASENOTES
+++ b/00-RELEASENOTES
@@ -14,6 +14,30 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
--------------------------------------------------------------------------------
+--[ Redis 2.8.15 ] Release date: 12 Sep 2014
+
+# UPGRADE URGENCY: LOW for Redis, HIGH for Sentinel.
+
+* [FIX] Sentinel critical bug fixed: the absolute majority was computed in a
+ wrong way because of a programming error. Now the implementation does
+ what the specification says and the majority to authorize a failover
+ (that should not be confused with the ODOWN quorum) is the majority of
+ *all* the Sentinels ever seen for a given master, regardless of their
+ current state.
+* [FIX] GETRANGE test no longer fails for 32 bit builds (Matt Stancliff).
+* [FIX] Limit SCAN latency when the hash table is in an odd state (very few
+ populted buckets because rehashing is in progress). (Xiaost and
+ Salvatore Sanfilippo)
+
+* [NEW] Redis is now able to load truncated AOF files without requiring a
+ redis-check-aof utility run. The default now is to load truncated
+ (but apparently not corrupted) AOFs, you can change this in redis.conf.
+ (Salvatore Sanfilippo).
+* [NEW] Sentinel: ability to announce itself with an arbitrary IP/port to work
+ in the context of natted networks. However this is probably still
+ not enough since there is no equivalent mechanism for slaves listed
+ in the master INFO output. (Dara Kong and Salvatore Sanfilippo)
+
--[ Redis 2.8.14 ] Release date: 1 Sep 2014
# UPGRADE URGENCY: HIGH for Lua scripting users, the server could crash because
diff --git a/bin/release/redis-2.8.14.zip b/bin/release/redis-2.8.14.zip
deleted file mode 100644
index 972050b69ef679925e5721c6ef04f465fbd60d7f..0000000000000000000000000000000000000000
Binary files a/bin/release/redis-2.8.14.zip and /dev/null differ
diff --git a/bin/release/redis-2.8.15.zip b/bin/release/redis-2.8.15.zip
new file mode 100644
index 0000000000000000000000000000000000000000..e6794e16bf5b235c6d3c1585f7b3382d2d4373c1
Binary files /dev/null and b/bin/release/redis-2.8.15.zip differ
diff --git a/msvs/compressOutputToReleaseFolder.ps1 b/msvs/compressOutputToReleaseFolder.ps1
index 66ba25e4c2d071a86883b116e499013dd3d4136e..fd55f6b6e3ce196c4e3015448285c9441f41a211 100644
--- a/msvs/compressOutputToReleaseFolder.ps1
+++ b/msvs/compressOutputToReleaseFolder.ps1
@@ -7,7 +7,7 @@ $CurDir = [System.IO.Directory]::GetCurrentDirectory()
$PubDir = [System.IO.Path]::Combine($CurDir, "x64\Release\pub" )
$SourceDir = [System.IO.Path]::Combine($CurDir, "x64\Release" )
$DocumentationDir = [System.IO.Path]::Combine($CurDir, "setups\documentation" )
-$Destination = [System.IO.Path]::Combine($CurDir, "..\bin\Release\redis-2.8.14.zip" )
+$Destination = [System.IO.Path]::Combine($CurDir, "..\bin\Release\redis-2.8.15.zip" )
[System.IO.Directory]::CreateDirectory($PubDir) | Out-Null
diff --git a/msvs/setups/PullBinaries.ps1 b/msvs/setups/PullBinaries.ps1
index 40e9963920cfc074c907cf0ea70b5c588a13972c..54b9f1efb41778024b2e604531688a0fc25c10ac 100644
--- a/msvs/setups/PullBinaries.ps1
+++ b/msvs/setups/PullBinaries.ps1
@@ -2,7 +2,7 @@
$CurDir = split-path -parent $MyInvocation.MyCommand.Definition
-$SourceZip = [System.IO.Path]::Combine($CurDir, "..\..\bin\Release\redis-2.8.12.zip" )
+$SourceZip = [System.IO.Path]::Combine($CurDir, "..\..\bin\Release\redis-2.8.15.zip" )
$Destination = [System.IO.Path]::Combine($CurDir, "signed_binaries" )
[System.IO.Directory]::CreateDirectory($Destination) | Out-Null
diff --git a/msvs/setups/chocolatey/Redis.nuspec b/msvs/setups/chocolatey/Redis.nuspec
index b89ca0a5df8586b876adc8062c3dfe44a6aea555..961ffaa1e68d593877ac0d40a39e82c56fc73d3f 100644
--- a/msvs/setups/chocolatey/Redis.nuspec
+++ b/msvs/setups/chocolatey/Redis.nuspec
@@ -3,7 +3,7 @@
redis-64
redis-64
- 2.8.14
+ 2.8.15
Jonathan Pickett
Microsoft Open Technologies, Inc.
Redis is a very popular open-source, networked, in-memory, key-value data store known for high performance, flexibility, a rich set of data structures, and a simple straightforward API.
@@ -14,7 +14,7 @@
https://github.com/MSOpenTech/redis/blob/2.8/license.txt
false
http://redis.io/images/redis.png
- Includes the changes from Redis 2.8.12 -> 2.8.14. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality.
+ Includes the changes from Redis 2.8.12 -> 2.8.15. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality.
diff --git a/msvs/setups/documentation/Redis Release Notes.docx b/msvs/setups/documentation/Redis Release Notes.docx
index 63b990ea6502e89b2dd3a186f2bc91324d5ec6ca..7131db225729b56c4713decf1b23ffa857f27f13 100644
Binary files a/msvs/setups/documentation/Redis Release Notes.docx and b/msvs/setups/documentation/Redis Release Notes.docx differ
diff --git a/msvs/setups/nuget/Redis.nuspec b/msvs/setups/nuget/Redis.nuspec
index 854213ef79f89059cf20a6f3ef78c8ce65418871..961ffaa1e68d593877ac0d40a39e82c56fc73d3f 100644
--- a/msvs/setups/nuget/Redis.nuspec
+++ b/msvs/setups/nuget/Redis.nuspec
@@ -3,7 +3,7 @@
redis-64
redis-64
- 2.8.14
+ 2.8.15
Jonathan Pickett
Microsoft Open Technologies, Inc.
Redis is a very popular open-source, networked, in-memory, key-value data store known for high performance, flexibility, a rich set of data structures, and a simple straightforward API.
@@ -14,7 +14,7 @@
https://github.com/MSOpenTech/redis/blob/2.8/license.txt
false
http://redis.io/images/redis.png
- Includes the changes from Redis 2.8.9 -> 2.8.14. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality.
+ Includes the changes from Redis 2.8.12 -> 2.8.15. Please see the release notes for the UNIX 2.8 branch to understand how this impacts Redis functionality.
diff --git a/redis.conf b/redis.conf
index 8b64e3665b4b57b09a5f02b783ae2802f5847303..32e38de445d55a4509333561f2937949b1d341e9 100644
--- a/redis.conf
+++ b/redis.conf
@@ -529,6 +529,30 @@ no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
+# An AOF file may be found to be truncated at the end during the Redis
+# startup process, when the AOF data gets loaded back into memory.
+# This may happen when the system where Redis is running
+# crashes, especially when an ext4 filesystem is mounted without the
+# data=ordered option (however this can't happen when Redis itself
+# crashes or aborts but the operating system still works correctly).
+#
+# Redis can either exit with an error when this happens, or load as much
+# data as possible (the default now) and start if the AOF file is found
+# to be truncated at the end. The following option controls this behavior.
+#
+# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
+# the Redis server starts emitting a log to inform the user of the event.
+# Otherwise if the option is set to no, the server aborts with an error
+# and refuses to start. When the option is set to no, the user requires
+# to fix the AOF file using the "redis-check-aof" utility before to restart
+# the server.
+#
+# Note that if the AOF file will be found to be corrupted in the middle
+# the server will still exit with an error. This option only applies when
+# Redis will try to read more data from the AOF file but not enough bytes
+# will be found.
+aof-load-truncated yes
+
################################ LUA SCRIPTING ###############################
# Max execution time of a Lua script in milliseconds.
diff --git a/sentinel.conf b/sentinel.conf
index 2384e9bc72a202ecf95e1fe39ce664a20218eb4e..4b3b79242053edf6663a0d3453fc04a0c51ff277 100644
--- a/sentinel.conf
+++ b/sentinel.conf
@@ -4,6 +4,28 @@
# The port that this sentinel instance will run on
port 26379
+# sentinel announce-ip
+# sentinel announce-port
+#
+# The above two configuration directives are useful in environments where,
+# because of NAT, Sentinel is reachable from outside via a non-local address.
+#
+# When announce-ip is provided, the Sentinel will claim the specified IP address
+# in HELLO messages used to gossip its presence, instead of auto-detecting the
+# local address as it usually does.
+#
+# Similarly when announce-port is provided and is valid and non-zero, Sentinel
+# will announce the specified TCP port.
+#
+# The two options don't need to be used together, if only announce-ip is
+# provided, the Sentinel will announce the specified IP and the server port
+# as specified by the "port" option. If only announce-port is provided, the
+# Sentinel will announce the auto-detected local IP and the specified port.
+#
+# Example:
+#
+# sentinel announce-ip 1.2.3.4
+
# dir
# Every long running process should have a well-defined working directory.
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
diff --git a/src/Win32_Interop/Win32_CommandLine.cpp b/src/Win32_Interop/Win32_CommandLine.cpp
index 68a9ff786c50b3bf1f36779eb14f53bd6b20a500..727795dcbd9c171679440794eb095c0bf45c44be 100644
--- a/src/Win32_Interop/Win32_CommandLine.cpp
+++ b/src/Win32_Interop/Win32_CommandLine.cpp
@@ -425,6 +425,7 @@ static RedisParamterMapper g_redisArgMap =
{ "client-output-buffer-limit", &fp4 }, // client-output-buffer-limit [class] [hard limit] [soft limit] [soft seconds]
{ "hz", &fp1 }, // hz [number]
{ "aof-rewrite-incremental-fsync", &fp1 }, // aof-rewrite-incremental-fsync [yes/no]
+ { "aof-load-truncated", &fp1 }, // aof-load-truncated [yes/no]
{ "latency-monitor-threshold", &fp1 }, // latency-monitor-threshold [number]
{ cInclude, &fp1 }, // include [path]
diff --git a/src/aof.c b/src/aof.c
index a81641fbd685d97e5e89408c2b67f64143feb08f..e4fe81b1289003b93b1ed7852241031c1b45cd9c 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -550,6 +550,14 @@ struct redisClient *createFakeClient(void) {
return c;
}
+void freeFakeClientArgv(struct redisClient *c) {
+ int j;
+
+ for (j = 0; j < c->argc; j++)
+ decrRefCount(c->argv[j]);
+ zfree(c->argv);
+}
+
void freeFakeClient(struct redisClient *c) {
sdsfree(c->querybuf);
listRelease(c->reply);
@@ -615,18 +623,35 @@ int loadAppendOnlyFile(char *filename) {
goto readerr;
}
if (buf[0] != '*') goto fmterr;
+ if (buf[1] == '\0') goto readerr;
argc = atoi(buf+1);
if (argc < 1) goto fmterr;
argv = zmalloc(sizeof(robj*)*argc);
+ fakeClient->argc = argc;
+ fakeClient->argv = argv;
+
for (j = 0; j < argc; j++) {
- if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;
+ if (fgets(buf,sizeof(buf),fp) == NULL) {
+ fakeClient->argc = j; /* Free up to j-1. */
+ freeFakeClientArgv(fakeClient);
+ goto readerr;
+ }
if (buf[0] != '$') goto fmterr;
len = strtol(buf+1,NULL,10);
argsds = sdsnewlen(NULL,len);
- if (len && fread(argsds,len,1,fp) == 0) goto fmterr;
+ if (len && fread(argsds,len,1,fp) == 0) {
+ sdsfree(argsds);
+ fakeClient->argc = j; /* Free up to j-1. */
+ freeFakeClientArgv(fakeClient);
+ goto readerr;
+ }
argv[j] = createObject(REDIS_STRING,argsds);
- if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
+ if (fread(buf,2,1,fp) == 0) {
+ fakeClient->argc = j+1; /* Free up to j. */
+ freeFakeClientArgv(fakeClient);
+ goto readerr; /* discard CRLF */
+ }
}
/* Command lookup */
@@ -635,9 +660,8 @@ int loadAppendOnlyFile(char *filename) {
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
exit(1);
}
+
/* Run the command in the context of a fake client */
- fakeClient->argc = argc;
- fakeClient->argv = argv;
cmd->proc(fakeClient);
/* The fake client should not have a reply */
@@ -647,15 +671,14 @@ int loadAppendOnlyFile(char *filename) {
/* Clean up. Command code may have changed argv/argc so we use the
* argv/argc of the client instead of the local variables. */
- for (j = 0; j < fakeClient->argc; j++)
- decrRefCount(fakeClient->argv[j]);
- zfree(fakeClient->argv);
+ freeFakeClientArgv(fakeClient);
}
/* This point can only be reached when EOF is reached without errors.
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
- if (fakeClient->flags & REDIS_MULTI) goto readerr;
+ if (fakeClient->flags & REDIS_MULTI) goto uxeof;
+loaded_ok: /* DB loaded, cleanup and return REDIS_OK to the caller. */
fclose(fp);
freeFakeClient(fakeClient);
server.aof_state = old_aof_state;
@@ -664,14 +687,23 @@ int loadAppendOnlyFile(char *filename) {
server.aof_rewrite_base_size = server.aof_current_size;
return REDIS_OK;
-readerr:
- if (feof(fp)) {
- redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file");
- } else {
+readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
+ if (!feof(fp)) {
redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
+ exit(1);
}
+
+uxeof: /* Unexpected AOF end of file. */
+ if (server.aof_load_truncated) {
+ redisLog(REDIS_WARNING,"!!! Warning: short read while loading the AOF file !!!");
+ redisLog(REDIS_WARNING,
+ "AOF loaded anyway because aof-load-truncated is enabled");
+ goto loaded_ok;
+ }
+ redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix . 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
exit(1);
-fmterr:
+
+fmterr: /* Format error. */
redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix ");
exit(1);
}
@@ -1158,7 +1190,7 @@ void aofRemoveTempFile(pid_t childpid) {
unlink(tmpfile);
}
-/* Update the server.aof_current_size filed explicitly using stat(2)
+/* Update the server.aof_current_size field explicitly using stat(2)
* to check the size of the file. This is useful after a rewrite or after
* a restart, normally the size is updated just adding the write length
* to the current length, that is much faster. */
diff --git a/src/config.c b/src/config.c
index 156d16090aa7b91d9062a33c8d5b8d668ddd8d41..046954540dfcfda79253427a89c70388db01e413 100644
--- a/src/config.c
+++ b/src/config.c
@@ -395,7 +395,12 @@ void loadServerConfigFromString(char *config) {
} else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
argc == 2)
{
- if ((server.aof_rewrite_incremental_fsync = yesnotoi(argv[1])) == -1) {
+ if ((server.aof_rewrite_incremental_fsync =
+ yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) {
+ if ((server.aof_load_truncated = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
@@ -750,6 +755,11 @@ void configSetCommand(redisClient *c) {
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;
sds *v = sdssplitlen(o->ptr,(int)sdslen(o->ptr)," ",1,&vlen);
@@ -1070,6 +1080,8 @@ void configGetCommand(redisClient *c) {
server.repl_disable_tcp_nodelay);
config_get_bool_field("aof-rewrite-incremental-fsync",
server.aof_rewrite_incremental_fsync);
+ config_get_bool_field("aof-load-truncated",
+ server.aof_load_truncated);
/* Everything we can't handle with macros follows. */
@@ -1440,7 +1452,7 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, c
return;
}
- /* Compare the strings as sds strings to have a binary safe comparison. */
+ /* Set force to zero if the value is set to its default. */
if (defvalue && strcmp(value,defvalue) == 0) force = 0;
line = sdsnew(option);
@@ -1840,6 +1852,7 @@ int rewriteConfig(char *path) {
rewriteConfigClientoutputbufferlimitOption(state);
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-load-truncated",server.aof_load_truncated,REDIS_DEFAULT_AOF_LOAD_TRUNCATED);
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
/* Step 3: remove all the orphaned lines in the old file, that is, lines
diff --git a/src/db.c b/src/db.c
index 0e538f6be5ace02b18021b7284d70a548169239d..8e6163d4d57540163bd893b65b377cbd4365833c 100644
--- a/src/db.c
+++ b/src/db.c
@@ -487,6 +487,11 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
if (ht) {
void *privdata[2];
+ /* We set the max number of iterations to ten times the specified
+ * COUNT, so if the hash table is in a pathological state (very
+ * sparsely populated) we avoid to block too much time at the cost
+ * of returning no or very few elements. */
+ long maxiterations = count*10;
/* We pass two pointers to the callback: the list to which it will
* add new elements, and the object containing the dictionary so that
@@ -495,7 +500,9 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
privdata[1] = o;
do {
cursor = dictScan(ht, cursor, scanCallback, privdata);
- } while (cursor && listLength(keys) < (unsigned long)count);
+ } while (cursor &&
+ maxiterations-- &&
+ listLength(keys) < (unsigned long)count);
} else if (o->type == REDIS_SET) {
int pos = 0;
int64_t ll;
diff --git a/src/redis.c b/src/redis.c
index 6ce61cb99967b9fd39a9114792a234ca6c3accf9..830fead90044950e92e4ba77219a5abf734a8898 100644
--- a/src/redis.c
+++ b/src/redis.c
@@ -1399,6 +1399,7 @@ void initServerConfig(void) {
server.aof_selected_db = -1; /* Make sure the first time will not match */
server.aof_flush_postponed_start = 0;
server.aof_rewrite_incremental_fsync = REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
+ server.aof_load_truncated = REDIS_DEFAULT_AOF_LOAD_TRUNCATED;
server.pidfile = zstrdup(REDIS_DEFAULT_PID_FILE);
server.rdb_filename = zstrdup(REDIS_DEFAULT_RDB_FILENAME);
server.aof_filename = zstrdup(REDIS_DEFAULT_AOF_FILENAME);
diff --git a/src/redis.h b/src/redis.h
index c8d9f726ae05842d6be7acf70609710365d246df..34efa304c9fc2d6cb2f4af768a04f855b0f4b8fa 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -132,6 +132,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 3
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
+#define REDIS_DEFAULT_AOF_LOAD_TRUNCATED 1
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0
@@ -722,6 +723,7 @@ struct redisServer {
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
+ int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */
/* RDB persistence */
long long dirty; /* Changes to DB from the last save */
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
diff --git a/src/scripting.c b/src/scripting.c
index a0a6587fc67af130530302216c3b46cc1eae2bb6..84aa1a708d180a5b6e6ee463d83e3b3389c63c10 100644
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -222,10 +222,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
}
/* Build the arguments vector */
- if (!argv) {
- argv = zmalloc(sizeof(robj*)*argc);
- argv_size = argc;
- } else if (argv_size < argc) {
+ if (argv_size < argc) {
argv = zrealloc(argv,sizeof(robj*)*argc);
argv_size = argc;
}
@@ -402,6 +399,7 @@ cleanup:
if (c->argv != argv) {
zfree(c->argv);
argv = NULL;
+ argv_size = 0;
}
if (raise_error) {
diff --git a/src/sentinel.c b/src/sentinel.c
index e3c242d08509377972e149fdccd68f698124563c..3c43ac691d23c70d6f1321580be34bba1376c95c 100644
--- a/src/sentinel.c
+++ b/src/sentinel.c
@@ -213,6 +213,10 @@ struct sentinelState {
mstime_t tilt_start_time; /* When TITL started. */
mstime_t previous_time; /* Last time we ran the time handler. */
list *scripts_queue; /* Queue of user scripts to execute. */
+ char *announce_ip; /* IP addr that is gossiped to other sentinels if
+ not NULL. */
+ int announce_port; /* Port that is gossiped to other sentinels if
+ non zero. */
} sentinel;
/* A script execution job. */
@@ -474,6 +478,8 @@ void initSentinel(void) {
sentinel.previous_time = mstime();
sentinel.running_scripts = 0;
sentinel.scripts_queue = listCreate();
+ sentinel.announce_ip = NULL;
+ sentinel.announce_port = 0;
}
/* This function gets called when the server is in Sentinel mode, started,
@@ -1570,6 +1576,13 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
return "Wrong hostname or port for sentinel.";
}
if (argc == 5) si->runid = sdsnew(argv[4]);
+ } else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) {
+ /* announce-ip */
+ if (strlen(argv[1]))
+ sentinel.announce_ip = sdsnew(argv[1]);
+ } else if (!strcasecmp(argv[0],"announce-port") && argc == 2) {
+ /* announce-port */
+ sentinel.announce_port = atoi(argv[1]);
} else {
return "Unrecognized sentinel configuration statement.";
}
@@ -1701,6 +1714,20 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
"sentinel current-epoch %llu", (unsigned long long) sentinel.current_epoch);
rewriteConfigRewriteLine(state,"sentinel",line,1);
+ /* sentinel announce-ip. */
+ if (sentinel.announce_ip) {
+ line = sdsnew("sentinel announce-ip ");
+ line = sdscatrepr(line, sentinel.announce_ip, sdslen(sentinel.announce_ip));
+ rewriteConfigRewriteLine(state,"sentinel",line,1);
+ }
+
+ /* sentinel announce-port. */
+ if (sentinel.announce_port) {
+ line = sdscatprintf(sdsempty(),"sentinel announce-port %d",
+ sentinel.announce_port);
+ rewriteConfigRewriteLine(state,"sentinel",line,1);
+ }
+
dictReleaseIterator(di);
}
@@ -2360,19 +2387,30 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
char ip[REDIS_IP_STR_LEN];
char payload[REDIS_IP_STR_LEN+1024];
int retval;
+ char *announce_ip;
+ int announce_port;
sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master;
sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master);
if (ri->flags & SRI_DISCONNECTED) return REDIS_ERR;
- /* Try to obtain our own IP address. */
- if (anetSockName(ri->cc->c.fd,ip,sizeof(ip),NULL) == -1) return REDIS_ERR;
+ /* Use the specified announce address if specified, otherwise try to
+ * obtain our own IP address. */
+ if (sentinel.announce_ip) {
+ announce_ip = sentinel.announce_ip;
+ } else {
+ if (anetSockName(ri->cc->c.fd,ip,sizeof(ip),NULL) == -1)
+ return REDIS_ERR;
+ announce_ip = ip;
+ }
+ announce_port = sentinel.announce_port ?
+ sentinel.announce_port : server.port;
/* Format and send the Hello message. */
snprintf(payload,sizeof(payload),
"%s,%d,%s,%llu," /* Info about this sentinel. */
"%s,%s,%d,%llu", /* Info about current master. */
- ip, server.port, server.runid,
+ announce_ip, announce_port, server.runid,
(unsigned long long) sentinel.current_epoch,
/* --- */
master->name,master_addr->ip,master_addr->port,
@@ -3334,9 +3372,9 @@ int sentinelLeaderIncr(dict *counters, char *runid) {
/* Scan all the Sentinels attached to this master to check if there
* is a leader for the specified epoch.
*
- * To be a leader for a given epoch, we should have the majorify of
- * the Sentinels we know that reported the same instance as
- * leader for the same epoch. */
+ * To be a leader for a given epoch, we should have the majority of
+ * the Sentinels we know (ever seen since the last SENTINEL RESET) that
+ * reported the same instance as leader for the same epoch. */
char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
dict *counters;
dictIterator *di;
@@ -3350,13 +3388,14 @@ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
redisAssert(master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS));
counters = dictCreate(&leaderVotesDictType,NULL);
+ voters = dictSize(master->sentinels)+1; /* All the other sentinels and me. */
+
/* Count other sentinels votes */
di = dictGetIterator(master->sentinels);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
if (ri->leader != NULL && ri->leader_epoch == sentinel.current_epoch)
sentinelLeaderIncr(counters,ri->leader);
- voters++;
}
dictReleaseIterator(di);
@@ -3390,7 +3429,6 @@ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
winner = myvote;
}
}
- voters++; /* Anyway, count me as one of the voters. */
voters_quorum = voters/2+1;
if (winner && (max_votes < voters_quorum || max_votes < master->quorum))
diff --git a/src/t_string.c b/src/t_string.c
index 76b35bcd2745fd1bc5489c3de62105232f1b5742..f0669381505c383457df33ec62a088c723f41603 100644
--- a/src/t_string.c
+++ b/src/t_string.c
@@ -231,31 +231,14 @@ void setrangeCommand(redisClient *c) {
void getrangeCommand(redisClient *c) {
robj *o;
-#ifdef _WIN64
- int64_t start, end;
-#else
- long start, end;
-#endif
+ long long start, end;
char *str, llbuf[32];
size_t strlen;
-
-#ifdef _WIN64
- // In the VS compiler, longs are not upgraded to 64-bits wien compiled as 64-bits. With GCC this is not the case.
- // (https://software.intel.com/en-us/articles/size-of-long-integer-type-on-different-architecture-and-os).
- //
- // The immediate fix here is to use a 64bit type and call the conversion appropriate function. Longer term we need to
- // eliminate the use of 'long' throughout the code base.
- if (getLongLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK)
+ if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
return;
if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
return;
-#else
- if (getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK)
- return;
- if (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)
- return;
-#endif
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
@@ -268,15 +251,15 @@ void getrangeCommand(redisClient *c) {
}
/* Convert negative indexes */
- if (start < 0) start = (long)(strlen+start);
- if (end < 0) end = (long)(strlen+end);
+ if (start < 0) start = strlen+start;
+ if (end < 0) end = strlen+end;
if (start < 0) start = 0;
if (end < 0) end = 0;
- if ((size_t)end >= strlen) end = strlen-1;
+ if ((unsigned long long)end >= strlen) end = strlen-1;
/* Precondition: end >= 0 && end < strlen, so the only condition where
* nothing can be returned is: start > end. */
- if (start > end) {
+ if (start > end || strlen == 0) {
addReply(c,shared.emptybulk);
} else {
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
diff --git a/src/version.h b/src/version.h
index 059c06dafc679d6bc842832102a7a96b4c6c7cdc..5834efb1d466c170d77f2ebc30062658bdd2ff53 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1 +1 @@
-#define REDIS_VERSION "2.8.14"
+#define REDIS_VERSION "2.8.15"
diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl
index 4003cee11f7740eaacaa8e38274568e405fb99c6..462ec33e714113c46ae2b9cc11945182257a1b58 100644
--- a/tests/integration/aof.tcl
+++ b/tests/integration/aof.tcl
@@ -23,6 +23,57 @@ proc start_server_aof {overrides code} {
}
tags {"aof"} {
+ ## Server can start when aof-load-truncated is set to yes and AOF
+ ## is truncated, with an incomplete MULTI block.
+ create_aof {
+ append_to_aof [formatCommand set foo hello]
+ append_to_aof [formatCommand multi]
+ append_to_aof [formatCommand set bar world]
+ }
+
+ start_server_aof [list dir $server_path aof-load-truncated yes] {
+ test "Unfinished MULTI: Server should start if load-truncated is yes" {
+ assert_equal 1 [is_alive $srv]
+ }
+ }
+
+ ## Should also start with truncated AOF without incomplete MULTI block.
+ create_aof {
+ append_to_aof [formatCommand set foo hello]
+ append_to_aof [string range [formatCommand set bar world] 0 end-1]
+ }
+
+ start_server_aof [list dir $server_path aof-load-truncated yes] {
+ test "Short read: Server should start if load-truncated is yes" {
+ assert_equal 1 [is_alive $srv]
+ }
+ }
+
+ ## Test that the server exits when the AOF contains a format error
+ create_aof {
+ append_to_aof [formatCommand set foo hello]
+ append_to_aof "!!!"
+ append_to_aof [formatCommand set foo hello]
+ }
+
+ start_server_aof [list dir $server_path aof-load-truncated yes] {
+ test "Bad format: Server should have logged an error" {
+ set pattern "*Bad file format reading the append only file*"
+ set retry 10
+ while {$retry} {
+ set result [exec tail -n1 < [dict get $srv stdout]]
+ if {[string match $pattern $result]} {
+ break
+ }
+ incr retry -1
+ after 1000
+ }
+ if {$retry == 0} {
+ error "assertion:expected error not found on config file"
+ }
+ }
+ }
+
## Test the server doesn't start when the AOF contains an unfinished MULTI
create_aof {
append_to_aof [formatCommand set foo hello]
@@ -30,7 +81,7 @@ tags {"aof"} {
append_to_aof [formatCommand set bar world]
}
- start_server_aof [list dir $server_path] {
+ start_server_aof [list dir $server_path aof-load-truncated no] {
test "Unfinished MULTI: Server should have logged an error" {
set pattern "*Unexpected end of file reading the append only file*"
set retry 10
@@ -54,9 +105,9 @@ tags {"aof"} {
append_to_aof [string range [formatCommand set bar world] 0 end-1]
}
- start_server_aof [list dir $server_path] {
+ start_server_aof [list dir $server_path aof-load-truncated no] {
test "Short read: Server should have logged an error" {
- set pattern "*Bad file format reading the append only file*"
+ set pattern "*Unexpected end of file reading the append only file*"
set retry 10
while {$retry} {
set result [exec tail -n1 < [dict get $srv stdout]]
@@ -86,7 +137,7 @@ tags {"aof"} {
}
## Test that the server can be started using the truncated AOF
- start_server_aof [list dir $server_path] {
+ start_server_aof [list dir $server_path aof-load-truncated no] {
test "Fixed AOF: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
@@ -110,7 +161,7 @@ tags {"aof"} {
append_to_aof [formatCommand spop set]
}
- start_server_aof [list dir $server_path] {
+ start_server_aof [list dir $server_path aof-load-truncated no] {
test "AOF+SPOP: Server should have been started" {
assert_equal 1 [is_alive $srv]
}
@@ -133,7 +184,7 @@ tags {"aof"} {
append_to_aof [formatCommand rpush list bar]
}
- start_server_aof [list dir $server_path] {
+ start_server_aof [list dir $server_path aof-load-truncated no] {
test "AOF+EXPIRE: Server should have been started" {
assert_equal 1 [is_alive $srv]
}