diff -Nru redis-2.8.2/00-RELEASENOTES redis-2.8.4/00-RELEASENOTES --- redis-2.8.2/00-RELEASENOTES 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/00-RELEASENOTES 2014-01-13 16:09:58.000000000 +0000 @@ -14,6 +14,44 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP. -------------------------------------------------------------------------------- +--[ Redis 2.8.4 ] Release date: 13 Jan 2014 + +# UPGRADE URGENCY: MODERATE for Redis and Sentinel. + +* [FIX] Makefile compatibility with non common make variants improved. +* [FIX] SDIFF crash in very unlikely to trigger state fixed. +* [FIX] Config rewriting fixed: don't wipe options unknown to the rewrite + process. +* [FIX] Set TCP port to 0 works again to disable TCP networking. +* [FIX] Fixed replication with old Redis instances as masters by not + sending REPLCONF ACK to them. +* [FIX] Fix keyspace notifications rewrite and CONFIG GET output. +* [FIX] Fix RESTORE TTL handling in 32 bit systems (32 bit overflow). + +* [NEW] Sentinel now has a run time configuration API. +* [NEW] Log when we lost connection with master or slave. +* [NEW] When instance is turned from slave to master now inherits the + old master replication offset when possible. This improves the + Sentinel failover procedure. + +--[ Redis 2.8.3 ] Release date: 11 Dec 2013 + +# UPGRADE URGENCY: MODERATE for Redis, HIGH for Sentinel. + +* [FIX] Sentinel instance role sampling fixed, the system is now more + reliable during failover and when reconfiguring instances with + non matching configuration. +* [FIX] Inline requests are now handled even when terminated with just LF. +* [FIX] Replication timeout handling greatly improved, now the slave is able + to ping the master while removing the old data from memory, and while + loading the new RDB file. This avoid false timeouts sensed by + masters. +* [FIX] Fixed a replication bug involving 32 bit instances and big datasets + hard to compress that resulted into more than 2GB of RDB file sent. +* [FIX] Return error for inline requests with unbalanced quotes. +* [FIX] Publish the slave replication offset even when disconnected from the + master if there is still a cached master instance. + --[ Redis 2.8.2 ] Release date: 2 Dec 2013 # UPGRADE URGENCY: MODERATE for both Redis and Sentinel. diff -Nru redis-2.8.2/CONTRIBUTING redis-2.8.4/CONTRIBUTING --- redis-2.8.2/CONTRIBUTING 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/CONTRIBUTING 2014-01-13 16:09:58.000000000 +0000 @@ -22,7 +22,7 @@ 1. Drop a message to the Redis Google Group with a proposal of semantics/API. -2. If in steps 1 you get an acknowledge from the project leaders, use the +2. If in step 1 you get an acknowledge from the project leaders, use the following procedure to submit a patch: a. Fork Redis on github ( http://help.github.com/fork-a-repo/ ) diff -Nru redis-2.8.2/debian/changelog redis-2.8.4/debian/changelog --- redis-2.8.2/debian/changelog 2013-12-06 14:37:57.000000000 +0000 +++ redis-2.8.4/debian/changelog 2014-01-14 12:31:16.000000000 +0000 @@ -1,3 +1,18 @@ +redis (2:2.8.4-2) unstable; urgency=low + + * Symlink redis-sentinel to redis-server as it's the same binary. + * Install sentinel.conf. + + -- Chris Lamb Tue, 14 Jan 2014 12:31:14 +0000 + +redis (2:2.8.4-1) unstable; urgency=low + + * New upstream version. + * Sync debian/redis.conf. + * Also ship redis-sentinel (Closes: #735272) + + -- Chris Lamb Tue, 14 Jan 2014 10:42:09 +0000 + redis (2:2.8.2-1) unstable; urgency=low * New upstream version. diff -Nru redis-2.8.2/debian/redis-server.install redis-2.8.4/debian/redis-server.install --- redis-2.8.2/debian/redis-server.install 2013-12-06 14:37:57.000000000 +0000 +++ redis-2.8.4/debian/redis-server.install 2014-01-14 12:31:16.000000000 +0000 @@ -1,2 +1,3 @@ src/redis-server /usr/bin debian/redis.conf /etc/redis +sentinel.conf /etc/redis diff -Nru redis-2.8.2/debian/redis-server.links redis-2.8.4/debian/redis-server.links --- redis-2.8.2/debian/redis-server.links 1970-01-01 00:00:00.000000000 +0000 +++ redis-2.8.4/debian/redis-server.links 2014-01-14 12:31:16.000000000 +0000 @@ -0,0 +1 @@ +usr/bin/redis-server usr/bin/redis-sentinel diff -Nru redis-2.8.2/debian/redis.conf redis-2.8.4/debian/redis.conf --- redis-2.8.2/debian/redis.conf 2013-12-06 14:37:57.000000000 +0000 +++ redis-2.8.4/debian/redis.conf 2014-01-14 12:31:16.000000000 +0000 @@ -12,6 +12,26 @@ # # units are case insensitive so 1GB 1Gb 1gB are all the same. +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Notice option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# include /path/to/local.conf +# include /path/to/other.conf + +################################ GENERAL ##################################### + # By default Redis does not run as a daemon. Use 'yes' if you need it. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. daemonize yes @@ -68,7 +88,7 @@ # warning (only very important / critical messages are logged) loglevel notice -# Specify the log file name. Also the emptry string can be used to force +# Specify the log file name. Also the empty string can be used to force # Redis to log on the standard output. Note that if you use standard # output for logging but daemonize, logs will be sent to /dev/null logfile /var/log/redis/redis-server.log @@ -88,7 +108,7 @@ # dbid is a number between 0 and 'databases'-1 databases 16 -################################ SNAPSHOTTING ################################# +################################ SNAPSHOTTING ################################ # # Save the DB on disk: # @@ -116,16 +136,16 @@ # By default Redis will stop accepting writes if RDB snapshots are enabled # (at least one save point) and the latest background save failed. -# This will make the user aware (in an hard way) that data is not persisting +# This will make the user aware (in a hard way) that data is not persisting # on disk properly, otherwise chances are that no one will notice and some -# distater will happen. +# disaster will happen. # # If the background saving process will start working again Redis will # automatically allow writes again. # # However if you have setup your proper monitoring of the Redis server # and persistence, you may want to disable this feature so that Redis will -# continue to work as usually even if there are problems with disk, +# continue to work as usual even if there are problems with disk, # permissions, and so forth. stop-writes-on-bgsave-error yes @@ -197,7 +217,7 @@ # Note: read only slaves are not designed to be exposed to untrusted clients # on the internet. It's just a protection layer against misuse of the instance. # Still a read only slave exports by default all the administrative commands -# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve # security of read only slaves using 'rename-command' to shadow all the # administrative / dangerous commands. slave-read-only yes @@ -263,7 +283,7 @@ # # A slave with a low priority number is considered better for promotion, so # for instance if there are three slaves with priority 10, 100, 25 Sentinel will -# pick the one wtih priority 10, that is the lowest. +# pick the one with priority 10, that is the lowest. # # However a special priority of 0 marks the slave as not able to perform the # role of master, so a slave with priority of 0 will never be selected by @@ -343,7 +363,7 @@ # Don't use more memory than the specified amount of bytes. # When the memory limit is reached Redis will try to remove keys -# accordingly to the eviction policy selected (see maxmemmory-policy). +# according to the eviction policy selected (see maxmemory-policy). # # If Redis can't remove keys according to the policy, or if the policy is # set to 'noeviction', Redis will start to reply with errors to commands @@ -351,7 +371,7 @@ # to reply to read-only commands like GET. # # This option is usually useful when using Redis as an LRU cache, or to set -# an hard memory limit for an instance (using the 'noeviction' policy). +# a hard memory limit for an instance (using the 'noeviction' policy). # # WARNING: If you have slaves attached to an instance with maxmemory on, # the size of the output buffers needed to feed the slaves are subtracted @@ -420,7 +440,8 @@ appendonly no # The name of the append only file (default: "appendonly.aof") -# appendfilename appendonly.aof + +appendfilename "appendonly.aof" # The fsync() call tells the Operating System to actually write data on disk # instead to wait for more data in the output buffer. Some OS will really flush @@ -467,6 +488,7 @@ # # If you have latency problems turn this to "yes". Otherwise leave it as # "no" that is the safest pick from the point of view of durability. + no-appendfsync-on-rewrite no # Automatic rewrite of the append only file. @@ -607,7 +629,7 @@ # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in # order to help rehashing the main Redis hash table (the one mapping top-level # keys to values). The hash table implementation Redis uses (see dict.c) -# performs a lazy rehashing: the more operation you run into an hash table +# performs a lazy rehashing: the more operation you run into a hash table # that is rehashing, the more rehashing "steps" are performed, so if the # server is idle the rehashing is never complete and some more memory is used # by the hash table. @@ -633,7 +655,7 @@ # # normal -> normal clients # slave -> slave clients and MONITOR clients -# pubsub -> clients subcribed to at least one pubsub channel or pattern +# pubsub -> clients subscribed to at least one pubsub channel or pattern # # The syntax of every client-output-buffer-limit directive is the following: # @@ -662,7 +684,7 @@ client-output-buffer-limit pubsub 32mb 8mb 60 # Redis calls an internal function to perform many background tasks, like -# closing connections of clients in timeot, purging expired keys that are +# closing connections of clients in timeout, purging expired keys that are # never requested, and so forth. # # Not all tasks are performed with the same frequency, but Redis checks for @@ -684,12 +706,3 @@ # big latency spikes. aof-rewrite-incremental-fsync yes -################################## INCLUDES ################################### - -# Include one or more other config files here. This is useful if you -# have a standard template that goes to all Redis server but also need -# to customize a few per-server settings. Include files can include -# other files, so use this wisely. -# -# include /path/to/local.conf -# include /path/to/other.conf diff -Nru redis-2.8.2/redis.conf redis-2.8.4/redis.conf --- redis-2.8.2/redis.conf 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/redis.conf 2014-01-13 16:09:58.000000000 +0000 @@ -12,6 +12,26 @@ # # units are case insensitive so 1GB 1Gb 1gB are all the same. +################################## INCLUDES ################################### + +# Include one or more other config files here. This is useful if you +# have a standard template that goes to all Redis server but also need +# to customize a few per-server settings. Include files can include +# other files, so use this wisely. +# +# Notice option "include" won't be rewritten by command "CONFIG REWRITE" +# from admin or Redis Sentinel. Since Redis always uses the last processed +# line as value of a configuration directive, you'd better put includes +# at the beginning of this file to avoid overwriting config change at runtime. +# +# If instead you are interested in using includes to override configuration +# options, it is better to use include as the last line. +# +# include /path/to/local.conf +# include /path/to/other.conf + +################################ GENERAL ##################################### + # By default Redis does not run as a daemon. Use 'yes' if you need it. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. daemonize no @@ -34,7 +54,7 @@ # bind 192.168.1.100 10.0.0.1 # bind 127.0.0.1 -# Specify the path for the unix socket that will be used to listen for +# Specify the path for the Unix socket that will be used to listen for # incoming connections. There is no default, so Redis will not listen # on a unix socket when not specified. # @@ -68,7 +88,7 @@ # warning (only very important / critical messages are logged) loglevel notice -# Specify the log file name. Also the emptry string can be used to force +# Specify the log file name. Also the empty string can be used to force # Redis to log on the standard output. Note that if you use standard # output for logging but daemonize, logs will be sent to /dev/null logfile "" @@ -88,7 +108,7 @@ # dbid is a number between 0 and 'databases'-1 databases 16 -################################ SNAPSHOTTING ################################# +################################ SNAPSHOTTING ################################ # # Save the DB on disk: # @@ -116,16 +136,16 @@ # By default Redis will stop accepting writes if RDB snapshots are enabled # (at least one save point) and the latest background save failed. -# This will make the user aware (in an hard way) that data is not persisting +# This will make the user aware (in a hard way) that data is not persisting # on disk properly, otherwise chances are that no one will notice and some -# distater will happen. +# disaster will happen. # # If the background saving process will start working again Redis will # automatically allow writes again. # # However if you have setup your proper monitoring of the Redis server # and persistence, you may want to disable this feature so that Redis will -# continue to work as usually even if there are problems with disk, +# continue to work as usual even if there are problems with disk, # permissions, and so forth. stop-writes-on-bgsave-error yes @@ -197,7 +217,7 @@ # Note: read only slaves are not designed to be exposed to untrusted clients # on the internet. It's just a protection layer against misuse of the instance. # Still a read only slave exports by default all the administrative commands -# such as CONFIG, DEBUG, and so forth. To a limited extend you can improve +# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve # security of read only slaves using 'rename-command' to shadow all the # administrative / dangerous commands. slave-read-only yes @@ -263,7 +283,7 @@ # # A slave with a low priority number is considered better for promotion, so # for instance if there are three slaves with priority 10, 100, 25 Sentinel will -# pick the one wtih priority 10, that is the lowest. +# pick the one with priority 10, that is the lowest. # # However a special priority of 0 marks the slave as not able to perform the # role of master, so a slave with priority of 0 will never be selected by @@ -343,7 +363,7 @@ # Don't use more memory than the specified amount of bytes. # When the memory limit is reached Redis will try to remove keys -# accordingly to the eviction policy selected (see maxmemmory-policy). +# according to the eviction policy selected (see maxmemory-policy). # # If Redis can't remove keys according to the policy, or if the policy is # set to 'noeviction', Redis will start to reply with errors to commands @@ -351,7 +371,7 @@ # to reply to read-only commands like GET. # # This option is usually useful when using Redis as an LRU cache, or to set -# an hard memory limit for an instance (using the 'noeviction' policy). +# a hard memory limit for an instance (using the 'noeviction' policy). # # WARNING: If you have slaves attached to an instance with maxmemory on, # the size of the output buffers needed to feed the slaves are subtracted @@ -420,7 +440,8 @@ appendonly no # The name of the append only file (default: "appendonly.aof") -# appendfilename appendonly.aof + +appendfilename "appendonly.aof" # The fsync() call tells the Operating System to actually write data on disk # instead to wait for more data in the output buffer. Some OS will really flush @@ -467,6 +488,7 @@ # # If you have latency problems turn this to "yes". Otherwise leave it as # "no" that is the safest pick from the point of view of durability. + no-appendfsync-on-rewrite no # Automatic rewrite of the append only file. @@ -607,7 +629,7 @@ # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in # order to help rehashing the main Redis hash table (the one mapping top-level # keys to values). The hash table implementation Redis uses (see dict.c) -# performs a lazy rehashing: the more operation you run into an hash table +# performs a lazy rehashing: the more operation you run into a hash table # that is rehashing, the more rehashing "steps" are performed, so if the # server is idle the rehashing is never complete and some more memory is used # by the hash table. @@ -633,7 +655,7 @@ # # normal -> normal clients # slave -> slave clients and MONITOR clients -# pubsub -> clients subcribed to at least one pubsub channel or pattern +# pubsub -> clients subscribed to at least one pubsub channel or pattern # # The syntax of every client-output-buffer-limit directive is the following: # @@ -662,7 +684,7 @@ client-output-buffer-limit pubsub 32mb 8mb 60 # Redis calls an internal function to perform many background tasks, like -# closing connections of clients in timeot, purging expired keys that are +# closing connections of clients in timeout, purging expired keys that are # never requested, and so forth. # # Not all tasks are performed with the same frequency, but Redis checks for @@ -684,12 +706,3 @@ # big latency spikes. aof-rewrite-incremental-fsync yes -################################## INCLUDES ################################### - -# Include one or more other config files here. This is useful if you -# have a standard template that goes to all Redis server but also need -# to customize a few per-server settings. Include files can include -# other files, so use this wisely. -# -# include /path/to/local.conf -# include /path/to/other.conf diff -Nru redis-2.8.2/sentinel.conf redis-2.8.4/sentinel.conf --- redis-2.8.2/sentinel.conf 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/sentinel.conf 2014-01-13 16:09:58.000000000 +0000 @@ -6,7 +6,7 @@ # sentinel monitor # -# Tells Sentinel to monitor this slave, and to consider it in O_DOWN +# Tells Sentinel to monitor this master, and to consider it in O_DOWN # (Objectively Down) state only if at least sentinels agree. # # Note that whatever is the ODOWN quorum, a Sentinel will require to @@ -63,7 +63,7 @@ # times the failover timeout. # # - The time needed for a slave replicating to a wrong master according -# to a Sentinel currnet configuration, to be forced to replicate +# to a Sentinel current configuration, to be forced to replicate # with the right master, is exactly the failover timeout (counting since # the moment a Sentinel detected the misconfiguration). # @@ -102,7 +102,7 @@ # # sentinel notification-script # -# Call the specified notification script for any sentienl event that is +# Call the specified notification script for any sentinel event that is # generated in the WARNING level (for instance -sdown, -odown, and so forth). # This script should notify the system administrator via email, SMS, or any # other messaging system, that there is something wrong with the monitored diff -Nru redis-2.8.2/src/Makefile redis-2.8.4/src/Makefile --- redis-2.8.2/src/Makefile 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/Makefile 2014-01-13 16:09:58.000000000 +0000 @@ -55,15 +55,19 @@ DEBUG=-g -ggdb ifeq ($(uname_S),SunOS) + # SunOS INSTALL=cp -pf FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6 FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread -else ifeq ($(uname_S),Darwin) - else +ifeq ($(uname_S),Darwin) + # Darwin (nothing to do) +else + # All the other OSes (notably Linux) FINAL_LDFLAGS+= -rdynamic FINAL_LIBS+= -pthread endif +endif # Include paths to dependencies FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src diff -Nru redis-2.8.2/src/Makefile.dep redis-2.8.4/src/Makefile.dep --- redis-2.8.2/src/Makefile.dep 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/Makefile.dep 2014-01-13 16:09:58.000000000 +0000 @@ -1,5 +1,5 @@ adlist.o: adlist.c adlist.h zmalloc.h -ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c +ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c ae_select.c ae_evport.c ae_epoll.c ae_epoll.o: ae_epoll.c ae_evport.o: ae_evport.c ae_kqueue.o: ae_kqueue.c @@ -24,7 +24,7 @@ debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h -dict.o: dict.c fmacros.h dict.h zmalloc.h +dict.o: dict.c fmacros.h dict.h zmalloc.h redisassert.h endianconv.o: endianconv.c intset.o: intset.c intset.h zmalloc.h endianconv.h config.h lzf_c.o: lzf_c.c lzfP.h @@ -70,18 +70,18 @@ ../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \ adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \ rio.h -rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h +rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h config.h redis.h \ + ../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h dict.h adlist.h \ + zmalloc.h anet.h ziplist.h intset.h version.h rdb.h scripting.o: scripting.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h rand.h \ - ../deps/lua/src/lauxlib.h ../deps/lua/src/lua.h \ - ../deps/lua/src/lualib.h + ../deps/lua/src/lauxlib.h ../deps/lua/src/lualib.h sds.o: sds.c sds.h zmalloc.h sentinel.o: sentinel.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ ziplist.h intset.h version.h util.h rdb.h rio.h \ - ../deps/hiredis/hiredis.h ../deps/hiredis/async.h \ - ../deps/hiredis/hiredis.h + ../deps/hiredis/hiredis.h ../deps/hiredis/async.h setproctitle.o: setproctitle.c sha1.o: sha1.c sha1.h config.h slowlog.o: slowlog.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ @@ -108,7 +108,8 @@ t_zset.o: t_zset.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ ziplist.h intset.h version.h util.h rdb.h rio.h -util.o: util.c fmacros.h util.h -ziplist.o: ziplist.c zmalloc.h util.h ziplist.h endianconv.h config.h +util.o: util.c fmacros.h util.h sds.h +ziplist.o: ziplist.c zmalloc.h util.h sds.h ziplist.h endianconv.h \ + config.h redisassert.h zipmap.o: zipmap.c zmalloc.h endianconv.h config.h zmalloc.o: zmalloc.c config.h zmalloc.h diff -Nru redis-2.8.2/src/ae_epoll.c redis-2.8.4/src/ae_epoll.c --- redis-2.8.2/src/ae_epoll.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/ae_epoll.c 2014-01-13 16:09:58.000000000 +0000 @@ -45,7 +45,7 @@ zfree(state); return -1; } - state->epfd = epoll_create(1024); /* 1024 is just an hint for the kernel */ + state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */ if (state->epfd == -1) { zfree(state->events); zfree(state); diff -Nru redis-2.8.2/src/anet.c redis-2.8.4/src/anet.c --- redis-2.8.2/src/anet.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/anet.c 2014-01-13 16:09:58.000000000 +0000 @@ -163,12 +163,21 @@ return ANET_OK; } -int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) +/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to + * do the actual work. It resolves the hostname "host" and set the string + * representation of the IP address into the buffer pointed by "ipbuf". + * + * If flags is set to ANET_IP_ONLY the function only resolves hostnames + * that are actually already IPv4 or IPv6 addresses. This turns the function + * into a validating / normalizing function. */ +int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, + int flags) { struct addrinfo hints, *info; int rv; memset(&hints,0,sizeof(hints)); + if (flags & ANET_IP_ONLY) hints.ai_flags = AI_NUMERICHOST; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* specify socktype to avoid dups */ @@ -188,6 +197,14 @@ return ANET_OK; } +int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) { + return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE); +} + +int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) { + return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY); +} + static int anetSetReuseAddr(char *err, int fd) { int yes = 1; /* Make sure connection-intensive things like the redis benckmark @@ -219,43 +236,50 @@ #define ANET_CONNECT_NONBLOCK 1 static int anetTcpGenericConnect(char *err, char *addr, int port, int flags) { - int s, rv; - char _port[6]; /* strlen("65535"); */ + int s = ANET_ERR, rv; + char portstr[6]; /* strlen("65535") + 1; */ struct addrinfo hints, *servinfo, *p; - snprintf(_port,6,"%d",port); + snprintf(portstr,sizeof(portstr),"%d",port); memset(&hints,0,sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + if ((rv = getaddrinfo(addr,portstr,&hints,&servinfo)) != 0) { anetSetError(err, "%s", gai_strerror(rv)); return ANET_ERR; } for (p = servinfo; p != NULL; p = p->ai_next) { + /* Try to create the socket and to connect it. + * If we fail in the socket() call, or on connect(), we retry with + * the next entry in servinfo. */ if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) continue; - - /* if we set err then goto cleanup, otherwise next */ if (anetSetReuseAddr(err,s) == ANET_ERR) goto error; if (flags & ANET_CONNECT_NONBLOCK && anetNonBlock(err,s) != ANET_OK) goto error; if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { - if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) goto end; + /* If the socket is non-blocking, it is ok for connect() to + * return an EINPROGRESS error here. */ + if (errno == EINPROGRESS && flags & ANET_CONNECT_NONBLOCK) + goto end; close(s); + s = ANET_ERR; continue; } - /* break with the socket */ + /* If we ended an iteration of the for loop without errors, we + * have a connected socket. Let's return to the caller. */ goto end; } - if (p == NULL) { + if (p == NULL) anetSetError(err, "creating socket: %s", strerror(errno)); - goto error; - } error: - s = ANET_ERR; + if (s != ANET_ERR) { + close(s); + s = ANET_ERR; + } end: freeaddrinfo(servinfo); return s; @@ -481,7 +505,7 @@ socklen_t salen = sizeof(sa); if (getpeername(fd,(struct sockaddr*)&sa,&salen) == -1) { - *port = 0; + if (port) *port = 0; ip[0] = '?'; ip[1] = '\0'; return -1; @@ -503,7 +527,7 @@ socklen_t salen = sizeof(sa); if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) { - *port = 0; + if (port) *port = 0; ip[0] = '?'; ip[1] = '\0'; return -1; diff -Nru redis-2.8.2/src/anet.h redis-2.8.4/src/anet.h --- redis-2.8.2/src/anet.h 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/anet.h 2014-01-13 16:09:58.000000000 +0000 @@ -35,6 +35,10 @@ #define ANET_ERR -1 #define ANET_ERR_LEN 256 +/* Flags used with certain functions. */ +#define ANET_NONE 0 +#define ANET_IP_ONLY (1<<0) + #if defined(__sun) #define AF_LOCAL AF_UNIX #endif @@ -45,6 +49,7 @@ int anetUnixNonBlockConnect(char *err, char *path); int anetRead(int fd, char *buf, int count); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len); +int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetTcpServer(char *err, int port, char *bindaddr); int anetTcp6Server(char *err, int port, char *bindaddr); int anetUnixServer(char *err, char *path, mode_t perm); diff -Nru redis-2.8.2/src/aof.c redis-2.8.4/src/aof.c --- redis-2.8.2/src/aof.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/aof.c 2014-01-13 16:09:58.000000000 +0000 @@ -771,7 +771,7 @@ return 1; } -/* Write either the key or the value of the currently selected item of an hash. +/* Write either the key or the value of the currently selected item of a hash. * The 'hi' argument passes a valid Redis hash iterator. * The 'what' filed specifies if to write a key or a value and can be * either REDIS_HASH_KEY or REDIS_HASH_VALUE. diff -Nru redis-2.8.2/src/config.c redis-2.8.4/src/config.c --- redis-2.8.2/src/config.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/config.c 2014-01-13 16:09:58.000000000 +0000 @@ -1107,8 +1107,8 @@ /* We use the following dictionary type to store where a configuration * option is mentioned in the old configuration file, so it's * like "maxmemory" -> list of line numbers (first line is zero). */ -unsigned int dictSdsHash(const void *key); -int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2); +unsigned int dictSdsCaseHash(const void *key); +int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2); void dictSdsDestructor(void *privdata, void *val); void dictListDestructor(void *privdata, void *val); @@ -1117,17 +1117,27 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state); dictType optionToLineDictType = { - dictSdsHash, /* hash function */ + dictSdsCaseHash, /* hash function */ NULL, /* key dup */ NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ + dictSdsKeyCaseCompare, /* key compare */ dictSdsDestructor, /* key destructor */ dictListDestructor /* val destructor */ }; +dictType optionSetDictType = { + dictSdsCaseHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCaseCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL /* val destructor */ +}; + /* The config rewrite state. */ struct rewriteConfigState { dict *option_to_line; /* Option -> list of config file lines map */ + dict *rewritten; /* Dictionary of already processed options */ int numlines; /* Number of lines in current config */ sds *lines; /* Current lines as an array of sds strings */ int has_tail; /* True if we already added directives that were @@ -1151,6 +1161,16 @@ listAddNodeTail(l,(void*)(long)linenum); } +/* Add the specified option to the set of processed options. + * 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 + * remain untouched. */ +void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, char *option) { + sds opt = sdsnew(option); + + if (dictAdd(state->rewritten,opt,NULL) != DICT_OK) sdsfree(opt); +} + /* Read the old file, split it into lines to populate a newly created * config rewrite state, and return it to the caller. * @@ -1165,6 +1185,7 @@ if (fp == NULL && errno != ENOENT) return NULL; state->option_to_line = dictCreate(&optionToLineDictType,NULL); + state->rewritten = dictCreate(&optionSetDictType,NULL); state->numlines = 0; state->lines = NULL; state->has_tail = 0; @@ -1232,6 +1253,8 @@ sds o = sdsnew(option); list *l = dictFetchValue(state->option_to_line,o); + rewriteConfigMarkAsProcessed(state,option); + if (!l && !force) { /* Option not used previously, and we are not forced to use it. */ sdsfree(line); @@ -1288,7 +1311,6 @@ rewriteConfigFormatMemory(buf,sizeof(buf),value); line = sdscatprintf(sdsempty(),"%s %s",option,buf); rewriteConfigRewriteLine(state,option,line,force); - } /* Rewrite a yes/no option. */ @@ -1307,7 +1329,10 @@ /* String options set to NULL need to be not present at all in the * configuration file to be set to NULL again at the next reboot. */ - if (value == NULL) return; + if (value == NULL) { + rewriteConfigMarkAsProcessed(state,option); + return; + } /* Compare the strings as sds strings to have a binary safe comparison. */ if (defvalue && strcmp(value,defvalue) == 0) force = 0; @@ -1391,13 +1416,18 @@ server.saveparams[j].seconds, server.saveparams[j].changes); rewriteConfigRewriteLine(state,"save",line,1); } + /* Mark "save" as processed in case server.saveparamslen is zero. */ + rewriteConfigMarkAsProcessed(state,"save"); } /* Rewrite the dir option, always using absolute paths.*/ void rewriteConfigDirOption(struct rewriteConfigState *state) { char cwd[1024]; - if (getcwd(cwd,sizeof(cwd)) == NULL) return; /* no rewrite on error. */ + if (getcwd(cwd,sizeof(cwd)) == NULL) { + rewriteConfigMarkAsProcessed(state,"dir"); + return; /* no rewrite on error. */ + } rewriteConfigStringOption(state,"dir",cwd,NULL); } @@ -1408,23 +1438,15 @@ /* If this is a master, we want all the slaveof config options * in the file to be removed. */ - if (server.masterhost == NULL) return; + if (server.masterhost == NULL) { + rewriteConfigMarkAsProcessed(state,"slaveof"); + return; + } line = sdscatprintf(sdsempty(),"%s %s %d", option, server.masterhost, server.masterport); rewriteConfigRewriteLine(state,option,line,1); } -/* Rewrite the appendonly option. */ -void rewriteConfigAppendonlyOption(struct rewriteConfigState *state) { - int force = server.aof_state != REDIS_AOF_OFF; - char *option = "appendonly"; - sds line; - - line = sdscatprintf(sdsempty(),"%s %s", option, - (server.aof_state == REDIS_AOF_OFF) ? "no" : "yes"); - rewriteConfigRewriteLine(state,option,line,force); -} - /* Rewrite the notify-keyspace-events option. */ void rewriteConfigNotifykeyspaceeventsOption(struct rewriteConfigState *state) { int force = server.notify_keyspace_events != 0; @@ -1473,7 +1495,10 @@ char *option = "bind"; /* Nothing to rewrite if we don't have bind addresses. */ - if (server.bindaddr_count == 0) return; + if (server.bindaddr_count == 0) { + rewriteConfigMarkAsProcessed(state,option); + return; + } /* Rewrite as bind ... */ addresses = sdsjoin(server.bindaddr,server.bindaddr_count," "); @@ -1509,6 +1534,7 @@ void rewriteConfigReleaseState(struct rewriteConfigState *state) { sdsfreesplitres(state->lines,state->numlines); dictRelease(state->option_to_line); + dictRelease(state->rewritten); zfree(state); } @@ -1526,6 +1552,14 @@ while((de = dictNext(di)) != NULL) { list *l = dictGetVal(de); + sds option = dictGetKey(de); + + /* Don't blank lines about options the rewrite process + * don't understand. */ + if (dictFind(state->rewritten,option) == NULL) { + redisLog(REDIS_DEBUG,"Not rewritten option: %s", option); + continue; + } while(listLength(l)) { listNode *ln = listFirst(l); @@ -1615,8 +1649,6 @@ /* Step 2: rewrite every single option, replacing or appending it inside * the rewrite state. */ - /* TODO: Turn every default into a define, use it also in - * initServerConfig(). */ rewriteConfigYesNoOption(state,"daemonize",server.daemonize,0); rewriteConfigStringOption(state,"pidfile",server.pidfile,REDIS_DEFAULT_PID_FILE); rewriteConfigNumericalOption(state,"port",server.port,REDIS_SERVERPORT); @@ -1652,6 +1684,8 @@ rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,REDIS_DEFAULT_REPL_BACKLOG_TIME_LIMIT); rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY); rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,REDIS_DEFAULT_SLAVE_PRIORITY); + rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,REDIS_DEFAULT_MIN_SLAVES_TO_WRITE); + rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,REDIS_DEFAULT_MIN_SLAVES_MAX_LAG); rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL); rewriteConfigNumericalOption(state,"maxclients",server.maxclients,REDIS_MAX_CLIENTS); rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,REDIS_DEFAULT_MAXMEMORY); @@ -1664,7 +1698,8 @@ "noeviction", REDIS_MAXMEMORY_NO_EVICTION, NULL, REDIS_DEFAULT_MAXMEMORY_POLICY); rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,REDIS_DEFAULT_MAXMEMORY_SAMPLES); - rewriteConfigAppendonlyOption(state); + rewriteConfigYesNoOption(state,"appendonly",server.aof_state != REDIS_AOF_OFF,0); + rewriteConfigStringOption(state,"appendfilename",server.aof_filename,REDIS_DEFAULT_AOF_FILENAME); rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync, "everysec", AOF_FSYNC_EVERYSEC, "always", AOF_FSYNC_ALWAYS, diff -Nru redis-2.8.2/src/db.c redis-2.8.4/src/db.c --- redis-2.8.2/src/db.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/db.c 2014-01-13 16:09:58.000000000 +0000 @@ -166,14 +166,14 @@ } } -long long emptyDb() { +long long emptyDb(void(callback)(void*)) { int j; long long removed = 0; for (j = 0; j < server.dbnum; j++) { removed += dictSize(server.db[j].dict); - dictEmpty(server.db[j].dict); - dictEmpty(server.db[j].expires); + dictEmpty(server.db[j].dict,callback); + dictEmpty(server.db[j].expires,callback); } return removed; } @@ -209,14 +209,14 @@ void flushdbCommand(redisClient *c) { server.dirty += dictSize(c->db->dict); signalFlushedDb(c->db->id); - dictEmpty(c->db->dict); - dictEmpty(c->db->expires); + dictEmpty(c->db->dict,NULL); + dictEmpty(c->db->expires,NULL); addReply(c,shared.ok); } void flushallCommand(redisClient *c) { signalFlushedDb(-1); - server.dirty += emptyDb(); + server.dirty += emptyDb(NULL); addReply(c,shared.ok); if (server.rdb_child_pid != -1) { kill(server.rdb_child_pid,SIGUSR1); @@ -360,7 +360,7 @@ } /* This command implements SCAN, HSCAN and SSCAN commands. - * If object 'o' is passed, then it must be an Hash or Set object, otherwise + * If object 'o' is passed, then it must be a Hash or Set object, otherwise * if 'o' is NULL the command will operate on the dictionary associated with * the current database. * @@ -368,7 +368,7 @@ * the client arguments vector is a key so it skips it before iterating * in order to parse options. * - * In the case of an Hash object the function returns both the field and value + * In the case of a Hash object the function returns both the field and value * of every element on the Hash. */ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) { int rv; @@ -423,12 +423,12 @@ /* Step 2: Iterate the collection. * * Note that if the object is encoded with a ziplist, intset, or any other - * representation that is not an hash table, we are sure that it is also + * representation that is not a hash table, we are sure that it is also * composed of a small number of elements. So to avoid taking state we * just return everything inside the object in a single call, setting the * cursor to zero to signal the end of the iteration. */ - /* Handle the case of an hash table. */ + /* Handle the case of a hash table. */ ht = NULL; if (o == NULL) { ht = c->db->dict; @@ -510,7 +510,7 @@ listDelNode(keys, node); } - /* If this is an hash or a sorted set, we have a flat list of + /* If this is a hash or a sorted set, we have a flat list of * key-value elements, so if this element was filtered, remove the * value, or skip it if it was not filtered: we only match keys. */ if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) { diff -Nru redis-2.8.2/src/debug.c redis-2.8.4/src/debug.c --- redis-2.8.2/src/debug.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/debug.c 2014-01-13 16:09:58.000000000 +0000 @@ -261,7 +261,7 @@ addReply(c,shared.err); return; } - emptyDb(); + emptyDb(NULL); if (rdbLoad(server.rdb_filename) != REDIS_OK) { addReplyError(c,"Error trying to load the RDB dump"); return; @@ -269,7 +269,7 @@ redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD"); addReply(c,shared.ok); } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { - emptyDb(); + emptyDb(NULL); if (loadAppendOnlyFile(server.aof_filename) != REDIS_OK) { addReply(c,shared.err); return; diff -Nru redis-2.8.2/src/dict.c redis-2.8.4/src/dict.c --- redis-2.8.2/src/dict.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/dict.c 2014-01-13 16:09:58.000000000 +0000 @@ -53,7 +53,7 @@ * around when there is a child performing saving operations. * * Note that even when dict_can_resize is set to 0, not all resizes are - * prevented: an hash table is still allowed to grow if the ratio between + * prevented: a hash table is still allowed to grow if the ratio between * the number of elements and the buckets > dict_force_resize_ratio. */ static int dict_can_resize = 1; static unsigned int dict_force_resize_ratio = 5; @@ -444,14 +444,15 @@ } /* Destroy an entire dictionary */ -int _dictClear(dict *d, dictht *ht) -{ +int _dictClear(dict *d, dictht *ht, void(callback)(void *)) { unsigned long i; /* Free all the elements */ for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; + if (callback && (i & 65535) == 0) callback(d->privdata); + if ((he = ht->table[i]) == NULL) continue; while(he) { nextHe = he->next; @@ -472,8 +473,8 @@ /* Clear & Release the hash table */ void dictRelease(dict *d) { - _dictClear(d,&d->ht[0]); - _dictClear(d,&d->ht[1]); + _dictClear(d,&d->ht[0],NULL); + _dictClear(d,&d->ht[1],NULL); zfree(d); } @@ -853,7 +854,7 @@ } /* Returns the index of a free slot that can be populated with - * an hash entry for the given 'key'. + * a hash entry for the given 'key'. * If the key already exists, -1 is returned. * * Note that if we are in the process of rehashing the hash table, the @@ -882,9 +883,9 @@ return idx; } -void dictEmpty(dict *d) { - _dictClear(d,&d->ht[0]); - _dictClear(d,&d->ht[1]); +void dictEmpty(dict *d, void(callback)(void*)) { + _dictClear(d,&d->ht[0],callback); + _dictClear(d,&d->ht[1],callback); d->rehashidx = -1; d->iterators = 0; } diff -Nru redis-2.8.2/src/dict.h redis-2.8.4/src/dict.h --- redis-2.8.2/src/dict.h 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/dict.h 2014-01-13 16:09:58.000000000 +0000 @@ -160,7 +160,7 @@ void dictPrintStats(dict *d); unsigned int dictGenHashFunction(const void *key, int len); unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len); -void dictEmpty(dict *d); +void dictEmpty(dict *d, void(callback)(void*)); void dictEnableResize(void); void dictDisableResize(void); int dictRehash(dict *d, int n); diff -Nru redis-2.8.2/src/migrate.c redis-2.8.4/src/migrate.c --- redis-2.8.2/src/migrate.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/migrate.c 2014-01-13 16:09:58.000000000 +0000 @@ -84,7 +84,7 @@ /* RESTORE key ttl serialized-value */ void restoreCommand(redisClient *c) { - long ttl; + long long ttl; rio payload; int type; robj *obj; @@ -96,7 +96,7 @@ } /* Check if the TTL value makes sense */ - if (getLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != REDIS_OK) { + if (getLongLongFromObjectOrReply(c,c->argv[2],&ttl,NULL) != REDIS_OK) { return; } else if (ttl < 0) { addReplyError(c,"Invalid TTL value, must be >= 0"); @@ -104,7 +104,8 @@ } /* Verify RDB version and data checksum. */ - if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR) { + if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR) + { addReplyError(c,"DUMP payload version or checksum are wrong"); return; } diff -Nru redis-2.8.2/src/networking.c redis-2.8.4/src/networking.c --- redis-2.8.2/src/networking.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/networking.c 2014-01-13 16:09:58.000000000 +0000 @@ -100,9 +100,7 @@ c->bpop.keys = dictCreate(&setDictType,NULL); c->bpop.timeout = 0; c->bpop.target = NULL; - c->io_keys = listCreate(); c->watched_keys = listCreate(); - listSetFreeMethod(c->io_keys,decrRefCountVoid); c->pubsub_channels = dictCreate(&setDictType,NULL); c->pubsub_patterns = listCreate(); listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid); @@ -639,24 +637,33 @@ * * Note that before doing this we make sure that the client is not in * some unexpected state, by checking its flags. */ - if (server.master && - (c->flags & REDIS_MASTER) && - !(c->flags & (REDIS_CLOSE_AFTER_REPLY| - REDIS_CLOSE_ASAP| - REDIS_BLOCKED| - REDIS_UNBLOCKED))) - { - replicationCacheMaster(c); - return; + if (server.master && c->flags & REDIS_MASTER) { + redisLog(REDIS_WARNING,"Connection with master lost."); + if (!(c->flags & (REDIS_CLOSE_AFTER_REPLY| + REDIS_CLOSE_ASAP| + REDIS_BLOCKED| + REDIS_UNBLOCKED))) + { + replicationCacheMaster(c); + return; + } } - /* Note that if the client we are freeing is blocked into a blocking - * call, we have to set querybuf to NULL *before* to call - * unblockClientWaitingData() to avoid processInputBuffer() will get - * called. Also it is important to remove the file events after - * this, because this call adds the READABLE event. */ + /* Log link disconnection with slave */ + if (c->flags & REDIS_SLAVE) { + char ip[REDIS_IP_STR_LEN]; + + if (anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) { + redisLog(REDIS_WARNING,"Connection with slave %s:%d lost.", + ip, c->slave_listening_port); + } + } + + /* Free the query buffer */ sdsfree(c->querybuf); c->querybuf = NULL; + + /* Deallocate structures used to block on blocking ops. */ if (c->flags & REDIS_BLOCKED) unblockClientWaitingData(c); dictRelease(c->bpop.keys); @@ -664,11 +671,13 @@ /* UNWATCH all the keys */ unwatchAllKeys(c); listRelease(c->watched_keys); + /* Unsubscribe from all the pubsub channels */ pubsubUnsubscribeAllChannels(c,0); pubsubUnsubscribeAllPatterns(c,0); dictRelease(c->pubsub_channels); listRelease(c->pubsub_patterns); + /* Close socket, unregister events, and remove list of replies and * accumulated arguments. */ if (c->fd != -1) { @@ -678,22 +687,24 @@ } listRelease(c->reply); freeClientArgv(c); + /* Remove from the list of clients */ if (c->fd != -1) { ln = listSearchKey(server.clients,c); redisAssert(ln != NULL); listDelNode(server.clients,ln); } + /* When client was just unblocked because of a blocking operation, - * remove it from the list with unblocked clients. */ + * remove it from the list of unblocked clients. */ if (c->flags & REDIS_UNBLOCKED) { ln = listSearchKey(server.unblocked_clients,c); redisAssert(ln != NULL); listDelNode(server.unblocked_clients,ln); } - listRelease(c->io_keys); - /* Master/slave cleanup. - * Case 1: we lost the connection with a slave. */ + + /* Master/slave cleanup Case 1: + * we lost the connection with a slave. */ if (c->flags & REDIS_SLAVE) { if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) close(c->repldbfd); @@ -709,7 +720,8 @@ refreshGoodSlavesCount(); } - /* Case 2: we lost the connection with the master. */ + /* Master/slave cleanup Case 2: + * we lost the connection with the master. */ if (c->flags & REDIS_MASTER) replicationHandleMasterDisconnection(); /* If this client was scheduled for async freeing we need to remove it @@ -720,7 +732,8 @@ listDelNode(server.clients_to_close,ln); } - /* Release memory */ + /* Release other dynamically allocated client structure fields, + * and finally release the client structure itself. */ if (c->name) decrRefCount(c->name); zfree(c->argv); freeClientMultiState(c); @@ -840,11 +853,14 @@ } int processInlineBuffer(redisClient *c) { - char *newline = strstr(c->querybuf,"\r\n"); + char *newline; int argc, j; sds *argv, aux; size_t querylen; + /* Search for end of line */ + newline = strchr(c->querybuf,'\n'); + /* Nothing to do without a \r\n */ if (newline == NULL) { if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { @@ -854,11 +870,26 @@ return REDIS_ERR; } + /* Handle the \r\n case. */ + if (newline && newline != c->querybuf && *(newline-1) == '\r') + newline--; + /* Split the input buffer up to the \r\n */ querylen = newline-(c->querybuf); aux = sdsnewlen(c->querybuf,querylen); argv = sdssplitargs(aux,&argc); sdsfree(aux); + if (argv == NULL) { + addReplyError(c,"Protocol error: unbalanced quotes in request"); + setProtocolError(c,0); + return REDIS_ERR; + } + + /* Newline from slaves can be used to refresh the last ACK time. + * This is useful for a slave to ping back while loading a big + * RDB file. */ + if (querylen == 0 && c->flags & REDIS_SLAVE) + c->repl_ack_time = server.unixtime; /* Leave data after the first line of the query in the buffer */ sdsrange(c->querybuf,querylen+2,-1); @@ -1148,7 +1179,7 @@ *biggest_input_buffer = bib; } -/* This is an helper function for getClientPeerId(). +/* This is a helper function for getClientPeerId(). * It writes the specified ip/port to "peerid" as a null termiated string * in the form ip:port if ip does not contain ":" itself, otherwise * [ip]:port format is used (for IPv6 addresses basically). */ diff -Nru redis-2.8.2/src/notify.c redis-2.8.4/src/notify.c --- redis-2.8.2/src/notify.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/notify.c 2014-01-13 16:09:58.000000000 +0000 @@ -67,17 +67,19 @@ sds keyspaceEventsFlagsToString(int flags) { sds res; - if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL) - return sdsnew("A"); res = sdsempty(); - if (flags & REDIS_NOTIFY_GENERIC) res = sdscatlen(res,"g",1); - if (flags & REDIS_NOTIFY_STRING) res = sdscatlen(res,"$",1); - if (flags & REDIS_NOTIFY_LIST) res = sdscatlen(res,"l",1); - if (flags & REDIS_NOTIFY_SET) res = sdscatlen(res,"s",1); - if (flags & REDIS_NOTIFY_HASH) res = sdscatlen(res,"h",1); - if (flags & REDIS_NOTIFY_ZSET) res = sdscatlen(res,"z",1); - if (flags & REDIS_NOTIFY_EXPIRED) res = sdscatlen(res,"x",1); - if (flags & REDIS_NOTIFY_EVICTED) res = sdscatlen(res,"e",1); + if ((flags & REDIS_NOTIFY_ALL) == REDIS_NOTIFY_ALL) { + res = sdscatlen(res,"A",1); + } else { + if (flags & REDIS_NOTIFY_GENERIC) res = sdscatlen(res,"g",1); + if (flags & REDIS_NOTIFY_STRING) res = sdscatlen(res,"$",1); + if (flags & REDIS_NOTIFY_LIST) res = sdscatlen(res,"l",1); + if (flags & REDIS_NOTIFY_SET) res = sdscatlen(res,"s",1); + if (flags & REDIS_NOTIFY_HASH) res = sdscatlen(res,"h",1); + if (flags & REDIS_NOTIFY_ZSET) res = sdscatlen(res,"z",1); + if (flags & REDIS_NOTIFY_EXPIRED) res = sdscatlen(res,"x",1); + if (flags & REDIS_NOTIFY_EVICTED) res = sdscatlen(res,"e",1); + } if (flags & REDIS_NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1); if (flags & REDIS_NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1); return res; diff -Nru redis-2.8.2/src/object.c redis-2.8.4/src/object.c --- redis-2.8.2/src/object.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/object.c 2014-01-13 16:09:58.000000000 +0000 @@ -587,7 +587,7 @@ } } -/* This is an helper function for the DEBUG command. We need to lookup keys +/* This is a helper function for the DEBUG command. We need to lookup keys * without any modification of LRU or other parameters. */ robj *objectCommandLookup(redisClient *c, robj *key) { dictEntry *de; diff -Nru redis-2.8.2/src/rdb.c redis-2.8.4/src/rdb.c --- redis-2.8.2/src/rdb.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/rdb.c 2014-01-13 16:09:58.000000000 +0000 @@ -892,7 +892,7 @@ o = createHashObject(); - /* Too many entries? Use an hash table. */ + /* Too many entries? Use a hash table. */ if (len > server.hash_max_ziplist_entries) hashTypeConvert(o, REDIS_ENCODING_HT); @@ -1064,7 +1064,10 @@ if (server.rdb_checksum) rioGenericUpdateChecksum(r, buf, len); if (server.loading_process_events_interval_bytes && - (r->processed_bytes + len)/server.loading_process_events_interval_bytes > r->processed_bytes/server.loading_process_events_interval_bytes) { + (r->processed_bytes + len)/server.loading_process_events_interval_bytes > r->processed_bytes/server.loading_process_events_interval_bytes) + { + if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER) + replicationSendNewlineToMaster(); loadingProgress(r->processed_bytes); aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); } diff -Nru redis-2.8.2/src/redis-check-dump.c redis-2.8.4/src/redis-check-dump.c --- redis-2.8.2/src/redis-check-dump.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/redis-check-dump.c 2014-01-13 16:09:58.000000000 +0000 @@ -60,7 +60,7 @@ #define REDIS_ENCODING_RAW 0 /* Raw representation */ #define REDIS_ENCODING_INT 1 /* Encoded as integer */ #define REDIS_ENCODING_ZIPMAP 2 /* Encoded as zipmap */ -#define REDIS_ENCODING_HT 3 /* Encoded as an hash table */ +#define REDIS_ENCODING_HT 3 /* Encoded as a hash table */ /* Object types only used for dumping to disk */ #define REDIS_EXPIRETIME_MS 252 diff -Nru redis-2.8.2/src/redis.c redis-2.8.4/src/redis.c --- redis-2.8.2/src/redis.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/redis.c 2014-01-13 16:09:58.000000000 +0000 @@ -370,7 +370,7 @@ /*====================== Hash table type implementation ==================== */ -/* This is an hash table type that uses the SDS dynamic strings library as +/* This is a hash table type that uses the SDS dynamic strings library as * keys and radis objects as values (objects can hold SDS strings, * lists, sets). */ @@ -1302,7 +1302,7 @@ server.aof_rewrite_incremental_fsync = REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC; server.pidfile = zstrdup(REDIS_DEFAULT_PID_FILE); server.rdb_filename = zstrdup(REDIS_DEFAULT_RDB_FILENAME); - server.aof_filename = zstrdup("appendonly.aof"); + server.aof_filename = zstrdup(REDIS_DEFAULT_AOF_FILENAME); server.requirepass = NULL; server.rdb_compression = REDIS_DEFAULT_RDB_COMPRESSION; server.rdb_checksum = REDIS_DEFAULT_RDB_CHECKSUM; @@ -1523,7 +1523,8 @@ server.db = zmalloc(sizeof(redisDb)*server.dbnum); /* Open the TCP listening socket for the user commands. */ - if (listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR) + if (server.port != 0 && + listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR) exit(1); /* Open the listening Unix domain socket. */ @@ -2416,6 +2417,13 @@ "role:%s\r\n", server.masterhost == NULL ? "master" : "slave"); if (server.masterhost) { + long long slave_repl_offset = 1; + + if (server.master) + slave_repl_offset = server.master->reploff; + else if (server.cached_master) + slave_repl_offset = server.cached_master->reploff; + info = sdscatprintf(info, "master_host:%s\r\n" "master_port:%d\r\n" @@ -2430,7 +2438,7 @@ server.master ? ((int)(server.unixtime-server.master->lastinteraction)) : -1, server.repl_state == REDIS_REPL_TRANSFER, - server.master ? server.master->reploff : -1 + slave_repl_offset ); if (server.repl_state == REDIS_REPL_TRANSFER) { diff -Nru redis-2.8.2/src/redis.h redis-2.8.4/src/redis.h --- redis-2.8.2/src/redis.h 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/redis.h 2014-01-13 16:09:58.000000000 +0000 @@ -113,6 +113,7 @@ #define REDIS_DEFAULT_REPL_DISABLE_TCP_NODELAY 0 #define REDIS_DEFAULT_MAXMEMORY 0 #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_ACTIVE_REHASHING 1 #define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1 @@ -228,7 +229,7 @@ #define REDIS_MASTER_FORCE_REPLY (1<<13) /* Queue replies even if is master */ #define REDIS_FORCE_AOF (1<<14) /* Force AOF propagation of current cmd. */ #define REDIS_FORCE_REPL (1<<15) /* Force replication of current cmd. */ -#define REDIS_PRE_PSYNC_SLAVE (1<<16) /* Slave don't understand PSYNC. */ +#define REDIS_PRE_PSYNC (1<<16) /* Instance don't understand PSYNC. */ /* Client request types */ #define REDIS_REQ_INLINE 1 @@ -466,7 +467,7 @@ int authenticated; /* when requirepass is non-NULL */ int replstate; /* replication state if this is a slave */ int repldbfd; /* replication DB file descriptor */ - long repldboff; /* replication DB file offset */ + off_t repldboff; /* replication DB file offset */ off_t repldbsize; /* replication DB file size */ long long reploff; /* replication offset if this is our master */ long long repl_ack_off; /* replication ack offset, if this is a slave */ @@ -475,8 +476,6 @@ int slave_listening_port; /* As configured with: SLAVECONF listening-port */ multiState mstate; /* MULTI/EXEC state */ blockingState bpop; /* blocking state */ - list *io_keys; /* Keys this client is waiting to be loaded from the - * swap file in order to continue. */ list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */ dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */ list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */ @@ -1027,6 +1026,7 @@ int replicationScriptCacheExists(sds sha1); void replicationSetMaster(char *ip, int port); void replicationUnsetMaster(void); +void replicationSendNewlineToMaster(void); /* Generic persistence functions */ void startLoading(FILE *fp); @@ -1167,7 +1167,7 @@ int dbExists(redisDb *db, robj *key); robj *dbRandomKey(redisDb *db); int dbDelete(redisDb *db, robj *key); -long long emptyDb(); +long long emptyDb(void(callback)(void*)); int selectDb(redisClient *c, int id); void signalModifiedKey(redisDb *db, robj *key); void signalFlushedDb(int dbid); diff -Nru redis-2.8.2/src/replication.c redis-2.8.4/src/replication.c --- redis-2.8.2/src/replication.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/replication.c 2014-01-13 16:09:58.000000000 +0000 @@ -458,7 +458,7 @@ /* If a slave uses SYNC, we are dealing with an old implementation * of the replication protocol (like redis-cli --slave). Flag the client * so that we don't expect to receive REPLCONF ACK feedbacks. */ - c->flags |= REDIS_PRE_PSYNC_SLAVE; + c->flags |= REDIS_PRE_PSYNC; } /* Full resynchronization. */ @@ -701,6 +701,31 @@ server.repl_state = REDIS_REPL_CONNECT; } +/* Avoid the master to detect the slave is timing out while loading the + * RDB file in initial synchronization. We send a single newline character + * that is valid protocol but is guaranteed to either be sent entierly or + * not, since the byte is indivisible. + * + * The function is called in two contexts: while we flush the current + * data with emptyDb(), and while we load the new data received as an + * RDB file from the master. */ +void replicationSendNewlineToMaster(void) { + static time_t newline_sent; + if (time(NULL) != newline_sent) { + newline_sent = time(NULL); + if (write(server.repl_transfer_s,"\n",1) == -1) { + /* Pinging back in this stage is best-effort. */ + } + } +} + +/* Callback used by emptyDb() while flushing away old data to load + * the new dataset received by the master. */ +void replicationEmptyDbCallback(void *privdata) { + REDIS_NOTUSED(privdata); + replicationSendNewlineToMaster(); +} + /* Asynchronously read the SYNC payload we receive from a master */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { @@ -780,14 +805,15 @@ replicationAbortSyncTransfer(); return; } - redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory"); + redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Flushing old data"); signalFlushedDb(-1); - emptyDb(); + emptyDb(replicationEmptyDbCallback); /* Before loading the DB into memory we need to delete the readable * handler, otherwise it will get called recursively since * rdbLoad() will call the event loop to process events from time to * time for non blocking loading. */ aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE); + redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory"); if (rdbLoad(server.rdb_filename) != REDIS_OK) { redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk"); replicationAbortSyncTransfer(); @@ -803,6 +829,10 @@ server.master->reploff = server.repl_master_initial_offset; memcpy(server.master->replrunid, server.repl_master_runid, sizeof(server.repl_master_runid)); + /* If master offset is set to -1, this master is old and is not + * PSYNC capable, so we flag it accordingly. */ + if (server.master->reploff == -1) + server.master->flags |= REDIS_PRE_PSYNC; redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success"); /* Restart the AOF subsystem now that we finished the sync. This * will trigger an AOF rewrite, and when done will start appending @@ -965,7 +995,7 @@ /* If we reach this point we receied either an error since the master does * not understand PSYNC, or an unexpected reply from the master. - * Reply with PSYNC_NOT_SUPPORTED in both cases. */ + * Return PSYNC_NOT_SUPPORTED to the caller in both cases. */ if (strncmp(reply,"-ERR",4)) { /* If it's not an error, log the unexpected event. */ @@ -1216,6 +1246,7 @@ freeReplicationBacklog(); /* Don't allow our chained slaves to PSYNC. */ cancelReplicationHandshake(); server.repl_state = REDIS_REPL_CONNECT; + server.master_repl_offset = 0; } /* Cancel replication, setting the instance as a master itself. */ @@ -1223,7 +1254,17 @@ if (server.masterhost == NULL) return; /* Nothing to do. */ sdsfree(server.masterhost); server.masterhost = NULL; - if (server.master) freeClient(server.master); + if (server.master) { + if (listLength(server.slaves) == 0) { + /* If this instance is turned into a master and there are no + * slaves, it inherits the replication offset from the master. + * Under certain conditions this makes replicas comparable by + * replication offset to understand what is the most updated. */ + server.master_repl_offset = server.master->reploff; + freeReplicationBacklog(); + } + freeClient(server.master); + } replicationDiscardCachedMaster(); cancelReplicationHandshake(); server.repl_state = REDIS_REPL_NONE; @@ -1399,7 +1440,7 @@ * connected slave, in order to be able to replicate EVALSHA as it is without * translating it to EVAL every time it is possible. * - * We use a capped collection implemented by an hash table for fast lookup + * We use a capped collection implemented by a hash table for fast lookup * of scripts we can send as EVALSHA, plus a linked list that is used for * eviction of the oldest entry when the max number of items is reached. * @@ -1444,7 +1485,7 @@ * to reclaim otherwise unused memory. */ void replicationScriptCacheFlush(void) { - dictEmpty(server.repl_scriptcache_dict); + dictEmpty(server.repl_scriptcache_dict,NULL); listRelease(server.repl_scriptcache_fifo); server.repl_scriptcache_fifo = listCreate(); } @@ -1517,8 +1558,11 @@ } } - /* Send ACK to master from time to time. */ - if (server.masterhost && server.master) + /* Send ACK to master from time to time. + * Note that we do not send periodic acks to masters that don't + * support PSYNC and replication offsets. */ + if (server.masterhost && server.master && + !(server.master->flags & REDIS_PRE_PSYNC)) replicationSendAck(); /* If we have attached slaves, PING them from time to time. @@ -1562,7 +1606,7 @@ redisClient *slave = ln->value; if (slave->replstate != REDIS_REPL_ONLINE) continue; - if (slave->flags & REDIS_PRE_PSYNC_SLAVE) continue; + if (slave->flags & REDIS_PRE_PSYNC) continue; if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout) { char ip[REDIS_IP_STR_LEN]; diff -Nru redis-2.8.2/src/scripting.c redis-2.8.4/src/scripting.c --- redis-2.8.2/src/scripting.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/scripting.c 2014-01-13 16:09:58.000000000 +0000 @@ -895,7 +895,7 @@ /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); - /* Set an hook in order to be able to stop the script execution if it + /* Set a hook in order to be able to stop the script execution if it * is running for too much time. * We set the hook only if the time limit is enabled as the hook will * make the Lua script execution slower. */ @@ -1059,7 +1059,7 @@ if (server.lua_caller == NULL) { addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n")); } else if (server.lua_write_dirty) { - addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in an hard way using the SHUTDOWN NOSAVE command.\r\n")); + addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n")); } else { server.lua_kill = 1; addReply(c,shared.ok); diff -Nru redis-2.8.2/src/sds.c redis-2.8.4/src/sds.c --- redis-2.8.2/src/sds.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/sds.c 2014-01-13 16:09:58.000000000 +0000 @@ -383,7 +383,7 @@ * Example: * * s = sdsnew("Hello World"); - * sdstrim(s,1,-1); => "ello Worl" + * sdsrange(s,1,-1); => "ello World" */ void sdsrange(sds s, int start, int end) { struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); @@ -582,7 +582,7 @@ (c >= 'A' && c <= 'F'); } -/* Helper function for sdssplitargs() that converts an hex digit into an +/* Helper function for sdssplitargs() that converts a hex digit into an * integer from 0 to 15 */ int hex_digit_to_int(char c) { switch(c) { diff -Nru redis-2.8.2/src/sentinel.c redis-2.8.4/src/sentinel.c --- redis-2.8.2/src/sentinel.c 2013-12-02 15:07:46.000000000 +0000 +++ redis-2.8.4/src/sentinel.c 2014-01-13 16:09:58.000000000 +0000 @@ -135,7 +135,7 @@ if the link is idle and must be reconnected. */ 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 - we received an hello from this Sentinel + we received a hello from this Sentinel via Pub/Sub. */ mstime_t last_master_down_reply_time; /* Time of last reply to SENTINEL is-master-down command. */ @@ -370,6 +370,7 @@ void sentinelCommand(redisClient *c); void sentinelInfoCommand(redisClient *c); +void sentinelSetCommand(redisClient *c); struct redisCommand sentinelcmds[] = { {"ping",pingCommand,1,"",0,NULL,0,0,0,0,0}, @@ -393,7 +394,7 @@ /* Remove usual Redis commands from the command table, then just add * the SENTINEL command. */ - dictEmpty(server.commands); + dictEmpty(server.commands,NULL); for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) { int retval; struct redisCommand *cmd = sentinelcmds+j; @@ -942,7 +943,7 @@ } /* Release this instance and all its slaves, sentinels, hiredis connections. - * This function also takes care of unlinking the instance from the main + * This function does not take care of unlinking the instance from the main * masters table (if it is a master) or from its master sentinels/slaves table * if it is a slave or sentinel. */ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) { @@ -1133,6 +1134,8 @@ ri->slave_master_host = NULL; ri->last_avail_time = mstime(); ri->last_pong_time = mstime(); + ri->role_reported_time = mstime(); + ri->role_reported = SRI_MASTER; if (flags & SENTINEL_GENERATE_EVENT) sentinelEvent(REDIS_WARNING,"+reset-master",ri,"%@"); } @@ -1492,7 +1495,7 @@ /* ====================== hiredis connection handling ======================= */ -/* Completely disconnect an hiredis link from an instance. */ +/* Completely disconnect a hiredis link from an instance. */ void sentinelKillLink(sentinelRedisInstance *ri, redisAsyncContext *c) { if (ri->cc == c) { ri->cc = NULL; @@ -1504,7 +1507,7 @@ redisAsyncFree(c); } -/* This function takes an hiredis context that is in an error condition +/* 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. * @@ -1638,8 +1641,6 @@ sds *lines; int numlines, j; int role = 0; - int runid_changed = 0; /* true if runid changed. */ - int first_runid = 0; /* true if this is the first runid we receive. */ /* The following fields must be reset to a given value in the case they * are not found at all in the INFO output. */ @@ -1655,10 +1656,8 @@ if (sdslen(l) >= 47 && !memcmp(l,"run_id:",7)) { if (ri->runid == NULL) { ri->runid = sdsnewlen(l+7,40); - first_runid = 1; } else { if (strncmp(ri->runid,l+7,40) != 0) { - runid_changed = 1; sentinelEvent(REDIS_NOTICE,"+reboot",ri,"%@"); sdsfree(ri->runid); ri->runid = sdsnewlen(l+7,40); @@ -1762,22 +1761,22 @@ * Some things will not happen if sentinel.tilt is true, but some will * still be processed. */ + /* Remember when the role changed. */ + if (role != ri->role_reported) { + ri->role_reported_time = mstime(); + ri->role_reported = role; + if (role == SRI_SLAVE) ri->slave_conf_change_time = mstime(); + } + /* Handle master -> slave role switch. */ if ((ri->flags & SRI_MASTER) && role == SRI_SLAVE) { - if (ri->role_reported != SRI_SLAVE) { - ri->role_reported_time = mstime(); - ri->role_reported = SRI_SLAVE; - ri->slave_conf_change_time = mstime(); - } + /* Nothing to do, but masters claiming to be slaves are + * considered to be unreachable by Sentinel, so eventually + * a failover will be triggered. */ } /* Handle slave -> master role switch. */ if ((ri->flags & SRI_SLAVE) && role == SRI_MASTER) { - if (ri->role_reported != SRI_MASTER) { - ri->role_reported_time = mstime(); - ri->role_reported = SRI_MASTER; - } - /* If this is a promoted slave we can change state to the * failover state machine. */ if (!sentinel.tilt && @@ -2272,6 +2271,30 @@ addReplyBulkCString(c,"quorum"); addReplyBulkLongLong(c,ri->quorum); fields++; + + addReplyBulkCString(c,"down-after-milliseconds"); + addReplyBulkLongLong(c,ri->down_after_period); + fields++; + + addReplyBulkCString(c,"failover-timeout"); + addReplyBulkLongLong(c,ri->failover_timeout); + fields++; + + addReplyBulkCString(c,"parallel-syncs"); + addReplyBulkLongLong(c,ri->parallel_syncs); + fields++; + + if (ri->notification_script) { + addReplyBulkCString(c,"notification-script"); + addReplyBulkCString(c,ri->notification_script); + fields++; + } + + if (ri->client_reconfig_script) { + addReplyBulkCString(c,"client-reconfig-script"); + addReplyBulkCString(c,ri->client_reconfig_script); + fields++; + } } /* Only slaves */ @@ -2358,8 +2381,14 @@ if (!strcasecmp(c->argv[1]->ptr,"masters")) { /* SENTINEL MASTERS */ if (c->argc != 2) goto numargserr; - addReplyDictOfRedisInstances(c,sentinel.masters); + } else if (!strcasecmp(c->argv[1]->ptr,"master")) { + /* SENTINEL MASTER */ + sentinelRedisInstance *ri; + + if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2])) + == NULL) return; + addReplySentinelRedisInstance(c,ri); } else if (!strcasecmp(c->argv[1]->ptr,"slaves")) { /* SENTINEL SLAVES */ sentinelRedisInstance *ri; @@ -2377,7 +2406,7 @@ return; addReplyDictOfRedisInstances(c,ri->sentinels); } else if (!strcasecmp(c->argv[1]->ptr,"is-master-down-by-addr")) { - /* SENTINEL IS-MASTER-DOWN-BY-ADDR */ + /* SENTINEL IS-MASTER-DOWN-BY-ADDR */ sentinelRedisInstance *ri; long long req_epoch; uint64_t leader_epoch = 0; @@ -2460,6 +2489,53 @@ if (c->argc != 2) goto numargserr; sentinelPendingScriptsCommand(c); + } else if (!strcasecmp(c->argv[1]->ptr,"monitor")) { + /* SENTINEL MONITOR */ + long quorum, port; + char buf[32]; + + if (c->argc != 6) goto numargserr; + if (getLongFromObjectOrReply(c,c->argv[5],&quorum,"Invalid quorum") + != REDIS_OK) return; + if (getLongFromObjectOrReply(c,c->argv[4],&port,"Invalid port") + != REDIS_OK) return; + /* Make sure the IP field is actually a valid IP before passing it + * to createSentinelRedisInstance(), otherwise we may trigger a + * DNS lookup at runtime. */ + if (anetResolveIP(NULL,c->argv[3]->ptr,buf,sizeof(buf)) == ANET_ERR) { + addReplyError(c,"Invalid IP address specified"); + return; + } + if (createSentinelRedisInstance(c->argv[2]->ptr,SRI_MASTER, + c->argv[3]->ptr,port,quorum,NULL) == NULL) + { + switch(errno) { + case EBUSY: + addReplyError(c,"Duplicated master name"); + break; + case EINVAL: + addReplyError(c,"Invalid port number"); + break; + default: + addReplyError(c,"Unspecified error adding the instance"); + break; + } + } else { + sentinelFlushConfig(); + addReply(c,shared.ok); + } + } else if (!strcasecmp(c->argv[1]->ptr,"remove")) { + /* SENTINEL REMOVE */ + sentinelRedisInstance *ri; + + if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2])) + == NULL) return; + dictDelete(sentinel.masters,c->argv[2]->ptr); + sentinelFlushConfig(); + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"set")) { + if (c->argc < 3 || c->argc % 2 == 0) goto numargserr; + sentinelSetCommand(c); } else { addReplyErrorFormat(c,"Unknown sentinel subcommand '%s'", (char*)c->argv[1]->ptr); @@ -2467,10 +2543,11 @@ return; numargserr: - addReplyErrorFormat(c,"Wrong number of commands for 'sentinel %s'", + addReplyErrorFormat(c,"Wrong number of arguments for 'sentinel %s'", (char*)c->argv[1]->ptr); } +/* SENTINEL INFO [section] */ void sentinelInfoCommand(redisClient *c) { char *section = c->argc == 2 ? c->argv[1]->ptr : "default"; sds info = sdsempty(); @@ -2530,6 +2607,86 @@ addReply(c,shared.crlf); } +/* SENTINEL SET [