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] }