From: Salvatore Sanfilippo <antirez@gmail.com> Date: Sat, 21 Apr 2012 10:27:15 +0000 (-0700) Subject: Merge pull request #414 from chobits/unstable X-Git-Url: https://git.saurik.com/redis.git/commitdiff_plain/28d1ee2cda01bccdd2424f8fdbb4a924de37eaa3?hp=f2305046ef4695cfe60f35aa4a9e8df565fb1811 Merge pull request #414 from chobits/unstable fix typos in tests/assets/default.conf --- diff --git a/README b/README index bba2439c..1c3f5746 100644 --- a/README +++ b/README @@ -7,6 +7,13 @@ documentation at http://redis.io Building Redis -------------- +Redis can be compiled and used on Linux, OSX, OpenBSD, NetBSD, FreeBSD. +We support big endian and little endian architectures. + +It may compile on Solaris derived systems (for instance SmartOS) but our +support for this platform is "best effort" and Redis is not guaranteed to +work as well as in Linux, OSX, and *BSD there. + It is as simple as: % make diff --git a/deps/Makefile b/deps/Makefile index b881c814..7cd9c0f6 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -1,17 +1,6 @@ # Redis dependency Makefile -UNAME_S:=$(shell sh -c 'uname -s 2> /dev/null || echo not') - -LUA_CFLAGS=-O2 -Wall $(ARCH) -ifeq ($(UNAME_S),SunOS) - # Make isinf() available - LUA_CFLAGS+= -D__C99FEATURES__=1 -endif - -JEMALLOC_CFLAGS= -ifeq ($(ARCH),-m32) - JEMALLOC_CFLAGS+=CFLAGS="-std=gnu99 -Wall -pipe -g3 -fvisibility=hidden -O3 -funroll-loops -m32" -endif +uname_S:= $(shell sh -c 'uname -s 2>/dev/null || echo not') CCCOLOR="\033[34m" LINKCOLOR="\033[34;1m" @@ -23,37 +12,67 @@ ENDCOLOR="\033[0m" default: @echo "Explicit target required" -# Clean everything when ARCH is different -ifneq ($(shell sh -c '[ -f .make-arch ] && cat .make-arch'), $(ARCH)) -.make-arch: distclean -else -.make-arch: +.PHONY: default + +# Prerequisites target +.make-prerequisites: + @touch $@ + +# Clean everything when CFLAGS is different +ifneq ($(shell sh -c '[ -f .make-cflags ] && cat .make-cflags || echo none'), $(CFLAGS)) +.make-cflags: distclean + -(echo "$(CFLAGS)" > .make-cflags) +.make-prerequisites: .make-cflags endif -.make-arch: - -(echo $(ARCH) > .make-arch) +# Clean everything when LDFLAGS is different +ifneq ($(shell sh -c '[ -f .make-ldflags ] && cat .make-ldflags || echo none'), $(LDFLAGS)) +.make-ldflags: distclean + -(echo "$(LDFLAGS)" > .make-ldflags) +.make-prerequisites: .make-ldflags +endif distclean: -(cd hiredis && $(MAKE) clean) > /dev/null || true -(cd linenoise && $(MAKE) clean) > /dev/null || true -(cd lua && $(MAKE) clean) > /dev/null || true -(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true - -(rm -f .make-arch) + -(rm -f .make-*) + +.PHONY: distclean + +hiredis: .make-prerequisites + @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) + cd hiredis && $(MAKE) static + +.PHONY: hiredis + +linenoise: .make-prerequisites + @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) + cd linenoise && $(MAKE) + +.PHONY: linenoise + +ifeq ($(uname_S),SunOS) + # Make isinf() available + LUA_CFLAGS= -D__C99FEATURES__=1 +endif + +LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI $(CFLAGS) +LUA_LDFLAGS+= $(LDFLAGS) -hiredis: .make-arch - @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)hiredis$(ENDCOLOR) - cd hiredis && $(MAKE) static ARCH="$(ARCH)" +lua: .make-prerequisites + @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) + cd lua/src && $(MAKE) all CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(LUA_LDFLAGS)" -linenoise: .make-arch - @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)linenoise$(ENDCOLOR) - cd linenoise && $(MAKE) ARCH="$(ARCH)" +.PHONY: lua -lua: .make-arch - @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)lua$(ENDCOLOR) - cd lua && $(MAKE) CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(ARCH)" ansi +JEMALLOC_CFLAGS= -std=gnu99 -Wall -pipe -g3 -O3 -funroll-loops $(CFLAGS) +JEMALLOC_LDFLAGS= $(LDFLAGS) -jemalloc: .make-arch - @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)jemalloc$(ENDCOLOR) - cd jemalloc && ./configure $(JEMALLOC_CFLAGS) --with-jemalloc-prefix=je_ --enable-cc-silence && $(MAKE) lib/libjemalloc.a +jemalloc: .make-prerequisites + @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) + cd jemalloc && ./configure --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" + cd jemalloc && $(MAKE) lib/libjemalloc.a -.PHONY: default conditional_clean hiredis linenoise lua jemalloc +.PHONY: jemalloc diff --git a/deps/linenoise/Makefile b/deps/linenoise/Makefile index 841f3907..1dd894b4 100644 --- a/deps/linenoise/Makefile +++ b/deps/linenoise/Makefile @@ -1,10 +1,21 @@ -linenoise_example: linenoise.h linenoise.c +STD= +WARN= -Wall +OPT= -Os + +R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) +R_LDFLAGS= $(LDFLAGS) +DEBUG= -g + +R_CC=$(CC) $(R_CFLAGS) +R_LD=$(CC) $(R_LDFLAGS) + +linenoise.o: linenoise.h linenoise.c linenoise_example: linenoise.o example.o - $(CC) $(ARCH) -Wall -W -Os -g -o linenoise_example linenoise.o example.o + $(R_LD) -o $@ $^ .c.o: - $(CC) $(ARCH) -c -Wall -W -Os -g $< + $(R_CC) -c $< clean: rm -f linenoise_example *.o diff --git a/redis.conf b/redis.conf index 1b79e09e..b1ea6c2e 100644 --- a/redis.conf +++ b/redis.conf @@ -114,6 +114,15 @@ stop-writes-on-bgsave-error yes # the dataset will likely be bigger if you have compressible values or keys. rdbcompression yes +# Since verison 5 of RDB a CRC64 checksum is placed at the end of the file. +# This makes the format more resistant to corruption but there is a performance +# hit to pay (around 10%) when saving and loading RDB files, so you can disable it +# for maximum performances. +# +# RDB files created with checksum disabled have a checksum of zero that will +# tell the loading code to skip the check. +rdbchecksum yes + # The filename where to dump the DB dbfilename dump.rdb @@ -289,21 +298,23 @@ slave-read-only yes ############################## APPEND ONLY MODE ############################### -# By default Redis asynchronously dumps the dataset on disk. If you can live -# with the idea that the latest records will be lost if something like a crash -# happens this is the preferred way to run Redis. If instead you care a lot -# about your data and don't want to that a single record can get lost you should -# enable the append only mode: when this mode is enabled Redis will append -# every write operation received in the file appendonly.aof. This file will -# be read on startup in order to rebuild the full dataset in memory. +# By default Redis asynchronously dumps the dataset on disk. This mode is +# good enough in many applications, but an issue with the Redis process or +# a power outage may result into a few minutes of writes lost (depending on +# the configured save points). # -# Note that you can have both the async dumps and the append only file if you -# like (you have to comment the "save" statements above to disable the dumps). -# Still if append only mode is enabled Redis will load the data from the -# log file at startup ignoring the dump.rdb file. +# The Append Only File is an alternative persistence mode that provides +# much better durability. For instance using the default data fsync policy +# (see later in the config file) Redis can lose just one second of writes in a +# dramatic event like a server power outage, or a single write if something +# wrong with the Redis process itself happens, but the operating system is +# still running correctly. # -# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append -# log file in background when it gets too big. +# AOF and RDB persistence can be enabled at the same time without problems. +# If the AOF is enabled on startup Redis will load the AOF, that is the file +# with the better durability guarantees. +# +# Please check http://redis.io/topics/persistence for more information. appendonly no @@ -318,7 +329,7 @@ appendonly no # # no: don't fsync, just let the OS flush the data when it wants. Faster. # always: fsync after every write to the append only log . Slow, Safest. -# everysec: fsync only if one second passed since the last fsync. Compromise. +# everysec: fsync only one time every second. Compromise. # # The default is "everysec" that's usually the right compromise between # speed and data safety. It's up to you to understand if you can relax this to @@ -328,6 +339,9 @@ appendonly no # or on the contrary, use "always" that's very slow but a bit safer than # everysec. # +# More details please check the following article: +# http://antirez.com/post/redis-persistence-demystified.html +# # If unsure, use "everysec". # appendfsync always diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..aee7aacf --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,5 @@ +*.gcda +*.gcno +*.gcov +redis.info +lcov-html diff --git a/src/Makefile b/src/Makefile index 43922c2d..a0913688 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,27 +1,32 @@ # Redis Makefile # Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com> # This file is released under the BSD license, see the COPYING file +# +# The Makefile composes the final FINAL_CFLAGS and FINAL_LDFLAGS using +# what is needed for Redis plus the standard CFLAGS and LDFLAGS passed. +# However when building the dependencies (Jemalloc, Lua, Hiredis, ...) +# CFLAGS and LDFLAGS are propagated to the dependencies, so to pass +# flags only to be used when compiling / linking Redis itself REDIS_CFLAGS +# and REDIS_LDFLAGS are used instead (this is the case of 'make gcov'). +# +# Dependencies are stored in the Makefile.dep file. To rebuild this file +# Just use 'make dep', but this is only needed by developers. release_hdr := $(shell sh -c './mkreleasehdr.sh') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') OPTIMIZATION?=-O2 DEPENDENCY_TARGETS=hiredis linenoise lua -ifeq ($(uname_S),SunOS) - CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6 - CCLINK?=-ldl -lnsl -lsocket -lm -lpthread - DEBUG?=-g -ggdb -else - CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF) - CCLINK?=-lm -pthread - DEBUG?=-g -rdynamic -ggdb -endif +# Default settings +STD= -std=c99 -pedantic +WARN= -Wall +OPT= $(OPTIMIZATION) # Default allocator ifeq ($(uname_S),Linux) - MALLOC?=jemalloc + MALLOC=jemalloc else - MALLOC?=libc + MALLOC=libc endif # Backwards compatibility for selecting an allocator @@ -37,25 +42,42 @@ ifeq ($(USE_JEMALLOC),yes) MALLOC=jemalloc endif +# Override default settings if possible +-include .make-settings + +ifeq ($(uname_S),SunOS) + FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS) -D__EXTENSIONS__ -D_XPG6 + FINAL_LDFLAGS= $(LDFLAGS) $(REDIS_LDFLAGS) + FINAL_LIBS= -ldl -lnsl -lsocket -lm -lpthread + DEBUG= -g -ggdb +else + FINAL_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS) + FINAL_LDFLAGS= $(LDFLAGS) $(REDIS_LDFLAGS) + FINAL_LIBS= -lm -pthread + DEBUG= -g -rdynamic -ggdb +endif + +# Include paths to dependencies +FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src + ifeq ($(MALLOC),tcmalloc) - ALLOC_LINK=-ltcmalloc - ALLOC_FLAGS=-DUSE_TCMALLOC + FINAL_CFLAGS+= -DUSE_TCMALLOC + FINAL_LIBS+= -ltcmalloc endif ifeq ($(MALLOC),tcmalloc_minimal) - ALLOC_LINK=-ltcmalloc_minimal - ALLOC_FLAGS=-DUSE_TCMALLOC + FINAL_CFLAGS+= -DUSE_TCMALLOC + FINAL_LIBS+= -ltcmalloc_minimal endif ifeq ($(MALLOC),jemalloc) - ALLOC_LINK=../deps/jemalloc/lib/libjemalloc.a -ldl - ALLOC_FLAGS=-DUSE_JEMALLOC -I../deps/jemalloc/include DEPENDENCY_TARGETS+= jemalloc + FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include + FINAL_LIBS+= ../deps/jemalloc/lib/libjemalloc.a -ldl endif -CCLINK+= $(ALLOC_LINK) -CFLAGS+= $(ALLOC_FLAGS) -CCOPT= $(CFLAGS) $(ARCH) $(PROF) +REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) +REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS) PREFIX= /usr/local INSTALL_BIN= $(PREFIX)/bin @@ -69,209 +91,134 @@ MAKECOLOR="\033[32;1m" ENDCOLOR="\033[0m" ifndef V -QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR); -QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR); +QUIET_CC = @printf ' %b %b\n' $(CCCOLOR)CC$(ENDCOLOR) $(SRCCOLOR)$@$(ENDCOLOR) 1>&2; +QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) 1>&2; endif -OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o -BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o -CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o -CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o -CHECKAOFOBJ = redis-check-aof.o - -PRGNAME = redis-server -BENCHPRGNAME = redis-benchmark -CLIPRGNAME = redis-cli -CHECKDUMPPRGNAME = redis-check-dump -CHECKAOFPRGNAME = redis-check-aof - -all: redis-benchmark redis-cli redis-check-dump redis-check-aof redis-server +REDIS_SERVER_NAME= redis-server +REDIS_SERVER_OBJ= adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o +REDIS_CLI_NAME= redis-cli +REDIS_CLI_OBJ= anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o +REDIS_BENCHMARK_NAME= redis-benchmark +REDIS_BENCHMARK_OBJ= ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o redis-benchmark.o +REDIS_CHECK_DUMP_NAME= redis-check-dump +REDIS_CHECK_DUMP_OBJ= redis-check-dump.o lzf_c.o lzf_d.o +REDIS_CHECK_AOF_NAME= redis-check-aof +REDIS_CHECK_AOF_OBJ= redis-check-aof.o + +all: $(REDIS_SERVER_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME) @echo "" @echo "Hint: To run 'make test' is a good idea ;)" @echo "" -# Deps (use make dep to generate this) -adlist.o: adlist.c adlist.h zmalloc.h -ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c -ae_epoll.o: ae_epoll.c -ae_kqueue.o: ae_kqueue.c -ae_select.o: ae_select.c -anet.o: anet.c fmacros.h anet.h -aof.o: aof.c redis.h fmacros.h config.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 bio.h -bio.o: bio.c redis.h fmacros.h config.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 bio.h -cluster.o: cluster.c redis.h fmacros.h config.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 -config.o: config.c redis.h fmacros.h config.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 -crc16.o: crc16.c redis.h fmacros.h config.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 -db.o: db.c redis.h fmacros.h config.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 -debug.o: debug.c redis.h fmacros.h config.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 -dict.o: dict.c fmacros.h dict.h zmalloc.h -endianconv.o: endianconv.c -intset.o: intset.c intset.h zmalloc.h endianconv.h -lzf_c.o: lzf_c.c lzfP.h -lzf_d.o: lzf_d.c lzfP.h -multi.o: multi.c redis.h fmacros.h config.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 -networking.o: networking.c redis.h fmacros.h config.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 -object.o: object.c redis.h fmacros.h config.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 -pqsort.o: pqsort.c -pubsub.o: pubsub.c redis.h fmacros.h config.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 -rand.o: rand.c -rdb.o: rdb.c redis.h fmacros.h config.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 lzf.h \ - zipmap.h -redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \ - ../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h -redis-check-aof.o: redis-check-aof.c fmacros.h config.h -redis-check-dump.o: redis-check-dump.c lzf.h -redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \ - sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h -redis.o: redis.c redis.h fmacros.h config.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 \ - slowlog.h bio.h asciilogo.h -release.o: release.c release.h -replication.o: replication.c redis.h fmacros.h config.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 -scripting.o: scripting.c redis.h fmacros.h config.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 -sds.o: sds.c sds.h zmalloc.h -sha1.o: sha1.c sha1.h config.h -slowlog.o: slowlog.c redis.h fmacros.h config.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 slowlog.h -sort.o: sort.c redis.h fmacros.h config.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 \ - pqsort.h -syncio.o: syncio.c redis.h fmacros.h config.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 -t_hash.o: t_hash.c redis.h fmacros.h config.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 -t_list.o: t_list.c redis.h fmacros.h config.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 -t_set.o: t_set.c redis.h fmacros.h config.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 -t_string.o: t_string.c redis.h fmacros.h config.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 -t_zset.o: t_zset.c redis.h fmacros.h config.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 -zipmap.o: zipmap.c zmalloc.h endianconv.h -zmalloc.o: zmalloc.c config.h zmalloc.h - -# Clean local objects when ARCH is different -ifneq ($(shell sh -c '[ -f .make-arch ] && cat .make-arch'), $(ARCH)) -.make-arch: clean -else -.make-arch: -endif +.PHONY: all -.make-arch: - -(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS) ARCH="$(ARCH)") - -(echo $(ARCH) > .make-arch) - -# Clean local objects when allocator changes -ifneq ($(shell sh -c '[ -f .make-malloc ] && cat .make-malloc'), $(MALLOC)) -.make-malloc: clean -else -.make-malloc: -endif - -.make-malloc: - -(echo $(MALLOC) > .make-malloc) +# Deps (use make dep to generate this) +include Makefile.dep -# Union of make-prerequisites -.make-prerequisites: .make-arch .make-malloc +dep: + $(REDIS_CC) -MM *.c > Makefile.dep + +.PHONY: dep + +persist-settings: distclean + echo STD=$(STD) >> .make-settings + echo WARN=$(WARN) >> .make-settings + echo OPT=$(OPT) >> .make-settings + echo MALLOC=$(MALLOC) >> .make-settings + echo CFLAGS=$(CFLAGS) >> .make-settings + echo LDFLAGS=$(LDFLAGS) >> .make-settings + echo REDIS_CFLAGS=$(REDIS_CFLAGS) >> .make-settings + echo REDIS_LDFLAGS=$(REDIS_LDFLAGS) >> .make-settings + echo PREV_FINAL_CFLAGS=$(FINAL_CFLAGS) >> .make-settings + echo PREV_FINAL_LDFLAGS=$(FINAL_LDFLAGS) >> .make-settings + -(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS)) + +.PHONY: persist-settings + +# Prerequisites target +.make-prerequisites: @touch $@ -redis-server: .make-prerequisites $(OBJ) - $(QUIET_LINK)$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ) ../deps/lua/src/liblua.a $(CCLINK) +# Clean everything, persist settings and build dependencies if anything changed +ifneq ($(strip $(PREV_FINAL_CFLAGS)), $(strip $(FINAL_CFLAGS))) +.make-prerequisites: persist-settings +endif -redis-benchmark: .make-prerequisites $(BENCHOBJ) - $(QUIET_LINK)$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ) ../deps/hiredis/libhiredis.a $(CCLINK) +ifneq ($(strip $(PREV_FINAL_LDFLAGS)), $(strip $(FINAL_LDFLAGS))) +.make-prerequisites: persist-settings +endif -redis-benchmark.o: redis-benchmark.c .make-prerequisites - $(QUIET_CC)$(CC) -c $(CFLAGS) -I../deps/hiredis $(DEBUG) $(COMPILE_TIME) $< +# redis-server +$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) + $(REDIS_LD) -o $@ $^ ../deps/lua/src/liblua.a $(FINAL_LIBS) -redis-cli: .make-prerequisites $(CLIOBJ) - $(QUIET_LINK)$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ) ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(CCLINK) +# redis-cli +$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ) + $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) -redis-cli.o: redis-cli.c .make-prerequisites - $(QUIET_CC)$(CC) -c $(CFLAGS) -I../deps/hiredis -I../deps/linenoise $(DEBUG) $(COMPILE_TIME) $< +# redis-benchmark +$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) + $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) -redis-check-dump: .make-prerequisites $(CHECKDUMPOBJ) - $(QUIET_LINK)$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ) $(CCLINK) +# redis-check-dump +$(REDIS_CHECK_DUMP_NAME): $(REDIS_CHECK_DUMP_OBJ) + $(REDIS_LD) -o $@ $^ $(FINAL_LIBS) -redis-check-aof: .make-prerequisites $(CHECKAOFOBJ) - $(QUIET_LINK)$(CC) -o $(CHECKAOFPRGNAME) $(CCOPT) $(DEBUG) $(CHECKAOFOBJ) $(CCLINK) +# redis-check-aof +$(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ) + $(REDIS_LD) -o $@ $^ $(FINAL_LIBS) -# Because the jemalloc.h header is generated as a part of the jemalloc build -# process, building it should complete before building any other object. Instead of -# depending on a single artifact, simply build all dependencies first. +# Because the jemalloc.h header is generated as a part of the jemalloc build, +# building it should complete before building any other object. Instead of +# depending on a single artifact, build all dependencies first. %.o: %.c .make-prerequisites - $(QUIET_CC)$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) -I../deps/lua/src $< - -.PHONY: all clean distclean + $(REDIS_CC) -c $< clean: - rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) $(CHECKAOFPRGNAME) *.o *.gcda *.gcno *.gcov + rm -rf $(REDIS_SERVER_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html + +.PHONY: clean distclean: clean -(cd ../deps && $(MAKE) distclean) - -(rm -f .make-arch .make-malloc) + -(rm -f .make-*) -dep: - $(CC) -MM *.c -I ../deps/hiredis -I ../deps/linenoise +.PHONY: distclean -test: redis-server redis-check-aof +test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) @(cd ..; ./runtest) -bench: - ./redis-benchmark +lcov: + $(MAKE) gcov + @(set -e; cd ..; ./runtest --clients 1) + @geninfo -o redis.info . + @genhtml --legend -o lcov-html redis.info -log: - git log '--pretty=format:%ad %s (%cn)' --date=short > ../Changelog +.PHONY: lcov + +bench: $(REDIS_BENCHMARK_NAME) + ./$(REDIS_BENCHMARK_NAME) 32bit: @echo "" @echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386" @echo "" - $(MAKE) ARCH="-m32" JEMALLOC_CFLAGS='CFLAGS="-std=gnu99 -Wall -pipe -g3 -fvisibility=hidden -O3 -funroll-loops -m32"' - -gprof: - $(MAKE) PROF="-pg" + $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" gcov: - $(MAKE) PROF="-fprofile-arcs -ftest-coverage" + $(MAKE) REDIS_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" REDIS_LDFLAGS="-fprofile-arcs -ftest-coverage" noopt: - $(MAKE) OPTIMIZATION="" - -32bitgprof: - $(MAKE) PROF="-pg" ARCH="-arch i386" + $(MAKE) OPT="-O0" src/help.h: @../utils/generate-command-help.rb > help.h install: all mkdir -p $(INSTALL_BIN) - $(INSTALL) $(PRGNAME) $(INSTALL_BIN) - $(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN) - $(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN) - $(INSTALL) $(CHECKDUMPPRGNAME) $(INSTALL_BIN) - $(INSTALL) $(CHECKAOFPRGNAME) $(INSTALL_BIN) + $(INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN) + $(INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN) + $(INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN) + $(INSTALL) $(REDIS_CHECK_DUMP_NAME) $(INSTALL_BIN) + $(INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN) diff --git a/src/Makefile.dep b/src/Makefile.dep new file mode 100644 index 00000000..d0254e85 --- /dev/null +++ b/src/Makefile.dep @@ -0,0 +1,104 @@ +adlist.o: adlist.c adlist.h zmalloc.h +ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c +ae_epoll.o: ae_epoll.c +ae_kqueue.o: ae_kqueue.c +ae_select.o: ae_select.c +anet.o: anet.c fmacros.h anet.h +aof.o: aof.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 bio.h +bio.o: bio.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 bio.h +cluster.o: cluster.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 endianconv.h +config.o: config.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 +crc16.o: crc16.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 +crc64.o: crc64.c +db.o: db.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 +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 +dict.o: dict.c fmacros.h dict.h zmalloc.h +endianconv.o: endianconv.c +intset.o: intset.c intset.h zmalloc.h endianconv.h +lzf_c.o: lzf_c.c lzfP.h +lzf_d.o: lzf_d.c lzfP.h +memtest.o: memtest.c +multi.o: multi.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 +networking.o: networking.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 +object.o: object.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 +pqsort.o: pqsort.c +pubsub.o: pubsub.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 +rand.o: rand.c +rdb.o: rdb.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 lzf.h zipmap.h \ + endianconv.h +redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \ + ../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h +redis-check-aof.o: redis-check-aof.c fmacros.h config.h +redis-check-dump.o: redis-check-dump.c lzf.h +redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \ + sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h +redis.o: redis.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 slowlog.h bio.h \ + asciilogo.h +release.o: release.c release.h +replication.o: replication.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 +rio.o: rio.c fmacros.h rio.h sds.h util.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 +sds.o: sds.c sds.h zmalloc.h +sha1.o: sha1.c sha1.h config.h +slowlog.o: slowlog.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 slowlog.h +sort.o: sort.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 pqsort.h +syncio.o: syncio.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 +t_hash.o: t_hash.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 +t_list.o: t_list.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 +t_set.o: t_set.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 +t_string.o: t_string.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 +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 +zipmap.o: zipmap.c zmalloc.h endianconv.h +zmalloc.o: zmalloc.c config.h zmalloc.h diff --git a/src/ae.c b/src/ae.c index 4099b125..668277a7 100644 --- a/src/ae.c +++ b/src/ae.c @@ -35,6 +35,7 @@ #include <sys/types.h> #include <unistd.h> #include <stdlib.h> +#include <poll.h> #include <string.h> #include "ae.h" @@ -369,21 +370,17 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags) /* Wait for millseconds until the given file descriptor becomes * writable/readable/exception */ int aeWait(int fd, int mask, long long milliseconds) { - struct timeval tv; - fd_set rfds, wfds, efds; + struct pollfd pfd; int retmask = 0, retval; - tv.tv_sec = milliseconds/1000; - tv.tv_usec = (milliseconds%1000)*1000; - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - - if (mask & AE_READABLE) FD_SET(fd,&rfds); - if (mask & AE_WRITABLE) FD_SET(fd,&wfds); - if ((retval = select(fd+1, &rfds, &wfds, &efds, &tv)) > 0) { - if (FD_ISSET(fd,&rfds)) retmask |= AE_READABLE; - if (FD_ISSET(fd,&wfds)) retmask |= AE_WRITABLE; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + if (mask & AE_READABLE) pfd.events |= POLLIN; + if (mask & AE_WRITABLE) pfd.events |= POLLOUT; + + if ((retval = poll(&pfd, 1, milliseconds))== 1) { + if (pfd.revents & POLLIN) retmask |= AE_READABLE; + if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; return retmask; } else { return retval; diff --git a/src/anet.c b/src/anet.c index ba4e6cce..434d945c 100644 --- a/src/anet.c +++ b/src/anet.c @@ -262,7 +262,11 @@ static int anetListen(char *err, int s, struct sockaddr *sa, socklen_t len) { close(s); return ANET_ERR; } - if (listen(s, 511) == -1) { /* the magic 511 constant is from nginx */ + + /* Use a backlog of 512 entries. We pass 511 to the listen() call because + * the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); + * which will thus give us a backlog of 512 entries */ + if (listen(s, 511) == -1) { anetSetError(err, "listen: %s", strerror(errno)); close(s); return ANET_ERR; diff --git a/src/aof.c b/src/aof.c index 3f7cd10f..115da29b 100644 --- a/src/aof.c +++ b/src/aof.c @@ -803,9 +803,9 @@ int rewriteAppendOnlyFileBackground(void) { if (server.sofd > 0) close(server.sofd); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) { - _exit(0); + exitFromChild(0); } else { - _exit(1); + exitFromChild(1); } } else { /* Parent */ diff --git a/src/cluster.c b/src/cluster.c index f76e8ff5..93f095c3 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -1,4 +1,5 @@ #include "redis.h" +#include "endianconv.h" #include <arpa/inet.h> #include <fcntl.h> @@ -1457,9 +1458,86 @@ void clusterCommand(redisClient *c) { } /* ----------------------------------------------------------------------------- - * RESTORE and MIGRATE commands + * DUMP, RESTORE and MIGRATE commands * -------------------------------------------------------------------------- */ +/* Generates a DUMP-format representation of the object 'o', adding it to the + * io stream pointed by 'rio'. This function can't fail. */ +void createDumpPayload(rio *payload, robj *o) { + unsigned char buf[2]; + uint64_t crc; + + /* Serialize the object in a RDB-like format. It consist of an object type + * byte followed by the serialized object. This is understood by RESTORE. */ + rioInitWithBuffer(payload,sdsempty()); + redisAssert(rdbSaveObjectType(payload,o)); + redisAssert(rdbSaveObject(payload,o)); + + /* Write the footer, this is how it looks like: + * ----------------+---------------------+---------------+ + * ... RDB payload | 2 bytes RDB version | 8 bytes CRC64 | + * ----------------+---------------------+---------------+ + * RDB version and CRC are both in little endian. + */ + + /* RDB version */ + buf[0] = REDIS_RDB_VERSION & 0xff; + buf[1] = (REDIS_RDB_VERSION >> 8) & 0xff; + payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,buf,2); + + /* CRC64 */ + crc = crc64(0,(unsigned char*)payload->io.buffer.ptr, + sdslen(payload->io.buffer.ptr)); + memrev64ifbe(&crc); + payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,&crc,8); +} + +/* Verify that the RDB version of the dump payload matches the one of this Redis + * instance and that the checksum is ok. + * If the DUMP payload looks valid REDIS_OK is returned, otherwise REDIS_ERR + * is returned. */ +int verifyDumpPayload(unsigned char *p, size_t len) { + unsigned char *footer; + uint16_t rdbver; + uint64_t crc; + + /* At least 2 bytes of RDB version and 8 of CRC64 should be present. */ + if (len < 10) return REDIS_ERR; + footer = p+(len-10); + + /* Verify RDB version */ + rdbver = (footer[1] << 8) | footer[0]; + if (rdbver != REDIS_RDB_VERSION) return REDIS_ERR; + + /* Verify CRC64 */ + crc = crc64(0,p,len-8); + memrev64ifbe(&crc); + return (memcmp(&crc,footer+2,8) == 0) ? REDIS_OK : REDIS_ERR; +} + +/* DUMP keyname + * DUMP is actually not used by Redis Cluster but it is the obvious + * complement of RESTORE and can be useful for different applications. */ +void dumpCommand(redisClient *c) { + robj *o, *dumpobj; + rio payload; + + /* Check if the key is here. */ + if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { + addReply(c,shared.nullbulk); + return; + } + + /* Create the DUMP encoded representation. */ + createDumpPayload(&payload,o); + + /* Transfer to the client */ + dumpobj = createObject(REDIS_STRING,payload.io.buffer.ptr); + addReplyBulk(c,dumpobj); + decrRefCount(dumpobj); + return; +} + /* RESTORE key ttl serialized-value */ void restoreCommand(redisClient *c) { long ttl; @@ -1481,6 +1559,12 @@ void restoreCommand(redisClient *c) { return; } + /* Verify RDB version and data checksum. */ + if (verifyDumpPayload(c->argv[3]->ptr,sdslen(c->argv[3]->ptr)) == REDIS_ERR) { + addReplyError(c,"DUMP payload version or checksum are wrong"); + return; + } + rioInitWithBuffer(&payload,c->argv[3]->ptr); if (((type = rdbLoadObjectType(&payload)) == -1) || ((obj = rdbLoadObject(type,&payload)) == NULL)) @@ -1491,7 +1575,7 @@ void restoreCommand(redisClient *c) { /* Create the key and set the TTL if any */ dbAdd(c->db,c->argv[1],obj); - if (ttl) setExpire(c->db,c->argv[1],time(NULL)+ttl); + if (ttl) setExpire(c->db,c->argv[1],mstime()+ttl); signalModifiedKey(c->db,c->argv[1]); addReply(c,shared.ok); server.dirty++; @@ -1502,7 +1586,7 @@ void migrateCommand(redisClient *c) { int fd; long timeout; long dbid; - time_t ttl; + long long ttl = 0, expireat; robj *o; rio cmd, payload; @@ -1530,28 +1614,32 @@ void migrateCommand(redisClient *c) { return; } if ((aeWait(fd,AE_WRITABLE,timeout*1000) & AE_WRITABLE) == 0) { - addReplyError(c,"Timeout connecting to the client"); + addReplySds(c,sdsnew("-IOERR error or timeout connecting to the client\r\n")); return; } + /* Create RESTORE payload and generate the protocol to call the command. */ rioInitWithBuffer(&cmd,sdsempty()); redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',2)); redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"SELECT",6)); redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid)); - ttl = getExpire(c->db,c->argv[3]); + expireat = getExpire(c->db,c->argv[3]); + if (expireat != -1) { + ttl = expireat-mstime(); + if (ttl < 1) ttl = 1; + } redisAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',4)); redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"RESTORE",7)); redisAssertWithInfo(c,NULL,c->argv[3]->encoding == REDIS_ENCODING_RAW); redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,c->argv[3]->ptr,sdslen(c->argv[3]->ptr))); - redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,(ttl == -1) ? 0 : ttl)); + redisAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl)); /* Finally the last argument that is the serailized object payload - * in the form: <type><rdb-serialized-object>. */ - rioInitWithBuffer(&payload,sdsempty()); - redisAssertWithInfo(c,NULL,rdbSaveObjectType(&payload,o)); - redisAssertWithInfo(c,NULL,rdbSaveObject(&payload,o) != -1); - redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,payload.io.buffer.ptr,sdslen(payload.io.buffer.ptr))); + * in the DUMP format. */ + createDumpPayload(&payload,o); + redisAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,payload.io.buffer.ptr, + sdslen(payload.io.buffer.ptr))); sdsfree(payload.io.buffer.ptr); /* Tranfer the query to the other node in 64K chunks. */ @@ -1562,7 +1650,7 @@ void migrateCommand(redisClient *c) { while ((towrite = sdslen(buf)-pos) > 0) { towrite = (towrite > (64*1024) ? (64*1024) : towrite); - nwritten = syncWrite(fd,buf+nwritten,towrite,timeout); + nwritten = syncWrite(fd,buf+pos,towrite,timeout); if (nwritten != (signed)towrite) goto socket_wr_err; pos += nwritten; } @@ -1601,50 +1689,18 @@ void migrateCommand(redisClient *c) { return; socket_wr_err: - redisLog(REDIS_NOTICE,"Can't write to target node for MIGRATE: %s", - strerror(errno)); - addReplyErrorFormat(c,"MIGRATE failed, writing to target node: %s.", - strerror(errno)); + addReplySds(c,sdsnew("-IOERR error or timeout writing to target instance\r\n")); sdsfree(cmd.io.buffer.ptr); close(fd); return; socket_rd_err: - redisLog(REDIS_NOTICE,"Can't read from target node for MIGRATE: %s", - strerror(errno)); - addReplyErrorFormat(c,"MIGRATE failed, reading from target node: %s.", - strerror(errno)); + addReplySds(c,sdsnew("-IOERR error or timeout reading from target node\r\n")); sdsfree(cmd.io.buffer.ptr); close(fd); return; } -/* DUMP keyname - * DUMP is actually not used by Redis Cluster but it is the obvious - * complement of RESTORE and can be useful for different applications. */ -void dumpCommand(redisClient *c) { - robj *o, *dumpobj; - rio payload; - - /* Check if the key is here. */ - if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { - addReply(c,shared.nullbulk); - return; - } - - /* Serialize the object in a RDB-like format. It consist of an object type - * byte followed by the serialized object. This is understood by RESTORE. */ - rioInitWithBuffer(&payload,sdsempty()); - redisAssertWithInfo(c,NULL,rdbSaveObjectType(&payload,o)); - redisAssertWithInfo(c,NULL,rdbSaveObject(&payload,o)); - - /* Transfer to the client */ - dumpobj = createObject(REDIS_STRING,payload.io.buffer.ptr); - addReplyBulk(c,dumpobj); - decrRefCount(dumpobj); - return; -} - /* The ASKING command is required after a -ASK redirection. * The client should issue ASKING before to actualy send the command to * the target instance. See the Redis Cluster specification for more diff --git a/src/config.c b/src/config.c index 8dffe288..6f98e5e5 100644 --- a/src/config.c +++ b/src/config.c @@ -155,6 +155,9 @@ void loadServerConfigFromString(char *config) { loadServerConfig(argv[1],NULL); } else if (!strcasecmp(argv[0],"maxclients") && argc == 2) { server.maxclients = atoi(argv[1]); + if (server.maxclients < 1) { + err = "Invalid max clients limit"; goto loaderr; + } } else if (!strcasecmp(argv[0],"maxmemory") && argc == 2) { server.maxmemory = memtoll(argv[1],NULL); } else if (!strcasecmp(argv[0],"maxmemory-policy") && argc == 2) { @@ -210,6 +213,10 @@ void loadServerConfigFromString(char *config) { if ((server.rdb_compression = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; } + } else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) { + if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) { + err = "argument must be 'yes' or 'no'"; goto loaderr; + } } else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) { if ((server.activerehashing = yesnotoi(argv[1])) == -1) { err = "argument must be 'yes' or 'no'"; goto loaderr; @@ -633,6 +640,16 @@ void configSetCommand(redisClient *c) { enableWatchdog(ll); else disableWatchdog(); + } else if (!strcasecmp(c->argv[2]->ptr,"rdbcompression")) { + int yn = yesnotoi(o->ptr); + + if (yn == -1) goto badfmt; + server.rdb_compression = yn; + } else if (!strcasecmp(c->argv[2]->ptr,"rdbchecksum")) { + int yn = yesnotoi(o->ptr); + + if (yn == -1) goto badfmt; + server.rdb_checksum = yn; } else { addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", (char*)c->argv[2]->ptr); @@ -734,6 +751,7 @@ void configGetCommand(redisClient *c) { server.stop_writes_on_bgsave_err); config_get_bool_field("daemonize", server.daemonize); config_get_bool_field("rdbcompression", server.rdb_compression); + config_get_bool_field("rdbchecksum", server.rdb_checksum); config_get_bool_field("activerehashing", server.activerehashing); /* Everything we can't handle with macros follows. */ diff --git a/src/config.h b/src/config.h index 6a69364a..136fd40c 100644 --- a/src/config.h +++ b/src/config.h @@ -59,10 +59,11 @@ #define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */ #define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp)*/ -#if defined(vax) || defined(ns32000) || defined(sun386) || defined(__i386__) || \ - defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \ - defined(__alpha__) || defined(__alpha) -#define BYTE_ORDER LITTLE_ENDIAN +#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || \ + defined(vax) || defined(ns32000) || defined(sun386) || \ + defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \ + defined(__alpha__) || defined(__alpha) +#define BYTE_ORDER LITTLE_ENDIAN #endif #if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \ diff --git a/src/crc64.c b/src/crc64.c new file mode 100644 index 00000000..ecdba90e --- /dev/null +++ b/src/crc64.c @@ -0,0 +1,191 @@ +/* Redis uses the CRC64 variant with "Jones" coefficients and init value of 0. + * + * Specification of this CRC64 variant follows: + * Name: crc-64-jones + * Width: 64 bites + * Poly: 0xad93d23594c935a9 + * Reflected In: True + * Xor_In: 0xffffffffffffffff + * Reflected_Out: True + * Xor_Out: 0x0 + * Check("123456789"): 0xe9c6d914c4b8d9ca + * + * Copyright (c) 2012, Salvatore Sanfilippo <antirez at gmail dot com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ + +#include <stdint.h> + +static const uint64_t crc64_tab[256] = { + UINT64_C(0x0000000000000000), UINT64_C(0x7ad870c830358979), + UINT64_C(0xf5b0e190606b12f2), UINT64_C(0x8f689158505e9b8b), + UINT64_C(0xc038e5739841b68f), UINT64_C(0xbae095bba8743ff6), + UINT64_C(0x358804e3f82aa47d), UINT64_C(0x4f50742bc81f2d04), + UINT64_C(0xab28ecb46814fe75), UINT64_C(0xd1f09c7c5821770c), + UINT64_C(0x5e980d24087fec87), UINT64_C(0x24407dec384a65fe), + UINT64_C(0x6b1009c7f05548fa), UINT64_C(0x11c8790fc060c183), + UINT64_C(0x9ea0e857903e5a08), UINT64_C(0xe478989fa00bd371), + UINT64_C(0x7d08ff3b88be6f81), UINT64_C(0x07d08ff3b88be6f8), + UINT64_C(0x88b81eabe8d57d73), UINT64_C(0xf2606e63d8e0f40a), + UINT64_C(0xbd301a4810ffd90e), UINT64_C(0xc7e86a8020ca5077), + UINT64_C(0x4880fbd87094cbfc), UINT64_C(0x32588b1040a14285), + UINT64_C(0xd620138fe0aa91f4), UINT64_C(0xacf86347d09f188d), + UINT64_C(0x2390f21f80c18306), UINT64_C(0x594882d7b0f40a7f), + UINT64_C(0x1618f6fc78eb277b), UINT64_C(0x6cc0863448deae02), + UINT64_C(0xe3a8176c18803589), UINT64_C(0x997067a428b5bcf0), + UINT64_C(0xfa11fe77117cdf02), UINT64_C(0x80c98ebf2149567b), + UINT64_C(0x0fa11fe77117cdf0), UINT64_C(0x75796f2f41224489), + UINT64_C(0x3a291b04893d698d), UINT64_C(0x40f16bccb908e0f4), + UINT64_C(0xcf99fa94e9567b7f), UINT64_C(0xb5418a5cd963f206), + UINT64_C(0x513912c379682177), UINT64_C(0x2be1620b495da80e), + UINT64_C(0xa489f35319033385), UINT64_C(0xde51839b2936bafc), + UINT64_C(0x9101f7b0e12997f8), UINT64_C(0xebd98778d11c1e81), + UINT64_C(0x64b116208142850a), UINT64_C(0x1e6966e8b1770c73), + UINT64_C(0x8719014c99c2b083), UINT64_C(0xfdc17184a9f739fa), + UINT64_C(0x72a9e0dcf9a9a271), UINT64_C(0x08719014c99c2b08), + UINT64_C(0x4721e43f0183060c), UINT64_C(0x3df994f731b68f75), + UINT64_C(0xb29105af61e814fe), UINT64_C(0xc849756751dd9d87), + UINT64_C(0x2c31edf8f1d64ef6), UINT64_C(0x56e99d30c1e3c78f), + UINT64_C(0xd9810c6891bd5c04), UINT64_C(0xa3597ca0a188d57d), + UINT64_C(0xec09088b6997f879), UINT64_C(0x96d1784359a27100), + UINT64_C(0x19b9e91b09fcea8b), UINT64_C(0x636199d339c963f2), + UINT64_C(0xdf7adabd7a6e2d6f), UINT64_C(0xa5a2aa754a5ba416), + UINT64_C(0x2aca3b2d1a053f9d), UINT64_C(0x50124be52a30b6e4), + UINT64_C(0x1f423fcee22f9be0), UINT64_C(0x659a4f06d21a1299), + UINT64_C(0xeaf2de5e82448912), UINT64_C(0x902aae96b271006b), + UINT64_C(0x74523609127ad31a), UINT64_C(0x0e8a46c1224f5a63), + UINT64_C(0x81e2d7997211c1e8), UINT64_C(0xfb3aa75142244891), + UINT64_C(0xb46ad37a8a3b6595), UINT64_C(0xceb2a3b2ba0eecec), + UINT64_C(0x41da32eaea507767), UINT64_C(0x3b024222da65fe1e), + UINT64_C(0xa2722586f2d042ee), UINT64_C(0xd8aa554ec2e5cb97), + UINT64_C(0x57c2c41692bb501c), UINT64_C(0x2d1ab4dea28ed965), + UINT64_C(0x624ac0f56a91f461), UINT64_C(0x1892b03d5aa47d18), + UINT64_C(0x97fa21650afae693), UINT64_C(0xed2251ad3acf6fea), + UINT64_C(0x095ac9329ac4bc9b), UINT64_C(0x7382b9faaaf135e2), + UINT64_C(0xfcea28a2faafae69), UINT64_C(0x8632586aca9a2710), + UINT64_C(0xc9622c4102850a14), UINT64_C(0xb3ba5c8932b0836d), + UINT64_C(0x3cd2cdd162ee18e6), UINT64_C(0x460abd1952db919f), + UINT64_C(0x256b24ca6b12f26d), UINT64_C(0x5fb354025b277b14), + UINT64_C(0xd0dbc55a0b79e09f), UINT64_C(0xaa03b5923b4c69e6), + UINT64_C(0xe553c1b9f35344e2), UINT64_C(0x9f8bb171c366cd9b), + UINT64_C(0x10e3202993385610), UINT64_C(0x6a3b50e1a30ddf69), + UINT64_C(0x8e43c87e03060c18), UINT64_C(0xf49bb8b633338561), + UINT64_C(0x7bf329ee636d1eea), UINT64_C(0x012b592653589793), + UINT64_C(0x4e7b2d0d9b47ba97), UINT64_C(0x34a35dc5ab7233ee), + UINT64_C(0xbbcbcc9dfb2ca865), UINT64_C(0xc113bc55cb19211c), + UINT64_C(0x5863dbf1e3ac9dec), UINT64_C(0x22bbab39d3991495), + UINT64_C(0xadd33a6183c78f1e), UINT64_C(0xd70b4aa9b3f20667), + UINT64_C(0x985b3e827bed2b63), UINT64_C(0xe2834e4a4bd8a21a), + UINT64_C(0x6debdf121b863991), UINT64_C(0x1733afda2bb3b0e8), + UINT64_C(0xf34b37458bb86399), UINT64_C(0x8993478dbb8deae0), + UINT64_C(0x06fbd6d5ebd3716b), UINT64_C(0x7c23a61ddbe6f812), + UINT64_C(0x3373d23613f9d516), UINT64_C(0x49aba2fe23cc5c6f), + UINT64_C(0xc6c333a67392c7e4), UINT64_C(0xbc1b436e43a74e9d), + UINT64_C(0x95ac9329ac4bc9b5), UINT64_C(0xef74e3e19c7e40cc), + UINT64_C(0x601c72b9cc20db47), UINT64_C(0x1ac40271fc15523e), + UINT64_C(0x5594765a340a7f3a), UINT64_C(0x2f4c0692043ff643), + UINT64_C(0xa02497ca54616dc8), UINT64_C(0xdafce7026454e4b1), + UINT64_C(0x3e847f9dc45f37c0), UINT64_C(0x445c0f55f46abeb9), + UINT64_C(0xcb349e0da4342532), UINT64_C(0xb1eceec59401ac4b), + UINT64_C(0xfebc9aee5c1e814f), UINT64_C(0x8464ea266c2b0836), + UINT64_C(0x0b0c7b7e3c7593bd), UINT64_C(0x71d40bb60c401ac4), + UINT64_C(0xe8a46c1224f5a634), UINT64_C(0x927c1cda14c02f4d), + UINT64_C(0x1d148d82449eb4c6), UINT64_C(0x67ccfd4a74ab3dbf), + UINT64_C(0x289c8961bcb410bb), UINT64_C(0x5244f9a98c8199c2), + UINT64_C(0xdd2c68f1dcdf0249), UINT64_C(0xa7f41839ecea8b30), + UINT64_C(0x438c80a64ce15841), UINT64_C(0x3954f06e7cd4d138), + UINT64_C(0xb63c61362c8a4ab3), UINT64_C(0xcce411fe1cbfc3ca), + UINT64_C(0x83b465d5d4a0eece), UINT64_C(0xf96c151de49567b7), + UINT64_C(0x76048445b4cbfc3c), UINT64_C(0x0cdcf48d84fe7545), + UINT64_C(0x6fbd6d5ebd3716b7), UINT64_C(0x15651d968d029fce), + UINT64_C(0x9a0d8ccedd5c0445), UINT64_C(0xe0d5fc06ed698d3c), + UINT64_C(0xaf85882d2576a038), UINT64_C(0xd55df8e515432941), + UINT64_C(0x5a3569bd451db2ca), UINT64_C(0x20ed197575283bb3), + UINT64_C(0xc49581ead523e8c2), UINT64_C(0xbe4df122e51661bb), + UINT64_C(0x3125607ab548fa30), UINT64_C(0x4bfd10b2857d7349), + UINT64_C(0x04ad64994d625e4d), UINT64_C(0x7e7514517d57d734), + UINT64_C(0xf11d85092d094cbf), UINT64_C(0x8bc5f5c11d3cc5c6), + UINT64_C(0x12b5926535897936), UINT64_C(0x686de2ad05bcf04f), + UINT64_C(0xe70573f555e26bc4), UINT64_C(0x9ddd033d65d7e2bd), + UINT64_C(0xd28d7716adc8cfb9), UINT64_C(0xa85507de9dfd46c0), + UINT64_C(0x273d9686cda3dd4b), UINT64_C(0x5de5e64efd965432), + UINT64_C(0xb99d7ed15d9d8743), UINT64_C(0xc3450e196da80e3a), + UINT64_C(0x4c2d9f413df695b1), UINT64_C(0x36f5ef890dc31cc8), + UINT64_C(0x79a59ba2c5dc31cc), UINT64_C(0x037deb6af5e9b8b5), + UINT64_C(0x8c157a32a5b7233e), UINT64_C(0xf6cd0afa9582aa47), + UINT64_C(0x4ad64994d625e4da), UINT64_C(0x300e395ce6106da3), + UINT64_C(0xbf66a804b64ef628), UINT64_C(0xc5bed8cc867b7f51), + UINT64_C(0x8aeeace74e645255), UINT64_C(0xf036dc2f7e51db2c), + UINT64_C(0x7f5e4d772e0f40a7), UINT64_C(0x05863dbf1e3ac9de), + UINT64_C(0xe1fea520be311aaf), UINT64_C(0x9b26d5e88e0493d6), + UINT64_C(0x144e44b0de5a085d), UINT64_C(0x6e963478ee6f8124), + UINT64_C(0x21c640532670ac20), UINT64_C(0x5b1e309b16452559), + UINT64_C(0xd476a1c3461bbed2), UINT64_C(0xaeaed10b762e37ab), + UINT64_C(0x37deb6af5e9b8b5b), UINT64_C(0x4d06c6676eae0222), + UINT64_C(0xc26e573f3ef099a9), UINT64_C(0xb8b627f70ec510d0), + UINT64_C(0xf7e653dcc6da3dd4), UINT64_C(0x8d3e2314f6efb4ad), + UINT64_C(0x0256b24ca6b12f26), UINT64_C(0x788ec2849684a65f), + UINT64_C(0x9cf65a1b368f752e), UINT64_C(0xe62e2ad306bafc57), + UINT64_C(0x6946bb8b56e467dc), UINT64_C(0x139ecb4366d1eea5), + UINT64_C(0x5ccebf68aecec3a1), UINT64_C(0x2616cfa09efb4ad8), + UINT64_C(0xa97e5ef8cea5d153), UINT64_C(0xd3a62e30fe90582a), + UINT64_C(0xb0c7b7e3c7593bd8), UINT64_C(0xca1fc72bf76cb2a1), + UINT64_C(0x45775673a732292a), UINT64_C(0x3faf26bb9707a053), + UINT64_C(0x70ff52905f188d57), UINT64_C(0x0a2722586f2d042e), + UINT64_C(0x854fb3003f739fa5), UINT64_C(0xff97c3c80f4616dc), + UINT64_C(0x1bef5b57af4dc5ad), UINT64_C(0x61372b9f9f784cd4), + UINT64_C(0xee5fbac7cf26d75f), UINT64_C(0x9487ca0fff135e26), + UINT64_C(0xdbd7be24370c7322), UINT64_C(0xa10fceec0739fa5b), + UINT64_C(0x2e675fb4576761d0), UINT64_C(0x54bf2f7c6752e8a9), + UINT64_C(0xcdcf48d84fe75459), UINT64_C(0xb71738107fd2dd20), + UINT64_C(0x387fa9482f8c46ab), UINT64_C(0x42a7d9801fb9cfd2), + UINT64_C(0x0df7adabd7a6e2d6), UINT64_C(0x772fdd63e7936baf), + UINT64_C(0xf8474c3bb7cdf024), UINT64_C(0x829f3cf387f8795d), + UINT64_C(0x66e7a46c27f3aa2c), UINT64_C(0x1c3fd4a417c62355), + UINT64_C(0x935745fc4798b8de), UINT64_C(0xe98f353477ad31a7), + UINT64_C(0xa6df411fbfb21ca3), UINT64_C(0xdc0731d78f8795da), + UINT64_C(0x536fa08fdfd90e51), UINT64_C(0x29b7d047efec8728), +}; + +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l) { + uint64_t j; + + for (j = 0; j < l; j++) { + uint8_t byte = s[j]; + crc = crc64_tab[(uint8_t)crc ^ byte] ^ (crc >> 8); + } + return crc; +} + +/* Test main */ +#ifdef TEST_MAIN +#include <stdio.h> +int main(void) { + printf("e9c6d914c4b8d9ca == %016llx\n", + (unsigned long long) crc64(0,(unsigned char*)"123456789",9)); + return 0; +} +#endif diff --git a/src/db.c b/src/db.c index 492e7aba..fff63f48 100644 --- a/src/db.c +++ b/src/db.c @@ -510,24 +510,23 @@ int expireIfNeeded(redisDb *db, robj *key) { * Expires Commands *----------------------------------------------------------------------------*/ -/* Given an string object return true if it contains exactly the "ms" - * or "MS" string. This is used in order to check if the last argument - * of EXPIRE, EXPIREAT or TTL is "ms" to switch into millisecond input/output */ -int stringObjectEqualsMs(robj *a) { - char *arg = a->ptr; - return tolower(arg[0]) == 'm' && tolower(arg[1]) == 's' && arg[2] == '\0'; -} - -void expireGenericCommand(redisClient *c, long long offset, int unit) { +/* This is the generic command implementation for EXPIRE, PEXPIRE, EXPIREAT + * and PEXPIREAT. Because the commad second argument may be relative or absolute + * the "basetime" argument is used to signal what the base time is (either 0 + * for *AT variants of the command, or the current time for relative expires). + * + * unit is either UNIT_SECONDS or UNIT_MILLISECONDS, and is only used for + * the argv[2] parameter. The basetime is always specified in milliesconds. */ +void expireGenericCommand(redisClient *c, long long basetime, int unit) { dictEntry *de; robj *key = c->argv[1], *param = c->argv[2]; - long long milliseconds; + long long when; /* unix time in milliseconds when the key will expire. */ - if (getLongLongFromObjectOrReply(c, param, &milliseconds, NULL) != REDIS_OK) + if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK) return; - if (unit == UNIT_SECONDS) milliseconds *= 1000; - milliseconds -= offset; + if (unit == UNIT_SECONDS) when *= 1000; + when += basetime; de = dictFind(c->db->dict,key->ptr); if (de == NULL) { @@ -540,7 +539,7 @@ void expireGenericCommand(redisClient *c, long long offset, int unit) { * * Instead we take the other branch of the IF statement setting an expire * (possibly in the past) and wait for an explicit DEL from the master. */ - if (milliseconds <= 0 && !server.loading && !server.masterhost) { + if (when <= mstime() && !server.loading && !server.masterhost) { robj *aux; redisAssertWithInfo(c,key,dbDelete(c->db,key)); @@ -554,7 +553,6 @@ void expireGenericCommand(redisClient *c, long long offset, int unit) { addReply(c, shared.cone); return; } else { - long long when = mstime()+milliseconds; setExpire(c->db,key,when); addReply(c,shared.cone); signalModifiedKey(c->db,key); @@ -564,19 +562,19 @@ void expireGenericCommand(redisClient *c, long long offset, int unit) { } void expireCommand(redisClient *c) { - expireGenericCommand(c,0,UNIT_SECONDS); + expireGenericCommand(c,mstime(),UNIT_SECONDS); } void expireatCommand(redisClient *c) { - expireGenericCommand(c,mstime(),UNIT_SECONDS); + expireGenericCommand(c,0,UNIT_SECONDS); } void pexpireCommand(redisClient *c) { - expireGenericCommand(c,0,UNIT_MILLISECONDS); + expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); } void pexpireatCommand(redisClient *c) { - expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); + expireGenericCommand(c,0,UNIT_MILLISECONDS); } void ttlGenericCommand(redisClient *c, int output_ms) { diff --git a/src/debug.c b/src/debug.c index e7b3ba40..37e6a1f7 100644 --- a/src/debug.c +++ b/src/debug.c @@ -105,7 +105,6 @@ void computeDatasetDigest(unsigned char *final) { mixDigest(digest,key,sdslen(key)); - /* Make sure the key is loaded if VM is active */ o = dictGetVal(de); aux = htonl(o->type); diff --git a/src/dict.c b/src/dict.c index 6fd4584e..2835de56 100644 --- a/src/dict.c +++ b/src/dict.c @@ -633,6 +633,21 @@ void dictEmpty(dict *d) { d->iterators = 0; } +void dictEnableResize(void) { + dict_can_resize = 1; +} + +void dictDisableResize(void) { + dict_can_resize = 0; +} + +#if 0 + +The following is code that we don't use for Redis currently, but that is part +of the library. + +/* ----------------------- Debugging ------------------------*/ + #define DICT_STATS_VECTLEN 50 static void _dictPrintStatsHt(dictht *ht) { unsigned long i, slots = 0, chainlen, maxchainlen = 0; @@ -686,20 +701,6 @@ void dictPrintStats(dict *d) { } } -void dictEnableResize(void) { - dict_can_resize = 1; -} - -void dictDisableResize(void) { - dict_can_resize = 0; -} - -#if 0 - -/* The following are just example hash table types implementations. - * Not useful for Redis so they are commented out. - */ - /* ----------------------- StringCopy Hash Table Type ------------------------*/ static unsigned int _dictStringCopyHTHashFunction(const void *key) diff --git a/src/help.h b/src/help.h index 51613c9b..cde95c1f 100644 --- a/src/help.h +++ b/src/help.h @@ -1,4 +1,4 @@ -/* Automatically generated by generate-command-help.rb, do not edit. */ +/* Automatically generated by utils/generate-command-help.rb, do not edit. */ #ifndef __REDIS_HELP_H #define __REDIS_HELP_H @@ -13,7 +13,8 @@ static char *commandGroups[] = { "pubsub", "transactions", "connection", - "server" + "server", + "scripting" }; struct commandHelp { @@ -27,612 +28,697 @@ struct commandHelp { "key value", "Append a value to a key", 1, - "1.3.3" }, + "2.0.0" }, { "AUTH", "password", "Authenticate to the server", 8, - "0.08" }, + "1.0.0" }, { "BGREWRITEAOF", "-", "Asynchronously rewrite the append-only file", 9, - "1.07" }, + "1.0.0" }, { "BGSAVE", "-", "Asynchronously save the dataset to disk", 9, - "0.07" }, + "1.0.0" }, { "BLPOP", "key [key ...] timeout", "Remove and get the first element in a list, or block until one is available", 2, - "1.3.1" }, + "2.0.0" }, { "BRPOP", "key [key ...] timeout", "Remove and get the last element in a list, or block until one is available", 2, - "1.3.1" }, + "2.0.0" }, { "BRPOPLPUSH", "source destination timeout", "Pop a value from a list, push it to another list and return it; or block until one is available", 2, - "2.1.7" }, + "2.2.0" }, { "CONFIG GET", "parameter", "Get the value of a configuration parameter", 9, - "2.0" }, + "2.0.0" }, { "CONFIG RESETSTAT", "-", "Reset the stats returned by INFO", 9, - "2.0" }, + "2.0.0" }, { "CONFIG SET", "parameter value", "Set a configuration parameter to the given value", 9, - "2.0" }, + "2.0.0" }, { "DBSIZE", "-", "Return the number of keys in the selected database", 9, - "0.07" }, + "1.0.0" }, { "DEBUG OBJECT", "key", "Get debugging information about a key", 9, - "0.101" }, + "1.0.0" }, { "DEBUG SEGFAULT", "-", "Make the server crash", 9, - "0.101" }, + "1.0.0" }, { "DECR", "key", "Decrement the integer value of a key by one", 1, - "0.07" }, + "1.0.0" }, { "DECRBY", "key decrement", "Decrement the integer value of a key by the given number", 1, - "0.07" }, + "1.0.0" }, { "DEL", "key [key ...]", "Delete a key", 0, - "0.07" }, + "1.0.0" }, { "DISCARD", "-", "Discard all commands issued after MULTI", 7, - "1.3.3" }, + "2.0.0" }, + { "DUMP", + "key", + "Return a serialized verison of the value stored at the specified key.", + 0, + "2.6.0" }, { "ECHO", "message", "Echo the given string", 8, - "0.07" }, + "1.0.0" }, + { "EVAL", + "script numkeys key [key ...] arg [arg ...]", + "Execute a Lua script server side", + 10, + "2.6.0" }, { "EXEC", "-", "Execute all commands issued after MULTI", 7, - "1.1.95" }, + "1.2.0" }, { "EXISTS", "key", "Determine if a key exists", - 9, - "0.07" }, + 0, + "1.0.0" }, { "EXPIRE", "key seconds", "Set a key's time to live in seconds", 0, - "0.09" }, + "1.0.0" }, { "EXPIREAT", "key timestamp", "Set the expiration for a key as a UNIX timestamp", 0, - "1.1" }, + "1.2.0" }, { "FLUSHALL", "-", "Remove all keys from all databases", 9, - "0.07" }, + "1.0.0" }, { "FLUSHDB", "-", "Remove all keys from the current database", 9, - "0.07" }, + "1.0.0" }, { "GET", "key", "Get the value of a key", 1, - "0.07" }, + "1.0.0" }, { "GETBIT", "key offset", "Returns the bit value at offset in the string value stored at key", 1, - "2.1.8" }, + "2.2.0" }, + { "GETRANGE", + "key start end", + "Get a substring of the string stored at a key", + 1, + "2.4.0" }, { "GETSET", "key value", "Set the string value of a key and return its old value", 1, - "0.091" }, + "1.0.0" }, { "HDEL", - "key field", - "Delete a hash field", + "key field [field ...]", + "Delete one or more hash fields", 5, - "1.3.10" }, + "2.0.0" }, { "HEXISTS", "key field", "Determine if a hash field exists", 5, - "1.3.10" }, + "2.0.0" }, { "HGET", "key field", "Get the value of a hash field", 5, - "1.3.10" }, + "2.0.0" }, { "HGETALL", "key", "Get all the fields and values in a hash", 5, - "1.3.10" }, + "2.0.0" }, { "HINCRBY", "key field increment", "Increment the integer value of a hash field by the given number", 5, - "1.3.10" }, + "2.0.0" }, + { "HINCRBYFLOAT", + "key field increment", + "Increment the float value of a hash field by the given amount", + 5, + "2.6.0" }, { "HKEYS", "key", "Get all the fields in a hash", 5, - "1.3.10" }, + "2.0.0" }, { "HLEN", "key", "Get the number of fields in a hash", 5, - "1.3.10" }, + "2.0.0" }, { "HMGET", "key field [field ...]", "Get the values of all the given hash fields", 5, - "1.3.10" }, + "2.0.0" }, { "HMSET", "key field value [field value ...]", "Set multiple hash fields to multiple values", 5, - "1.3.8" }, + "2.0.0" }, { "HSET", "key field value", "Set the string value of a hash field", 5, - "1.3.10" }, + "2.0.0" }, { "HSETNX", "key field value", "Set the value of a hash field, only if the field does not exist", 5, - "1.3.8" }, + "2.0.0" }, { "HVALS", "key", "Get all the values in a hash", 5, - "1.3.10" }, + "2.0.0" }, { "INCR", "key", "Increment the integer value of a key by one", 1, - "0.07" }, + "1.0.0" }, { "INCRBY", "key increment", - "Increment the integer value of a key by the given number", + "Increment the integer value of a key by the given amount", 1, - "0.07" }, + "1.0.0" }, + { "INCRBYFLOAT", + "key increment", + "Increment the float value of a key by the given amount", + 1, + "2.6.0" }, { "INFO", "-", "Get information and statistics about the server", 9, - "0.07" }, + "1.0.0" }, { "KEYS", "pattern", "Find all keys matching the given pattern", 0, - "0.07" }, + "1.0.0" }, { "LASTSAVE", "-", "Get the UNIX time stamp of the last successful save to disk", 9, - "0.07" }, + "1.0.0" }, { "LINDEX", "key index", "Get an element from a list by its index", 2, - "0.07" }, + "1.0.0" }, { "LINSERT", "key BEFORE|AFTER pivot value", "Insert an element before or after another element in a list", 2, - "2.1.1" }, + "2.2.0" }, { "LLEN", "key", "Get the length of a list", 2, - "0.07" }, + "1.0.0" }, { "LPOP", "key", "Remove and get the first element in a list", 2, - "0.07" }, + "1.0.0" }, { "LPUSH", - "key value", - "Prepend a value to a list", + "key value [value ...]", + "Prepend one or multiple values to a list", 2, - "0.07" }, + "1.0.0" }, { "LPUSHX", "key value", "Prepend a value to a list, only if the list exists", 2, - "2.1.1" }, + "2.2.0" }, { "LRANGE", "key start stop", "Get a range of elements from a list", 2, - "0.07" }, + "1.0.0" }, { "LREM", "key count value", "Remove elements from a list", 2, - "0.07" }, + "1.0.0" }, { "LSET", "key index value", "Set the value of an element in a list by its index", 2, - "0.07" }, + "1.0.0" }, { "LTRIM", "key start stop", "Trim a list to the specified range", 2, - "0.07" }, + "1.0.0" }, { "MGET", "key [key ...]", "Get the values of all the given keys", 1, - "0.07" }, + "1.0.0" }, + { "MIGRATE", + "host port key destination db timeout", + "Atomically transfer a key from a Redis instance to another one.", + 0, + "2.6.0" }, { "MONITOR", "-", "Listen for all requests received by the server in real time", 9, - "0.07" }, + "1.0.0" }, { "MOVE", "key db", "Move a key to another database", 0, - "0.07" }, + "1.0.0" }, { "MSET", "key value [key value ...]", "Set multiple keys to multiple values", 1, - "1.001" }, + "1.0.1" }, { "MSETNX", "key value [key value ...]", "Set multiple keys to multiple values, only if none of the keys exist", 1, - "1.001" }, + "1.0.1" }, { "MULTI", "-", "Mark the start of a transaction block", 7, - "1.1.95" }, + "1.2.0" }, + { "OBJECT", + "subcommand [arguments [arguments ...]]", + "Inspect the internals of Redis objects", + 0, + "2.2.3" }, { "PERSIST", "key", "Remove the expiration from a key", 0, - "2.1.2" }, + "2.2.0" }, + { "PEXPIRE", + "key milliseconds", + "Set a key's time to live in milliseconds", + 0, + "2.6.0" }, + { "PEXPIREAT", + "key milliseconds timestamp", + "Set the expiration for a key as a UNIX timestamp specified in milliseconds", + 0, + "2.6.0" }, { "PING", "-", "Ping the server", 8, - "0.07" }, + "1.0.0" }, + { "PSETEX", + "key milliseconds value", + "Set the value and expiration in milliseconds of a key", + 1, + "2.6.0" }, { "PSUBSCRIBE", - "pattern", + "pattern [pattern ...]", "Listen for messages published to channels matching the given patterns", 6, - "1.3.8" }, + "2.0.0" }, + { "PTTL", + "key", + "Get the time to live for a key in milliseconds", + 0, + "2.6.0" }, { "PUBLISH", "channel message", "Post a message to a channel", 6, - "1.3.8" }, + "2.0.0" }, { "PUNSUBSCRIBE", "[pattern [pattern ...]]", "Stop listening for messages posted to channels matching the given patterns", 6, - "1.3.8" }, + "2.0.0" }, { "QUIT", "-", "Close the connection", 8, - "0.07" }, + "1.0.0" }, { "RANDOMKEY", "-", "Return a random key from the keyspace", 0, - "0.07" }, + "1.0.0" }, { "RENAME", "key newkey", "Rename a key", 0, - "0.07" }, + "1.0.0" }, { "RENAMENX", "key newkey", "Rename a key, only if the new key does not exist", 0, - "0.07" }, + "1.0.0" }, + { "RESTORE", + "key ttl serialized value", + "Create a key using the provided serialized value, previously obtained using DUMP.", + 0, + "2.6.0" }, { "RPOP", "key", "Remove and get the last element in a list", 2, - "0.07" }, + "1.0.0" }, { "RPOPLPUSH", "source destination", "Remove the last element in a list, append it to another list and return it", 2, - "1.1" }, + "1.2.0" }, { "RPUSH", - "key value", - "Append a value to a list", + "key value [value ...]", + "Append one or multiple values to a list", 2, - "0.07" }, + "1.0.0" }, { "RPUSHX", "key value", "Append a value to a list, only if the list exists", 2, - "2.1.1" }, + "2.2.0" }, { "SADD", - "key member", - "Add a member to a set", + "key member [member ...]", + "Add one or more members to a set", 3, - "0.07" }, + "1.0.0" }, { "SAVE", "-", "Synchronously save the dataset to disk", 9, - "0.07" }, + "1.0.0" }, { "SCARD", "key", "Get the number of members in a set", 3, - "0.07" }, + "1.0.0" }, + { "SCRIPT EXISTS", + "script [script ...]", + "Check existence of scripts in the script cache.", + 10, + "2.6.0" }, + { "SCRIPT FLUSH", + "-", + "Remove all the scripts from the script cache.", + 10, + "2.6.0" }, + { "SCRIPT KILL", + "-", + "Kill the script currently in execution.", + 10, + "2.6.0" }, + { "SCRIPT LOAD", + "script", + "Load the specified Lua script into the script cache.", + 10, + "2.6.0" }, { "SDIFF", "key [key ...]", "Subtract multiple sets", 3, - "0.100" }, + "1.0.0" }, { "SDIFFSTORE", "destination key [key ...]", "Subtract multiple sets and store the resulting set in a key", 3, - "0.100" }, + "1.0.0" }, { "SELECT", "index", "Change the selected database for the current connection", 8, - "0.07" }, + "1.0.0" }, { "SET", "key value", "Set the string value of a key", 1, - "0.07" }, + "1.0.0" }, { "SETBIT", "key offset value", "Sets or clears the bit at offset in the string value stored at key", 1, - "2.1.8" }, + "2.2.0" }, { "SETEX", "key seconds value", "Set the value and expiration of a key", 1, - "1.3.10" }, + "2.0.0" }, { "SETNX", "key value", "Set the value of a key, only if the key does not exist", 1, - "0.07" }, + "1.0.0" }, { "SETRANGE", "key offset value", "Overwrite part of a string at key starting at the specified offset", 1, - "2.1.8" }, + "2.2.0" }, { "SHUTDOWN", - "-", + "[NOSAVE] [SAVE]", "Synchronously save the dataset to disk and then shut down the server", 9, - "0.07" }, + "1.0.0" }, { "SINTER", "key [key ...]", "Intersect multiple sets", 3, - "0.07" }, + "1.0.0" }, { "SINTERSTORE", "destination key [key ...]", "Intersect multiple sets and store the resulting set in a key", 3, - "0.07" }, + "1.0.0" }, { "SISMEMBER", "key member", "Determine if a given value is a member of a set", 3, - "0.07" }, + "1.0.0" }, { "SLAVEOF", "host port", "Make the server a slave of another instance, or promote it as master", 9, - "0.100" }, + "1.0.0" }, + { "SLOWLOG", + "subcommand [argument]", + "Manages the Redis slow queries log", + 9, + "2.2.12" }, { "SMEMBERS", "key", "Get all the members in a set", 3, - "0.07" }, + "1.0.0" }, { "SMOVE", "source destination member", "Move a member from one set to another", 3, - "0.091" }, + "1.0.0" }, { "SORT", "key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]", "Sort the elements in a list, set or sorted set", 0, - "0.07" }, + "1.0.0" }, { "SPOP", "key", "Remove and return a random member from a set", 3, - "0.101" }, + "1.0.0" }, { "SRANDMEMBER", "key", "Get a random member from a set", 3, - "1.001" }, + "1.0.0" }, { "SREM", - "key member", - "Remove a member from a set", + "key member [member ...]", + "Remove one or more members from a set", 3, - "0.07" }, + "1.0.0" }, { "STRLEN", "key", "Get the length of the value stored in a key", 1, - "2.1.2" }, + "2.2.0" }, { "SUBSCRIBE", - "channel", + "channel [channel ...]", "Listen for messages published to the given channels", 6, - "1.3.8" }, - { "SUBSTR", - "key start end", - "Get a substring of the string stored at a key", - 1, - "1.3.4" }, + "2.0.0" }, { "SUNION", "key [key ...]", "Add multiple sets", 3, - "0.091" }, + "1.0.0" }, { "SUNIONSTORE", "destination key [key ...]", "Add multiple sets and store the resulting set in a key", 3, - "0.091" }, + "1.0.0" }, { "SYNC", "-", "Internal command used for replication", 9, - "0.07" }, + "1.0.0" }, + { "TIME", + "-", + "Return the current server time", + 9, + "2.6.0" }, { "TTL", "key", "Get the time to live for a key", 0, - "0.100" }, + "1.0.0" }, { "TYPE", "key", "Determine the type stored at key", 0, - "0.07" }, + "1.0.0" }, { "UNSUBSCRIBE", "[channel [channel ...]]", "Stop listening for messages posted to the given channels", 6, - "1.3.8" }, + "2.0.0" }, { "UNWATCH", "-", "Forget about all watched keys", 7, - "2.1.0" }, + "2.2.0" }, { "WATCH", "key [key ...]", "Watch the given keys to determine execution of the MULTI/EXEC block", 7, - "2.1.0" }, + "2.2.0" }, { "ZADD", - "key score member", - "Add a member to a sorted set, or update its score if it already exists", + "key score member [score] [member]", + "Add one or more members to a sorted set, or update its score if it already exists", 4, - "1.1" }, + "1.2.0" }, { "ZCARD", "key", "Get the number of members in a sorted set", 4, - "1.1" }, + "1.2.0" }, { "ZCOUNT", "key min max", "Count the members in a sorted set with scores within the given values", 4, - "1.3.3" }, + "2.0.0" }, { "ZINCRBY", "key increment member", "Increment the score of a member in a sorted set", 4, - "1.1" }, + "1.2.0" }, { "ZINTERSTORE", "destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]", "Intersect multiple sorted sets and store the resulting sorted set in a new key", 4, - "1.3.10" }, + "2.0.0" }, { "ZRANGE", "key start stop [WITHSCORES]", "Return a range of members in a sorted set, by index", 4, - "1.1" }, + "1.2.0" }, { "ZRANGEBYSCORE", "key min max [WITHSCORES] [LIMIT offset count]", "Return a range of members in a sorted set, by score", 4, - "1.050" }, + "1.0.5" }, { "ZRANK", "key member", "Determine the index of a member in a sorted set", 4, - "1.3.4" }, + "2.0.0" }, { "ZREM", - "key member", - "Remove a member from a sorted set", + "key member [member ...]", + "Remove one or more members from a sorted set", 4, - "1.1" }, + "1.2.0" }, { "ZREMRANGEBYRANK", "key start stop", "Remove all members in a sorted set within the given indexes", 4, - "1.3.4" }, + "2.0.0" }, { "ZREMRANGEBYSCORE", "key min max", "Remove all members in a sorted set within the given scores", 4, - "1.1" }, + "1.2.0" }, { "ZREVRANGE", "key start stop [WITHSCORES]", "Return a range of members in a sorted set, by index, with scores ordered from high to low", 4, - "1.1" }, + "1.2.0" }, { "ZREVRANGEBYSCORE", "key max min [WITHSCORES] [LIMIT offset count]", "Return a range of members in a sorted set, by score, with scores ordered from high to low", 4, - "2.1.6" }, + "2.2.0" }, { "ZREVRANK", "key member", "Determine the index of a member in a sorted set, with scores ordered from high to low", 4, - "1.3.4" }, + "2.0.0" }, { "ZSCORE", "key member", "Get the score associated with the given member in a sorted set", 4, - "1.1" }, + "1.2.0" }, { "ZUNIONSTORE", "destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]", "Add multiple sorted sets and store the resulting sorted set in a new key", 4, - "1.3.10" } + "2.0.0" } }; #endif diff --git a/src/memtest.c b/src/memtest.c index 272ec502..09b4d831 100644 --- a/src/memtest.c +++ b/src/memtest.c @@ -16,11 +16,11 @@ #endif #ifdef MEMTEST_32BIT -#define ULONG_ONEZERO 0xaaaaaaaaaaaaaaaaUL -#define ULONG_ZEROONE 0x5555555555555555UL -#else #define ULONG_ONEZERO 0xaaaaaaaaUL #define ULONG_ZEROONE 0x55555555UL +#else +#define ULONG_ONEZERO 0xaaaaaaaaaaaaaaaaUL +#define ULONG_ZEROONE 0x5555555555555555UL #endif static struct winsize ws; @@ -47,7 +47,7 @@ void memtest_progress_end(void) { } void memtest_progress_step(size_t curr, size_t size, char c) { - size_t chars = (curr*progress_full)/size, j; + size_t chars = ((unsigned long long)curr*progress_full)/size, j; for (j = 0; j < chars-progress_printed; j++) { printf("%c",c); diff --git a/src/networking.c b/src/networking.c index 75f6cf22..f922e297 100644 --- a/src/networking.c +++ b/src/networking.c @@ -1085,7 +1085,7 @@ sds getClientInfoString(redisClient *client) { if (emask & AE_WRITABLE) *p++ = 'w'; *p = '\0'; return sdscatprintf(sdsempty(), - "addr=%s:%d fd=%d age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s", + "addr=%s:%d fd=%d age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s", ip,port,client->fd, (long)(server.unixtime - client->ctime), (long)(server.unixtime - client->lastinteraction), @@ -1093,6 +1093,7 @@ sds getClientInfoString(redisClient *client) { client->db->id, (int) dictSize(client->pubsub_channels), (int) listLength(client->pubsub_patterns), + (client->flags & REDIS_MULTI) ? client->mstate.count : -1, (unsigned long) sdslen(client->querybuf), (unsigned long) sdsavail(client->querybuf), (unsigned long) client->bufpos, diff --git a/src/object.c b/src/object.c index a15ebcfa..ba7ea323 100644 --- a/src/object.c +++ b/src/object.c @@ -9,18 +9,8 @@ robj *createObject(int type, void *ptr) { o->ptr = ptr; o->refcount = 1; - /* Set the LRU to the current lruclock (minutes resolution). - * We do this regardless of the fact VM is active as LRU is also - * used for the maxmemory directive when Redis is used as cache. - * - * Note that this code may run in the context of an I/O thread - * and accessing server.lruclock in theory is an error - * (no locks). But in practice this is safe, and even if we read - * garbage Redis will not fail. */ + /* Set the LRU to the current lruclock (minutes resolution). */ o->lru = server.lruclock; - /* The following is only needed if VM is active, but since the conditional - * is probably more costly than initializing the field it's better to - * have every field properly initialized anyway. */ return o; } diff --git a/src/rdb.c b/src/rdb.c index 1e23fa70..8ffd2c28 100644 --- a/src/rdb.c +++ b/src/rdb.c @@ -1,6 +1,7 @@ #include "redis.h" #include "lzf.h" /* LZF compression library */ #include "zipmap.h" +#include "endianconv.h" #include <math.h> #include <sys/types.h> @@ -26,11 +27,6 @@ int rdbLoadType(rio *rdb) { return type; } -int rdbSaveTime(rio *rdb, time_t t) { - int32_t t32 = (int32_t) t; - return rdbWriteRaw(rdb,&t32,4); -} - time_t rdbLoadTime(rio *rdb) { int32_t t32; if (rioRead(rdb,&t32,4) == 0) return -1; @@ -602,10 +598,12 @@ int rdbSave(char *filename) { dictIterator *di = NULL; dictEntry *de; char tmpfile[256]; + char magic[10]; int j; long long now = mstime(); FILE *fp; rio rdb; + uint64_t cksum; snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid()); fp = fopen(tmpfile,"w"); @@ -616,7 +614,10 @@ int rdbSave(char *filename) { } rioInitWithFile(&rdb,fp); - if (rdbWriteRaw(&rdb,"REDIS0004",9) == -1) goto werr; + if (server.rdb_checksum) + rdb.update_cksum = rioGenericUpdateChecksum; + snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION); + if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr; for (j = 0; j < server.dbnum; j++) { redisDb *db = server.db+j; @@ -644,9 +645,17 @@ int rdbSave(char *filename) { } dictReleaseIterator(di); } + di = NULL; /* So that we don't release it again on error. */ + /* EOF opcode */ if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr; + /* CRC64 checksum. It will be zero if checksum computation is disabled, the + * loading code skips the check in this case. */ + cksum = rdb.cksum; + memrev64ifbe(&cksum); + rioWrite(&rdb,&cksum,8); + /* Make sure data will not remain on the OS's output buffers */ fflush(fp); fsync(fileno(fp)); @@ -689,7 +698,7 @@ int rdbSaveBackground(char *filename) { if (server.ipfd > 0) close(server.ipfd); if (server.sofd > 0) close(server.sofd); retval = rdbSave(filename); - _exit((retval == REDIS_OK) ? 0 : 1); + exitFromChild((retval == REDIS_OK) ? 0 : 1); } else { /* Parent */ server.stat_fork_time = ustime()-start; @@ -720,7 +729,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { size_t len; unsigned int i; - redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",rdbtype,rdb->tell(rdb)); + redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",rdbtype,rioTell(rdb)); if (rdbtype == REDIS_RDB_TYPE_STRING) { /* Read string value */ if ((o = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL; @@ -1019,6 +1028,8 @@ int rdbLoad(char *filename) { return REDIS_ERR; } rioInitWithFile(&rdb,fp); + if (server.rdb_checksum) + rdb.update_cksum = rioGenericUpdateChecksum; if (rioRead(&rdb,buf,9) == 0) goto eoferr; buf[9] = '\0'; if (memcmp(buf,"REDIS",5) != 0) { @@ -1028,7 +1039,7 @@ int rdbLoad(char *filename) { return REDIS_ERR; } rdbver = atoi(buf+5); - if (rdbver < 1 || rdbver > 4) { + if (rdbver < 1 || rdbver > 5) { fclose(fp); redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver); errno = EINVAL; @@ -1042,7 +1053,7 @@ int rdbLoad(char *filename) { /* Serve the clients from time to time */ if (!(loops++ % 1000)) { - loadingProgress(rdb.tell(&rdb)); + loadingProgress(rioTell(&rdb)); aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); } @@ -1099,6 +1110,20 @@ int rdbLoad(char *filename) { decrRefCount(key); } + /* Verify the checksum if RDB version is >= 5 */ + if (rdbver >= 5 && server.rdb_checksum) { + uint64_t cksum, expected = rdb.cksum; + + if (rioRead(&rdb,&cksum,8) == 0) goto eoferr; + memrev64ifbe(&cksum); + if (cksum == 0) { + redisLog(REDIS_WARNING,"RDB file was saved with checksum disabled: no check performed."); + } else if (cksum != expected) { + redisLog(REDIS_WARNING,"Wrong RDB checksum. Aborting now."); + exit(1); + } + } + fclose(fp); stopLoading(); return REDIS_OK; diff --git a/src/rdb.h b/src/rdb.h index 45beaa93..2be5d9cd 100644 --- a/src/rdb.h +++ b/src/rdb.h @@ -7,6 +7,10 @@ /* TBD: include only necessary headers. */ #include "redis.h" +/* The current RDB version. When the format changes in a way that is no longer + * backward compatible this number gets incremented. */ +#define REDIS_RDB_VERSION 5 + /* Defines related to the dump file format. To store 32 bits lengths for short * keys requires a lot of space, so we check the most significant 2 bits of * the first byte to interpreter the length: diff --git a/src/redis-cli.c b/src/redis-cli.c index bdaa3964..3e0c5895 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -69,6 +69,7 @@ static struct config { int cluster_mode; int cluster_reissue_command; int slave_mode; + int bigkeys; int stdinarg; /* get last arg from stdin. (-x option) */ char *auth; int output; /* output mode, see OUTPUT_* defines */ @@ -655,6 +656,8 @@ static int parseOptions(int argc, char **argv) { config.latency_mode = 1; } else if (!strcmp(argv[i],"--slave")) { config.slave_mode = 1; + } else if (!strcmp(argv[i],"--bigkeys")) { + config.bigkeys = 1; } else if (!strcmp(argv[i],"--eval") && !lastarg) { config.eval = argv[++i]; } else if (!strcmp(argv[i],"-c")) { @@ -711,6 +714,7 @@ static void usage() { " --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" " --latency Enter a special mode continuously sampling latency.\n" " --slave Simulate a slave showing commands received from the master.\n" +" --bigkeys Sample Redis keys looking for big keys.\n" " --eval <file> Send an EVAL command using the Lua script at <file>.\n" " --help Output this help and exit\n" " --version Output version and exit\n" @@ -964,6 +968,87 @@ static void slaveMode(void) { while (cliReadReply(0) == REDIS_OK); } +#define TYPE_STRING 0 +#define TYPE_LIST 1 +#define TYPE_SET 2 +#define TYPE_HASH 3 +#define TYPE_ZSET 4 + +static void findBigKeys(void) { + unsigned long long biggest[5] = {0,0,0,0,0}; + unsigned long long samples = 0; + redisReply *reply1, *reply2, *reply3; + char *sizecmd, *typename[] = {"string","list","set","hash","zset"}; + int type; + + printf("\n# Press ctrl+c when you have had enough of it... :)\n"); + printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n"); + printf("# in order to reduce server load (usually not needed).\n\n"); + while(1) { + /* Sample with RANDOMKEY */ + reply1 = redisCommand(context,"RANDOMKEY"); + if (reply1 == NULL) { + fprintf(stderr,"\nI/O error\n"); + exit(1); + } else if (reply1->type == REDIS_REPLY_ERROR) { + fprintf(stderr, "RANDOMKEY error: %s\n", + reply1->str); + exit(1); + } + /* Get the key type */ + reply2 = redisCommand(context,"TYPE %s",reply1->str); + assert(reply2 && reply2->type == REDIS_REPLY_STATUS); + samples++; + + /* Get the key "size" */ + if (!strcmp(reply2->str,"string")) { + sizecmd = "STRLEN"; + type = TYPE_STRING; + } else if (!strcmp(reply2->str,"list")) { + sizecmd = "LLEN"; + type = TYPE_LIST; + } else if (!strcmp(reply2->str,"set")) { + sizecmd = "SCARD"; + type = TYPE_SET; + } else if (!strcmp(reply2->str,"hash")) { + sizecmd = "HLEN"; + type = TYPE_HASH; + } else if (!strcmp(reply2->str,"zset")) { + sizecmd = "ZCARD"; + type = TYPE_ZSET; + } else if (!strcmp(reply2->str,"none")) { + freeReplyObject(reply1); + freeReplyObject(reply2); + freeReplyObject(reply3); + continue; + } else { + fprintf(stderr, "Unknown key type '%s' for key '%s'\n", + reply2->str, reply1->str); + exit(1); + } + + reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str); + if (reply3 && reply3->type == REDIS_REPLY_INTEGER) { + if (biggest[type] < reply3->integer) { + printf("[%6s] %s | biggest so far with size %llu\n", + typename[type], reply1->str, + (unsigned long long) reply3->integer); + biggest[type] = reply3->integer; + } + } + + if ((samples % 1000000) == 0) + printf("(%llu keys sampled)\n", samples); + + if ((samples % 100) == 0 && config.interval) + usleep(config.interval); + + freeReplyObject(reply1); + freeReplyObject(reply2); + if (reply3) freeReplyObject(reply3); + } +} + int main(int argc, char **argv) { int firstarg; @@ -979,6 +1064,8 @@ int main(int argc, char **argv) { config.pubsub_mode = 0; config.latency_mode = 0; config.cluster_mode = 0; + config.slave_mode = 0; + config.bigkeys = 0; config.stdinarg = 0; config.auth = NULL; config.eval = NULL; @@ -1005,6 +1092,12 @@ int main(int argc, char **argv) { slaveMode(); } + /* Find big keys */ + if (config.bigkeys) { + cliConnect(0); + findBigKeys(); + } + /* Start interactive mode when no command is provided */ if (argc == 0 && !config.eval) { /* Note that in repl mode we don't abort on connection error. diff --git a/src/redis-trib.rb b/src/redis-trib.rb index 473e4922..1b83e19c 100755 --- a/src/redis-trib.rb +++ b/src/redis-trib.rb @@ -357,7 +357,7 @@ class RedisTrib keys = source.r.cluster("getkeysinslot",slot,10) break if keys.length == 0 keys.each{|key| - source.r.migrate(target.info[:host],target.info[:port],key,0,1) + source.r.migrate(target.info[:host],target.info[:port],key,0,1000) print "." if o[:verbose] STDOUT.flush } diff --git a/src/redis.c b/src/redis.c index 7ddeb886..cb4883cc 100644 --- a/src/redis.c +++ b/src/redis.c @@ -48,6 +48,7 @@ #include <float.h> #include <math.h> #include <sys/resource.h> +#include <sys/utsname.h> /* Our shared "common" objects */ @@ -61,6 +62,9 @@ double R_Zero, R_PosInf, R_NegInf, R_Nan; /*================================= Globals ================================= */ +/* Alternate stack for SIGSEGV/etc handlers */ +char altstack[SIGSTKSZ]; + /* Global vars */ struct redisServer server; /* server global state */ struct redisCommand *commandTable; @@ -222,7 +226,7 @@ struct redisCommand redisCommandTable[] = { {"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0}, {"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0}, {"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0}, - {"slaveof",slaveofCommand,3,"aws",0,NULL,0,0,0,0,0}, + {"slaveof",slaveofCommand,3,"as",0,NULL,0,0,0,0,0}, {"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0}, {"config",configCommand,-2,"ar",0,NULL,0,0,0,0,0}, {"subscribe",subscribeCommand,-2,"rps",0,NULL,0,0,0,0,0}, @@ -253,7 +257,6 @@ struct redisCommand redisCommandTable[] = { void redisLogRaw(int level, const char *msg) { const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING }; const char *c = ".-*#"; - time_t now = time(NULL); FILE *fp; char buf[64]; int rawmode = (level & REDIS_LOG_RAW); @@ -267,7 +270,12 @@ void redisLogRaw(int level, const char *msg) { if (rawmode) { fprintf(fp,"%s",msg); } else { - strftime(buf,sizeof(buf),"%d %b %H:%M:%S",localtime(&now)); + int off; + struct timeval tv; + + gettimeofday(&tv,NULL); + off = strftime(buf,sizeof(buf),"%d %b %H:%M:%S.",localtime(&tv.tv_sec)); + snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000); fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg); } fflush(fp); @@ -310,14 +318,15 @@ void redisLogFromHandler(int level, const char *msg) { STDOUT_FILENO; if (fd == -1) return; ll2string(buf,sizeof(buf),getpid()); - write(fd,"[",1); - write(fd,buf,strlen(buf)); - write(fd," | signal handler] (",20); + if (write(fd,"[",1) == -1) goto err; + if (write(fd,buf,strlen(buf)) == -1) goto err; + if (write(fd," | signal handler] (",20) == -1) goto err; ll2string(buf,sizeof(buf),time(NULL)); - write(fd,buf,strlen(buf)); - write(fd,") ",2); - write(fd,msg,strlen(msg)); - write(fd,"\n",1); + if (write(fd,buf,strlen(buf)) == -1) goto err; + if (write(fd,") ",2) == -1) goto err; + if (write(fd,msg,strlen(msg)) == -1) goto err; + if (write(fd,"\n",1) == -1) goto err; +err: if (server.logfile) close(fd); } @@ -348,6 +357,18 @@ long long mstime(void) { return ustime()/1000; } +/* After an RDB dump or AOF rewrite we exit from children using _exit() instead of + * exit(), because the latter may interact with the same file objects used by + * the parent process. However if we are testing the coverage normal exit() is + * used in order to obtain the right coverage information. */ +void exitFromChild(int retcode) { +#ifdef COVERAGE_TEST + exit(retcode); +#else + _exit(retcode); +#endif +} + /*====================== Hash table type implementation ==================== */ /* This is an hash table type that uses the SDS dynamic strings libary as @@ -977,16 +998,11 @@ void createSharedObjects(void) { shared.space = createObject(REDIS_STRING,sdsnew(" ")); shared.colon = createObject(REDIS_STRING,sdsnew(":")); shared.plus = createObject(REDIS_STRING,sdsnew("+")); - shared.select0 = createStringObject("select 0\r\n",10); - shared.select1 = createStringObject("select 1\r\n",10); - shared.select2 = createStringObject("select 2\r\n",10); - shared.select3 = createStringObject("select 3\r\n",10); - shared.select4 = createStringObject("select 4\r\n",10); - shared.select5 = createStringObject("select 5\r\n",10); - shared.select6 = createStringObject("select 6\r\n",10); - shared.select7 = createStringObject("select 7\r\n",10); - shared.select8 = createStringObject("select 8\r\n",10); - shared.select9 = createStringObject("select 9\r\n",10); + + for (j = 0; j < REDIS_SHARED_SELECT_CMDS; j++) { + shared.select[j] = createObject(REDIS_STRING, + sdscatprintf(sdsempty(),"select %d\r\n", j)); + } shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13); shared.pmessagebulk = createStringObject("$8\r\npmessage\r\n",14); shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15); @@ -1046,6 +1062,7 @@ void initServerConfig() { server.aof_filename = zstrdup("appendonly.aof"); server.requirepass = NULL; server.rdb_compression = 1; + server.rdb_checksum = 1; server.activerehashing = 1; server.maxclients = REDIS_MAX_CLIENTS; server.bpop_blocked_clients = 0; @@ -1084,7 +1101,7 @@ void initServerConfig() { server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT; server.repl_serve_stale_data = 1; server.repl_slave_ro = 1; - server.repl_down_since = -1; + server.repl_down_since = time(NULL); /* Client output buffer limits */ server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].hard_limit_bytes = 0; @@ -1136,7 +1153,6 @@ void adjustOpenFilesLimit(void) { rlim_t maxfiles = server.maxclients+32; struct rlimit limit; - if (maxfiles < 1024) maxfiles = 1024; if (getrlimit(RLIMIT_NOFILE,&limit) == -1) { redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s), assuming 1024 and setting the max clients configuration accordingly.", strerror(errno)); @@ -1147,10 +1163,18 @@ void adjustOpenFilesLimit(void) { /* Set the max number of files if the current limit is not enough * for our needs. */ if (oldlimit < maxfiles) { - limit.rlim_cur = maxfiles; - limit.rlim_max = maxfiles; - if (setrlimit(RLIMIT_NOFILE,&limit) == -1) { - server.maxclients = oldlimit-32; + rlim_t f; + + f = maxfiles; + while(f > oldlimit) { + limit.rlim_cur = f; + limit.rlim_max = f; + if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break; + f -= 128; + } + if (f < oldlimit) f = oldlimit; + if (f != maxfiles) { + server.maxclients = f-32; redisLog(REDIS_WARNING,"Unable to set the max number of files limit to %d (%s), setting the max clients configuration to %d.", (int) maxfiles, strerror(errno), (int) server.maxclients); } else { @@ -1575,7 +1599,7 @@ int processCommand(redisClient *c) { /* Lua script too slow? Only allow SHUTDOWN NOSAVE and SCRIPT KILL. */ if (server.lua_timedout && - !(c->cmd->proc != shutdownCommand && + !(c->cmd->proc == shutdownCommand && c->argc == 2 && tolower(((char*)c->argv[1]->ptr)[0]) == 'n') && !(c->cmd->proc == scriptCommand && @@ -1732,12 +1756,16 @@ sds genRedisInfoString(char *section) { /* Server */ if (allsections || defsections || !strcasecmp(section,"server")) { + struct utsname name; + if (sections++) info = sdscat(info,"\r\n"); + uname(&name); info = sdscatprintf(info, "# Server\r\n" "redis_version:%s\r\n" "redis_git_sha1:%s\r\n" "redis_git_dirty:%d\r\n" + "os:%s %s %s\r\n" "arch_bits:%d\r\n" "multiplexing_api:%s\r\n" "gcc_version:%d.%d.%d\r\n" @@ -1750,6 +1778,7 @@ sds genRedisInfoString(char *section) { REDIS_VERSION, redisGitSHA1(), strtol(redisGitDirty(),NULL,10) > 0, + name.sysname, name.release, name.machine, server.arch_bits, aeGetApiName(), #ifdef __GNUC__ @@ -1819,14 +1848,16 @@ sds genRedisInfoString(char *section) { "bgsave_in_progress:%d\r\n" "last_save_time:%ld\r\n" "last_bgsave_status:%s\r\n" - "bgrewriteaof_in_progress:%d\r\n", + "bgrewriteaof_in_progress:%d\r\n" + "bgrewriteaof_scheduled:%d\r\n", server.loading, server.aof_state != REDIS_AOF_OFF, server.dirty, server.rdb_child_pid != -1, server.lastsave, server.lastbgsave_status == REDIS_OK ? "ok" : "err", - server.aof_child_pid != -1); + server.aof_child_pid != -1, + server.aof_rewrite_scheduled); if (server.aof_state != REDIS_AOF_OFF) { info = sdscatprintf(info, @@ -2010,7 +2041,7 @@ sds genRedisInfoString(char *section) { } } - /* Clusetr */ + /* Cluster */ if (allsections || defsections || !strcasecmp(section,"cluster")) { if (sections++) info = sdscat(info,"\r\n"); info = sdscatprintf(info, @@ -2270,8 +2301,12 @@ void daemonize(void) { } void version() { - printf("Redis server v=%s sha=%s:%d malloc=%s\n", REDIS_VERSION, - redisGitSHA1(), atoi(redisGitDirty()) > 0, ZMALLOC_LIB); + printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d\n", + REDIS_VERSION, + redisGitSHA1(), + atoi(redisGitDirty()) > 0, + ZMALLOC_LIB, + sizeof(long) == 4 ? 32 : 64); exit(0); } @@ -2316,15 +2351,24 @@ static void sigtermHandler(int sig) { void setupSignalHandlers(void) { struct sigaction act; + stack_t stack; + + stack.ss_sp = altstack; + stack.ss_flags = 0; + stack.ss_size = SIGSTKSZ; + + sigaltstack(&stack, NULL); /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used. * Otherwise, sa_handler is used. */ sigemptyset(&act.sa_mask); - act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND; + act.sa_flags = 0; act.sa_handler = sigtermHandler; sigaction(SIGTERM, &act, NULL); #ifdef HAVE_BACKTRACE + /* Use alternate stack so we don't clobber stack in case of segv, or when we run out of stack .. + * also resethand & nodefer so we can get interrupted (and killed) if we cause SEGV during SEGV handler */ sigemptyset(&act.sa_mask); act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_SIGINFO; act.sa_sigaction = sigsegvHandler; diff --git a/src/redis.h b/src/redis.h index 5a3d1a9c..42a9b581 100644 --- a/src/redis.h +++ b/src/redis.h @@ -44,6 +44,7 @@ #define REDIS_CONFIGLINE_MAX 1024 #define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */ #define REDIS_MAX_WRITE_PER_EVENT (1024*64) +#define REDIS_SHARED_SELECT_CMDS 10 #define REDIS_SHARED_INTEGERS 10000 #define REDIS_SHARED_BULKHDR_LEN 32 #define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */ @@ -88,7 +89,6 @@ #define REDIS_SET 2 #define REDIS_ZSET 3 #define REDIS_HASH 4 -#define REDIS_VMPOINTER 8 /* Objects encoding. Some kind of objects like Strings and Hashes can be * internally represented in multiple ways. The 'encoding' field of the object @@ -366,10 +366,9 @@ struct sharedObjectsStruct { *colon, *nullbulk, *nullmultibulk, *queued, *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, - *roslaveerr, *oomerr, *plus, *select0, *select1, *select2, *select3, - *select4, *select5, *select6, *select7, *select8, *select9, - *messagebulk, *pmessagebulk, *subscribebulk, *unsubscribebulk, - *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop, + *roslaveerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk, + *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop, + *select[REDIS_SHARED_SELECT_CMDS], *integers[REDIS_SHARED_INTEGERS], *mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */ *bulkhdr[REDIS_SHARED_BULKHDR_LEN]; /* "$<value>\r\n" */ @@ -647,6 +646,7 @@ struct redisServer { int saveparamslen; /* Number of saving points */ char *rdb_filename; /* Name of RDB file */ int rdb_compression; /* Use compression in RDB? */ + int rdb_checksum; /* Use RDB checksum? */ time_t lastsave; /* Unix time of last save succeeede */ int lastbgsave_status; /* REDIS_OK or REDIS_ERR */ int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */ @@ -828,6 +828,8 @@ extern dictType hashDictType; long long ustime(void); long long mstime(void); void getRandomHexChars(char *p, unsigned int len); +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); +void exitFromChild(int retcode); /* networking.c -- Networking and Client related operations */ redisClient *createClient(int fd); @@ -945,9 +947,9 @@ int equalStringObjects(robj *a, robj *b); unsigned long estimateObjectIdleTime(robj *o); /* Synchronous I/O with timeout */ -int syncWrite(int fd, char *ptr, ssize_t size, int timeout); -int syncRead(int fd, char *ptr, ssize_t size, int timeout); -int syncReadLine(int fd, char *ptr, ssize_t size, int timeout); +ssize_t syncWrite(int fd, char *ptr, ssize_t size, long long timeout); +ssize_t syncRead(int fd, char *ptr, ssize_t size, long long timeout); +ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout); /* Replication */ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc); diff --git a/src/replication.c b/src/replication.c index c99be32a..ef148f1c 100644 --- a/src/replication.c +++ b/src/replication.c @@ -25,24 +25,15 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { if (slave->slaveseldb != dictid) { robj *selectcmd; - switch(dictid) { - case 0: selectcmd = shared.select0; break; - case 1: selectcmd = shared.select1; break; - case 2: selectcmd = shared.select2; break; - case 3: selectcmd = shared.select3; break; - case 4: selectcmd = shared.select4; break; - case 5: selectcmd = shared.select5; break; - case 6: selectcmd = shared.select6; break; - case 7: selectcmd = shared.select7; break; - case 8: selectcmd = shared.select8; break; - case 9: selectcmd = shared.select9; break; - default: + if (dictid >= 0 && dictid < REDIS_SHARED_SELECT_CMDS) { + selectcmd = shared.select[dictid]; + incrRefCount(selectcmd); + } else { selectcmd = createObject(REDIS_STRING, sdscatprintf(sdsempty(),"select %d\r\n",dictid)); - selectcmd->refcount = 0; - break; } addReply(slave,selectcmd); + decrRefCount(selectcmd); slave->slaveseldb = dictid; } addReplyMultiBulkLen(slave,argc); @@ -291,7 +282,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { /* If repl_transfer_left == -1 we still have to read the bulk length * from the master reply. */ if (server.repl_transfer_left == -1) { - if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout) == -1) { + if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) { redisLog(REDIS_WARNING, "I/O error reading bulk count from MASTER: %s", strerror(errno)); @@ -414,13 +405,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { size_t authlen; authlen = snprintf(authcmd,sizeof(authcmd),"AUTH %s\r\n",server.masterauth); - if (syncWrite(fd,authcmd,authlen,server.repl_syncio_timeout) == -1) { + if (syncWrite(fd,authcmd,authlen,server.repl_syncio_timeout*1000) == -1) { redisLog(REDIS_WARNING,"Unable to AUTH to MASTER: %s", strerror(errno)); goto error; } /* Read the AUTH result. */ - if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout) == -1) { + if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) { redisLog(REDIS_WARNING,"I/O error reading auth result from MASTER: %s", strerror(errno)); goto error; @@ -432,7 +423,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { } /* Issue the SYNC command */ - if (syncWrite(fd,"SYNC \r\n",7,server.repl_syncio_timeout) == -1) { + if (syncWrite(fd,"SYNC \r\n",7,server.repl_syncio_timeout*1000) == -1) { redisLog(REDIS_WARNING,"I/O error writing to MASTER: %s", strerror(errno)); goto error; diff --git a/src/rio.c b/src/rio.c index 95b1ee7e..44165d71 100644 --- a/src/rio.c +++ b/src/rio.c @@ -1,9 +1,25 @@ +/* rio.c is a simple stream-oriented I/O abstraction that provides an interface + * to write code that can consume/produce data using different concrete input + * and output devices. For instance the same rdb.c code using the rio abstraction + * can be used to read and write the RDB format using in-memory buffers or files. + * + * A rio object provides the following methods: + * read: read from stream. + * write: write to stream. + * tell: get the current offset. + * + * It is also possible to set a 'checksum' method that is used by rio.c in order + * to compute a checksum of the data written or read, or to query the rio object + * for the current checksum. */ + #include "fmacros.h" #include <string.h> #include <stdio.h> #include "rio.h" #include "util.h" +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); + /* Returns 1 or 0 for success/failure. */ static size_t rioBufferWrite(rio *r, const void *buf, size_t len) { r->io.buffer.ptr = sdscatlen(r->io.buffer.ptr,(char*)buf,len); @@ -44,6 +60,8 @@ static const rio rioBufferIO = { rioBufferRead, rioBufferWrite, rioBufferTell, + NULL, /* update_checksum */ + 0, /* current checksum */ { { NULL, 0 } } /* union for io-specific vars */ }; @@ -51,6 +69,8 @@ static const rio rioFileIO = { rioFileRead, rioFileWrite, rioFileTell, + NULL, /* update_checksum */ + 0, /* current checksum */ { { NULL, 0 } } /* union for io-specific vars */ }; @@ -65,6 +85,16 @@ void rioInitWithBuffer(rio *r, sds s) { r->io.buffer.pos = 0; } +/* This function can be installed both in memory and file streams when checksum + * computation is needed. */ +void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) { + r->cksum = crc64(r->cksum,buf,len); +} + +/* ------------------------------ Higher level interface --------------------------- + * The following higher level functions use lower level rio.c functions to help + * generating the Redis protocol for the Append Only File. */ + /* Write multi bulk count in the format: "*<count>\r\n". */ size_t rioWriteBulkCount(rio *r, char prefix, int count) { char cbuf[128]; diff --git a/src/rio.h b/src/rio.h index 2a830eb5..8befe667 100644 --- a/src/rio.h +++ b/src/rio.h @@ -2,6 +2,7 @@ #define __REDIS_RIO_H #include <stdio.h> +#include <stdint.h> #include "sds.h" struct _rio { @@ -11,6 +12,14 @@ struct _rio { size_t (*read)(struct _rio *, void *buf, size_t len); size_t (*write)(struct _rio *, const void *buf, size_t len); off_t (*tell)(struct _rio *); + /* The update_cksum method if not NULL is used to compute the checksum of all the + * data that was read or written so far. The method should be designed so that + * can be called with the current checksum, and the buf and len fields pointing + * to the new block of data to add to the checksum computation. */ + void (*update_cksum)(struct _rio *, const void *buf, size_t len); + + /* The current checksum */ + uint64_t cksum; /* Backend-specific vars. */ union { @@ -26,8 +35,26 @@ struct _rio { typedef struct _rio rio; -#define rioWrite(rio,buf,len) ((rio)->write((rio),(buf),(len))) -#define rioRead(rio,buf,len) ((rio)->read((rio),(buf),(len))) +/* The following functions are our interface with the stream. They'll call the + * actual implementation of read / write / tell, and will update the checksum + * if needed. */ + +static inline size_t rioWrite(rio *r, const void *buf, size_t len) { + if (r->update_cksum) r->update_cksum(r,buf,len); + return r->write(r,buf,len); +} + +static inline size_t rioRead(rio *r, void *buf, size_t len) { + if (r->read(r,buf,len) == 1) { + if (r->update_cksum) r->update_cksum(r,buf,len); + return 1; + } + return 0; +} + +static inline off_t rioTell(rio *r) { + return r->tell(r); +} void rioInitWithFile(rio *r, FILE *fp); void rioInitWithBuffer(rio *r, sds s); @@ -37,4 +64,6 @@ size_t rioWriteBulkString(rio *r, const char *buf, size_t len); size_t rioWriteBulkLongLong(rio *r, long long l); size_t rioWriteBulkDouble(rio *r, double d); +void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len); + #endif diff --git a/src/scripting.c b/src/scripting.c index 0f876961..4c7de33b 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -412,6 +412,43 @@ void luaLoadLibraries(lua_State *lua) { #endif } +/* This function installs metamethods in the global table _G that prevent + * the creation of globals accidentally. + * + * It should be the last to be called in the scripting engine initialization + * sequence, because it may interact with creation of globals. */ +void scriptingEnableGlobalsProtection(lua_State *lua) { + char *s[32]; + sds code = sdsempty(); + int j = 0; + + /* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html. + * Modified to be adapted to Redis. */ + s[j++]="local mt = {}\n"; + s[j++]="setmetatable(_G, mt)\n"; + s[j++]="mt.__newindex = function (t, n, v)\n"; + s[j++]=" if debug.getinfo(2) then\n"; + s[j++]=" local w = debug.getinfo(2, \"S\").what\n"; + s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n"; + s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n"; + s[j++]=" end\n"; + s[j++]=" end\n"; + s[j++]=" rawset(t, n, v)\n"; + s[j++]="end\n"; + s[j++]="mt.__index = function (t, n)\n"; + s[j++]=" if debug.getinfo(2) and debug.getinfo(2, \"S\").what ~= \"C\" then\n"; + s[j++]=" error(\"Script attempted to access unexisting global variable '\"..tostring(n)..\"'\", 2)\n"; + s[j++]=" end\n"; + s[j++]=" return rawget(t, n)\n"; + s[j++]="end\n"; + s[j++]=NULL; + + for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j])); + luaL_loadbuffer(lua,code,sdslen(code),"@enable_strict_lua"); + lua_pcall(lua,0,0,0); + sdsfree(code); +} + /* Initialize the scripting environment. * It is possible to call this function to reset the scripting environment * assuming that we call scriptingRelease() before. @@ -488,7 +525,7 @@ void scriptingInit(void) { " if b == false then b = '' end\n" " return a<b\n" "end\n"; - luaL_loadbuffer(lua,compare_func,strlen(compare_func),"cmp_func_def"); + luaL_loadbuffer(lua,compare_func,strlen(compare_func),"@cmp_func_def"); lua_pcall(lua,0,0,0); } @@ -501,6 +538,11 @@ void scriptingInit(void) { server.lua_client->flags |= REDIS_LUA_CLIENT; } + /* Lua beginners ofter don't use "local", this is likely to introduce + * subtle bugs in their code. To prevent problems we protect accesses + * to global variables. */ + scriptingEnableGlobalsProtection(lua); + server.lua = lua; } @@ -634,7 +676,7 @@ int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr)); funcdef = sdscatlen(funcdef," end",4); - if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"func definition")) { + if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) { addReplyErrorFormat(c,"Error compiling script (new function): %s\n", lua_tostring(lua,-1)); lua_pop(lua,1); diff --git a/src/sort.c b/src/sort.c index 3f02e49a..c1ed5517 100644 --- a/src/sort.c +++ b/src/sort.c @@ -9,21 +9,27 @@ redisSortOperation *createSortOperation(int type, robj *pattern) { return so; } -/* Return the value associated to the key with a name obtained - * substituting the first occurence of '*' in 'pattern' with 'subst'. +/* Return the value associated to the key with a name obtained using + * the following rules: + * + * 1) The first occurence of '*' in 'pattern' is substituted with 'subst'. + * + * 2) If 'pattern' matches the "->" string, everything on the left of + * the arrow is treated as the name of an hash field, and the part on the + * left as the key name containing an hash. The value of the specified + * field is returned. + * + * 3) If 'pattern' equals "#", the function simply returns 'subst' itself so + * that the SORT command can be used like: SORT key GET # to retrieve + * the Set/List elements directly. + * * The returned object will always have its refcount increased by 1 * when it is non-NULL. */ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { - char *p, *f; + char *p, *f, *k; sds spat, ssub; - robj keyobj, fieldobj, *o; + robj *keyobj, *fieldobj = NULL, *o; int prefixlen, sublen, postfixlen, fieldlen; - /* Expoit the internal sds representation to create a sds string allocated on the stack in order to make this function faster */ - struct { - int len; - int free; - char buf[REDIS_SORTKEY_MAX+1]; - } keyname, fieldname; /* If the pattern is "#" return the substitution object itself in order * to implement the "SORT ... GET #" feature. */ @@ -37,9 +43,10 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { * a decoded object on the fly. Otherwise getDecodedObject will just * increment the ref count, that we'll decrement later. */ subst = getDecodedObject(subst); - ssub = subst->ptr; - if (sdslen(spat)+sdslen(ssub)-1 > REDIS_SORTKEY_MAX) return NULL; + + /* If we can't find '*' in the pattern we return NULL as to GET a + * fixed key does not make sense. */ p = strchr(spat,'*'); if (!p) { decrRefCount(subst); @@ -47,46 +54,49 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { } /* Find out if we're dealing with a hash dereference. */ - if ((f = strstr(p+1, "->")) != NULL) { - fieldlen = sdslen(spat)-(f-spat); - /* this also copies \0 character */ - memcpy(fieldname.buf,f+2,fieldlen-1); - fieldname.len = fieldlen-2; + if ((f = strstr(p+1, "->")) != NULL && *(f+2) != '\0') { + fieldlen = sdslen(spat)-(f-spat)-2; + fieldobj = createStringObject(f+2,fieldlen); } else { fieldlen = 0; } + /* Perform the '*' substitution. */ prefixlen = p-spat; sublen = sdslen(ssub); - postfixlen = sdslen(spat)-(prefixlen+1)-fieldlen; - memcpy(keyname.buf,spat,prefixlen); - memcpy(keyname.buf+prefixlen,ssub,sublen); - memcpy(keyname.buf+prefixlen+sublen,p+1,postfixlen); - keyname.buf[prefixlen+sublen+postfixlen] = '\0'; - keyname.len = prefixlen+sublen+postfixlen; - decrRefCount(subst); + postfixlen = sdslen(spat)-(prefixlen+1)-(fieldlen ? fieldlen+2 : 0); + keyobj = createStringObject(NULL,prefixlen+sublen+postfixlen); + k = keyobj->ptr; + memcpy(k,spat,prefixlen); + memcpy(k+prefixlen,ssub,sublen); + memcpy(k+prefixlen+sublen,p+1,postfixlen); + decrRefCount(subst); /* Incremented by decodeObject() */ /* Lookup substituted key */ - initStaticStringObject(keyobj,((char*)&keyname)+(sizeof(struct sdshdr))); - o = lookupKeyRead(db,&keyobj); - if (o == NULL) return NULL; + o = lookupKeyRead(db,keyobj); + if (o == NULL) goto noobj; - if (fieldlen > 0) { - if (o->type != REDIS_HASH || fieldname.len < 1) return NULL; + if (fieldobj) { + if (o->type != REDIS_HASH) goto noobj; /* Retrieve value from hash by the field name. This operation * already increases the refcount of the returned object. */ - initStaticStringObject(fieldobj,((char*)&fieldname)+(sizeof(struct sdshdr))); - o = hashTypeGetObject(o, &fieldobj); + o = hashTypeGetObject(o, fieldobj); } else { - if (o->type != REDIS_STRING) return NULL; + if (o->type != REDIS_STRING) goto noobj; /* Every object that this function returns needs to have its refcount * increased. sortCommand decreases it again. */ incrRefCount(o); } - + decrRefCount(keyobj); + if (fieldobj) decrRefCount(fieldobj); return o; + +noobj: + decrRefCount(keyobj); + if (fieldlen) decrRefCount(fieldobj); + return NULL; } /* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with diff --git a/src/syncio.c b/src/syncio.c index 9958363b..b0c2969e 100644 --- a/src/syncio.c +++ b/src/syncio.c @@ -36,50 +36,79 @@ * of the SYNC command where the slave does it in a blocking way, and * the MIGRATE command that must be blocking in order to be atomic from the * point of view of the two instances (one migrating the key and one receiving - * the key). This is why need the following blocking I/O functions. */ + * the key). This is why need the following blocking I/O functions. + * + * All the functions take the timeout in milliseconds. */ + +#define REDIS_SYNCIO_RESOLUTION 10 /* Resolution in milliseconds */ -int syncWrite(int fd, char *ptr, ssize_t size, int timeout) { +/* Write the specified payload to 'fd'. If writing the whole payload will be done + * within 'timeout' milliseconds the operation succeeds and 'size' is returned. + * Otherwise the operation fails, -1 is returned, and an unspecified partial write + * could be performed against the file descriptor. */ +ssize_t syncWrite(int fd, char *ptr, ssize_t size, long long timeout) { ssize_t nwritten, ret = size; - time_t start = time(NULL); + long long start = mstime(); + long long remaining = timeout; - timeout++; - while(size) { - if (aeWait(fd,AE_WRITABLE,1000) & AE_WRITABLE) { + while(1) { + long long wait = (remaining > REDIS_SYNCIO_RESOLUTION) ? + remaining : REDIS_SYNCIO_RESOLUTION; + long long elapsed; + + if (aeWait(fd,AE_WRITABLE,wait) & AE_WRITABLE) { nwritten = write(fd,ptr,size); if (nwritten == -1) return -1; ptr += nwritten; size -= nwritten; + if (size == 0) return ret; } - if ((time(NULL)-start) > timeout) { + elapsed = mstime() - start; + if (elapsed >= timeout) { errno = ETIMEDOUT; return -1; } + remaining = timeout - elapsed; } - return ret; } -int syncRead(int fd, char *ptr, ssize_t size, int timeout) { +/* Read the specified amount of bytes from 'fd'. If all the bytes are read within + * 'timeout' milliseconds the operation succeed and 'size' is returned. + * Otherwise the operation fails, -1 is returned, and an unspecified amount of + * data could be read from the file descriptor. */ +ssize_t syncRead(int fd, char *ptr, ssize_t size, long long timeout) { ssize_t nread, totread = 0; - time_t start = time(NULL); + long long start = mstime(); + long long remaining = timeout; - timeout++; - while(size) { - if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) { + while(1) { + long long wait = (remaining > REDIS_SYNCIO_RESOLUTION) ? + remaining : REDIS_SYNCIO_RESOLUTION; + long long elapsed; + + if (aeWait(fd,AE_READABLE,wait) & AE_READABLE) { nread = read(fd,ptr,size); if (nread <= 0) return -1; ptr += nread; size -= nread; totread += nread; + if (size == 0) return totread; } - if ((time(NULL)-start) > timeout) { + elapsed = mstime() - start; + if (elapsed >= timeout) { errno = ETIMEDOUT; return -1; } + remaining = timeout - elapsed; } - return totread; } -int syncReadLine(int fd, char *ptr, ssize_t size, int timeout) { +/* Read a line making sure that every char will not require more than 'timeout' + * milliseconds to be read. + * + * On success the number of bytes read is returned, otherwise -1. + * On success the string is always correctly terminated with a 0 byte. */ +ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout) { ssize_t nread = 0; size--; diff --git a/src/t_list.c b/src/t_list.c index 6a16a632..ca03916b 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -699,6 +699,8 @@ void rpoplpushCommand(redisClient *c) { checkType(c,sobj,REDIS_LIST)) return; if (listTypeLength(sobj) == 0) { + /* This may only happen after loading very old RDB files. Recent + * versions of Redis delete keys of empty lists. */ addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2]); diff --git a/src/version.h b/src/version.h index 9ef74b08..08f8649f 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define REDIS_VERSION "2.9.5" +#define REDIS_VERSION "2.9.7" diff --git a/src/zmalloc.h b/src/zmalloc.h index 8079fb53..ff555619 100644 --- a/src/zmalloc.h +++ b/src/zmalloc.h @@ -38,7 +38,7 @@ #if defined(USE_TCMALLOC) #define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR)) #include <google/tcmalloc.h> -#if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6 +#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1) #define HAVE_MALLOC_SIZE 1 #define zmalloc_size(p) tc_malloc_size(p) #else @@ -49,7 +49,7 @@ #define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX)) #define JEMALLOC_MANGLE #include <jemalloc/jemalloc.h> -#if JEMALLOC_VERSION_MAJOR >= 2 && JEMALLOC_VERSION_MINOR >= 1 +#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2) #define HAVE_MALLOC_SIZE 1 #define zmalloc_size(p) JEMALLOC_P(malloc_usable_size)(p) #else diff --git a/tests/helpers/bg_complex_data.tcl b/tests/helpers/bg_complex_data.tcl new file mode 100644 index 00000000..dffd7c66 --- /dev/null +++ b/tests/helpers/bg_complex_data.tcl @@ -0,0 +1,10 @@ +source tests/support/redis.tcl +source tests/support/util.tcl + +proc bg_complex_data {host port db ops} { + set r [redis $host $port] + $r select $db + createComplexDataset $r $ops +} + +bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] diff --git a/tests/integration/replication-4.tcl b/tests/integration/replication-4.tcl new file mode 100644 index 00000000..69fcab37 --- /dev/null +++ b/tests/integration/replication-4.tcl @@ -0,0 +1,54 @@ +proc start_bg_complex_data {host port db ops} { + exec tclsh8.5 tests/helpers/bg_complex_data.tcl $host $port $db $ops & +} + +proc stop_bg_complex_data {handle} { + catch {exec /bin/kill -9 $handle} +} + +start_server {tags {"repl"}} { + start_server {} { + + set master [srv 0 client] + set master_host [srv 0 host] + set master_port [srv 0 port] + set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000] + set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000] + set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000] + + test {First server should have role slave after SLAVEOF} { + r -1 slaveof [srv 0 host] [srv 0 port] + after 1000 + s -1 role + } {slave} + + test {Test replication with parallel clients writing in differnet DBs} { + lappend slave [srv 0 client] + after 5000 + stop_bg_complex_data $load_handle0 + stop_bg_complex_data $load_handle1 + stop_bg_complex_data $load_handle2 + set retry 10 + while {$retry && ([$master debug digest] ne [$slave debug digest])}\ + { + after 1000 + incr retry -1 + } + assert {[$master dbsize] > 0} + + if {[r debug digest] ne [r -1 debug digest]} { + set csv1 [csvdump r] + set csv2 [csvdump {r -1}] + set fd [open /tmp/repldump1.txt w] + puts -nonewline $fd $csv1 + close $fd + set fd [open /tmp/repldump2.txt w] + puts -nonewline $fd $csv2 + close $fd + puts "Master - Slave inconsistency" + puts "Run diff -u against /tmp/repldump*.txt for more info" + } + assert_equal [r debug digest] [r -1 debug digest] + } + } +} diff --git a/tests/integration/replication.tcl b/tests/integration/replication.tcl index 2c7d98de..7c1edb55 100644 --- a/tests/integration/replication.tcl +++ b/tests/integration/replication.tcl @@ -97,7 +97,7 @@ start_server {tags {"repl"}} { [lindex $slaves 2] slaveof $master_host $master_port # Wait for all the three slaves to reach the "online" state - set retry 100 + set retry 500 while {$retry} { set info [r -3 info] if {[string match {*slave0:*,online*slave1:*,online*slave2:*,online*} $info]} { diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl index ca6cf34b..99415b64 100644 --- a/tests/support/redis.tcl +++ b/tests/support/redis.tcl @@ -142,9 +142,15 @@ proc ::redis::redis_multi_bulk_read fd { set count [redis_read_line $fd] if {$count == -1} return {} set l {} + set err {} for {set i 0} {$i < $count} {incr i} { - lappend l [redis_read_reply $fd] + if {[catch { + lappend l [redis_read_reply $fd] + } e] && $err eq {}} { + set err $e + } } + if {$err ne {}} {return -code error $err} return $l } diff --git a/tests/support/server.tcl b/tests/support/server.tcl index 984270ad..b1ab38fc 100644 --- a/tests/support/server.tcl +++ b/tests/support/server.tcl @@ -46,11 +46,16 @@ proc kill_server config { } # kill server and wait for the process to be totally exited + catch {exec kill $pid} while {[is_alive $config]} { - if {[incr wait 10] % 1000 == 0} { + incr wait 10 + + if {$wait >= 5000} { + puts "Forcing process $pid to exit..." + catch {exec kill -KILL $pid} + } elseif {$wait % 1000 == 0} { puts "Waiting for process $pid to exit..." } - catch {exec kill $pid} after 10 } diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index bef8231a..598a3929 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -29,6 +29,7 @@ set ::all_tests { integration/replication integration/replication-2 integration/replication-3 + integration/replication-4 integration/aof integration/rdb integration/convert-zipmap-hash-on-load @@ -37,7 +38,9 @@ set ::all_tests { unit/scripting unit/maxmemory unit/introspection + unit/limits unit/obuf-limits + unit/dump } # Index to the next test to run in the ::all_tests list. set ::next_test 0 @@ -343,6 +346,7 @@ proc print_help_screen {} { "--quiet Don't show individual tests." "--single <unit> Just execute the specified unit (see next option)." "--list-tests List all the available test units." + "--clients <num> Number of test clients (16)." "--force-failure Force the execution of a test that always fails." "--help Print this help screen." } "\n"] @@ -388,6 +392,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} { set ::client 1 set ::test_server_port $arg incr j + } elseif {$opt eq {--clients}} { + set ::numclients $arg + incr j } elseif {$opt eq {--help}} { print_help_screen exit 0 diff --git a/tests/unit/aofrw.tcl b/tests/unit/aofrw.tcl index 358266ef..e341d77b 100644 --- a/tests/unit/aofrw.tcl +++ b/tests/unit/aofrw.tcl @@ -1,4 +1,15 @@ start_server {tags {"aofrw"}} { + + test {Turning off AOF kills the background writing child if any} { + r config set appendonly yes + waitForBgrewriteaof r + r multi + r bgrewriteaof + r config set appendonly no + r exec + set result [exec cat [srv 0 stdout] | tail -n1] + } {*Killing*AOF*child*} + foreach d {string int} { foreach e {ziplist linkedlist} { test "AOF rewrite of list with $e encoding, $d data" { @@ -104,4 +115,30 @@ start_server {tags {"aofrw"}} { } } } + + test {BGREWRITEAOF is delayed if BGSAVE is in progress} { + r multi + r bgsave + r bgrewriteaof + r info persistence + set res [r exec] + assert_match {*scheduled*} [lindex $res 1] + assert_match {*bgrewriteaof_scheduled:1*} [lindex $res 2] + while {[string match {*bgrewriteaof_scheduled:1*} [r info persistence]]} { + after 100 + } + } + + test {BGREWRITEAOF is refused if already in progress} { + catch { + r multi + r bgrewriteaof + r bgrewriteaof + r exec + } e + assert_match {*ERR*already*} $e + while {[string match {*bgrewriteaof_scheduled:1*} [r info persistence]]} { + after 100 + } + } } diff --git a/tests/unit/dump.tcl b/tests/unit/dump.tcl new file mode 100644 index 00000000..b73cde0c --- /dev/null +++ b/tests/unit/dump.tcl @@ -0,0 +1,112 @@ +start_server {tags {"dump"}} { + test {DUMP / RESTORE are able to serialize / unserialize a simple key} { + r set foo bar + set encoded [r dump foo] + r del foo + list [r exists foo] [r restore foo 0 $encoded] [r ttl foo] [r get foo] + } {0 OK -1 bar} + + test {RESTORE can set an arbitrary expire to the materialized key} { + r set foo bar + set encoded [r dump foo] + r del foo + r restore foo 5000 $encoded + set ttl [r pttl foo] + assert {$ttl >= 3000 && $ttl <= 5000} + r get foo + } {bar} + + test {RESTORE returns an error of the key already exists} { + r set foo bar + set e {} + catch {r restore foo 0 "..."} e + set e + } {*is busy*} + + test {DUMP of non existing key returns nil} { + r dump nonexisting_key + } {} + + test {MIGRATE is able to migrate a key between two instances} { + set first [srv 0 client] + r set key "Some Value" + start_server {tags {"repl"}} { + set second [srv 0 client] + set second_host [srv 0 host] + set second_port [srv 0 port] + + assert {[$first exists key] == 1} + assert {[$second exists key] == 0} + set ret [r -1 migrate $second_host $second_port key 9 5000] + assert {$ret eq {OK}} + assert {[$first exists key] == 0} + assert {[$second exists key] == 1} + assert {[$second get key] eq {Some Value}} + assert {[$second ttl key] == -1} + } + } + + test {MIGRATE propagates TTL correctly} { + set first [srv 0 client] + r set key "Some Value" + start_server {tags {"repl"}} { + set second [srv 0 client] + set second_host [srv 0 host] + set second_port [srv 0 port] + + assert {[$first exists key] == 1} + assert {[$second exists key] == 0} + $first expire key 10 + set ret [r -1 migrate $second_host $second_port key 9 5000] + assert {$ret eq {OK}} + assert {[$first exists key] == 0} + assert {[$second exists key] == 1} + assert {[$second get key] eq {Some Value}} + assert {[$second ttl key] >= 7 && [$second ttl key] <= 10} + } + } + + test {MIGRATE can correctly transfer large values} { + set first [srv 0 client] + r del key + for {set j 0} {$j < 5000} {incr j} { + r rpush key 1 2 3 4 5 6 7 8 9 10 + r rpush key "item 1" "item 2" "item 3" "item 4" "item 5" \ + "item 6" "item 7" "item 8" "item 9" "item 10" + } + assert {[string length [r dump key]] > (1024*64)} + start_server {tags {"repl"}} { + set second [srv 0 client] + set second_host [srv 0 host] + set second_port [srv 0 port] + + assert {[$first exists key] == 1} + assert {[$second exists key] == 0} + set ret [r -1 migrate $second_host $second_port key 9 10000] + assert {$ret eq {OK}} + assert {[$first exists key] == 0} + assert {[$second exists key] == 1} + assert {[$second ttl key] == -1} + assert {[$second llen key] == 5000*20} + } + } + + test {MIGRATE timeout actually works} { + set first [srv 0 client] + r set key "Some Value" + start_server {tags {"repl"}} { + set second [srv 0 client] + set second_host [srv 0 host] + set second_port [srv 0 port] + + assert {[$first exists key] == 1} + assert {[$second exists key] == 0} + + set rd [redis_deferring_client] + $rd debug sleep 5.0 ; # Make second server unable to reply. + set e {} + catch {r -1 migrate $second_host $second_port key 9 1000} e + assert_match {IOERR*} $e + } + } +} diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl index a768e2ab..9db0395a 100644 --- a/tests/unit/introspection.tcl +++ b/tests/unit/introspection.tcl @@ -1,5 +1,22 @@ start_server {tags {"introspection"}} { test {CLIENT LIST} { r client list - } {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 qbuf=0 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*} + } {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*} + + test {MONITOR can log executed commands} { + set rd [redis_deferring_client] + $rd monitor + r set foo bar + r get foo + list [$rd read] [$rd read] [$rd read] + } {*OK*"set" "foo"*"get" "foo"*} + + test {MONITOR can log commands issued by the scripting engine} { + set rd [redis_deferring_client] + $rd monitor + r eval {redis.call('set',KEYS[1],ARGV[1])} 1 foo bar + $rd read ;# Discard the OK + assert_match {*eval*} [$rd read] + assert_match {*lua*"set"*"foo"*"bar"*} [$rd read] + } } diff --git a/tests/unit/limits.tcl b/tests/unit/limits.tcl new file mode 100644 index 00000000..b37ea9b0 --- /dev/null +++ b/tests/unit/limits.tcl @@ -0,0 +1,16 @@ +start_server {tags {"limits"} overrides {maxclients 10}} { + test {Check if maxclients works refusing connections} { + set c 0 + catch { + while {$c < 50} { + incr c + set rd [redis_deferring_client] + $rd ping + $rd read + after 100 + } + } e + assert {$c > 8 && $c <= 10} + set e + } {*ERR max*reached*} +} diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl index b9307cc1..a60c65b4 100644 --- a/tests/unit/scripting.tcl +++ b/tests/unit/scripting.tcl @@ -219,6 +219,96 @@ start_server {tags {"scripting"}} { list [r eval {return redis.sha1hex('')} 0] \ [r eval {return redis.sha1hex('Pizza & Mandolino')} 0] } {da39a3ee5e6b4b0d3255bfef95601890afd80709 74822d82031af7493c20eefa13bd07ec4fada82f} + + test {Globals protection reading an undeclared global variable} { + catch {r eval {return a} 0} e + set e + } {*ERR*attempted to access unexisting global*} + + test {Globals protection setting an undeclared global*} { + catch {r eval {a=10} 0} e + set e + } {*ERR*attempted to create global*} + + test {Test an example script DECR_IF_GT} { + set decr_if_gt { + local current + + current = redis.call('get',KEYS[1]) + if not current then return nil end + if current > ARGV[1] then + return redis.call('decr',KEYS[1]) + else + return redis.call('get',KEYS[1]) + end + } + r set foo 5 + set res {} + lappend res [r eval $decr_if_gt 1 foo 2] + lappend res [r eval $decr_if_gt 1 foo 2] + lappend res [r eval $decr_if_gt 1 foo 2] + lappend res [r eval $decr_if_gt 1 foo 2] + lappend res [r eval $decr_if_gt 1 foo 2] + set res + } {4 3 2 2 2} + + test {Scripting engine resets PRNG at every script execution} { + set rand1 [r eval {return tostring(math.random())} 0] + set rand2 [r eval {return tostring(math.random())} 0] + assert_equal $rand1 $rand2 + } + + test {Scripting engine PRNG can be seeded correctly} { + set rand1 [r eval { + math.randomseed(ARGV[1]); return tostring(math.random()) + } 0 10] + set rand2 [r eval { + math.randomseed(ARGV[1]); return tostring(math.random()) + } 0 10] + set rand3 [r eval { + math.randomseed(ARGV[1]); return tostring(math.random()) + } 0 20] + assert_equal $rand1 $rand2 + assert {$rand2 ne $rand3} + } +} + +# Start a new server since the last test in this stanza will kill the +# instance at all. +start_server {tags {"scripting"}} { + test {Timedout read-only scripts can be killed by SCRIPT KILL} { + set rd [redis_deferring_client] + r config set lua-time-limit 10 + $rd eval {while true do end} 0 + after 200 + catch {r ping} e + assert_match {BUSY*} $e + r script kill + assert_equal [r ping] "PONG" + } + + test {Timedout scripts that modified data can't be killed by SCRIPT KILL} { + set rd [redis_deferring_client] + r config set lua-time-limit 10 + $rd eval {redis.call('set','x','y'); while true do end} 0 + after 200 + catch {r ping} e + assert_match {BUSY*} $e + catch {r script kill} e + assert_match {ERR*} $e + catch {r ping} e + assert_match {BUSY*} $e + } + + test {SHUTDOWN NOSAVE can kill a timedout script anyway} { + # The server sould be still unresponding to normal commands. + catch {r ping} e + assert_match {BUSY*} $e + catch {r shutdown nosave} + # Make sure the server was killed + catch {set rd [redis_deferring_client]} e + assert_match {*connection refused*} $e + } } start_server {tags {"scripting repl"}} { diff --git a/tests/unit/sort.tcl b/tests/unit/sort.tcl index ba412254..5a181641 100644 --- a/tests/unit/sort.tcl +++ b/tests/unit/sort.tcl @@ -190,6 +190,13 @@ start_server { r sort myset by score:* } {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} + test "SORT GET with pattern ending with just -> does not get hash field" { + r del mylist + r lpush mylist a + r set x:a-> 100 + r sort mylist by num get x:*-> + } {100} + tags {"slow"} { set num 100 set res [create_random_dataset $num lpush] diff --git a/tests/unit/type/hash.tcl b/tests/unit/type/hash.tcl index 47e10caa..950805d1 100644 --- a/tests/unit/type/hash.tcl +++ b/tests/unit/type/hash.tcl @@ -395,4 +395,28 @@ start_server {tags {"hash"}} { r hset hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk b r hget hash kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk } {b} + + foreach size {10 512} { + test "Hash fuzzing - $size fields" { + for {set times 0} {$times < 10} {incr times} { + catch {unset hash} + array set hash {} + r del hash + + # Create + for {set j 0} {$j < $size} {incr j} { + set field [randomValue] + set value [randomValue] + r hset hash $field $value + set hash($field) $value + } + + # Verify + foreach {k v} [array get hash] { + assert_equal $v [r hget hash $k] + } + assert_equal [array size hash] [r hlen hash] + } + } + } } diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl index 970e3ee7..85dde569 100644 --- a/tests/unit/type/list.tcl +++ b/tests/unit/type/list.tcl @@ -7,7 +7,7 @@ start_server { } { source "tests/unit/type/list-common.tcl" - test {LPUSH, RPUSH, LLENGTH, LINDEX - ziplist} { + test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - ziplist} { # first lpush then rpush assert_equal 1 [r lpush myziplist1 a] assert_equal 2 [r rpush myziplist1 b] @@ -16,6 +16,9 @@ start_server { assert_equal a [r lindex myziplist1 0] assert_equal b [r lindex myziplist1 1] assert_equal c [r lindex myziplist1 2] + assert_equal {} [r lindex myziplist2 3] + assert_equal c [r rpop myziplist1] + assert_equal a [r lpop myziplist1] assert_encoding ziplist myziplist1 # first rpush then lpush @@ -26,10 +29,13 @@ start_server { assert_equal c [r lindex myziplist2 0] assert_equal b [r lindex myziplist2 1] assert_equal a [r lindex myziplist2 2] + assert_equal {} [r lindex myziplist2 3] + assert_equal a [r rpop myziplist2] + assert_equal c [r lpop myziplist2] assert_encoding ziplist myziplist2 } - test {LPUSH, RPUSH, LLENGTH, LINDEX - regular list} { + test {LPUSH, RPUSH, LLENGTH, LINDEX, LPOP - regular list} { # first lpush then rpush assert_equal 1 [r lpush mylist1 $largevalue(linkedlist)] assert_encoding linkedlist mylist1 @@ -39,6 +45,9 @@ start_server { assert_equal $largevalue(linkedlist) [r lindex mylist1 0] assert_equal b [r lindex mylist1 1] assert_equal c [r lindex mylist1 2] + assert_equal {} [r lindex mylist1 3] + assert_equal c [r rpop mylist1] + assert_equal $largevalue(linkedlist) [r lpop mylist1] # first rpush then lpush assert_equal 1 [r rpush mylist2 $largevalue(linkedlist)] @@ -49,8 +58,15 @@ start_server { assert_equal c [r lindex mylist2 0] assert_equal b [r lindex mylist2 1] assert_equal $largevalue(linkedlist) [r lindex mylist2 2] + assert_equal {} [r lindex mylist2 3] + assert_equal $largevalue(linkedlist) [r rpop mylist2] + assert_equal c [r lpop mylist2] } + test {R/LPOP against empty list} { + r lpop non-existing-list + } {} + test {Variadic RPUSH/LPUSH} { r del mylist assert_equal 4 [r lpush mylist a b c d] @@ -396,6 +412,11 @@ start_server { } } + test {LINSERT raise error on bad syntax} { + catch {[r linsert xlist aft3r aa 42]} e + set e + } {*ERR*syntax*error*} + test {LPUSHX, RPUSHX convert from ziplist to list} { set large $largevalue(linkedlist) diff --git a/tests/unit/type/set.tcl b/tests/unit/type/set.tcl index bdd1f9bf..f4f28373 100644 --- a/tests/unit/type/set.tcl +++ b/tests/unit/type/set.tcl @@ -206,6 +206,13 @@ start_server { } } + test "SDIFF with first set empty" { + r del set1 set2 set3 + r sadd set2 1 2 3 4 + r sadd set3 a b c d + r sdiff set1 set2 set3 + } {} + test "SINTER against non-set should throw error" { r set key1 x assert_error "ERR*wrong kind*" {r sinter key1 noset} @@ -216,6 +223,23 @@ start_server { assert_error "ERR*wrong kind*" {r sunion key1 noset} } + test "SINTER should handle non existing key as empty" { + r del set1 set2 set3 + r sadd set1 a b c + r sadd set2 b c d + r sinter set1 set2 set3 + } {} + + test "SINTER with same integer elements but different encoding" { + r del set1 set2 + r sadd set1 1 2 3 + r sadd set2 1 2 3 a + r srem set2 a + assert_encoding intset set1 + assert_encoding hashtable set2 + lsort [r sinter set1 set2] + } {1 2 3} + test "SINTERSTORE against non existing keys should delete dstkey" { r set setres xxx assert_equal 0 [r sinterstore setres foo111 bar222] @@ -317,6 +341,13 @@ start_server { assert_error "ERR*wrong kind*" {r smove myset2 x foo} } + test "SMOVE with identical source and destination" { + r del set + r sadd set a b c + r smove set set b + lsort [r smembers set] + } {a b c} + tags {slow} { test {intsets implementation stress testing} { for {set j 0} {$j < 20} {incr j} { diff --git a/utils/generate-command-help.rb b/utils/generate-command-help.rb index 96cccc2b..f6ca8874 100755 --- a/utils/generate-command-help.rb +++ b/utils/generate-command-help.rb @@ -10,7 +10,8 @@ GROUPS = [ "pubsub", "transactions", "connection", - "server" + "server", + "scripting" ].freeze GROUPS_BY_NAME = Hash[* @@ -48,7 +49,7 @@ def commands require "json" require "uri" - url = URI.parse "https://github.com/antirez/redis-doc/raw/master/commands.json" + url = URI.parse "https://raw.github.com/antirez/redis-doc/master/commands.json" client = Net::HTTP.new url.host, url.port client.use_ssl = true response = client.get url.path diff --git a/utils/install_server.sh b/utils/install_server.sh index 06570a87..93b5b411 100755 --- a/utils/install_server.sh +++ b/utils/install_server.sh @@ -166,7 +166,7 @@ if [[ ! `which chkconfig` ]] ; then else # we're chkconfig, so lets add to chkconfig and put in runlevel 345 chkconfig --add redis_$REDIS_PORT && echo "Successfully added to chkconfig!" - chkconfig--level 345 redis_$REDIS_PORT on && echo "Successfully added to runlevels 345!" + chkconfig --level 345 redis_$REDIS_PORT on && echo "Successfully added to runlevels 345!" fi /etc/init.d/redis_$REDIS_PORT start || die "Failed starting service..."