From acb4ef0f1265534c2fe4672ec58c6fffffa55dad Mon Sep 17 00:00:00 2001 From: Grant Limberg Date: Mon, 11 May 2020 11:48:05 -0700 Subject: [PATCH] add hiredis-vip to controller build --- controller/PostgreSQL.cpp | 9 + controller/PostgreSQL.hpp | 3 + ext/central-controller-docker/Dockerfile | 4 +- ext/hiredis-vip-0.3.0/.gitignore | 7 + ext/hiredis-vip-0.3.0/.travis.yml | 16 + ext/hiredis-vip-0.3.0/CHANGELOG.md | 16 + ext/hiredis-vip-0.3.0/COPYING | 29 + ext/hiredis-vip-0.3.0/Makefile | 205 + ext/hiredis-vip-0.3.0/README.md | 255 + ext/hiredis-vip-0.3.0/adapters/ae.h | 154 + ext/hiredis-vip-0.3.0/adapters/glib.h | 153 + ext/hiredis-vip-0.3.0/adapters/libev.h | 147 + ext/hiredis-vip-0.3.0/adapters/libevent.h | 135 + ext/hiredis-vip-0.3.0/adapters/libuv.h | 122 + ext/hiredis-vip-0.3.0/adlist.c | 341 ++ ext/hiredis-vip-0.3.0/adlist.h | 93 + ext/hiredis-vip-0.3.0/async.c | 691 +++ ext/hiredis-vip-0.3.0/async.h | 130 + ext/hiredis-vip-0.3.0/command.c | 1700 ++++++ ext/hiredis-vip-0.3.0/command.h | 179 + ext/hiredis-vip-0.3.0/crc16.c | 87 + ext/hiredis-vip-0.3.0/dict.c | 338 ++ ext/hiredis-vip-0.3.0/dict.h | 126 + ext/hiredis-vip-0.3.0/examples/example-ae.c | 62 + ext/hiredis-vip-0.3.0/examples/example-glib.c | 73 + .../examples/example-libev.c | 52 + .../examples/example-libevent.c | 53 + .../examples/example-libuv.c | 53 + ext/hiredis-vip-0.3.0/examples/example.c | 78 + ext/hiredis-vip-0.3.0/fmacros.h | 23 + ext/hiredis-vip-0.3.0/hiarray.c | 188 + ext/hiredis-vip-0.3.0/hiarray.h | 56 + ext/hiredis-vip-0.3.0/hircluster.c | 4747 +++++++++++++++++ ext/hiredis-vip-0.3.0/hircluster.h | 178 + ext/hiredis-vip-0.3.0/hiredis.c | 1021 ++++ ext/hiredis-vip-0.3.0/hiredis.h | 221 + ext/hiredis-vip-0.3.0/hiutil.c | 554 ++ ext/hiredis-vip-0.3.0/hiutil.h | 265 + ext/hiredis-vip-0.3.0/net.c | 458 ++ ext/hiredis-vip-0.3.0/net.h | 53 + ext/hiredis-vip-0.3.0/read.c | 525 ++ ext/hiredis-vip-0.3.0/read.h | 129 + ext/hiredis-vip-0.3.0/sds.c | 1095 ++++ ext/hiredis-vip-0.3.0/sds.h | 105 + ext/hiredis-vip-0.3.0/test.c | 806 +++ ext/hiredis-vip-0.3.0/win32.h | 42 + make-linux.mk | 9 +- make-mac.mk | 7 +- 48 files changed, 15788 insertions(+), 5 deletions(-) create mode 100644 ext/hiredis-vip-0.3.0/.gitignore create mode 100644 ext/hiredis-vip-0.3.0/.travis.yml create mode 100644 ext/hiredis-vip-0.3.0/CHANGELOG.md create mode 100644 ext/hiredis-vip-0.3.0/COPYING create mode 100644 ext/hiredis-vip-0.3.0/Makefile create mode 100644 ext/hiredis-vip-0.3.0/README.md create mode 100644 ext/hiredis-vip-0.3.0/adapters/ae.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/glib.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/libev.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/libevent.h create mode 100644 ext/hiredis-vip-0.3.0/adapters/libuv.h create mode 100644 ext/hiredis-vip-0.3.0/adlist.c create mode 100644 ext/hiredis-vip-0.3.0/adlist.h create mode 100644 ext/hiredis-vip-0.3.0/async.c create mode 100644 ext/hiredis-vip-0.3.0/async.h create mode 100644 ext/hiredis-vip-0.3.0/command.c create mode 100644 ext/hiredis-vip-0.3.0/command.h create mode 100644 ext/hiredis-vip-0.3.0/crc16.c create mode 100644 ext/hiredis-vip-0.3.0/dict.c create mode 100644 ext/hiredis-vip-0.3.0/dict.h create mode 100644 ext/hiredis-vip-0.3.0/examples/example-ae.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-glib.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-libev.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-libevent.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example-libuv.c create mode 100644 ext/hiredis-vip-0.3.0/examples/example.c create mode 100644 ext/hiredis-vip-0.3.0/fmacros.h create mode 100644 ext/hiredis-vip-0.3.0/hiarray.c create mode 100644 ext/hiredis-vip-0.3.0/hiarray.h create mode 100644 ext/hiredis-vip-0.3.0/hircluster.c create mode 100644 ext/hiredis-vip-0.3.0/hircluster.h create mode 100644 ext/hiredis-vip-0.3.0/hiredis.c create mode 100644 ext/hiredis-vip-0.3.0/hiredis.h create mode 100644 ext/hiredis-vip-0.3.0/hiutil.c create mode 100644 ext/hiredis-vip-0.3.0/hiutil.h create mode 100644 ext/hiredis-vip-0.3.0/net.c create mode 100644 ext/hiredis-vip-0.3.0/net.h create mode 100644 ext/hiredis-vip-0.3.0/read.c create mode 100644 ext/hiredis-vip-0.3.0/read.h create mode 100644 ext/hiredis-vip-0.3.0/sds.c create mode 100644 ext/hiredis-vip-0.3.0/sds.h create mode 100644 ext/hiredis-vip-0.3.0/test.c create mode 100644 ext/hiredis-vip-0.3.0/win32.h diff --git a/controller/PostgreSQL.cpp b/controller/PostgreSQL.cpp index 6978bef35..e81ac0a69 100644 --- a/controller/PostgreSQL.cpp +++ b/controller/PostgreSQL.cpp @@ -740,6 +740,11 @@ void PostgreSQL::_membersWatcher_RabbitMQ() { } } +void PostgreSQL::_membersWatcher_Reids() { + char buff[11] = {0}; + +} + void PostgreSQL::networksDbWatcher() { PGconn *conn = getPgConn(NO_OVERRIDE); @@ -844,6 +849,10 @@ void PostgreSQL::_networksWatcher_RabbitMQ() { } } +void PostgreSQL::_networksWatcher_Redis() { + +} + void PostgreSQL::commitThread() { PGconn *conn = getPgConn(); diff --git a/controller/PostgreSQL.hpp b/controller/PostgreSQL.hpp index 035f5b5a3..d0a32edfc 100644 --- a/controller/PostgreSQL.hpp +++ b/controller/PostgreSQL.hpp @@ -64,6 +64,9 @@ private: void _networksWatcher_Postgres(PGconn *conn); void _networksWatcher_RabbitMQ(); + void _membersWatcher_Reids(); + void _networksWatcher_Redis(); + void commitThread(); void onlineNotificationThread(); diff --git a/ext/central-controller-docker/Dockerfile b/ext/central-controller-docker/Dockerfile index 12984ebda..04d4826c3 100644 --- a/ext/central-controller-docker/Dockerfile +++ b/ext/central-controller-docker/Dockerfile @@ -5,7 +5,7 @@ MAINTAINER Adam Ierymekno , Grant Limberg +Copyright (c) 2010-2011, Pieter Noordhuis + +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. diff --git a/ext/hiredis-vip-0.3.0/Makefile b/ext/hiredis-vip-0.3.0/Makefile new file mode 100644 index 000000000..58494bfc3 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/Makefile @@ -0,0 +1,205 @@ +# Hiredis Makefile +# Copyright (C) 2010-2011 Salvatore Sanfilippo +# Copyright (C) 2010-2011 Pieter Noordhuis +# This file is released under the BSD license, see the COPYING file + +OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o adlist.o hircluster.o +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +TESTS=hiredis-test +LIBNAME=libhiredis_vip +PKGCONFNAME=hiredis.pc + +HIREDIS_VIP_MAJOR=$(shell grep HIREDIS_VIP_MAJOR hircluster.h | awk '{print $$3}') +HIREDIS_VIP_MINOR=$(shell grep HIREDIS_VIP_MINOR hircluster.h | awk '{print $$3}') +HIREDIS_VIP_PATCH=$(shell grep HIREDIS_VIP_PATCH hircluster.h | awk '{print $$3}') + +# Installation related variables and target +PREFIX?=/usr/local +INCLUDE_PATH?=include/hiredis-vip +LIBRARY_PATH?=lib +PKGCONF_PATH?=pkgconfig +INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) +INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH) +INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH) + +# redis-server configuration used for testing +REDIS_PORT=56379 +REDIS_SERVER=redis-server +define REDIS_TEST_CONFIG + daemonize yes + pidfile /tmp/hiredis-test-redis.pid + port $(REDIS_PORT) + bind 127.0.0.1 + unixsocket /tmp/hiredis-test-redis.sock +endef +export REDIS_TEST_CONFIG + +# Fallback to gcc when $CC is not in $PATH. +CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') +OPTIMIZATION?=-O3 +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings +DEBUG?= -g -ggdb +REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH) +REAL_LDFLAGS=$(LDFLAGS) $(ARCH) + +DYLIBSUFFIX=so +STLIBSUFFIX=a +DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) +DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) +STLIB_MAKE_CMD=ar rcs $(STLIBNAME) + +# Platform-specific overrides +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +ifeq ($(uname_S),SunOS) + REAL_LDFLAGS+= -ldl -lnsl -lsocket + DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) + INSTALL= cp -r +endif +ifeq ($(uname_S),Darwin) + DYLIBSUFFIX=dylib + DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(DYLIBSUFFIX) + DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(DYLIBSUFFIX) + DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +endif + +all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) + +# Deps (use make dep to generate this) + +adlist.o: adlist.c adlist.h hiutil.h +async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h +command.o: command.c command.h hiredis.h read.h sds.h adlist.h hiutil.h hiarray.h +crc16.o: crc16.c hiutil.h +dict.o: dict.c fmacros.h dict.h +hiarray.o: hiarray.c hiarray.h hiutil.h +hircluster.o: hircluster.c fmacros.h hircluster.h hiredis.h read.h sds.h adlist.h hiarray.h hiutil.h async.h command.h dict.c dict.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h +hiutil.o: hiutil.c hiutil.h +net.o: net.c fmacros.h net.h hiredis.h read.h sds.h +read.o: read.c fmacros.h read.h sds.h +sds.o: sds.c sds.h +test.o: test.c fmacros.h hiredis.h read.h sds.h net.h + +$(DYLIBNAME): $(OBJ) + $(DYLIB_MAKE_CMD) $(OBJ) + +$(STLIBNAME): $(OBJ) + $(STLIB_MAKE_CMD) $(OBJ) + +dynamic: $(DYLIBNAME) +static: $(STLIBNAME) + +# Binaries: +hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + +hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) + +hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME) + +ifndef AE_DIR +hiredis-example-ae: + @echo "Please specify AE_DIR (e.g. /src)" + @false +else +hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME) +endif + +ifndef LIBUV_DIR +hiredis-example-libuv: + @echo "Please specify LIBUV_DIR (e.g. ../libuv/)" + @false +else +hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME) +endif + +hiredis-example: examples/example.c $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + +examples: $(EXAMPLES) + +hiredis-test: test.o $(STLIBNAME) + +hiredis-%: %.o $(STLIBNAME) + $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) + +test: hiredis-test + ./hiredis-test + +check: hiredis-test + @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - + $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \ + ( kill `cat /tmp/hiredis-test-redis.pid` && false ) + kill `cat /tmp/hiredis-test-redis.pid` + +.c.o: + $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< + +clean: + rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov + +dep: + $(CC) -MM *.c + +ifeq ($(uname_S),SunOS) + INSTALL?= cp -r +endif + +INSTALL?= cp -a + +$(PKGCONFNAME): hiredis.h + @echo "Generating $@ for pkgconfig..." + @echo prefix=$(PREFIX) > $@ + @echo exec_prefix=\$${prefix} >> $@ + @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ + @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ + @echo >> $@ + @echo Name: hiredis >> $@ + @echo Description: Minimalistic C client library for Redis. >> $@ + @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ + @echo Libs: -L\$${libdir} -lhiredis >> $@ + @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ + +install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) + mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h hiutil.h hiarray.h dict.h dict.c adlist.h fmacros.h hircluster.h adapters $(INSTALL_INCLUDE_PATH) + $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME) + $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) + mkdir -p $(INSTALL_PKGCONF_PATH) + $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) + +32bit: + @echo "" + @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" + @echo "" + $(MAKE) CFLAGS="-m32" LDFLAGS="-m32" + +32bit-vars: + $(eval CFLAGS=-m32) + $(eval LDFLAGS=-m32) + +gprof: + $(MAKE) CFLAGS="-pg" LDFLAGS="-pg" + +gcov: + $(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs" + +coverage: gcov + make check + mkdir -p tmp/lcov + lcov -d . -c -o tmp/lcov/hiredis.info + genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info + +noopt: + $(MAKE) OPTIMIZATION="" + +.PHONY: all test check clean dep install 32bit gprof gcov noopt diff --git a/ext/hiredis-vip-0.3.0/README.md b/ext/hiredis-vip-0.3.0/README.md new file mode 100644 index 000000000..897419390 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/README.md @@ -0,0 +1,255 @@ + +# HIREDIS-VIP + +Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. + +Hiredis-vip supported redis cluster. + +Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . + +## CLUSTER SUPPORT + +### FEATURES: + +* **`SUPPORT REDIS CLUSTER`**: + * Connect to redis cluster and run commands. + +* **`SUPPORT MULTI-KEY COMMAND`**: + * Support `MSET`, `MGET` and `DEL`. + +* **`SUPPORT PIPELING`**: + * Support redis pipeline and can contain multi-key command like above. + +* **`SUPPORT Asynchronous API`**: + * User can run commands with asynchronous mode. + +### CLUSTER API: + +```c +redisClusterContext *redisClusterConnect(const char *addrs, int flags); +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags); +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); +void redisClusterFree(redisClusterContext *cc); +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); +redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); +int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); +int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); +int redisClusterGetReply(redisClusterContext *cc, void **reply); +void redisClusterReset(redisClusterContext *cc); + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); +int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); +int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); + +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +void redisClusterAsyncFree(redisClusterAsyncContext *acc); +``` + +## Quick usage + +If you want used but not read the follow, please reference the examples: +https://github.com/vipshop/hiredis-vip/wiki + +## Cluster synchronous API + +To consume the synchronous API, there are only a few function calls that need to be introduced: + +```c +redisClusterContext *redisClusterConnect(const char *addrs, int flags); +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +void redisClusterFree(redisClusterContext *cc); +``` + +### Cluster connecting + +The function `redisClusterConnect` is used to create a so-called `redisClusterContext`. The +context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext` +struct has an integer `err` field that is non-zero when the connection is in +an error state. The field `errstr` will contain a string with a description of +the error. +After trying to connect to Redis using `redisClusterContext` you should +check the `err` field to see if establishing the connection was successful: +```c +redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); +if (cc != NULL && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error +} +``` + +### Cluster sending commands + +The next that will be introduced is `redisClusterCommand`. +This function takes a format similar to printf. In the simplest form, +it is used like this: +```c +reply = redisClusterCommand(clustercontext, "SET foo bar"); +``` + +The specifier `%s` interpolates a string in the command, and uses `strlen` to +determine the length of the string: +```c +reply = redisClusterCommand(clustercontext, "SET foo %s", value); +``` +Internally, Hiredis-vip splits the command in different arguments and will +convert it to the protocol used to communicate with Redis. +One or more spaces separates arguments, so you can use the specifiers +anywhere in an argument: +```c +reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); +``` + +### Cluster multi-key commands + +Hiredis-vip supports mget/mset/del multi-key commands. +Those multi-key commands is highly effective. +Millions of keys in one mget command just used several seconds. + +Example: +```c +reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); +``` + +### Cluster cleaning up + +To disconnect and free the context the following function can be used: +```c +void redisClusterFree(redisClusterContext *cc); +``` +This function immediately closes the socket and then frees the allocations done in +creating the context. + +### Cluster pipelining + +The function `redisClusterGetReply` is exported as part of the Hiredis API and can be used +when a reply is expected on the socket. To pipeline commands, the only things that needs +to be done is filling up the output buffer. For this cause, two commands can be used that +are identical to the `redisClusterCommand` family, apart from not returning a reply: +```c +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); +``` +After calling either function one or more times, `redisClusterGetReply` can be used to receive the +subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where +the latter means an error occurred while reading a reply. Just as with the other commands, +the `err` field in the context can be used to find out what the cause of this error is. +```c +void redisClusterReset(redisClusterContext *cc); +``` +Warning: You must call `redisClusterReset` function after one pipelining anyway. + +The following examples shows a simple cluster pipeline: +```c +redisReply *reply; +redisClusterAppendCommand(clusterContext,"SET foo bar"); +redisClusterAppendCommand(clusterContext,"GET foo"); +redisClusterGetReply(clusterContext,&reply); // reply for SET +freeReplyObject(reply); +redisClusterGetReply(clusterContext,&reply); // reply for GET +freeReplyObject(reply); +redisClusterReset(clusterContext); +``` + +## Cluster asynchronous API + +Hiredis-vip comes with an cluster asynchronous API that works easily with any event library. +Now we just support and test for libevent and redis ae, if you need for other event libraries, +please contact with us, and we will support it quickly. + +### Connecting + +The function `redisAsyncConnect` can be used to establish a non-blocking connection to +Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field +should be checked after creation to see if there were errors creating the connection. +Because the connection that will be created is non-blocking, the kernel is not able to +instantly return if the specified host and port is able to accept a connection. +```c +redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); +if (acc->err) { + printf("Error: %s\n", acc->errstr); + // handle error +} +``` + +The cluster asynchronous context can hold a disconnect callback function that is called when the +connection is disconnected (either because of an error or per user request). This function should +have the following prototype: +```c +void(const redisAsyncContext *c, int status); +``` +On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the +user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` +field in the context can be accessed to find out the cause of the error. + +You not need to reconnect in the disconnect callback, hiredis-vip will reconnect this connection itself +when commands come to this redis node. + +Setting the disconnect callback can only be done once per context. For subsequent calls it will +return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: +```c +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +``` +### Sending commands and their callbacks + +In an cluster asynchronous context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the cluster synchronous API, there is only a single way to send commands. +Because commands are sent to Redis cluster asynchronously, issuing a command requires a callback function +that is called when the reply is received. Reply callbacks should have the following prototype: +```c +void(redisClusterAsyncContext *acc, void *reply, void *privdata); +``` +The `privdata` argument can be used to curry arbitrary data to the callback from the point where +the command is initially queued for execution. + +The functions that can be used to issue commands in an asynchronous context are: +```c +int redisClusterAsyncCommand( + redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, + void *privdata, const char *format, ...); +``` +This function work like their blocking counterparts. The return value is `REDIS_OK` when the command +was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection +is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is +returned on calls to the `redisClusterAsyncCommand` family. + +If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback +for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only +valid for the duration of the callback. + +All pending callbacks are called with a `NULL` reply when the context encountered an error. + +### Disconnecting + +An cluster asynchronous connection can be terminated using: +```c +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +``` +When this function is called, the connection is **not** immediately terminated. Instead, new +commands are no longer accepted and the connection is only terminated when all pending commands +have been written to the socket, their respective replies have been read and their respective +callbacks have been executed. After this, the disconnection callback is executed with the +`REDIS_OK` status and the context object is freed. + +### Hooking it up to event library *X* + +There are a few hooks that need to be set on the cluster context object after it is created. +See the `adapters/` directory for bindings to *ae* and *libevent*. + +## AUTHORS + +Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). +The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). +The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). +Hiredis-vip is released under the BSD license. diff --git a/ext/hiredis-vip-0.3.0/adapters/ae.h b/ext/hiredis-vip-0.3.0/adapters/ae.h new file mode 100644 index 000000000..f861cf287 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/ae.h @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_AE_H__ +#define __HIREDIS_AE_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" + +#if 1 //shenzheng 2015-11-5 redis cluster +#include "../hircluster.h" +#endif //shenzheng 2015-11-5 redis cluster + +typedef struct redisAeEvents { + redisAsyncContext *context; + aeEventLoop *loop; + int fd; + int reading, writing; +} redisAeEvents; + +static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleRead(e->context); +} + +static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { + ((void)el); ((void)fd); ((void)mask); + + redisAeEvents *e = (redisAeEvents*)privdata; + redisAsyncHandleWrite(e->context); +} + +static void redisAeAddRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->reading) { + e->reading = 1; + aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); + } +} + +static void redisAeDelRead(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->reading) { + e->reading = 0; + aeDeleteFileEvent(loop,e->fd,AE_READABLE); + } +} + +static void redisAeAddWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (!e->writing) { + e->writing = 1; + aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); + } +} + +static void redisAeDelWrite(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + aeEventLoop *loop = e->loop; + if (e->writing) { + e->writing = 0; + aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); + } +} + +static void redisAeCleanup(void *privdata) { + redisAeEvents *e = (redisAeEvents*)privdata; + redisAeDelRead(privdata); + redisAeDelWrite(privdata); + free(e); +} + +static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisAeEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisAeEvents*)malloc(sizeof(*e)); + e->context = ac; + e->loop = loop; + e->fd = c->fd; + e->reading = e->writing = 0; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisAeAddRead; + ac->ev.delRead = redisAeDelRead; + ac->ev.addWrite = redisAeAddWrite; + ac->ev.delWrite = redisAeDelWrite; + ac->ev.cleanup = redisAeCleanup; + ac->ev.data = e; + + return REDIS_OK; +} + +#if 1 //shenzheng 2015-11-5 redis cluster + +static int redisAeAttach_link(redisAsyncContext *ac, void *base) +{ + redisAeAttach((aeEventLoop *)base, ac); +} + +static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) { + + if(acc == NULL || loop == NULL) + { + return REDIS_ERR; + } + + acc->adapter = loop; + acc->attach_fn = redisAeAttach_link; + + return REDIS_OK; +} + +#endif //shenzheng 2015-11-5 redis cluster + +#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/glib.h b/ext/hiredis-vip-0.3.0/adapters/glib.h new file mode 100644 index 000000000..e13eee73b --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/glib.h @@ -0,0 +1,153 @@ +#ifndef __HIREDIS_GLIB_H__ +#define __HIREDIS_GLIB_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct +{ + GSource source; + redisAsyncContext *ac; + GPollFD poll_fd; +} RedisSource; + +static void +redis_source_add_read (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_IN; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_del_read (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_IN; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_add_write (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events |= G_IO_OUT; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_del_write (gpointer data) +{ + RedisSource *source = data; + g_return_if_fail(source); + source->poll_fd.events &= ~G_IO_OUT; + g_main_context_wakeup(g_source_get_context(data)); +} + +static void +redis_source_cleanup (gpointer data) +{ + RedisSource *source = data; + + g_return_if_fail(source); + + redis_source_del_read(source); + redis_source_del_write(source); + /* + * It is not our responsibility to remove ourself from the + * current main loop. However, we will remove the GPollFD. + */ + if (source->poll_fd.fd >= 0) { + g_source_remove_poll(data, &source->poll_fd); + source->poll_fd.fd = -1; + } +} + +static gboolean +redis_source_prepare (GSource *source, + gint *timeout_) +{ + RedisSource *redis = (RedisSource *)source; + *timeout_ = -1; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_check (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + return !!(redis->poll_fd.events & redis->poll_fd.revents); +} + +static gboolean +redis_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + RedisSource *redis = (RedisSource *)source; + + if ((redis->poll_fd.revents & G_IO_OUT)) { + redisAsyncHandleWrite(redis->ac); + redis->poll_fd.revents &= ~G_IO_OUT; + } + + if ((redis->poll_fd.revents & G_IO_IN)) { + redisAsyncHandleRead(redis->ac); + redis->poll_fd.revents &= ~G_IO_IN; + } + + if (callback) { + return callback(user_data); + } + + return TRUE; +} + +static void +redis_source_finalize (GSource *source) +{ + RedisSource *redis = (RedisSource *)source; + + if (redis->poll_fd.fd >= 0) { + g_source_remove_poll(source, &redis->poll_fd); + redis->poll_fd.fd = -1; + } +} + +static GSource * +redis_source_new (redisAsyncContext *ac) +{ + static GSourceFuncs source_funcs = { + .prepare = redis_source_prepare, + .check = redis_source_check, + .dispatch = redis_source_dispatch, + .finalize = redis_source_finalize, + }; + redisContext *c = &ac->c; + RedisSource *source; + + g_return_val_if_fail(ac != NULL, NULL); + + source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); + source->ac = ac; + source->poll_fd.fd = c->fd; + source->poll_fd.events = 0; + source->poll_fd.revents = 0; + g_source_add_poll((GSource *)source, &source->poll_fd); + + ac->ev.addRead = redis_source_add_read; + ac->ev.delRead = redis_source_del_read; + ac->ev.addWrite = redis_source_add_write; + ac->ev.delWrite = redis_source_del_write; + ac->ev.cleanup = redis_source_cleanup; + ac->ev.data = source; + + return (GSource *)source; +} + +#endif /* __HIREDIS_GLIB_H__ */ diff --git a/ext/hiredis-vip-0.3.0/adapters/libev.h b/ext/hiredis-vip-0.3.0/adapters/libev.h new file mode 100644 index 000000000..2bf8d521f --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/libev.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_LIBEV_H__ +#define __HIREDIS_LIBEV_H__ +#include +#include +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisLibevEvents { + redisAsyncContext *context; + struct ev_loop *loop; + int reading, writing; + ev_io rev, wev; +} redisLibevEvents; + +static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleRead(e->context); +} + +static void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) { +#if EV_MULTIPLICITY + ((void)loop); +#endif + ((void)revents); + + redisLibevEvents *e = (redisLibevEvents*)watcher->data; + redisAsyncHandleWrite(e->context); +} + +static void redisLibevAddRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->reading) { + e->reading = 1; + ev_io_start(EV_A_ &e->rev); + } +} + +static void redisLibevDelRead(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->reading) { + e->reading = 0; + ev_io_stop(EV_A_ &e->rev); + } +} + +static void redisLibevAddWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (!e->writing) { + e->writing = 1; + ev_io_start(EV_A_ &e->wev); + } +} + +static void redisLibevDelWrite(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + struct ev_loop *loop = e->loop; + ((void)loop); + if (e->writing) { + e->writing = 0; + ev_io_stop(EV_A_ &e->wev); + } +} + +static void redisLibevCleanup(void *privdata) { + redisLibevEvents *e = (redisLibevEvents*)privdata; + redisLibevDelRead(privdata); + redisLibevDelWrite(privdata); + free(e); +} + +static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisLibevEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibevEvents*)malloc(sizeof(*e)); + e->context = ac; +#if EV_MULTIPLICITY + e->loop = loop; +#else + e->loop = NULL; +#endif + e->reading = e->writing = 0; + e->rev.data = e; + e->wev.data = e; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibevAddRead; + ac->ev.delRead = redisLibevDelRead; + ac->ev.addWrite = redisLibevAddWrite; + ac->ev.delWrite = redisLibevDelWrite; + ac->ev.cleanup = redisLibevCleanup; + ac->ev.data = e; + + /* Initialize read/write events */ + ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ); + ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE); + return REDIS_OK; +} + +#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libevent.h b/ext/hiredis-vip-0.3.0/adapters/libevent.h new file mode 100644 index 000000000..6bc911c77 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/libevent.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_LIBEVENT_H__ +#define __HIREDIS_LIBEVENT_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +#if 1 //shenzheng 2015-9-21 redis cluster +#include "../hircluster.h" +#endif //shenzheng 2015-9-21 redis cluster + +typedef struct redisLibeventEvents { + redisAsyncContext *context; + struct event rev, wev; +} redisLibeventEvents; + +static void redisLibeventReadEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleRead(e->context); +} + +static void redisLibeventWriteEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + redisLibeventEvents *e = (redisLibeventEvents*)arg; + redisAsyncHandleWrite(e->context); +} + +static void redisLibeventAddRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(&e->rev,NULL); +} + +static void redisLibeventDelRead(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(&e->rev); +} + +static void redisLibeventAddWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_add(&e->wev,NULL); +} + +static void redisLibeventDelWrite(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(&e->wev); +} + +static void redisLibeventCleanup(void *privdata) { + redisLibeventEvents *e = (redisLibeventEvents*)privdata; + event_del(&e->rev); + event_del(&e->wev); + free(e); +} + +static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { + redisContext *c = &(ac->c); + redisLibeventEvents *e; + + /* Nothing should be attached when something is already attached */ + if (ac->ev.data != NULL) + return REDIS_ERR; + + /* Create container for context and r/w events */ + e = (redisLibeventEvents*)malloc(sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e); + event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e); + event_base_set(base,&e->rev); + event_base_set(base,&e->wev); + return REDIS_OK; +} + +#if 1 //shenzheng 2015-9-21 redis cluster + +static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) +{ + redisLibeventAttach(ac, (struct event_base *)base); +} + +static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { + + if(acc == NULL || base == NULL) + { + return REDIS_ERR; + } + + acc->adapter = base; + acc->attach_fn = redisLibeventAttach_link; + + return REDIS_OK; +} + +#endif //shenzheng 2015-9-21 redis cluster + +#endif diff --git a/ext/hiredis-vip-0.3.0/adapters/libuv.h b/ext/hiredis-vip-0.3.0/adapters/libuv.h new file mode 100644 index 000000000..3c9a49f53 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adapters/libuv.h @@ -0,0 +1,122 @@ +#ifndef __HIREDIS_LIBUV_H__ +#define __HIREDIS_LIBUV_H__ +#include +#include +#include "../hiredis.h" +#include "../async.h" +#include + +typedef struct redisLibuvEvents { + redisAsyncContext* context; + uv_poll_t handle; + int events; +} redisLibuvEvents; + + +static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + if (status != 0) { + return; + } + + if (events & UV_READABLE) { + redisAsyncHandleRead(p->context); + } + if (events & UV_WRITABLE) { + redisAsyncHandleWrite(p->context); + } +} + + +static void redisLibuvAddRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_READABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelRead(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_READABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void redisLibuvAddWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events |= UV_WRITABLE; + + uv_poll_start(&p->handle, p->events, redisLibuvPoll); +} + + +static void redisLibuvDelWrite(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + p->events &= ~UV_WRITABLE; + + if (p->events) { + uv_poll_start(&p->handle, p->events, redisLibuvPoll); + } else { + uv_poll_stop(&p->handle); + } +} + + +static void on_close(uv_handle_t* handle) { + redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + + free(p); +} + + +static void redisLibuvCleanup(void *privdata) { + redisLibuvEvents* p = (redisLibuvEvents*)privdata; + + uv_close((uv_handle_t*)&p->handle, on_close); +} + + +static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { + redisContext *c = &(ac->c); + + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + + ac->ev.addRead = redisLibuvAddRead; + ac->ev.delRead = redisLibuvDelRead; + ac->ev.addWrite = redisLibuvAddWrite; + ac->ev.delWrite = redisLibuvDelWrite; + ac->ev.cleanup = redisLibuvCleanup; + + redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); + + if (!p) { + return REDIS_ERR; + } + + memset(p, 0, sizeof(*p)); + + if (uv_poll_init(loop, &p->handle, c->fd) != 0) { + return REDIS_ERR; + } + + ac->ev.data = p; + p->handle.data = p; + p->context = ac; + + return REDIS_OK; +} + +#endif diff --git a/ext/hiredis-vip-0.3.0/adlist.c b/ext/hiredis-vip-0.3.0/adlist.c new file mode 100644 index 000000000..b490a6bd1 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adlist.c @@ -0,0 +1,341 @@ +/* adlist.c - A generic doubly linked list implementation + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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 +#include "adlist.h" +#include "hiutil.h" + +/* Create a new list. The created list can be freed with + * AlFreeList(), but private value of every node need to be freed + * by the user before to call AlFreeList(). + * + * On error, NULL is returned. Otherwise the pointer to the new list. */ +hilist *listCreate(void) +{ + struct hilist *list; + + if ((list = hi_alloc(sizeof(*list))) == NULL) + return NULL; + list->head = list->tail = NULL; + list->len = 0; + list->dup = NULL; + list->free = NULL; + list->match = NULL; + return list; +} + +/* Free the whole list. + * + * This function can't fail. */ +void listRelease(hilist *list) +{ + unsigned long len; + listNode *current, *next; + + current = list->head; + len = list->len; + while(len--) { + next = current->next; + if (list->free) list->free(current->value); + hi_free(current); + current = next; + } + hi_free(list); +} + +/* Add a new node to the list, to head, containing the specified 'value' + * pointer as value. + * + * On error, NULL is returned and no operation is performed (i.e. the + * list remains unaltered). + * On success the 'list' pointer you pass to the function is returned. */ +hilist *listAddNodeHead(hilist *list, void *value) +{ + listNode *node; + + if ((node = hi_alloc(sizeof(*node))) == NULL) + return NULL; + node->value = value; + if (list->len == 0) { + list->head = list->tail = node; + node->prev = node->next = NULL; + } else { + node->prev = NULL; + node->next = list->head; + list->head->prev = node; + list->head = node; + } + list->len++; + return list; +} + +/* Add a new node to the list, to tail, containing the specified 'value' + * pointer as value. + * + * On error, NULL is returned and no operation is performed (i.e. the + * list remains unaltered). + * On success the 'list' pointer you pass to the function is returned. */ +hilist *listAddNodeTail(hilist *list, void *value) +{ + listNode *node; + + if ((node = hi_alloc(sizeof(*node))) == NULL) + return NULL; + node->value = value; + if (list->len == 0) { + list->head = list->tail = node; + node->prev = node->next = NULL; + } else { + node->prev = list->tail; + node->next = NULL; + list->tail->next = node; + list->tail = node; + } + list->len++; + return list; +} + +hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) { + listNode *node; + + if ((node = hi_alloc(sizeof(*node))) == NULL) + return NULL; + node->value = value; + if (after) { + node->prev = old_node; + node->next = old_node->next; + if (list->tail == old_node) { + list->tail = node; + } + } else { + node->next = old_node; + node->prev = old_node->prev; + if (list->head == old_node) { + list->head = node; + } + } + if (node->prev != NULL) { + node->prev->next = node; + } + if (node->next != NULL) { + node->next->prev = node; + } + list->len++; + return list; +} + +/* Remove the specified node from the specified list. + * It's up to the caller to free the private value of the node. + * + * This function can't fail. */ +void listDelNode(hilist *list, listNode *node) +{ + if (node->prev) + node->prev->next = node->next; + else + list->head = node->next; + if (node->next) + node->next->prev = node->prev; + else + list->tail = node->prev; + if (list->free) list->free(node->value); + hi_free(node); + list->len--; +} + +/* Returns a list iterator 'iter'. After the initialization every + * call to listNext() will return the next element of the list. + * + * This function can't fail. */ +listIter *listGetIterator(hilist *list, int direction) +{ + listIter *iter; + + if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL; + if (direction == AL_START_HEAD) + iter->next = list->head; + else + iter->next = list->tail; + iter->direction = direction; + return iter; +} + +/* Release the iterator memory */ +void listReleaseIterator(listIter *iter) { + hi_free(iter); +} + +/* Create an iterator in the list private iterator structure */ +void listRewind(hilist *list, listIter *li) { + li->next = list->head; + li->direction = AL_START_HEAD; +} + +void listRewindTail(hilist *list, listIter *li) { + li->next = list->tail; + li->direction = AL_START_TAIL; +} + +/* Return the next element of an iterator. + * It's valid to remove the currently returned element using + * listDelNode(), but not to remove other elements. + * + * The function returns a pointer to the next element of the list, + * or NULL if there are no more elements, so the classical usage patter + * is: + * + * iter = listGetIterator(list,); + * while ((node = listNext(iter)) != NULL) { + * doSomethingWith(listNodeValue(node)); + * } + * + * */ +listNode *listNext(listIter *iter) +{ + listNode *current = iter->next; + + if (current != NULL) { + if (iter->direction == AL_START_HEAD) + iter->next = current->next; + else + iter->next = current->prev; + } + return current; +} + +/* Duplicate the whole list. On out of memory NULL is returned. + * On success a copy of the original list is returned. + * + * The 'Dup' method set with listSetDupMethod() function is used + * to copy the node value. Otherwise the same pointer value of + * the original node is used as value of the copied node. + * + * The original list both on success or error is never modified. */ +hilist *listDup(hilist *orig) +{ + hilist *copy; + listIter *iter; + listNode *node; + + if ((copy = listCreate()) == NULL) + return NULL; + copy->dup = orig->dup; + copy->free = orig->free; + copy->match = orig->match; + iter = listGetIterator(orig, AL_START_HEAD); + while((node = listNext(iter)) != NULL) { + void *value; + + if (copy->dup) { + value = copy->dup(node->value); + if (value == NULL) { + listRelease(copy); + listReleaseIterator(iter); + return NULL; + } + } else + value = node->value; + if (listAddNodeTail(copy, value) == NULL) { + listRelease(copy); + listReleaseIterator(iter); + return NULL; + } + } + listReleaseIterator(iter); + return copy; +} + +/* Search the list for a node matching a given key. + * The match is performed using the 'match' method + * set with listSetMatchMethod(). If no 'match' method + * is set, the 'value' pointer of every node is directly + * compared with the 'key' pointer. + * + * On success the first matching node pointer is returned + * (search starts from head). If no matching node exists + * NULL is returned. */ +listNode *listSearchKey(hilist *list, void *key) +{ + listIter *iter; + listNode *node; + + iter = listGetIterator(list, AL_START_HEAD); + while((node = listNext(iter)) != NULL) { + if (list->match) { + if (list->match(node->value, key)) { + listReleaseIterator(iter); + return node; + } + } else { + if (key == node->value) { + listReleaseIterator(iter); + return node; + } + } + } + listReleaseIterator(iter); + return NULL; +} + +/* Return the element at the specified zero-based index + * where 0 is the head, 1 is the element next to head + * and so on. Negative integers are used in order to count + * from the tail, -1 is the last element, -2 the penultimate + * and so on. If the index is out of range NULL is returned. */ +listNode *listIndex(hilist *list, long index) { + listNode *n; + + if (index < 0) { + index = (-index)-1; + n = list->tail; + while(index-- && n) n = n->prev; + } else { + n = list->head; + while(index-- && n) n = n->next; + } + return n; +} + +/* Rotate the list removing the tail node and inserting it to the head. */ +void listRotate(hilist *list) { + listNode *tail = list->tail; + + if (listLength(list) <= 1) return; + + /* Detach current tail */ + list->tail = tail->prev; + list->tail->next = NULL; + /* Move it as head */ + list->head->prev = tail; + tail->prev = NULL; + tail->next = list->head; + list->head = tail; +} diff --git a/ext/hiredis-vip-0.3.0/adlist.h b/ext/hiredis-vip-0.3.0/adlist.h new file mode 100644 index 000000000..5b9a53ea5 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/adlist.h @@ -0,0 +1,93 @@ +/* adlist.h - A generic doubly linked list implementation + * + * Copyright (c) 2006-2012, Salvatore Sanfilippo + * 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. + */ + +#ifndef __ADLIST_H__ +#define __ADLIST_H__ + +/* Node, List, and Iterator are the only data structures used currently. */ + +typedef struct listNode { + struct listNode *prev; + struct listNode *next; + void *value; +} listNode; + +typedef struct listIter { + listNode *next; + int direction; +} listIter; + +typedef struct hilist { + listNode *head; + listNode *tail; + void *(*dup)(void *ptr); + void (*free)(void *ptr); + int (*match)(void *ptr, void *key); + unsigned long len; +} hilist; + +/* Functions implemented as macros */ +#define listLength(l) ((l)->len) +#define listFirst(l) ((l)->head) +#define listLast(l) ((l)->tail) +#define listPrevNode(n) ((n)->prev) +#define listNextNode(n) ((n)->next) +#define listNodeValue(n) ((n)->value) + +#define listSetDupMethod(l,m) ((l)->dup = (m)) +#define listSetFreeMethod(l,m) ((l)->free = (m)) +#define listSetMatchMethod(l,m) ((l)->match = (m)) + +#define listGetDupMethod(l) ((l)->dup) +#define listGetFree(l) ((l)->free) +#define listGetMatchMethod(l) ((l)->match) + +/* Prototypes */ +hilist *listCreate(void); +void listRelease(hilist *list); +hilist *listAddNodeHead(hilist *list, void *value); +hilist *listAddNodeTail(hilist *list, void *value); +hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after); +void listDelNode(hilist *list, listNode *node); +listIter *listGetIterator(hilist *list, int direction); +listNode *listNext(listIter *iter); +void listReleaseIterator(listIter *iter); +hilist *listDup(hilist *orig); +listNode *listSearchKey(hilist *list, void *key); +listNode *listIndex(hilist *list, long index); +void listRewind(hilist *list, listIter *li); +void listRewindTail(hilist *list, listIter *li); +void listRotate(hilist *list); + +/* Directions for iterators */ +#define AL_START_HEAD 0 +#define AL_START_TAIL 1 + +#endif /* __ADLIST_H__ */ diff --git a/ext/hiredis-vip-0.3.0/async.c b/ext/hiredis-vip-0.3.0/async.c new file mode 100644 index 000000000..75a3575de --- /dev/null +++ b/ext/hiredis-vip-0.3.0/async.c @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include +#include "async.h" +#include "net.h" +#include "dict.c" +#include "sds.h" + +#define _EL_ADD_READ(ctx) do { \ + if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_READ(ctx) do { \ + if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \ + } while(0) +#define _EL_ADD_WRITE(ctx) do { \ + if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \ + } while(0) +#define _EL_DEL_WRITE(ctx) do { \ + if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \ + } while(0) +#define _EL_CLEANUP(ctx) do { \ + if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ + } while(0); + +/* Forward declaration of function in hiredis.c */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); + +/* Functions managing dictionary of callbacks for pub/sub. */ +static unsigned int callbackHash(const void *key) { + return dictGenHashFunction((const unsigned char *)key, + sdslen((const sds)key)); +} + +static void *callbackValDup(void *privdata, const void *src) { + ((void) privdata); + redisCallback *dup = malloc(sizeof(*dup)); + memcpy(dup,src,sizeof(*dup)); + return dup; +} + +static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { + int l1, l2; + ((void) privdata); + + l1 = sdslen((const sds)key1); + l2 = sdslen((const sds)key2); + if (l1 != l2) return 0; + return memcmp(key1,key2,l1) == 0; +} + +static void callbackKeyDestructor(void *privdata, void *key) { + ((void) privdata); + sdsfree((sds)key); +} + +static void callbackValDestructor(void *privdata, void *val) { + ((void) privdata); + free(val); +} + +static dictType callbackDict = { + callbackHash, + NULL, + callbackValDup, + callbackKeyCompare, + callbackKeyDestructor, + callbackValDestructor +}; + +static redisAsyncContext *redisAsyncInitialize(redisContext *c) { + redisAsyncContext *ac; + + ac = realloc(c,sizeof(redisAsyncContext)); + if (ac == NULL) + return NULL; + + c = &(ac->c); + + /* The regular connect functions will always set the flag REDIS_CONNECTED. + * For the async API, we want to wait until the first write event is + * received up before setting this flag, so reset it here. */ + c->flags &= ~REDIS_CONNECTED; + + ac->err = 0; + ac->errstr = NULL; + ac->data = NULL; + ac->dataHandler = NULL; + + ac->ev.data = NULL; + ac->ev.addRead = NULL; + ac->ev.delRead = NULL; + ac->ev.addWrite = NULL; + ac->ev.delWrite = NULL; + ac->ev.cleanup = NULL; + + ac->onConnect = NULL; + ac->onDisconnect = NULL; + + ac->replies.head = NULL; + ac->replies.tail = NULL; + ac->sub.invalid.head = NULL; + ac->sub.invalid.tail = NULL; + ac->sub.channels = dictCreate(&callbackDict,NULL); + ac->sub.patterns = dictCreate(&callbackDict,NULL); + return ac; +} + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisAsyncCopyError(redisAsyncContext *ac) { + if (!ac) + return; + + redisContext *c = &(ac->c); + ac->err = c->err; + ac->errstr = c->errstr; +} + +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectNonBlock(ip,port); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + +redisAsyncContext *redisAsyncConnectUnix(const char *path) { + redisContext *c; + redisAsyncContext *ac; + + c = redisConnectUnixNonBlock(path); + if (c == NULL) + return NULL; + + ac = redisAsyncInitialize(c); + if (ac == NULL) { + redisFree(c); + return NULL; + } + + __redisAsyncCopyError(ac); + return ac; +} + +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { + if (ac->onConnect == NULL) { + ac->onConnect = fn; + + /* The common way to detect an established connection is to wait for + * the first write event to be fired. This assumes the related event + * library functions are already set. */ + _EL_ADD_WRITE(ac); + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { + if (ac->onDisconnect == NULL) { + ac->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Helper functions to push/shift callbacks */ +static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { + redisCallback *cb; + + /* Copy callback from stack to heap */ + cb = malloc(sizeof(*cb)); + if (cb == NULL) + return REDIS_ERR_OOM; + + if (source != NULL) { + memcpy(cb,source,sizeof(*cb)); + cb->next = NULL; + } + + /* Store callback in list */ + if (list->head == NULL) + list->head = cb; + if (list->tail != NULL) + list->tail->next = cb; + list->tail = cb; + return REDIS_OK; +} + +static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { + redisCallback *cb = list->head; + if (cb != NULL) { + list->head = cb->next; + if (cb == list->tail) + list->tail = NULL; + + /* Copy callback from heap to stack */ + if (target != NULL) + memcpy(target,cb,sizeof(*cb)); + free(cb); + return REDIS_OK; + } + return REDIS_ERR; +} + +static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { + redisContext *c = &(ac->c); + if (cb->fn != NULL) { + c->flags |= REDIS_IN_CALLBACK; + cb->fn(ac,reply,cb->privdata); + c->flags &= ~REDIS_IN_CALLBACK; + } +} + +/* Helper function to free the context. */ +static void __redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + dictIterator *it; + dictEntry *de; + + /* Execute pending callbacks with NULL reply. */ + while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Execute callbacks for invalid commands */ + while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) + __redisRunCallback(ac,&cb,NULL); + + /* Run subscription callbacks callbacks with NULL reply */ + it = dictGetIterator(ac->sub.channels); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.channels); + + it = dictGetIterator(ac->sub.patterns); + while ((de = dictNext(it)) != NULL) + __redisRunCallback(ac,dictGetEntryVal(de),NULL); + dictReleaseIterator(it); + dictRelease(ac->sub.patterns); + + /* Signal event lib to clean up */ + _EL_CLEANUP(ac); + + /* Execute disconnect callback. When redisAsyncFree() initiated destroying + * this context, the status will always be REDIS_OK. */ + if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { + if (c->flags & REDIS_FREEING) { + ac->onDisconnect(ac,REDIS_OK); + } else { + ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); + } + } + + if (ac->dataHandler) { + ac->dataHandler(ac); + } + + /* Cleanup self */ + redisFree(c); +} + +/* Free the async context. When this function is called from a callback, + * control needs to be returned to redisProcessCallbacks() before actual + * free'ing. To do so, a flag is set on the context which is picked up by + * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ +void redisAsyncFree(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_FREEING; + if (!(c->flags & REDIS_IN_CALLBACK)) + __redisAsyncFree(ac); +} + +/* Helper function to make the disconnect happen and clean up. */ +static void __redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + /* Make sure error is accessible if there is any */ + __redisAsyncCopyError(ac); + + if (ac->err == 0) { + /* For clean disconnects, there should be no pending callbacks. */ + assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); + } else { + /* Disconnection is caused by an error, make sure that pending + * callbacks cannot call new commands. */ + c->flags |= REDIS_DISCONNECTING; + } + + /* For non-clean disconnects, __redisAsyncFree() will execute pending + * callbacks with a NULL-reply. */ + __redisAsyncFree(ac); +} + +/* Tries to do a clean disconnect from Redis, meaning it stops new commands + * from being issued, but tries to flush the output buffer and execute + * callbacks for all remaining replies. When this function is called from a + * callback, there might be more replies and we can safely defer disconnecting + * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately + * when there are no pending callbacks. */ +void redisAsyncDisconnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + c->flags |= REDIS_DISCONNECTING; + if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) + __redisAsyncDisconnect(ac); +} + +static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { + redisContext *c = &(ac->c); + dict *callbacks; + dictEntry *de; + int pvariant; + char *stype; + sds sname; + + /* Custom reply functions are not supported for pub/sub. This will fail + * very hard when they are used... */ + if (reply->type == REDIS_REPLY_ARRAY) { + assert(reply->elements >= 2); + assert(reply->element[0]->type == REDIS_REPLY_STRING); + stype = reply->element[0]->str; + pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; + + if (pvariant) + callbacks = ac->sub.patterns; + else + callbacks = ac->sub.channels; + + /* Locate the right callback */ + assert(reply->element[1]->type == REDIS_REPLY_STRING); + sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); + de = dictFind(callbacks,sname); + if (de != NULL) { + memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); + + /* If this is an unsubscribe message, remove it. */ + if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { + dictDelete(callbacks,sname); + + /* If this was the last unsubscribe message, revert to + * non-subscribe mode. */ + assert(reply->element[2]->type == REDIS_REPLY_INTEGER); + if (reply->element[2]->integer == 0) + c->flags &= ~REDIS_SUBSCRIBED; + } + } + sdsfree(sname); + } else { + /* Shift callback for invalid commands. */ + __redisShiftCallback(&ac->sub.invalid,dstcb); + } + return REDIS_OK; +} + +void redisProcessCallbacks(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb = {NULL, NULL, NULL}; + void *reply = NULL; + int status; + + while((status = redisGetReply(c,&reply)) == REDIS_OK) { + if (reply == NULL) { + /* When the connection is being disconnected and there are + * no more replies, this is the cue to really disconnect. */ + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { + __redisAsyncDisconnect(ac); + return; + } + + /* If monitor mode, repush callback */ + if(c->flags & REDIS_MONITORING) { + __redisPushCallback(&ac->replies,&cb); + } + + /* When the connection is not being disconnected, simply stop + * trying to get replies and wait for the next loop tick. */ + break; + } + + /* Even if the context is subscribed, pending regular callbacks will + * get a reply before pub/sub messages arrive. */ + if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { + /* + * A spontaneous reply in a not-subscribed context can be the error + * reply that is sent when a new connection exceeds the maximum + * number of allowed connections on the server side. + * + * This is seen as an error instead of a regular reply because the + * server closes the connection after sending it. + * + * To prevent the error from being overwritten by an EOF error the + * connection is closed here. See issue #43. + * + * Another possibility is that the server is loading its dataset. + * In this case we also want to close the connection, and have the + * user wait until the server is ready to take our request. + */ + if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { + c->err = REDIS_ERR_OTHER; + snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); + c->reader->fn->freeObject(reply); + __redisAsyncDisconnect(ac); + return; + } + /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ + assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); + if(c->flags & REDIS_SUBSCRIBED) + __redisGetSubscribeCallback(ac,reply,&cb); + } + + if (cb.fn != NULL) { + __redisRunCallback(ac,&cb,reply); + c->reader->fn->freeObject(reply); + + /* Proceed with free'ing when redisAsyncFree() was called. */ + if (c->flags & REDIS_FREEING) { + __redisAsyncFree(ac); + return; + } + } else { + /* No callback for this reply. This can either be a NULL callback, + * or there were no callbacks to begin with. Either way, don't + * abort with an error, but simply ignore it because the client + * doesn't know what the server will spit out over the wire. */ + c->reader->fn->freeObject(reply); + } + } + + /* Disconnect when there was an error reading the reply */ + if (status != REDIS_OK) + __redisAsyncDisconnect(ac); +} + +/* Internal helper function to detect socket status the first time a read or + * write event fires. When connecting was not succesful, the connect callback + * is called with a REDIS_ERR status and the context is free'd. */ +static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisCheckSocketError(c) == REDIS_ERR) { + /* Try again later when connect(2) is still in progress. */ + if (errno == EINPROGRESS) + return REDIS_OK; + + if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); + __redisAsyncDisconnect(ac); + return REDIS_ERR; + } + + /* Mark context as connected. */ + c->flags |= REDIS_CONNECTED; + if (ac->onConnect) ac->onConnect(ac,REDIS_OK); + return REDIS_OK; +} + +/* This function should be called when the socket is readable. + * It processes all replies that can be read and executes their callbacks. + */ +void redisAsyncHandleRead(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferRead(c) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Always re-schedule reads */ + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +void redisAsyncHandleWrite(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + int done = 0; + + if (!(c->flags & REDIS_CONNECTED)) { + /* Abort connect was not successful. */ + if (__redisAsyncHandleConnect(ac) != REDIS_OK) + return; + /* Try again later when the context is still not connected. */ + if (!(c->flags & REDIS_CONNECTED)) + return; + } + + if (redisBufferWrite(c,&done) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Continue writing when not done, stop writing otherwise */ + if (!done) + _EL_ADD_WRITE(ac); + else + _EL_DEL_WRITE(ac); + + /* Always schedule reads after writes */ + _EL_ADD_READ(ac); + } +} + +/* Sets a pointer to the first argument and its length starting at p. Returns + * the number of bytes to skip to get to the following argument. */ +static const char *nextArgument(const char *start, const char **str, size_t *len) { + const char *p = start; + if (p[0] != '$') { + p = strchr(p,'$'); + if (p == NULL) return NULL; + } + + *len = (int)strtol(p+1,NULL,10); + p = strchr(p,'\r'); + assert(p); + *str = p+2; + return p+2+(*len)+2; +} + +/* Helper function for the redisAsyncCommand* family of functions. Writes a + * formatted command to the output buffer and registers the provided callback + * function with the context. */ +static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + redisContext *c = &(ac->c); + redisCallback cb; + int pvariant, hasnext; + const char *cstr, *astr; + size_t clen, alen; + const char *p; + sds sname; + int ret; + + /* Don't accept new commands when the connection is about to be closed. */ + if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; + + /* Setup callback */ + cb.fn = fn; + cb.privdata = privdata; + + /* Find out which command will be appended. */ + p = nextArgument(cmd,&cstr,&clen); + assert(p != NULL); + hasnext = (p[0] == '$'); + pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; + cstr += pvariant; + clen -= pvariant; + + if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { + c->flags |= REDIS_SUBSCRIBED; + + /* Add every channel/pattern to the list of subscription callbacks. */ + while ((p = nextArgument(p,&astr,&alen)) != NULL) { + sname = sdsnewlen(astr,alen); + if (pvariant) + ret = dictReplace(ac->sub.patterns,sname,&cb); + else + ret = dictReplace(ac->sub.channels,sname,&cb); + + if (ret == 0) sdsfree(sname); + } + } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { + /* It is only useful to call (P)UNSUBSCRIBE when the context is + * subscribed to one or more channels or patterns. */ + if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; + + /* (P)UNSUBSCRIBE does not have its own response: every channel or + * pattern that is unsubscribed will receive a message. This means we + * should not append a callback function for this command. */ + } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { + /* Set monitor flag and push callback */ + c->flags |= REDIS_MONITORING; + __redisPushCallback(&ac->replies,&cb); + } else { + if (c->flags & REDIS_SUBSCRIBED) + /* This will likely result in an error reply, but it needs to be + * received and passed to the callback. */ + __redisPushCallback(&ac->sub.invalid,&cb); + else + __redisPushCallback(&ac->replies,&cb); + } + + __redisAppendCommand(c,cmd,len); + + /* Always schedule a write when the write buffer is non-empty */ + _EL_ADD_WRITE(ac); + + return REDIS_OK; +} + +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { + char *cmd; + int len; + int status; + len = redisvFormatCommand(&cmd,format,ap); + + /* We don't want to pass -1 or -2 to future functions as a length. */ + if (len < 0) + return REDIS_ERR; + + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + free(cmd); + return status; +} + +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { + va_list ap; + int status; + va_start(ap,format); + status = redisvAsyncCommand(ac,fn,privdata,format,ap); + va_end(ap); + return status; +} + +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + int status; + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + sdsfree(cmd); + return status; +} + +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { + int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); + return status; +} diff --git a/ext/hiredis-vip-0.3.0/async.h b/ext/hiredis-vip-0.3.0/async.h new file mode 100644 index 000000000..2ba7142b8 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/async.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + +#ifndef __HIREDIS_ASYNC_H +#define __HIREDIS_ASYNC_H +#include "hiredis.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ +struct dict; /* dictionary header is included in async.c */ + +/* Reply callback prototype and container */ +typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); +typedef struct redisCallback { + struct redisCallback *next; /* simple singly linked list */ + redisCallbackFn *fn; + void *privdata; +} redisCallback; + +/* List of callbacks for either regular replies or pub/sub */ +typedef struct redisCallbackList { + redisCallback *head, *tail; +} redisCallbackList; + +/* Connection callback prototypes */ +typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); +typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); + +/* Context for an async connection to Redis */ +typedef struct redisAsyncContext { + /* Hold the regular context, so it can be realloc'ed. */ + redisContext c; + + /* Setup error flags so they can be used directly. */ + int err; + char *errstr; + + /* Not used by hiredis */ + void *data; + void (*dataHandler)(struct redisAsyncContext* ac); + + /* Event library data and hooks */ + struct { + void *data; + + /* Hooks that are called when the library expects to start + * reading/writing. These functions should be idempotent. */ + void (*addRead)(void *privdata); + void (*delRead)(void *privdata); + void (*addWrite)(void *privdata); + void (*delWrite)(void *privdata); + void (*cleanup)(void *privdata); + } ev; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + + /* Regular command callbacks */ + redisCallbackList replies; + + /* Subscription callbacks */ + struct { + redisCallbackList invalid; + struct dict *channels; + struct dict *patterns; + } sub; +} redisAsyncContext; + +/* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); +redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, + const char *source_addr); +redisAsyncContext *redisAsyncConnectUnix(const char *path); +int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +void redisAsyncDisconnect(redisAsyncContext *ac); +void redisAsyncFree(redisAsyncContext *ac); + +/* Handle read/write events */ +void redisAsyncHandleRead(redisAsyncContext *ac); +void redisAsyncHandleWrite(redisAsyncContext *ac); + +/* Command functions for an async context. Write the command to the + * output buffer and register the provided callback. */ +int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); +int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/command.c b/ext/hiredis-vip-0.3.0/command.c new file mode 100644 index 000000000..e32091b40 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/command.c @@ -0,0 +1,1700 @@ +#include +#include + +#include "command.h" +#include "hiutil.h" +#include "hiarray.h" + + +static uint64_t cmd_id = 0; /* command id counter */ + + +/* + * Return true, if the redis command take no key, otherwise + * return false + */ +static int +redis_argz(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_PING: + case CMD_REQ_REDIS_QUIT: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts no arguments, otherwise + * return false + */ +static int +redis_arg0(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_EXISTS: + case CMD_REQ_REDIS_PERSIST: + case CMD_REQ_REDIS_PTTL: + case CMD_REQ_REDIS_SORT: + case CMD_REQ_REDIS_TTL: + case CMD_REQ_REDIS_TYPE: + case CMD_REQ_REDIS_DUMP: + + case CMD_REQ_REDIS_DECR: + case CMD_REQ_REDIS_GET: + case CMD_REQ_REDIS_INCR: + case CMD_REQ_REDIS_STRLEN: + + case CMD_REQ_REDIS_HGETALL: + case CMD_REQ_REDIS_HKEYS: + case CMD_REQ_REDIS_HLEN: + case CMD_REQ_REDIS_HVALS: + + case CMD_REQ_REDIS_LLEN: + case CMD_REQ_REDIS_LPOP: + case CMD_REQ_REDIS_RPOP: + + case CMD_REQ_REDIS_SCARD: + case CMD_REQ_REDIS_SMEMBERS: + case CMD_REQ_REDIS_SPOP: + + case CMD_REQ_REDIS_ZCARD: + case CMD_REQ_REDIS_PFCOUNT: + case CMD_REQ_REDIS_AUTH: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts exactly 1 argument, otherwise + * return false + */ +static int +redis_arg1(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_EXPIRE: + case CMD_REQ_REDIS_EXPIREAT: + case CMD_REQ_REDIS_PEXPIRE: + case CMD_REQ_REDIS_PEXPIREAT: + + case CMD_REQ_REDIS_APPEND: + case CMD_REQ_REDIS_DECRBY: + case CMD_REQ_REDIS_GETBIT: + case CMD_REQ_REDIS_GETSET: + case CMD_REQ_REDIS_INCRBY: + case CMD_REQ_REDIS_INCRBYFLOAT: + case CMD_REQ_REDIS_SETNX: + + case CMD_REQ_REDIS_HEXISTS: + case CMD_REQ_REDIS_HGET: + + case CMD_REQ_REDIS_LINDEX: + case CMD_REQ_REDIS_LPUSHX: + case CMD_REQ_REDIS_RPOPLPUSH: + case CMD_REQ_REDIS_RPUSHX: + + case CMD_REQ_REDIS_SISMEMBER: + + case CMD_REQ_REDIS_ZRANK: + case CMD_REQ_REDIS_ZREVRANK: + case CMD_REQ_REDIS_ZSCORE: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts exactly 2 arguments, otherwise + * return false + */ +static int +redis_arg2(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_GETRANGE: + case CMD_REQ_REDIS_PSETEX: + case CMD_REQ_REDIS_SETBIT: + case CMD_REQ_REDIS_SETEX: + case CMD_REQ_REDIS_SETRANGE: + + case CMD_REQ_REDIS_HINCRBY: + case CMD_REQ_REDIS_HINCRBYFLOAT: + case CMD_REQ_REDIS_HSET: + case CMD_REQ_REDIS_HSETNX: + + case CMD_REQ_REDIS_LRANGE: + case CMD_REQ_REDIS_LREM: + case CMD_REQ_REDIS_LSET: + case CMD_REQ_REDIS_LTRIM: + + case CMD_REQ_REDIS_SMOVE: + + case CMD_REQ_REDIS_ZCOUNT: + case CMD_REQ_REDIS_ZLEXCOUNT: + case CMD_REQ_REDIS_ZINCRBY: + case CMD_REQ_REDIS_ZREMRANGEBYLEX: + case CMD_REQ_REDIS_ZREMRANGEBYRANK: + case CMD_REQ_REDIS_ZREMRANGEBYSCORE: + + case CMD_REQ_REDIS_RESTORE: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts exactly 3 arguments, otherwise + * return false + */ +static int +redis_arg3(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_LINSERT: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command accepts 0 or more arguments, otherwise + * return false + */ +static int +redis_argn(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_BITCOUNT: + + case CMD_REQ_REDIS_SET: + case CMD_REQ_REDIS_HDEL: + case CMD_REQ_REDIS_HMGET: + case CMD_REQ_REDIS_HMSET: + case CMD_REQ_REDIS_HSCAN: + + case CMD_REQ_REDIS_LPUSH: + case CMD_REQ_REDIS_RPUSH: + + case CMD_REQ_REDIS_SADD: + case CMD_REQ_REDIS_SDIFF: + case CMD_REQ_REDIS_SDIFFSTORE: + case CMD_REQ_REDIS_SINTER: + case CMD_REQ_REDIS_SINTERSTORE: + case CMD_REQ_REDIS_SREM: + case CMD_REQ_REDIS_SUNION: + case CMD_REQ_REDIS_SUNIONSTORE: + case CMD_REQ_REDIS_SRANDMEMBER: + case CMD_REQ_REDIS_SSCAN: + + case CMD_REQ_REDIS_PFADD: + case CMD_REQ_REDIS_PFMERGE: + + case CMD_REQ_REDIS_ZADD: + case CMD_REQ_REDIS_ZINTERSTORE: + case CMD_REQ_REDIS_ZRANGE: + case CMD_REQ_REDIS_ZRANGEBYSCORE: + case CMD_REQ_REDIS_ZREM: + case CMD_REQ_REDIS_ZREVRANGE: + case CMD_REQ_REDIS_ZRANGEBYLEX: + case CMD_REQ_REDIS_ZREVRANGEBYSCORE: + case CMD_REQ_REDIS_ZUNIONSTORE: + case CMD_REQ_REDIS_ZSCAN: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command is a vector command accepting one or + * more keys, otherwise return false + */ +static int +redis_argx(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_MGET: + case CMD_REQ_REDIS_DEL: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command is a vector command accepting one or + * more key-value pairs, otherwise return false + */ +static int +redis_argkvx(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_MSET: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Return true, if the redis command is either EVAL or EVALSHA. These commands + * have a special format with exactly 2 arguments, followed by one or more keys, + * followed by zero or more arguments (the documentation online seems to suggest + * that at least one argument is required, but that shouldn't be the case). + */ +static int +redis_argeval(struct cmd *r) +{ + switch (r->type) { + case CMD_REQ_REDIS_EVAL: + case CMD_REQ_REDIS_EVALSHA: + return 1; + + default: + break; + } + + return 0; +} + +/* + * Reference: http://redis.io/topics/protocol + * + * Redis >= 1.2 uses the unified protocol to send requests to the Redis + * server. In the unified protocol all the arguments sent to the server + * are binary safe and every request has the following general form: + * + * * CR LF + * $ CR LF + * CR LF + * ... + * $ CR LF + * CR LF + * + * Before the unified request protocol, redis protocol for requests supported + * the following commands + * 1). Inline commands: simple commands where arguments are just space + * separated strings. No binary safeness is possible. + * 2). Bulk commands: bulk commands are exactly like inline commands, but + * the last argument is handled in a special way in order to allow for + * a binary-safe last argument. + * + * only supports the Redis unified protocol for requests. + */ +void +redis_parse_cmd(struct cmd *r) +{ + int len; + char *p, *m, *token = NULL; + char *cmd_end; + char ch; + uint32_t rlen = 0; /* running length in parsing fsa */ + uint32_t rnarg = 0; /* running # arg used by parsing fsa */ + enum { + SW_START, + SW_NARG, + SW_NARG_LF, + SW_REQ_TYPE_LEN, + SW_REQ_TYPE_LEN_LF, + SW_REQ_TYPE, + SW_REQ_TYPE_LF, + SW_KEY_LEN, + SW_KEY_LEN_LF, + SW_KEY, + SW_KEY_LF, + SW_ARG1_LEN, + SW_ARG1_LEN_LF, + SW_ARG1, + SW_ARG1_LF, + SW_ARG2_LEN, + SW_ARG2_LEN_LF, + SW_ARG2, + SW_ARG2_LF, + SW_ARG3_LEN, + SW_ARG3_LEN_LF, + SW_ARG3, + SW_ARG3_LF, + SW_ARGN_LEN, + SW_ARGN_LEN_LF, + SW_ARGN, + SW_ARGN_LF, + SW_SENTINEL + } state; + + state = SW_START; + cmd_end = r->cmd + r->clen; + + ASSERT(state >= SW_START && state < SW_SENTINEL); + ASSERT(r->cmd != NULL && r->clen > 0); + + for (p = r->cmd; p < cmd_end; p++) { + ch = *p; + + switch (state) { + + case SW_START: + case SW_NARG: + if (token == NULL) { + if (ch != '*') { + goto error; + } + token = p; + /* req_start <- p */ + r->narg_start = p; + rnarg = 0; + state = SW_NARG; + } else if (isdigit(ch)) { + rnarg = rnarg * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if (rnarg == 0) { + goto error; + } + r->narg = rnarg; + r->narg_end = p; + token = NULL; + state = SW_NARG_LF; + } else { + goto error; + } + + break; + + case SW_NARG_LF: + switch (ch) { + case LF: + state = SW_REQ_TYPE_LEN; + break; + + default: + goto error; + } + + break; + + case SW_REQ_TYPE_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + token = p; + rlen = 0; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if (rlen == 0 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_REQ_TYPE_LEN_LF; + } else { + goto error; + } + + break; + + case SW_REQ_TYPE_LEN_LF: + switch (ch) { + case LF: + state = SW_REQ_TYPE; + break; + + default: + goto error; + } + + break; + + case SW_REQ_TYPE: + if (token == NULL) { + token = p; + } + + m = token + rlen; + if (m >= cmd_end) { + //m = cmd_end - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + m = token; + token = NULL; + r->type = CMD_UNKNOWN; + + switch (p - m) { + + case 3: + if (str3icmp(m, 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_GET; + break; + } + + if (str3icmp(m, 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_SET; + break; + } + + if (str3icmp(m, 't', 't', 'l')) { + r->type = CMD_REQ_REDIS_TTL; + break; + } + + if (str3icmp(m, 'd', 'e', 'l')) { + r->type = CMD_REQ_REDIS_DEL; + break; + } + + break; + + case 4: + if (str4icmp(m, 'p', 't', 't', 'l')) { + r->type = CMD_REQ_REDIS_PTTL; + break; + } + + if (str4icmp(m, 'd', 'e', 'c', 'r')) { + r->type = CMD_REQ_REDIS_DECR; + break; + } + + if (str4icmp(m, 'd', 'u', 'm', 'p')) { + r->type = CMD_REQ_REDIS_DUMP; + break; + } + + if (str4icmp(m, 'h', 'd', 'e', 'l')) { + r->type = CMD_REQ_REDIS_HDEL; + break; + } + + if (str4icmp(m, 'h', 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_HGET; + break; + } + + if (str4icmp(m, 'h', 'l', 'e', 'n')) { + r->type = CMD_REQ_REDIS_HLEN; + break; + } + + if (str4icmp(m, 'h', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_HSET; + break; + } + + if (str4icmp(m, 'i', 'n', 'c', 'r')) { + r->type = CMD_REQ_REDIS_INCR; + break; + } + + if (str4icmp(m, 'l', 'l', 'e', 'n')) { + r->type = CMD_REQ_REDIS_LLEN; + break; + } + + if (str4icmp(m, 'l', 'p', 'o', 'p')) { + r->type = CMD_REQ_REDIS_LPOP; + break; + } + + if (str4icmp(m, 'l', 'r', 'e', 'm')) { + r->type = CMD_REQ_REDIS_LREM; + break; + } + + if (str4icmp(m, 'l', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_LSET; + break; + } + + if (str4icmp(m, 'r', 'p', 'o', 'p')) { + r->type = CMD_REQ_REDIS_RPOP; + break; + } + + if (str4icmp(m, 's', 'a', 'd', 'd')) { + r->type = CMD_REQ_REDIS_SADD; + break; + } + + if (str4icmp(m, 's', 'p', 'o', 'p')) { + r->type = CMD_REQ_REDIS_SPOP; + break; + } + + if (str4icmp(m, 's', 'r', 'e', 'm')) { + r->type = CMD_REQ_REDIS_SREM; + break; + } + + if (str4icmp(m, 't', 'y', 'p', 'e')) { + r->type = CMD_REQ_REDIS_TYPE; + break; + } + + if (str4icmp(m, 'm', 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_MGET; + break; + } + if (str4icmp(m, 'm', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_MSET; + break; + } + + if (str4icmp(m, 'z', 'a', 'd', 'd')) { + r->type = CMD_REQ_REDIS_ZADD; + break; + } + + if (str4icmp(m, 'z', 'r', 'e', 'm')) { + r->type = CMD_REQ_REDIS_ZREM; + break; + } + + if (str4icmp(m, 'e', 'v', 'a', 'l')) { + r->type = CMD_REQ_REDIS_EVAL; + break; + } + + if (str4icmp(m, 's', 'o', 'r', 't')) { + r->type = CMD_REQ_REDIS_SORT; + break; + } + + if (str4icmp(m, 'p', 'i', 'n', 'g')) { + r->type = CMD_REQ_REDIS_PING; + r->noforward = 1; + break; + } + + if (str4icmp(m, 'q', 'u', 'i', 't')) { + r->type = CMD_REQ_REDIS_QUIT; + r->quit = 1; + break; + } + + if (str4icmp(m, 'a', 'u', 't', 'h')) { + r->type = CMD_REQ_REDIS_AUTH; + r->noforward = 1; + break; + } + + break; + + case 5: + if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) { + r->type = CMD_REQ_REDIS_HKEYS; + break; + } + + if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) { + r->type = CMD_REQ_REDIS_HMGET; + break; + } + + if (str5icmp(m, 'h', 'm', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_HMSET; + break; + } + + if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) { + r->type = CMD_REQ_REDIS_HVALS; + break; + } + + if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) { + r->type = CMD_REQ_REDIS_HSCAN; + break; + } + + if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) { + r->type = CMD_REQ_REDIS_LPUSH; + break; + } + + if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) { + r->type = CMD_REQ_REDIS_LTRIM; + break; + } + + if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) { + r->type = CMD_REQ_REDIS_RPUSH; + break; + } + + if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) { + r->type = CMD_REQ_REDIS_SCARD; + break; + } + + if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) { + r->type = CMD_REQ_REDIS_SDIFF; + break; + } + + if (str5icmp(m, 's', 'e', 't', 'e', 'x')) { + r->type = CMD_REQ_REDIS_SETEX; + break; + } + + if (str5icmp(m, 's', 'e', 't', 'n', 'x')) { + r->type = CMD_REQ_REDIS_SETNX; + break; + } + + if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) { + r->type = CMD_REQ_REDIS_SMOVE; + break; + } + + if (str5icmp(m, 's', 's', 'c', 'a', 'n')) { + r->type = CMD_REQ_REDIS_SSCAN; + break; + } + + if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) { + r->type = CMD_REQ_REDIS_ZCARD; + break; + } + + if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) { + r->type = CMD_REQ_REDIS_ZRANK; + break; + } + + if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) { + r->type = CMD_REQ_REDIS_ZSCAN; + break; + } + + if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) { + r->type = CMD_REQ_REDIS_PFADD; + break; + } + + break; + + case 6: + if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { + r->type = CMD_REQ_REDIS_APPEND; + break; + } + + if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_DECRBY; + break; + } + + if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) { + r->type = CMD_REQ_REDIS_EXISTS; + break; + } + + if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) { + r->type = CMD_REQ_REDIS_EXPIRE; + break; + } + + if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) { + r->type = CMD_REQ_REDIS_GETBIT; + break; + } + + if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) { + r->type = CMD_REQ_REDIS_GETSET; + break; + } + + if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) { + r->type = CMD_REQ_REDIS_PSETEX; + break; + } + + if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) { + r->type = CMD_REQ_REDIS_HSETNX; + break; + } + + if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_INCRBY; + break; + } + + if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) { + r->type = CMD_REQ_REDIS_LINDEX; + break; + } + + if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) { + r->type = CMD_REQ_REDIS_LPUSHX; + break; + } + + if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_LRANGE; + break; + } + + if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) { + r->type = CMD_REQ_REDIS_RPUSHX; + break; + } + + if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) { + r->type = CMD_REQ_REDIS_SETBIT; + break; + } + + if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) { + r->type = CMD_REQ_REDIS_SINTER; + break; + } + + if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) { + r->type = CMD_REQ_REDIS_STRLEN; + break; + } + + if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) { + r->type = CMD_REQ_REDIS_SUNION; + break; + } + + if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_ZCOUNT; + break; + } + + if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_ZRANGE; + break; + } + + if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZSCORE; + break; + } + + break; + + case 7: + if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) { + r->type = CMD_REQ_REDIS_PERSIST; + break; + } + + if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) { + r->type = CMD_REQ_REDIS_PEXPIRE; + break; + } + + if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) { + r->type = CMD_REQ_REDIS_HEXISTS; + break; + } + + if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) { + r->type = CMD_REQ_REDIS_HGETALL; + break; + } + + if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_HINCRBY; + break; + } + + if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) { + r->type = CMD_REQ_REDIS_LINSERT; + break; + } + + if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) { + r->type = CMD_REQ_REDIS_ZINCRBY; + break; + } + + if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) { + r->type = CMD_REQ_REDIS_EVALSHA; + break; + } + + if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_RESTORE; + break; + } + + if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_PFCOUNT; + break; + } + + if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) { + r->type = CMD_REQ_REDIS_PFMERGE; + break; + } + + break; + + case 8: + if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { + r->type = CMD_REQ_REDIS_EXPIREAT; + break; + } + + if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_BITCOUNT; + break; + } + + if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_GETRANGE; + break; + } + + if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_SETRANGE; + break; + } + + if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) { + r->type = CMD_REQ_REDIS_SMEMBERS; + break; + } + + if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) { + r->type = CMD_REQ_REDIS_ZREVRANK; + break; + } + + break; + + case 9: + if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { + r->type = CMD_REQ_REDIS_PEXPIREAT; + break; + } + + if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) { + r->type = CMD_REQ_REDIS_RPOPLPUSH; + break; + } + + if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) { + r->type = CMD_REQ_REDIS_SISMEMBER; + break; + } + + if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) { + r->type = CMD_REQ_REDIS_ZREVRANGE; + break; + } + + if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) { + r->type = CMD_REQ_REDIS_ZLEXCOUNT; + break; + } + + break; + + case 10: + if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_SDIFFSTORE; + break; + } + + case 11: + if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { + r->type = CMD_REQ_REDIS_INCRBYFLOAT; + break; + } + + if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_SINTERSTORE; + break; + } + + if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) { + r->type = CMD_REQ_REDIS_SRANDMEMBER; + break; + } + + if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_SUNIONSTORE; + break; + } + + if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZINTERSTORE; + break; + } + + if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZUNIONSTORE; + break; + } + + if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { + r->type = CMD_REQ_REDIS_ZRANGEBYLEX; + break; + } + + break; + + case 12: + if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { + r->type = CMD_REQ_REDIS_HINCRBYFLOAT; + break; + } + + + break; + + case 13: + if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZRANGEBYSCORE; + break; + } + + break; + + case 14: + if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { + r->type = CMD_REQ_REDIS_ZREMRANGEBYLEX; + break; + } + + break; + + case 15: + if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) { + r->type = CMD_REQ_REDIS_ZREMRANGEBYRANK; + break; + } + + break; + + case 16: + if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZREMRANGEBYSCORE; + break; + } + + if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + r->type = CMD_REQ_REDIS_ZREVRANGEBYSCORE; + break; + } + + break; + + default: + break; + } + + if (r->type == CMD_UNKNOWN) { + goto error; + } + + state = SW_REQ_TYPE_LF; + break; + + case SW_REQ_TYPE_LF: + switch (ch) { + case LF: + if (redis_argz(r)) { + goto done; + } else if (redis_argeval(r)) { + state = SW_ARG1_LEN; + } else { + state = SW_KEY_LEN; + } + break; + + default: + goto error; + } + + break; + + case SW_KEY_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + token = p; + rlen = 0; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + + if (rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_KEY_LEN_LF; + } else { + goto error; + } + + break; + + case SW_KEY_LEN_LF: + switch (ch) { + case LF: + state = SW_KEY; + break; + + default: + goto error; + } + + break; + + case SW_KEY: + if (token == NULL) { + token = p; + } + + m = token + rlen; + if (m >= cmd_end) { + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } else { /* got a key */ + struct keypos *kpos; + + p = m; /* move forward by rlen bytes */ + rlen = 0; + m = token; + token = NULL; + + kpos = hiarray_push(r->keys); + if (kpos == NULL) { + goto enomem; + } + kpos->start = m; + kpos->end = p; + //kpos->v_len = 0; + + state = SW_KEY_LF; + } + + break; + + case SW_KEY_LF: + switch (ch) { + case LF: + if (redis_arg0(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_arg1(r)) { + if (rnarg != 1) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_arg2(r)) { + if (rnarg != 2) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_arg3(r)) { + if (rnarg != 3) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARG1_LEN; + } else if (redis_argx(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_KEY_LEN; + } else if (redis_argkvx(r)) { + if (rnarg == 0) { + goto done; + } + if (r->narg % 2 == 0) { + goto error; + } + state = SW_ARG1_LEN; + } else if (redis_argeval(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARG1_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + + /* + //for mset value length + if(redis_argkvx(r)) + { + struct keypos *kpos; + uint32_t array_len = array_n(r->keys); + if(array_len == 0) + { + goto error; + } + + kpos = array_n(r->keys, array_len-1); + if (kpos == NULL || kpos->v_len != 0) { + goto error; + } + + kpos->v_len = rlen; + } + */ + state = SW_ARG1_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARG1_LEN_LF: + switch (ch) { + case LF: + state = SW_ARG1; + break; + + default: + goto error; + } + + break; + + case SW_ARG1: + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + + state = SW_ARG1_LF; + + break; + + case SW_ARG1_LF: + switch (ch) { + case LF: + if (redis_arg1(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_arg2(r)) { + if (rnarg != 1) { + goto error; + } + state = SW_ARG2_LEN; + } else if (redis_arg3(r)) { + if (rnarg != 2) { + goto error; + } + state = SW_ARG2_LEN; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else if (redis_argeval(r)) { + if (rnarg < 2) { + goto error; + } + state = SW_ARG2_LEN; + } else if (redis_argkvx(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_KEY_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARG2_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_ARG2_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARG2_LEN_LF: + switch (ch) { + case LF: + state = SW_ARG2; + break; + + default: + goto error; + } + + break; + + case SW_ARG2: + if (token == NULL && redis_argeval(r)) { + /* + * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must + * be tokenized and stored in contiguous memory. + */ + token = p; + } + + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + + if (redis_argeval(r)) { + uint32_t nkey; + char *chp; + + /* + * For EVAL/EVALSHA, we need to find the integer value of this + * argument. It tells us the number of keys in the script, and + * we need to error out if number of keys is 0. At this point, + * both p and m point to the end of the argument and r->token + * points to the start. + */ + if (p - token < 1) { + goto error; + } + + for (nkey = 0, chp = token; chp < p; chp++) { + if (isdigit(*chp)) { + nkey = nkey * 10 + (uint32_t)(*chp - '0'); + } else { + goto error; + } + } + if (nkey == 0) { + goto error; + } + + token = NULL; + } + + state = SW_ARG2_LF; + + break; + + case SW_ARG2_LF: + switch (ch) { + case LF: + if (redis_arg2(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_arg3(r)) { + if (rnarg != 1) { + goto error; + } + state = SW_ARG3_LEN; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else if (redis_argeval(r)) { + if (rnarg < 1) { + goto error; + } + state = SW_KEY_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARG3_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_ARG3_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARG3_LEN_LF: + switch (ch) { + case LF: + state = SW_ARG3; + break; + + default: + goto error; + } + + break; + + case SW_ARG3: + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + state = SW_ARG3_LF; + + break; + + case SW_ARG3_LF: + switch (ch) { + case LF: + if (redis_arg3(r)) { + if (rnarg != 0) { + goto error; + } + goto done; + } else if (redis_argn(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_ARGN_LEN: + if (token == NULL) { + if (ch != '$') { + goto error; + } + rlen = 0; + token = p; + } else if (isdigit(ch)) { + rlen = rlen * 10 + (uint32_t)(ch - '0'); + } else if (ch == CR) { + if ((p - token) <= 1 || rnarg == 0) { + goto error; + } + rnarg--; + token = NULL; + state = SW_ARGN_LEN_LF; + } else { + goto error; + } + + break; + + case SW_ARGN_LEN_LF: + switch (ch) { + case LF: + state = SW_ARGN; + break; + + default: + goto error; + } + + break; + + case SW_ARGN: + m = p + rlen; + if (m >= cmd_end) { + //rlen -= (uint32_t)(b->last - p); + //m = b->last - 1; + //p = m; + //break; + goto error; + } + + if (*m != CR) { + goto error; + } + + p = m; /* move forward by rlen bytes */ + rlen = 0; + state = SW_ARGN_LF; + + break; + + case SW_ARGN_LF: + switch (ch) { + case LF: + if (redis_argn(r) || redis_argeval(r)) { + if (rnarg == 0) { + goto done; + } + state = SW_ARGN_LEN; + } else { + goto error; + } + + break; + + default: + goto error; + } + + break; + + case SW_SENTINEL: + default: + NOT_REACHED(); + break; + } + } + + ASSERT(p == cmd_end); + + return; + +done: + + ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL); + + r->result = CMD_PARSE_OK; + + return; + +enomem: + + r->result = CMD_PARSE_ENOMEM; + + return; + +error: + + r->result = CMD_PARSE_ERROR; + errno = EINVAL; + if(r->errstr == NULL){ + r->errstr = hi_alloc(100*sizeof(*r->errstr)); + } + + len = _scnprintf(r->errstr, 100, "Parse command error. Cmd type: %d, state: %d, break position: %d.", + r->type, state, (int)(p - r->cmd)); + r->errstr[len] = '\0'; +} + +struct cmd *command_get() +{ + struct cmd *command; + command = hi_alloc(sizeof(struct cmd)); + if(command == NULL) + { + return NULL; + } + + command->id = ++cmd_id; + command->result = CMD_PARSE_OK; + command->errstr = NULL; + command->type = CMD_UNKNOWN; + command->cmd = NULL; + command->clen = 0; + command->keys = NULL; + command->narg_start = NULL; + command->narg_end = NULL; + command->narg = 0; + command->quit = 0; + command->noforward = 0; + command->slot_num = -1; + command->frag_seq = NULL; + command->reply = NULL; + command->sub_commands = NULL; + + command->keys = hiarray_create(1, sizeof(struct keypos)); + if (command->keys == NULL) + { + hi_free(command); + return NULL; + } + + return command; +} + +void command_destroy(struct cmd *command) +{ + if(command == NULL) + { + return; + } + + if(command->cmd != NULL) + { + free(command->cmd); + } + + if(command->errstr != NULL){ + hi_free(command->errstr); + } + + if(command->keys != NULL) + { + command->keys->nelem = 0; + hiarray_destroy(command->keys); + } + + if(command->frag_seq != NULL) + { + hi_free(command->frag_seq); + command->frag_seq = NULL; + } + + if(command->reply != NULL) + { + freeReplyObject(command->reply); + } + + if(command->sub_commands != NULL) + { + listRelease(command->sub_commands); + } + + hi_free(command); +} + + diff --git a/ext/hiredis-vip-0.3.0/command.h b/ext/hiredis-vip-0.3.0/command.h new file mode 100644 index 000000000..b7c388a69 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/command.h @@ -0,0 +1,179 @@ +#ifndef __COMMAND_H_ +#define __COMMAND_H_ + +#include + +#include "hiredis.h" +#include "adlist.h" + +typedef enum cmd_parse_result { + CMD_PARSE_OK, /* parsing ok */ + CMD_PARSE_ENOMEM, /* out of memory */ + CMD_PARSE_ERROR, /* parsing error */ + CMD_PARSE_REPAIR, /* more to parse -> repair parsed & unparsed data */ + CMD_PARSE_AGAIN, /* incomplete -> parse again */ +} cmd_parse_result_t; + +#define CMD_TYPE_CODEC(ACTION) \ + ACTION( UNKNOWN ) \ + ACTION( REQ_REDIS_DEL ) /* redis commands - keys */ \ + ACTION( REQ_REDIS_EXISTS ) \ + ACTION( REQ_REDIS_EXPIRE ) \ + ACTION( REQ_REDIS_EXPIREAT ) \ + ACTION( REQ_REDIS_PEXPIRE ) \ + ACTION( REQ_REDIS_PEXPIREAT ) \ + ACTION( REQ_REDIS_PERSIST ) \ + ACTION( REQ_REDIS_PTTL ) \ + ACTION( REQ_REDIS_SORT ) \ + ACTION( REQ_REDIS_TTL ) \ + ACTION( REQ_REDIS_TYPE ) \ + ACTION( REQ_REDIS_APPEND ) /* redis requests - string */ \ + ACTION( REQ_REDIS_BITCOUNT ) \ + ACTION( REQ_REDIS_DECR ) \ + ACTION( REQ_REDIS_DECRBY ) \ + ACTION( REQ_REDIS_DUMP ) \ + ACTION( REQ_REDIS_GET ) \ + ACTION( REQ_REDIS_GETBIT ) \ + ACTION( REQ_REDIS_GETRANGE ) \ + ACTION( REQ_REDIS_GETSET ) \ + ACTION( REQ_REDIS_INCR ) \ + ACTION( REQ_REDIS_INCRBY ) \ + ACTION( REQ_REDIS_INCRBYFLOAT ) \ + ACTION( REQ_REDIS_MGET ) \ + ACTION( REQ_REDIS_MSET ) \ + ACTION( REQ_REDIS_PSETEX ) \ + ACTION( REQ_REDIS_RESTORE ) \ + ACTION( REQ_REDIS_SET ) \ + ACTION( REQ_REDIS_SETBIT ) \ + ACTION( REQ_REDIS_SETEX ) \ + ACTION( REQ_REDIS_SETNX ) \ + ACTION( REQ_REDIS_SETRANGE ) \ + ACTION( REQ_REDIS_STRLEN ) \ + ACTION( REQ_REDIS_HDEL ) /* redis requests - hashes */ \ + ACTION( REQ_REDIS_HEXISTS ) \ + ACTION( REQ_REDIS_HGET ) \ + ACTION( REQ_REDIS_HGETALL ) \ + ACTION( REQ_REDIS_HINCRBY ) \ + ACTION( REQ_REDIS_HINCRBYFLOAT ) \ + ACTION( REQ_REDIS_HKEYS ) \ + ACTION( REQ_REDIS_HLEN ) \ + ACTION( REQ_REDIS_HMGET ) \ + ACTION( REQ_REDIS_HMSET ) \ + ACTION( REQ_REDIS_HSET ) \ + ACTION( REQ_REDIS_HSETNX ) \ + ACTION( REQ_REDIS_HSCAN) \ + ACTION( REQ_REDIS_HVALS ) \ + ACTION( REQ_REDIS_LINDEX ) /* redis requests - lists */ \ + ACTION( REQ_REDIS_LINSERT ) \ + ACTION( REQ_REDIS_LLEN ) \ + ACTION( REQ_REDIS_LPOP ) \ + ACTION( REQ_REDIS_LPUSH ) \ + ACTION( REQ_REDIS_LPUSHX ) \ + ACTION( REQ_REDIS_LRANGE ) \ + ACTION( REQ_REDIS_LREM ) \ + ACTION( REQ_REDIS_LSET ) \ + ACTION( REQ_REDIS_LTRIM ) \ + ACTION( REQ_REDIS_PFADD ) /* redis requests - hyperloglog */ \ + ACTION( REQ_REDIS_PFCOUNT ) \ + ACTION( REQ_REDIS_PFMERGE ) \ + ACTION( REQ_REDIS_RPOP ) \ + ACTION( REQ_REDIS_RPOPLPUSH ) \ + ACTION( REQ_REDIS_RPUSH ) \ + ACTION( REQ_REDIS_RPUSHX ) \ + ACTION( REQ_REDIS_SADD ) /* redis requests - sets */ \ + ACTION( REQ_REDIS_SCARD ) \ + ACTION( REQ_REDIS_SDIFF ) \ + ACTION( REQ_REDIS_SDIFFSTORE ) \ + ACTION( REQ_REDIS_SINTER ) \ + ACTION( REQ_REDIS_SINTERSTORE ) \ + ACTION( REQ_REDIS_SISMEMBER ) \ + ACTION( REQ_REDIS_SMEMBERS ) \ + ACTION( REQ_REDIS_SMOVE ) \ + ACTION( REQ_REDIS_SPOP ) \ + ACTION( REQ_REDIS_SRANDMEMBER ) \ + ACTION( REQ_REDIS_SREM ) \ + ACTION( REQ_REDIS_SUNION ) \ + ACTION( REQ_REDIS_SUNIONSTORE ) \ + ACTION( REQ_REDIS_SSCAN) \ + ACTION( REQ_REDIS_ZADD ) /* redis requests - sorted sets */ \ + ACTION( REQ_REDIS_ZCARD ) \ + ACTION( REQ_REDIS_ZCOUNT ) \ + ACTION( REQ_REDIS_ZINCRBY ) \ + ACTION( REQ_REDIS_ZINTERSTORE ) \ + ACTION( REQ_REDIS_ZLEXCOUNT ) \ + ACTION( REQ_REDIS_ZRANGE ) \ + ACTION( REQ_REDIS_ZRANGEBYLEX ) \ + ACTION( REQ_REDIS_ZRANGEBYSCORE ) \ + ACTION( REQ_REDIS_ZRANK ) \ + ACTION( REQ_REDIS_ZREM ) \ + ACTION( REQ_REDIS_ZREMRANGEBYRANK ) \ + ACTION( REQ_REDIS_ZREMRANGEBYLEX ) \ + ACTION( REQ_REDIS_ZREMRANGEBYSCORE ) \ + ACTION( REQ_REDIS_ZREVRANGE ) \ + ACTION( REQ_REDIS_ZREVRANGEBYSCORE ) \ + ACTION( REQ_REDIS_ZREVRANK ) \ + ACTION( REQ_REDIS_ZSCORE ) \ + ACTION( REQ_REDIS_ZUNIONSTORE ) \ + ACTION( REQ_REDIS_ZSCAN) \ + ACTION( REQ_REDIS_EVAL ) /* redis requests - eval */ \ + ACTION( REQ_REDIS_EVALSHA ) \ + ACTION( REQ_REDIS_PING ) /* redis requests - ping/quit */ \ + ACTION( REQ_REDIS_QUIT) \ + ACTION( REQ_REDIS_AUTH) \ + ACTION( RSP_REDIS_STATUS ) /* redis response */ \ + ACTION( RSP_REDIS_ERROR ) \ + ACTION( RSP_REDIS_INTEGER ) \ + ACTION( RSP_REDIS_BULK ) \ + ACTION( RSP_REDIS_MULTIBULK ) \ + ACTION( SENTINEL ) \ + + +#define DEFINE_ACTION(_name) CMD_##_name, +typedef enum cmd_type { + CMD_TYPE_CODEC(DEFINE_ACTION) +} cmd_type_t; +#undef DEFINE_ACTION + + +struct keypos { + char *start; /* key start pos */ + char *end; /* key end pos */ + uint32_t remain_len; /* remain length after keypos->end for more key-value pairs in command, like mset */ +}; + +struct cmd { + + uint64_t id; /* command id */ + + cmd_parse_result_t result; /* command parsing result */ + char *errstr; /* error info when the command parse failed */ + + cmd_type_t type; /* command type */ + + char *cmd; + uint32_t clen; /* command length */ + + struct hiarray *keys; /* array of keypos, for req */ + + char *narg_start; /* narg start (redis) */ + char *narg_end; /* narg end (redis) */ + uint32_t narg; /* # arguments (redis) */ + + unsigned quit:1; /* quit request? */ + unsigned noforward:1; /* not need forward (example: ping) */ + + int slot_num; /* this command should send to witch slot? + * -1:the keys in this command cross different slots*/ + struct cmd **frag_seq; /* sequence of fragment command, map from keys to fragments*/ + + redisReply *reply; + + hilist *sub_commands; /* just for pipeline and multi-key commands */ +}; + +void redis_parse_cmd(struct cmd *r); + +struct cmd *command_get(void); +void command_destroy(struct cmd *command); + +#endif diff --git a/ext/hiredis-vip-0.3.0/crc16.c b/ext/hiredis-vip-0.3.0/crc16.c new file mode 100644 index 000000000..0f304f6e4 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/crc16.c @@ -0,0 +1,87 @@ +/* + * Copyright 2001-2010 Georges Menie (www.menie.org) + * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) + * 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 the University of California, Berkeley 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 REGENTS 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 REGENTS AND 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. + */ + +/* CRC16 implementation according to CCITT standards. + * + * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the + * following parameters: + * + * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" + * Width : 16 bit + * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) + * Initialization : 0000 + * Reflect Input byte : False + * Reflect Output CRC : False + * Xor constant to output CRC : 0000 + * Output for "123456789" : 31C3 + */ +#include "hiutil.h" + +static const uint16_t crc16tab[256]= { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, + 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, + 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, + 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, + 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, + 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, + 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, + 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, + 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, + 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, + 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, + 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, + 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, + 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, + 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, + 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, + 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, + 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, + 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, + 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, + 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, + 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, + 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, + 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, + 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, + 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, + 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, + 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, + 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, + 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 +}; + +uint16_t crc16(const char *buf, int len) { + int counter; + uint16_t crc = 0; + for (counter = 0; counter < len; counter++) + crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; + return crc; +} diff --git a/ext/hiredis-vip-0.3.0/dict.c b/ext/hiredis-vip-0.3.0/dict.c new file mode 100644 index 000000000..79b1041ca --- /dev/null +++ b/ext/hiredis-vip-0.3.0/dict.c @@ -0,0 +1,338 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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 "fmacros.h" +#include +#include +#include +#include "dict.h" + +/* -------------------------- private prototypes ---------------------------- */ + +static int _dictExpandIfNeeded(dict *ht); +static unsigned long _dictNextPower(unsigned long size); +static int _dictKeyIndex(dict *ht, const void *key); +static int _dictInit(dict *ht, dictType *type, void *privDataPtr); + +/* -------------------------- hash functions -------------------------------- */ + +/* Generic hash function (a popular one from Bernstein). + * I tested a few and this was the best. */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len) { + unsigned int hash = 5381; + + while (len--) + hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */ + return hash; +} + +/* ----------------------------- API implementation ------------------------- */ + +/* Reset an hashtable already initialized with ht_init(). + * NOTE: This function should only called by ht_destroy(). */ +static void _dictReset(dict *ht) { + ht->table = NULL; + ht->size = 0; + ht->sizemask = 0; + ht->used = 0; +} + +/* Create a new hash table */ +static dict *dictCreate(dictType *type, void *privDataPtr) { + dict *ht = malloc(sizeof(*ht)); + _dictInit(ht,type,privDataPtr); + return ht; +} + +/* Initialize the hash table */ +static int _dictInit(dict *ht, dictType *type, void *privDataPtr) { + _dictReset(ht); + ht->type = type; + ht->privdata = privDataPtr; + return DICT_OK; +} + +/* Expand or create the hashtable */ +static int dictExpand(dict *ht, unsigned long size) { + dict n; /* the new hashtable */ + unsigned long realsize = _dictNextPower(size), i; + + /* the size is invalid if it is smaller than the number of + * elements already inside the hashtable */ + if (ht->used > size) + return DICT_ERR; + + _dictInit(&n, ht->type, ht->privdata); + n.size = realsize; + n.sizemask = realsize-1; + n.table = calloc(realsize,sizeof(dictEntry*)); + + /* Copy all the elements from the old to the new table: + * note that if the old hash table is empty ht->size is zero, + * so dictExpand just creates an hash table. */ + n.used = ht->used; + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if (ht->table[i] == NULL) continue; + + /* For each hash entry on this slot... */ + he = ht->table[i]; + while(he) { + unsigned int h; + + nextHe = he->next; + /* Get the new element index */ + h = dictHashKey(ht, he->key) & n.sizemask; + he->next = n.table[h]; + n.table[h] = he; + ht->used--; + /* Pass to the next element */ + he = nextHe; + } + } + assert(ht->used == 0); + free(ht->table); + + /* Remap the new hashtable in the old */ + *ht = n; + return DICT_OK; +} + +/* Add an element to the target hash table */ +static int dictAdd(dict *ht, void *key, void *val) { + int index; + dictEntry *entry; + + /* Get the index of the new element, or -1 if + * the element already exists. */ + if ((index = _dictKeyIndex(ht, key)) == -1) + return DICT_ERR; + + /* Allocates the memory and stores key */ + entry = malloc(sizeof(*entry)); + entry->next = ht->table[index]; + ht->table[index] = entry; + + /* Set the hash entry fields. */ + dictSetHashKey(ht, entry, key); + dictSetHashVal(ht, entry, val); + ht->used++; + return DICT_OK; +} + +/* Add an element, discarding the old if the key already exists. + * Return 1 if the key was added from scratch, 0 if there was already an + * element with such key and dictReplace() just performed a value update + * operation. */ +static int dictReplace(dict *ht, void *key, void *val) { + dictEntry *entry, auxentry; + + /* Try to add the element. If the key + * does not exists dictAdd will suceed. */ + if (dictAdd(ht, key, val) == DICT_OK) + return 1; + /* It already exists, get the entry */ + entry = dictFind(ht, key); + /* Free the old value and set the new one */ + /* Set the new value and free the old one. Note that it is important + * to do that in this order, as the value may just be exactly the same + * as the previous one. In this context, think to reference counting, + * you want to increment (set), and then decrement (free), and not the + * reverse. */ + auxentry = *entry; + dictSetHashVal(ht, entry, val); + dictFreeEntryVal(ht, &auxentry); + return 0; +} + +/* Search and remove an element */ +static int dictDelete(dict *ht, const void *key) { + unsigned int h; + dictEntry *de, *prevde; + + if (ht->size == 0) + return DICT_ERR; + h = dictHashKey(ht, key) & ht->sizemask; + de = ht->table[h]; + + prevde = NULL; + while(de) { + if (dictCompareHashKeys(ht,key,de->key)) { + /* Unlink the element from the list */ + if (prevde) + prevde->next = de->next; + else + ht->table[h] = de->next; + + dictFreeEntryKey(ht,de); + dictFreeEntryVal(ht,de); + free(de); + ht->used--; + return DICT_OK; + } + prevde = de; + de = de->next; + } + return DICT_ERR; /* not found */ +} + +/* Destroy an entire hash table */ +static int _dictClear(dict *ht) { + unsigned long i; + + /* Free all the elements */ + for (i = 0; i < ht->size && ht->used > 0; i++) { + dictEntry *he, *nextHe; + + if ((he = ht->table[i]) == NULL) continue; + while(he) { + nextHe = he->next; + dictFreeEntryKey(ht, he); + dictFreeEntryVal(ht, he); + free(he); + ht->used--; + he = nextHe; + } + } + /* Free the table and the allocated cache structure */ + free(ht->table); + /* Re-initialize the table */ + _dictReset(ht); + return DICT_OK; /* never fails */ +} + +/* Clear & Release the hash table */ +static void dictRelease(dict *ht) { + _dictClear(ht); + free(ht); +} + +static dictEntry *dictFind(dict *ht, const void *key) { + dictEntry *he; + unsigned int h; + + if (ht->size == 0) return NULL; + h = dictHashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return he; + he = he->next; + } + return NULL; +} + +static dictIterator *dictGetIterator(dict *ht) { + dictIterator *iter = malloc(sizeof(*iter)); + + iter->ht = ht; + iter->index = -1; + iter->entry = NULL; + iter->nextEntry = NULL; + return iter; +} + +static dictEntry *dictNext(dictIterator *iter) { + while (1) { + if (iter->entry == NULL) { + iter->index++; + if (iter->index >= + (signed)iter->ht->size) break; + iter->entry = iter->ht->table[iter->index]; + } else { + iter->entry = iter->nextEntry; + } + if (iter->entry) { + /* We need to save the 'next' here, the iterator user + * may delete the entry we are returning. */ + iter->nextEntry = iter->entry->next; + return iter->entry; + } + } + return NULL; +} + +static void dictReleaseIterator(dictIterator *iter) { + free(iter); +} + +/* ------------------------- private functions ------------------------------ */ + +/* Expand the hash table if needed */ +static int _dictExpandIfNeeded(dict *ht) { + /* If the hash table is empty expand it to the intial size, + * if the table is "full" dobule its size. */ + if (ht->size == 0) + return dictExpand(ht, DICT_HT_INITIAL_SIZE); + if (ht->used == ht->size) + return dictExpand(ht, ht->size*2); + return DICT_OK; +} + +/* Our hash table capability is a power of two */ +static unsigned long _dictNextPower(unsigned long size) { + unsigned long i = DICT_HT_INITIAL_SIZE; + + if (size >= LONG_MAX) return LONG_MAX; + while(1) { + if (i >= size) + return i; + i *= 2; + } +} + +/* Returns the index of a free slot that can be populated with + * an hash entry for the given 'key'. + * If the key already exists, -1 is returned. */ +static int _dictKeyIndex(dict *ht, const void *key) { + unsigned int h; + dictEntry *he; + + /* Expand the hashtable if needed */ + if (_dictExpandIfNeeded(ht) == DICT_ERR) + return -1; + /* Compute the key hash value */ + h = dictHashKey(ht, key) & ht->sizemask; + /* Search if this slot does not already contain the given key */ + he = ht->table[h]; + while(he) { + if (dictCompareHashKeys(ht, key, he->key)) + return -1; + he = he->next; + } + return h; +} + diff --git a/ext/hiredis-vip-0.3.0/dict.h b/ext/hiredis-vip-0.3.0/dict.h new file mode 100644 index 000000000..95fcd280e --- /dev/null +++ b/ext/hiredis-vip-0.3.0/dict.h @@ -0,0 +1,126 @@ +/* Hash table implementation. + * + * This file implements in memory hash tables with insert/del/replace/find/ + * get-random-element operations. Hash tables will auto resize if needed + * tables of power of two in size are used, collisions are handled by + * chaining. See the source code for more information... :) + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * 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. + */ + +#ifndef __DICT_H +#define __DICT_H + +#define DICT_OK 0 +#define DICT_ERR 1 + +/* Unused arguments generate annoying warnings... */ +#define DICT_NOTUSED(V) ((void) V) + +typedef struct dictEntry { + void *key; + void *val; + struct dictEntry *next; +} dictEntry; + +typedef struct dictType { + unsigned int (*hashFunction)(const void *key); + void *(*keyDup)(void *privdata, const void *key); + void *(*valDup)(void *privdata, const void *obj); + int (*keyCompare)(void *privdata, const void *key1, const void *key2); + void (*keyDestructor)(void *privdata, void *key); + void (*valDestructor)(void *privdata, void *obj); +} dictType; + +typedef struct dict { + dictEntry **table; + dictType *type; + unsigned long size; + unsigned long sizemask; + unsigned long used; + void *privdata; +} dict; + +typedef struct dictIterator { + dict *ht; + int index; + dictEntry *entry, *nextEntry; +} dictIterator; + +/* This is the initial size of every hash table */ +#define DICT_HT_INITIAL_SIZE 4 + +/* ------------------------------- Macros ------------------------------------*/ +#define dictFreeEntryVal(ht, entry) \ + if ((ht)->type->valDestructor) \ + (ht)->type->valDestructor((ht)->privdata, (entry)->val) + +#define dictSetHashVal(ht, entry, _val_) do { \ + if ((ht)->type->valDup) \ + entry->val = (ht)->type->valDup((ht)->privdata, _val_); \ + else \ + entry->val = (_val_); \ +} while(0) + +#define dictFreeEntryKey(ht, entry) \ + if ((ht)->type->keyDestructor) \ + (ht)->type->keyDestructor((ht)->privdata, (entry)->key) + +#define dictSetHashKey(ht, entry, _key_) do { \ + if ((ht)->type->keyDup) \ + entry->key = (ht)->type->keyDup((ht)->privdata, _key_); \ + else \ + entry->key = (_key_); \ +} while(0) + +#define dictCompareHashKeys(ht, key1, key2) \ + (((ht)->type->keyCompare) ? \ + (ht)->type->keyCompare((ht)->privdata, key1, key2) : \ + (key1) == (key2)) + +#define dictHashKey(ht, key) (ht)->type->hashFunction(key) + +#define dictGetEntryKey(he) ((he)->key) +#define dictGetEntryVal(he) ((he)->val) +#define dictSlots(ht) ((ht)->size) +#define dictSize(ht) ((ht)->used) + +/* API */ +static unsigned int dictGenHashFunction(const unsigned char *buf, int len); +static dict *dictCreate(dictType *type, void *privDataPtr); +static int dictExpand(dict *ht, unsigned long size); +static int dictAdd(dict *ht, void *key, void *val); +static int dictReplace(dict *ht, void *key, void *val); +static int dictDelete(dict *ht, const void *key); +static void dictRelease(dict *ht); +static dictEntry * dictFind(dict *ht, const void *key); +static dictIterator *dictGetIterator(dict *ht); +static dictEntry *dictNext(dictIterator *iter); +static void dictReleaseIterator(dictIterator *iter); + +#endif /* __DICT_H */ diff --git a/ext/hiredis-vip-0.3.0/examples/example-ae.c b/ext/hiredis-vip-0.3.0/examples/example-ae.c new file mode 100644 index 000000000..8efa7306a --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-ae.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include + +#include +#include +#include + +/* Put event loop in the global scope, so it can be explicitly stopped */ +static aeEventLoop *loop; + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + aeStop(loop); + return; + } + + printf("Disconnected...\n"); + aeStop(loop); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + loop = aeCreateEventLoop(64); + redisAeAttach(loop, c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + aeMain(loop); + return 0; +} + diff --git a/ext/hiredis-vip-0.3.0/examples/example-glib.c b/ext/hiredis-vip-0.3.0/examples/example-glib.c new file mode 100644 index 000000000..d6e10f8e8 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-glib.c @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +static GMainLoop *mainloop; + +static void +connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_printerr("Failed to connect: %s\n", ac->errstr); + g_main_loop_quit(mainloop); + } else { + g_printerr("Connected...\n"); + } +} + +static void +disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED, + int status) +{ + if (status != REDIS_OK) { + g_error("Failed to disconnect: %s", ac->errstr); + } else { + g_printerr("Disconnected...\n"); + g_main_loop_quit(mainloop); + } +} + +static void +command_cb(redisAsyncContext *ac, + gpointer r, + gpointer user_data G_GNUC_UNUSED) +{ + redisReply *reply = r; + + if (reply) { + g_print("REPLY: %s\n", reply->str); + } + + redisAsyncDisconnect(ac); +} + +gint +main (gint argc G_GNUC_UNUSED, + gchar *argv[] G_GNUC_UNUSED) +{ + redisAsyncContext *ac; + GMainContext *context = NULL; + GSource *source; + + ac = redisAsyncConnect("127.0.0.1", 6379); + if (ac->err) { + g_printerr("%s\n", ac->errstr); + exit(EXIT_FAILURE); + } + + source = redis_source_new(ac); + mainloop = g_main_loop_new(context, FALSE); + g_source_attach(source, context); + + redisAsyncSetConnectCallback(ac, connect_cb); + redisAsyncSetDisconnectCallback(ac, disconnect_cb); + redisAsyncCommand(ac, command_cb, NULL, "SET key 1234"); + redisAsyncCommand(ac, command_cb, NULL, "GET key"); + + g_main_loop_run(mainloop); + + return EXIT_SUCCESS; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libev.c b/ext/hiredis-vip-0.3.0/examples/example-libev.c new file mode 100644 index 000000000..cc8b166ec --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-libev.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibevAttach(EV_DEFAULT_ c); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + ev_loop(EV_DEFAULT_ 0); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libevent.c b/ext/hiredis-vip-0.3.0/examples/example-libevent.c new file mode 100644 index 000000000..d333c22b7 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-libevent.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + struct event_base *base = event_base_new(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example-libuv.c b/ext/hiredis-vip-0.3.0/examples/example-libuv.c new file mode 100644 index 000000000..a5462d410 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example-libuv.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include +#include +#include + +void getCallback(redisAsyncContext *c, void *r, void *privdata) { + redisReply *reply = r; + if (reply == NULL) return; + printf("argv[%s]: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisAsyncDisconnect(c); +} + +void connectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Connected...\n"); +} + +void disconnectCallback(const redisAsyncContext *c, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", c->errstr); + return; + } + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + uv_loop_t* loop = uv_default_loop(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisLibuvAttach(c,loop); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1])); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + uv_run(loop, UV_RUN_DEFAULT); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/examples/example.c b/ext/hiredis-vip-0.3.0/examples/example.c new file mode 100644 index 000000000..25226a807 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/examples/example.c @@ -0,0 +1,78 @@ +#include +#include +#include + +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = (argc > 2) ? atoi(argv[2]) : 6379; + + struct timeval timeout = { 1, 500000 }; // 1.5 seconds + c = redisConnectWithTimeout(hostname, port, timeout); + if (c == NULL || c->err) { + if (c) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + } else { + printf("Connection error: can't allocate redis context\n"); + } + exit(1); + } + + /* PING server */ + reply = redisCommand(c,"PING"); + printf("PING: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key */ + reply = redisCommand(c,"SET %s %s", "foo", "hello world"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + /* Set a key using binary safe API */ + reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5); + printf("SET (binary API): %s\n", reply->str); + freeReplyObject(reply); + + /* Try a GET and two INCR */ + reply = redisCommand(c,"GET foo"); + printf("GET foo: %s\n", reply->str); + freeReplyObject(reply); + + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + /* again ... */ + reply = redisCommand(c,"INCR counter"); + printf("INCR counter: %lld\n", reply->integer); + freeReplyObject(reply); + + /* Create a list of numbers, from 0 to 9 */ + reply = redisCommand(c,"DEL mylist"); + freeReplyObject(reply); + for (j = 0; j < 10; j++) { + char buf[64]; + + snprintf(buf,64,"%d",j); + reply = redisCommand(c,"LPUSH mylist element-%s", buf); + freeReplyObject(reply); + } + + /* Let's check what we have inside the list */ + reply = redisCommand(c,"LRANGE mylist 0 -1"); + if (reply->type == REDIS_REPLY_ARRAY) { + for (j = 0; j < reply->elements; j++) { + printf("%u) %s\n", j, reply->element[j]->str); + } + } + freeReplyObject(reply); + + /* Disconnects and frees the context */ + redisFree(c); + + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/fmacros.h b/ext/hiredis-vip-0.3.0/fmacros.h new file mode 100644 index 000000000..a3b1df034 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/fmacros.h @@ -0,0 +1,23 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#if defined(__linux__) +#ifndef _BSD_SOURCE +#define _BSD_SOURCE +#endif +#define _DEFAULT_SOURCE +#endif + +#if defined(__sun__) +#define _POSIX_C_SOURCE 200112L +#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) +#define _XOPEN_SOURCE 600 +#else +#define _XOPEN_SOURCE +#endif + +#if __APPLE__ && __MACH__ +#define _OSX +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiarray.c b/ext/hiredis-vip-0.3.0/hiarray.c new file mode 100644 index 000000000..cf742ecf6 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiarray.c @@ -0,0 +1,188 @@ +#include + +#include "hiutil.h" +#include "hiarray.h" + +struct hiarray * +hiarray_create(uint32_t n, size_t size) +{ + struct hiarray *a; + + ASSERT(n != 0 && size != 0); + + a = hi_alloc(sizeof(*a)); + if (a == NULL) { + return NULL; + } + + a->elem = hi_alloc(n * size); + if (a->elem == NULL) { + hi_free(a); + return NULL; + } + + a->nelem = 0; + a->size = size; + a->nalloc = n; + + return a; +} + +void +hiarray_destroy(struct hiarray *a) +{ + hiarray_deinit(a); + hi_free(a); +} + +int +hiarray_init(struct hiarray *a, uint32_t n, size_t size) +{ + ASSERT(n != 0 && size != 0); + + a->elem = hi_alloc(n * size); + if (a->elem == NULL) { + return HI_ENOMEM; + } + + a->nelem = 0; + a->size = size; + a->nalloc = n; + + return HI_OK; +} + +void +hiarray_deinit(struct hiarray *a) +{ + ASSERT(a->nelem == 0); + + if (a->elem != NULL) { + hi_free(a->elem); + } +} + +uint32_t +hiarray_idx(struct hiarray *a, void *elem) +{ + uint8_t *p, *q; + uint32_t off, idx; + + ASSERT(elem >= a->elem); + + p = a->elem; + q = elem; + off = (uint32_t)(q - p); + + ASSERT(off % (uint32_t)a->size == 0); + + idx = off / (uint32_t)a->size; + + return idx; +} + +void * +hiarray_push(struct hiarray *a) +{ + void *elem, *new; + size_t size; + + if (a->nelem == a->nalloc) { + + /* the array is full; allocate new array */ + size = a->size * a->nalloc; + new = hi_realloc(a->elem, 2 * size); + if (new == NULL) { + return NULL; + } + + a->elem = new; + a->nalloc *= 2; + } + + elem = (uint8_t *)a->elem + a->size * a->nelem; + a->nelem++; + + return elem; +} + +void * +hiarray_pop(struct hiarray *a) +{ + void *elem; + + ASSERT(a->nelem != 0); + + a->nelem--; + elem = (uint8_t *)a->elem + a->size * a->nelem; + + return elem; +} + +void * +hiarray_get(struct hiarray *a, uint32_t idx) +{ + void *elem; + + ASSERT(a->nelem != 0); + ASSERT(idx < a->nelem); + + elem = (uint8_t *)a->elem + (a->size * idx); + + return elem; +} + +void * +hiarray_top(struct hiarray *a) +{ + ASSERT(a->nelem != 0); + + return hiarray_get(a, a->nelem - 1); +} + +void +hiarray_swap(struct hiarray *a, struct hiarray *b) +{ + struct hiarray tmp; + + tmp = *a; + *a = *b; + *b = tmp; +} + +/* + * Sort nelem elements of the array in ascending order based on the + * compare comparator. + */ +void +hiarray_sort(struct hiarray *a, hiarray_compare_t compare) +{ + ASSERT(a->nelem != 0); + + qsort(a->elem, a->nelem, a->size, compare); +} + +/* + * Calls the func once for each element in the array as long as func returns + * success. On failure short-circuits and returns the error status. + */ +int +hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) +{ + uint32_t i, nelem; + + ASSERT(array_n(a) != 0); + ASSERT(func != NULL); + + for (i = 0, nelem = hiarray_n(a); i < nelem; i++) { + void *elem = hiarray_get(a, i); + rstatus_t status; + + status = func(elem, data); + if (status != HI_OK) { + return status; + } + } + + return HI_OK; +} diff --git a/ext/hiredis-vip-0.3.0/hiarray.h b/ext/hiredis-vip-0.3.0/hiarray.h new file mode 100644 index 000000000..fda3a4b8b --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiarray.h @@ -0,0 +1,56 @@ +#ifndef __HIARRAY_H_ +#define __HIARRAY_H_ + +#include + +typedef int (*hiarray_compare_t)(const void *, const void *); +typedef int (*hiarray_each_t)(void *, void *); + +struct hiarray { + uint32_t nelem; /* # element */ + void *elem; /* element */ + size_t size; /* element size */ + uint32_t nalloc; /* # allocated element */ +}; + +#define null_hiarray { 0, NULL, 0, 0 } + +static inline void +hiarray_null(struct hiarray *a) +{ + a->nelem = 0; + a->elem = NULL; + a->size = 0; + a->nalloc = 0; +} + +static inline void +hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc) +{ + a->nelem = 0; + a->elem = elem; + a->size = size; + a->nalloc = nalloc; +} + +static inline uint32_t +hiarray_n(const struct hiarray *a) +{ + return a->nelem; +} + +struct hiarray *hiarray_create(uint32_t n, size_t size); +void hiarray_destroy(struct hiarray *a); +int hiarray_init(struct hiarray *a, uint32_t n, size_t size); +void hiarray_deinit(struct hiarray *a); + +uint32_t hiarray_idx(struct hiarray *a, void *elem); +void *hiarray_push(struct hiarray *a); +void *hiarray_pop(struct hiarray *a); +void *hiarray_get(struct hiarray *a, uint32_t idx); +void *hiarray_top(struct hiarray *a); +void hiarray_swap(struct hiarray *a, struct hiarray *b); +void hiarray_sort(struct hiarray *a, hiarray_compare_t compare); +int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); + +#endif diff --git a/ext/hiredis-vip-0.3.0/hircluster.c b/ext/hiredis-vip-0.3.0/hircluster.c new file mode 100644 index 000000000..edf9cb2f9 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hircluster.c @@ -0,0 +1,4747 @@ + +#include "fmacros.h" +#include +#include +#include +#include +#include + +#include "hircluster.h" +#include "hiutil.h" +#include "adlist.h" +#include "hiarray.h" +#include "command.h" +#include "dict.c" + +#define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" +#define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" + +#define REDIS_COMMAND_ASKING "ASKING" +#define REDIS_COMMAND_PING "PING" + +#define REDIS_PROTOCOL_ASKING "*1\r\n$6\r\nASKING\r\n" + +#define IP_PORT_SEPARATOR ":" + +#define CLUSTER_ADDRESS_SEPARATOR "," + +#define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5 + +typedef struct cluster_async_data +{ + redisClusterAsyncContext *acc; + struct cmd *command; + redisClusterCallbackFn *callback; + int retry_count; + void *privdata; +}cluster_async_data; + +typedef enum CLUSTER_ERR_TYPE{ + CLUSTER_NOT_ERR = 0, + CLUSTER_ERR_MOVED, + CLUSTER_ERR_ASK, + CLUSTER_ERR_TRYAGAIN, + CLUSTER_ERR_CROSSSLOT, + CLUSTER_ERR_CLUSTERDOWN, + CLUSTER_ERR_SENTINEL +}CLUSTER_ERR_TYPE; + +static void cluster_node_deinit(cluster_node *node); +static void cluster_slot_destroy(cluster_slot *slot); +static void cluster_open_slot_destroy(copen_slot *oslot); + +void listClusterNodeDestructor(void *val) +{ + cluster_node_deinit(val); + + hi_free(val); +} + +void listClusterSlotDestructor(void *val) +{ + cluster_slot_destroy(val); +} + +unsigned int dictSdsHash(const void *key) { + return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); +} + +int dictSdsKeyCompare(void *privdata, const void *key1, + const void *key2) +{ + int l1,l2; + DICT_NOTUSED(privdata); + + l1 = sdslen((sds)key1); + l2 = sdslen((sds)key2); + if (l1 != l2) return 0; + return memcmp(key1, key2, l1) == 0; +} + +void dictSdsDestructor(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + + sdsfree(val); +} + +void dictClusterNodeDestructor(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + + cluster_node_deinit(val); + + hi_free(val); +} + +/* Cluster nodes hash table, mapping nodes + * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) + * or addresses(1.2.3.4:6379) to clusterNode structures. + * Those nodes need destroy. + */ +dictType clusterNodesDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + dictClusterNodeDestructor /* val destructor */ +}; + +/* Cluster nodes hash table, mapping nodes + * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) + * or addresses(1.2.3.4:6379) to clusterNode structures. + * Those nodes do not need destroy. + */ +dictType clusterNodesRefDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL /* val destructor */ +}; + + +void listCommandFree(void *command) +{ + struct cmd *cmd = command; + command_destroy(cmd); +} + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +/* Forward declaration of function in hiredis.c */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); + +/* Helper function for the redisClusterCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was succesfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + + +/* ----------------------------------------------------------------------------- + * Key space handling + * -------------------------------------------------------------------------- */ + +/* We have 16384 hash slots. The hash slot of a given key is obtained + * as the least significant 14 bits of the crc16 of the key. + * + * However if the key contains the {...} pattern, only the part between + * { and } is hashed. This may be useful in the future to force certain + * keys to be in the same node (assuming no resharding is in progress). */ +static unsigned int keyHashSlot(char *key, int keylen) { + int s, e; /* start-end indexes of { and } */ + + for (s = 0; s < keylen; s++) + if (key[s] == '{') break; + + /* No '{' ? Hash the whole key. This is the base case. */ + if (s == keylen) return crc16(key,keylen) & 0x3FFF; + + /* '{' found? Check if we have the corresponding '}'. */ + for (e = s+1; e < keylen; e++) + if (key[e] == '}') break; + + /* No '}' or nothing betweeen {} ? Hash the whole key. */ + if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF; + + /* If we are here there is both a { and a } on its right. Hash + * what is in the middle between { and }. */ + return crc16(key+s+1,e-s-1) & 0x3FFF; +} + +static void __redisClusterSetError(redisClusterContext *cc, int type, const char *str) { + size_t len; + + if(cc == NULL){ + return; + } + + cc->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(cc->errstr)-1) ? len : (sizeof(cc->errstr)-1); + memcpy(cc->errstr,str,len); + cc->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + __redis_strerror_r(errno, cc->errstr, sizeof(cc->errstr)); + } +} + +static int cluster_reply_error_type(redisReply *reply) +{ + + if(reply == NULL) + { + return REDIS_ERR; + } + + if(reply->type == REDIS_REPLY_ERROR) + { + if((int)strlen(REDIS_ERROR_MOVED) < reply->len && + strncmp(reply->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) + { + return CLUSTER_ERR_MOVED; + } + else if((int)strlen(REDIS_ERROR_ASK) < reply->len && + strncmp(reply->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0) + { + return CLUSTER_ERR_ASK; + } + else if((int)strlen(REDIS_ERROR_TRYAGAIN) < reply->len && + strncmp(reply->str, REDIS_ERROR_TRYAGAIN, strlen(REDIS_ERROR_TRYAGAIN)) == 0) + { + return CLUSTER_ERR_TRYAGAIN; + } + else if((int)strlen(REDIS_ERROR_CROSSSLOT) < reply->len && + strncmp(reply->str, REDIS_ERROR_CROSSSLOT, strlen(REDIS_ERROR_CROSSSLOT)) == 0) + { + return CLUSTER_ERR_CROSSSLOT; + } + else if((int)strlen(REDIS_ERROR_CLUSTERDOWN) < reply->len && + strncmp(reply->str, REDIS_ERROR_CLUSTERDOWN, strlen(REDIS_ERROR_CLUSTERDOWN)) == 0) + { + return CLUSTER_ERR_CLUSTERDOWN; + } + else + { + return CLUSTER_ERR_SENTINEL; + } + } + + return CLUSTER_NOT_ERR; +} + +static int cluster_node_init(cluster_node *node) +{ + if(node == NULL){ + return REDIS_ERR; + } + + node->name = NULL; + node->addr = NULL; + node->host = NULL; + node->port = 0; + node->role = REDIS_ROLE_NULL; + node->myself = 0; + node->slaves = NULL; + node->con = NULL; + node->acon = NULL; + node->slots = NULL; + node->failure_count = 0; + node->data = NULL; + node->migrating = NULL; + node->importing = NULL; + + return REDIS_OK; +} + +static void cluster_node_deinit(cluster_node *node) +{ + copen_slot **oslot; + + if(node == NULL) + { + return; + } + + sdsfree(node->name); + sdsfree(node->addr); + sdsfree(node->host); + node->port = 0; + node->role = REDIS_ROLE_NULL; + node->myself = 0; + + if(node->con != NULL) + { + redisFree(node->con); + } + + if(node->acon != NULL) + { + redisAsyncFree(node->acon); + } + + if(node->slots != NULL) + { + listRelease(node->slots); + } + + if(node->slaves != NULL) + { + listRelease(node->slaves); + } + + if(node->migrating) + { + while(hiarray_n(node->migrating)) + { + oslot = hiarray_pop(node->migrating); + cluster_open_slot_destroy(*oslot); + } + + hiarray_destroy(node->migrating); + node->migrating = NULL; + } + + if(node->importing) + { + while(hiarray_n(node->importing)) + { + oslot = hiarray_pop(node->importing); + cluster_open_slot_destroy(*oslot); + } + + hiarray_destroy(node->importing); + node->importing = NULL; + } +} + +static int cluster_slot_init(cluster_slot *slot, cluster_node *node) +{ + slot->start = 0; + slot->end = 0; + slot->node = node; + + return REDIS_OK; +} + +static cluster_slot *cluster_slot_create(cluster_node *node) +{ + cluster_slot *slot; + + slot = hi_alloc(sizeof(*slot)); + if(slot == NULL){ + return NULL; + } + + cluster_slot_init(slot, node); + + if(node != NULL){ + ASSERT(node->role == REDIS_ROLE_MASTER); + if(node->slots == NULL){ + node->slots = listCreate(); + if(node->slots == NULL) + { + cluster_slot_destroy(slot); + return NULL; + } + + node->slots->free = listClusterSlotDestructor; + } + + listAddNodeTail(node->slots, slot); + } + + return slot; +} + +static int cluster_slot_ref_node(cluster_slot * slot, cluster_node *node) +{ + if(slot == NULL || node == NULL){ + return REDIS_ERR; + } + + + if(node->role != REDIS_ROLE_MASTER){ + return REDIS_ERR; + } + + if(node->slots == NULL){ + node->slots = listCreate(); + if(node->slots == NULL) + { + return REDIS_ERR; + } + + node->slots->free = listClusterSlotDestructor; + } + + listAddNodeTail(node->slots, slot); + slot->node = node; + + return REDIS_OK; +} + +static void cluster_slot_destroy(cluster_slot *slot) +{ + slot->start = 0; + slot->end = 0; + slot->node = NULL; + + hi_free(slot); +} + +static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, + sds remote_name, cluster_node *node) +{ + copen_slot *oslot; + + oslot = hi_alloc(sizeof(*oslot)); + if(oslot == NULL){ + return NULL; + } + + oslot->slot_num = 0; + oslot->migrate = 0; + oslot->node = NULL; + oslot->remote_name = NULL; + + oslot->slot_num = slot_num; + oslot->migrate = migrate; + oslot->node = node; + oslot->remote_name = sdsdup(remote_name); + + return oslot; +} + +static void cluster_open_slot_destroy(copen_slot *oslot) +{ + oslot->slot_num = 0; + oslot->migrate = 0; + oslot->node = NULL; + + if(oslot->remote_name != NULL){ + sdsfree(oslot->remote_name); + oslot->remote_name = NULL; + } + + hi_free(oslot); +} + +/** + * Return a new node with the "cluster slots" command reply. + */ +static cluster_node *node_get_with_slots( + redisClusterContext *cc, redisReply *host_elem, + redisReply *port_elem, uint8_t role) +{ + cluster_node *node = NULL; + + if(host_elem == NULL || port_elem == NULL){ + return NULL; + } + + if(host_elem->type != REDIS_REPLY_STRING || + host_elem->len <= 0){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "node ip is not string."); + goto error; + } + + if(port_elem->type != REDIS_REPLY_INTEGER || + port_elem->integer <= 0){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "node port is not integer."); + goto error; + } + + if(!hi_valid_port((int)port_elem->integer)){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "node port is not valid."); + goto error; + } + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL){ + __redisClusterSetError(cc, + REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cluster_node_init(node); + + if(role == REDIS_ROLE_MASTER){ + node->slots = listCreate(); + if(node->slots == NULL){ + hi_free(node); + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slots for node listCreate error"); + goto error; + } + + node->slots->free = listClusterSlotDestructor; + } + + node->name = NULL; + node->addr = sdsnewlen(host_elem->str, host_elem->len); + node->addr = sdscatfmt(node->addr, ":%i", port_elem->integer); + + node->host = sdsnewlen(host_elem->str, host_elem->len); + node->port = (int)port_elem->integer; + node->role = role; + + return node; + +error: + + if(node != NULL){ + hi_free(node); + } + + return NULL; +} + +/** + * Return a new node with the "cluster nodes" command reply. + */ +static cluster_node *node_get_with_nodes( + redisClusterContext *cc, + sds *node_infos, int info_count, uint8_t role) +{ + sds *ip_port = NULL; + int count_ip_port = 0; + cluster_node *node; + + if(info_count < 8) + { + return NULL; + } + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cluster_node_init(node); + + if(role == REDIS_ROLE_MASTER) + { + node->slots = listCreate(); + if(node->slots == NULL) + { + hi_free(node); + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slots for node listCreate error"); + goto error; + } + + node->slots->free = listClusterSlotDestructor; + } + + node->name = node_infos[0]; + node->addr = node_infos[1]; + + ip_port = sdssplitlen(node_infos[1], sdslen(node_infos[1]), + IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port); + if(ip_port == NULL || count_ip_port != 2) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split ip port error"); + goto error; + } + node->host = ip_port[0]; + node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + node->role = role; + + sdsfree(ip_port[1]); + free(ip_port); + + node_infos[0] = NULL; + node_infos[1] = NULL; + + return node; + +error: + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, count_ip_port); + } + + if(node != NULL) + { + hi_free(node); + } + + return NULL; +} + +static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) +{ + dictIterator *di; + dictEntry *de_f, *de_t; + cluster_node *node_f, *node_t; + redisContext *c; + redisAsyncContext *ac; + + if(nodes_f == NULL || nodes_t == NULL){ + return; + } + + di = dictGetIterator(nodes_t); + while((de_t = dictNext(di)) != NULL){ + node_t = dictGetEntryVal(de_t); + if(node_t == NULL){ + continue; + } + + de_f = dictFind(nodes_f, node_t->addr); + if(de_f == NULL){ + continue; + } + + node_f = dictGetEntryVal(de_f); + if(node_f->con != NULL){ + c = node_f->con; + node_f->con = node_t->con; + node_t->con = c; + } + + if(node_f->acon != NULL){ + ac = node_f->acon; + node_f->acon = node_t->acon; + node_t->acon = ac; + + node_t->acon->data = node_t; + if (node_f->acon) + node_f->acon->data = node_f; + } + } + + dictReleaseIterator(di); + +} + +static int +cluster_slot_start_cmp(const void *t1, const void *t2) +{ + const cluster_slot **s1 = t1, **s2 = t2; + + return (*s1)->start > (*s2)->start?1:-1; +} + +static int +cluster_master_slave_mapping_with_name(redisClusterContext *cc, + dict **nodes, cluster_node *node, sds master_name) +{ + int ret; + dictEntry *di; + cluster_node *node_old; + listNode *lnode; + + if(node == NULL || master_name == NULL) + { + return REDIS_ERR; + } + + if(*nodes == NULL) + { + *nodes = dictCreate( + &clusterNodesRefDictType, NULL); + } + + di = dictFind(*nodes, master_name); + if(di == NULL) + { + ret = dictAdd(*nodes, + sdsnewlen(master_name, sdslen(master_name)), node); + if(ret != DICT_OK) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "the address already exists in the nodes"); + return REDIS_ERR; + } + + } + else + { + node_old = dictGetEntryVal(di); + if(node_old == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "dict get value null"); + return REDIS_ERR; + } + + if(node->role == REDIS_ROLE_MASTER && + node_old->role == REDIS_ROLE_MASTER) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "two masters have the same name"); + return REDIS_ERR; + } + else if(node->role == REDIS_ROLE_MASTER + && node_old->role == REDIS_ROLE_SLAVE) + { + if(node->slaves == NULL) + { + node->slaves = listCreate(); + if(node->slaves == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + return REDIS_ERR; + } + + node->slaves->free = + listClusterNodeDestructor; + } + + if(node_old->slaves != NULL) + { + node_old->slaves->free = NULL; + while(listLength(node_old->slaves) > 0) + { + lnode = listFirst(node_old->slaves); + listAddNodeHead(node->slaves, lnode->value); + listDelNode(node_old->slaves, lnode); + } + listRelease(node_old->slaves); + node_old->slaves = NULL; + } + + listAddNodeHead(node->slaves, node_old); + + dictSetHashVal(*nodes, di, node); + } + else if(node->role == REDIS_ROLE_SLAVE) + { + if(node_old->slaves == NULL) + { + node_old->slaves = listCreate(); + if(node_old->slaves == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + return REDIS_ERR; + } + + node_old->slaves->free = + listClusterNodeDestructor; + } + + listAddNodeTail(node_old->slaves, node); + } + else + { + NOT_REACHED(); + } + } + + return REDIS_OK; +} + +/** + * Parse the "cluster slots" command reply to nodes dict. + */ +dict * +parse_cluster_slots(redisClusterContext *cc, + redisReply *reply, int flags) +{ + int ret; + cluster_slot *slot = NULL; + dict *nodes = NULL; + dictEntry *den; + redisReply *elem_slots; + redisReply *elem_slots_begin, *elem_slots_end; + redisReply *elem_nodes; + redisReply *elem_ip, *elem_port; + cluster_node *master = NULL, *slave; + sds address; + uint32_t i, idx; + + if(reply == NULL){ + return NULL; + } + + nodes = dictCreate(&clusterNodesDictType, NULL); + if(nodes == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "out of memory"); + goto error; + } + + if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "reply is not an array."); + goto error; + } + + for(i = 0; i < reply->elements; i ++){ + elem_slots = reply->element[i]; + if(elem_slots->type != REDIS_REPLY_ARRAY || + elem_slots->elements < 3){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "first sub_reply is not an array."); + goto error; + } + + slot = cluster_slot_create(NULL); + if(slot == NULL){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Slot create failed: out of memory."); + goto error; + } + + //one slots region + for(idx = 0; idx < elem_slots->elements; idx ++){ + if(idx == 0){ + elem_slots_begin = elem_slots->element[idx]; + if(elem_slots_begin->type != REDIS_REPLY_INTEGER){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "slot begin is not an integer."); + goto error; + } + slot->start = (int)(elem_slots_begin->integer); + }else if(idx == 1){ + elem_slots_end = elem_slots->element[idx]; + if(elem_slots_end->type != REDIS_REPLY_INTEGER){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "slot end is not an integer."); + goto error; + } + + slot->end = (int)(elem_slots_end->integer); + + if(slot->start > slot->end){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "slot begin is bigger than slot end."); + goto error; + } + }else{ + elem_nodes = elem_slots->element[idx]; + if(elem_nodes->type != REDIS_REPLY_ARRAY || + elem_nodes->elements != 3){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "nodes sub_reply is not an correct array."); + goto error; + } + + elem_ip = elem_nodes->element[0]; + elem_port = elem_nodes->element[1]; + + if(elem_ip == NULL || elem_port == NULL || + elem_ip->type != REDIS_REPLY_STRING || + elem_port->type != REDIS_REPLY_INTEGER){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command(cluster slots) reply error: " + "master ip or port is not correct."); + goto error; + } + + //this is master. + if(idx == 2){ + address = sdsnewlen(elem_ip->str, elem_ip->len); + address = sdscatfmt(address, ":%i", elem_port->integer); + + den = dictFind(nodes, address); + //master already exits, break to the next slots region. + if(den != NULL){ + sdsfree(address); + + master = dictGetEntryVal(den); + ret = cluster_slot_ref_node(slot, master); + if(ret != REDIS_OK){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Slot ref node failed: out of memory."); + goto error; + } + + slot = NULL; + break; + } + + sdsfree(address); + master = node_get_with_slots(cc, elem_ip, + elem_port, REDIS_ROLE_MASTER); + if(master == NULL){ + goto error; + } + + ret = dictAdd(nodes, + sdsnewlen(master->addr, sdslen(master->addr)), master); + if(ret != DICT_OK){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "The address already exists in the nodes"); + cluster_node_deinit(master); + hi_free(master); + goto error; + } + + ret = cluster_slot_ref_node(slot, master); + if(ret != REDIS_OK){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Slot ref node failed: out of memory."); + goto error; + } + + slot = NULL; + }else if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ + slave = node_get_with_slots(cc, elem_ip, + elem_port, REDIS_ROLE_SLAVE); + if(slave == NULL){ + goto error; + } + + if(master->slaves == NULL){ + master->slaves = listCreate(); + if(master->slaves == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + cluster_node_deinit(slave); + goto error; + } + + master->slaves->free = + listClusterNodeDestructor; + } + + listAddNodeTail(master->slaves, slave); + } + } + } + } + + return nodes; + +error: + + if(nodes != NULL){ + dictRelease(nodes); + } + + if(slot != NULL){ + cluster_slot_destroy(slot); + } + + return NULL; +} + +/** + * Parse the "cluster nodes" command reply to nodes dict. + */ +dict * +parse_cluster_nodes(redisClusterContext *cc, + char *str, int str_len, int flags) +{ + int ret; + dict *nodes = NULL; + dict *nodes_name = NULL; + cluster_node *master, *slave; + cluster_slot *slot; + char *pos, *start, *end, *line_start, *line_end; + char *role; + int role_len; + uint8_t myself = 0; + int slot_start, slot_end; + sds *part = NULL, *slot_start_end = NULL; + int count_part = 0, count_slot_start_end = 0; + int k; + int len; + + nodes = dictCreate(&clusterNodesDictType, NULL); + if(nodes == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "out of memory"); + goto error; + } + + start = str; + end = start + str_len; + + line_start = start; + + for(pos = start; pos < end; pos ++){ + if(*pos == '\n'){ + line_end = pos - 1; + len = line_end - line_start; + + part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); + + if(part == NULL || count_part < 8){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split cluster nodes error"); + goto error; + } + + //the address string is ":0", skip this node. + if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0){ + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + + continue; + } + + if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0){ + role_len = sdslen(part[2]) - 7; + role = part[2] + 7; + myself = 1; + }else{ + role_len = sdslen(part[2]); + role = part[2]; + } + + //add master node + if(role_len >= 6 && memcmp(role, "master", 6) == 0){ + if(count_part < 8){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Master node parts number error: less than 8."); + goto error; + } + + master = node_get_with_nodes(cc, + part, count_part, REDIS_ROLE_MASTER); + if(master == NULL){ + goto error; + } + + ret = dictAdd(nodes, + sdsnewlen(master->addr, sdslen(master->addr)), master); + if(ret != DICT_OK){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "The address already exists in the nodes"); + cluster_node_deinit(master); + hi_free(master); + goto error; + } + + if(flags & HIRCLUSTER_FLAG_ADD_SLAVE){ + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, master, master->name); + if(ret != REDIS_OK){ + cluster_node_deinit(master); + hi_free(master); + goto error; + } + } + + if(myself) master->myself = 1; + + for(k = 8; k < count_part; k ++){ + slot_start_end = sdssplitlen(part[k], + sdslen(part[k]), "-", 1, &count_slot_start_end); + + if(slot_start_end == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split slot start end error(NULL)"); + goto error; + }else if(count_slot_start_end == 1){ + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); + slot_end = slot_start; + }else if(count_slot_start_end == 2){ + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; + slot_end = + hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; + }else{ + //add open slot for master + if(flags & HIRCLUSTER_FLAG_ADD_OPENSLOT && + count_slot_start_end == 3 && + sdslen(slot_start_end[0]) > 1 && + sdslen(slot_start_end[1]) == 1 && + sdslen(slot_start_end[2]) > 1 && + slot_start_end[0][0] == '[' && + slot_start_end[2][sdslen(slot_start_end[2])-1] == ']'){ + + copen_slot *oslot, **oslot_elem; + + sdsrange(slot_start_end[0], 1, -1); + sdsrange(slot_start_end[2], 0, -2); + + if(slot_start_end[1][0] == '>'){ + oslot = cluster_open_slot_create( + hi_atoi(slot_start_end[0], + sdslen(slot_start_end[0])), + 1, slot_start_end[2], master); + if(oslot == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create open slot error"); + goto error; + } + + if(master->migrating == NULL){ + master->migrating = hiarray_create(1, sizeof(oslot)); + if(master->migrating == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create migrating array error"); + cluster_open_slot_destroy(oslot); + goto error; + } + } + + oslot_elem = hiarray_push(master->migrating); + if(oslot_elem == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Push migrating array error: out of memory"); + cluster_open_slot_destroy(oslot); + goto error; + } + + *oslot_elem = oslot; + }else if(slot_start_end[1][0] == '<'){ + oslot = cluster_open_slot_create(hi_atoi(slot_start_end[0], + sdslen(slot_start_end[0])), 0, slot_start_end[2], + master); + if(oslot == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create open slot error"); + goto error; + } + + if(master->importing == NULL){ + master->importing = hiarray_create(1, sizeof(oslot)); + if(master->importing == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "create migrating array error"); + cluster_open_slot_destroy(oslot); + goto error; + } + } + + oslot_elem = hiarray_push(master->importing); + if(oslot_elem == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "push migrating array error: out of memory"); + cluster_open_slot_destroy(oslot); + goto error; + } + + *oslot_elem = oslot; + } + } + + slot_start = -1; + slot_end = -1; + } + + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + + if(slot_start < 0 || slot_end < 0 || + slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS){ + continue; + } + + slot = cluster_slot_create(master); + if(slot == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + goto error; + } + + slot->start = (uint32_t)slot_start; + slot->end = (uint32_t)slot_end; + } + + } + //add slave node + else if((flags & HIRCLUSTER_FLAG_ADD_SLAVE) && + (role_len >= 5 && memcmp(role, "slave", 5) == 0)){ + slave = node_get_with_nodes(cc, part, + count_part, REDIS_ROLE_SLAVE); + if(slave == NULL){ + goto error; + } + + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, slave, part[3]); + if(ret != REDIS_OK){ + cluster_node_deinit(slave); + hi_free(slave); + goto error; + } + + if(myself) slave->myself = 1; + } + + if(myself == 1){ + myself = 0; + } + + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + } + } + + if(nodes_name != NULL){ + dictRelease(nodes_name); + } + + return nodes; + +error: + + if(part != NULL){ + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + } + + if(slot_start_end != NULL){ + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + } + + if(nodes != NULL){ + dictRelease(nodes); + } + + if(nodes_name != NULL){ + dictRelease(nodes_name); + } + + return NULL; +} + +/** + * Update route with the "cluster nodes" or "cluster slots" command reply. + */ +static int +cluster_update_route_by_addr(redisClusterContext *cc, + const char *ip, int port) +{ + redisContext *c = NULL; + redisReply *reply = NULL; + dict *nodes = NULL; + struct hiarray *slots = NULL; + cluster_node *master; + cluster_slot *slot, **slot_elem; + dictIterator *dit = NULL; + dictEntry *den; + listIter *lit = NULL; + listNode *lnode; + cluster_node *table[REDIS_CLUSTER_SLOTS]; + uint32_t j, k; + + if(cc == NULL){ + return REDIS_ERR; + } + + if(ip == NULL || port <= 0){ + __redisClusterSetError(cc, + REDIS_ERR_OTHER,"Ip or port error!"); + goto error; + } + + if(cc->timeout){ + c = redisConnectWithTimeout(ip, port, *cc->timeout); + }else{ + c = redisConnect(ip, port); + } + + if (c == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Init redis context error(return NULL)"); + goto error; + }else if(c->err){ + __redisClusterSetError(cc,c->err,c->errstr); + goto error; + } + + if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){ + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); + if(reply == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster slots) reply error(NULL)."); + goto error; + }else if(reply->type != REDIS_REPLY_ARRAY){ + if(reply->type == REDIS_REPLY_ERROR){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + reply->str); + }else{ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster slots) reply error: type is not array."); + } + + goto error; + } + + nodes = parse_cluster_slots(cc, reply, cc->flags); + }else{ + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); + if(reply == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster nodes) reply error(NULL)."); + goto error; + }else if(reply->type != REDIS_REPLY_STRING){ + if(reply->type == REDIS_REPLY_ERROR){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + reply->str); + }else{ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Command(cluster nodes) reply error: type is not string."); + } + + goto error; + } + + nodes = parse_cluster_nodes(cc, reply->str, reply->len, cc->flags); + } + + if(nodes == NULL){ + goto error; + } + + memset(table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + slots = hiarray_create(dictSize(nodes), sizeof(cluster_slot*)); + if(slots == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Slots array create failed: out of memory"); + goto error; + } + + dit = dictGetIterator(nodes); + if(dit == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Dict get iterator failed: out of memory"); + goto error; + } + + while((den = dictNext(dit))){ + master = dictGetEntryVal(den); + if(master->role != REDIS_ROLE_MASTER){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Node role must be master"); + goto error; + } + + if(master->slots == NULL){ + continue; + } + + lit = listGetIterator(master->slots, AL_START_HEAD); + if(lit == NULL){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "List get iterator failed: out of memory"); + goto error; + } + + while((lnode = listNext(lit))){ + slot = listNodeValue(lnode); + if(slot->start > slot->end || + slot->end >= REDIS_CLUSTER_SLOTS){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Slot region for node is error"); + goto error; + } + + slot_elem = hiarray_push(slots); + *slot_elem = slot; + } + + listReleaseIterator(lit); + } + + dictReleaseIterator(dit); + + hiarray_sort(slots, cluster_slot_start_cmp); + for(j = 0; j < hiarray_n(slots); j ++){ + slot_elem = hiarray_get(slots, j); + + for(k = (*slot_elem)->start; k <= (*slot_elem)->end; k ++){ + if(table[k] != NULL){ + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Diffent node hold a same slot"); + goto error; + } + + table[k] = (*slot_elem)->node; + } + } + + cluster_nodes_swap_ctx(cc->nodes, nodes); + if(cc->nodes != NULL){ + dictRelease(cc->nodes); + cc->nodes = NULL; + } + cc->nodes = nodes; + + if(cc->slots != NULL) + { + cc->slots->nelem = 0; + hiarray_destroy(cc->slots); + cc->slots = NULL; + } + cc->slots = slots; + + memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + cc->route_version ++; + + freeReplyObject(reply); + + if(c != NULL){ + redisFree(c); + } + + return REDIS_OK; + +error: + + if(dit != NULL){ + dictReleaseIterator(dit); + } + + if(lit != NULL){ + listReleaseIterator(lit); + } + + if(slots != NULL) + { + if(slots == cc->slots) + { + cc->slots = NULL; + } + + slots->nelem = 0; + hiarray_destroy(slots); + } + + if(nodes != NULL){ + if(nodes == cc->nodes){ + cc->nodes = NULL; + } + + dictRelease(nodes); + } + + if(reply != NULL){ + freeReplyObject(reply); + reply = NULL; + } + + if(c != NULL){ + redisFree(c); + } + + return REDIS_ERR; +} + + +/** + * Update route with the "cluster nodes" command reply. + */ +static int +cluster_update_route_with_nodes_old(redisClusterContext *cc, + const char *ip, int port) +{ + int ret; + redisContext *c = NULL; + redisReply *reply = NULL; + struct hiarray *slots = NULL; + dict *nodes = NULL; + dict *nodes_name = NULL; + cluster_node *master, *slave; + cluster_slot **slot; + char *pos, *start, *end, *line_start, *line_end; + char *role; + int role_len; + uint8_t myself = 0; + int slot_start, slot_end; + sds *part = NULL, *slot_start_end = NULL; + int count_part = 0, count_slot_start_end = 0; + int j, k; + int len; + cluster_node *table[REDIS_CLUSTER_SLOTS] = {NULL}; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(ip == NULL || port <= 0) + { + __redisClusterSetError(cc, + REDIS_ERR_OTHER,"ip or port error!"); + goto error; + } + + if(cc->timeout) + { + c = redisConnectWithTimeout(ip, port, *cc->timeout); + } + else + { + c = redisConnect(ip, port); + } + + if (c == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "init redis context error(return NULL)"); + goto error; + } + else if(c->err) + { + __redisClusterSetError(cc,c->err,c->errstr); + goto error; + } + + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); + + if(reply == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command(cluster nodes) reply error(NULL)"); + goto error; + } + else if(reply->type != REDIS_REPLY_STRING) + { + if(reply->type == REDIS_REPLY_ERROR) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + reply->str); + } + else + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command(cluster nodes) reply error(type is not string)"); + } + + goto error; + } + + nodes = dictCreate(&clusterNodesDictType, NULL); + + slots = hiarray_create(10, sizeof(cluster_slot*)); + if(slots == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "array create error"); + goto error; + } + + start = reply->str; + end = start + reply->len; + + line_start = start; + + for(pos = start; pos < end; pos ++) + { + if(*pos == '\n') + { + line_end = pos - 1; + len = line_end - line_start; + + part = sdssplitlen(line_start, len + 1, " ", 1, &count_part); + + if(part == NULL || count_part < 8) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split cluster nodes error"); + goto error; + } + + //the address string is ":0", skip this node. + if(sdslen(part[1]) == 2 && strcmp(part[1], ":0") == 0) + { + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + + continue; + } + + if(sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0) + { + role_len = sdslen(part[2]) - 7; + role = part[2] + 7; + myself = 1; + } + else + { + role_len = sdslen(part[2]); + role = part[2]; + } + + //add master node + if(role_len >= 6 && memcmp(role, "master", 6) == 0) + { + if(count_part < 8) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "master node part number error"); + goto error; + } + + master = node_get_with_nodes(cc, + part, count_part, REDIS_ROLE_MASTER); + if(master == NULL) + { + goto error; + } + + ret = dictAdd(nodes, + sdsnewlen(master->addr, sdslen(master->addr)), master); + if(ret != DICT_OK) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "the address already exists in the nodes"); + cluster_node_deinit(master); + hi_free(master); + goto error; + } + + if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) + { + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, master, master->name); + if(ret != REDIS_OK) + { + cluster_node_deinit(master); + hi_free(master); + goto error; + } + } + + if(myself == 1) + { + master->con = c; + c = NULL; + } + + for(k = 8; k < count_part; k ++) + { + slot_start_end = sdssplitlen(part[k], + sdslen(part[k]), "-", 1, &count_slot_start_end); + + if(slot_start_end == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split slot start end error(NULL)"); + goto error; + } + else if(count_slot_start_end == 1) + { + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0])); + slot_end = slot_start; + } + else if(count_slot_start_end == 2) + { + slot_start = + hi_atoi(slot_start_end[0], sdslen(slot_start_end[0]));; + slot_end = + hi_atoi(slot_start_end[1], sdslen(slot_start_end[1]));; + } + else + { + slot_start = -1; + slot_end = -1; + } + + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + + if(slot_start < 0 || slot_end < 0 || + slot_start > slot_end || slot_end >= REDIS_CLUSTER_SLOTS) + { + continue; + } + + for(j = slot_start; j <= slot_end; j ++) + { + if(table[j] != NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "diffent node hold a same slot"); + goto error; + } + table[j] = master; + } + + slot = hiarray_push(slots); + if(slot == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slot push in array error"); + goto error; + } + + *slot = cluster_slot_create(master); + if(*slot == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + goto error; + } + + (*slot)->start = (uint32_t)slot_start; + (*slot)->end = (uint32_t)slot_end; + } + + } + //add slave node + else if((cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) && + (role_len >= 5 && memcmp(role, "slave", 5) == 0)) + { + slave = node_get_with_nodes(cc, part, + count_part, REDIS_ROLE_SLAVE); + if(slave == NULL) + { + goto error; + } + + ret = cluster_master_slave_mapping_with_name(cc, + &nodes_name, slave, part[3]); + if(ret != REDIS_OK) + { + cluster_node_deinit(slave); + hi_free(slave); + goto error; + } + + if(myself == 1) + { + slave->con = c; + c = NULL; + } + } + + if(myself == 1) + { + myself = 0; + } + + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + } + } + + if(cc->slots != NULL) + { + cc->slots->nelem = 0; + hiarray_destroy(cc->slots); + cc->slots = NULL; + } + cc->slots = slots; + + cluster_nodes_swap_ctx(cc->nodes, nodes); + + if(cc->nodes != NULL) + { + dictRelease(cc->nodes); + cc->nodes = NULL; + } + cc->nodes = nodes; + + hiarray_sort(cc->slots, cluster_slot_start_cmp); + + memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + cc->route_version ++; + + freeReplyObject(reply); + + if(c != NULL) + { + redisFree(c); + } + + if(nodes_name != NULL) + { + dictRelease(nodes_name); + } + + return REDIS_OK; + +error: + + if(part != NULL) + { + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + } + + if(slot_start_end != NULL) + { + sdsfreesplitres(slot_start_end, count_slot_start_end); + count_slot_start_end = 0; + slot_start_end = NULL; + } + + if(slots != NULL) + { + if(slots == cc->slots) + { + cc->slots = NULL; + } + + slots->nelem = 0; + hiarray_destroy(slots); + } + + if(nodes != NULL) + { + if(nodes == cc->nodes) + { + cc->nodes = NULL; + } + + dictRelease(nodes); + } + + if(nodes_name != NULL) + { + dictRelease(nodes_name); + } + + if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } + + if(c != NULL) + { + redisFree(c); + } + + return REDIS_ERR; +} + +int +cluster_update_route(redisClusterContext *cc) +{ + int ret; + int flag_err_not_set = 1; + cluster_node *node; + dictIterator *it; + dictEntry *de; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(cc->ip != NULL && cc->port > 0) + { + ret = cluster_update_route_by_addr(cc, cc->ip, cc->port); + if(ret == REDIS_OK) + { + return REDIS_OK; + } + + flag_err_not_set = 0; + } + + if(cc->nodes == NULL) + { + if(flag_err_not_set) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no server address"); + } + + return REDIS_ERR; + } + + it = dictGetIterator(cc->nodes); + while ((de = dictNext(it)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL || node->host == NULL || node->port < 0) + { + continue; + } + + ret = cluster_update_route_by_addr(cc, node->host, node->port); + if(ret == REDIS_OK) + { + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + dictReleaseIterator(it); + return REDIS_OK; + } + + flag_err_not_set = 0; + } + + dictReleaseIterator(it); + + if(flag_err_not_set) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no valid server address"); + } + + return REDIS_ERR; +} + +static void print_cluster_node_list(redisClusterContext *cc) +{ + dictIterator *di = NULL; + dictEntry *de; + listIter *it; + listNode *ln; + cluster_node *master, *slave; + hilist *slaves; + + if(cc == NULL) + { + return; + } + + di = dictGetIterator(cc->nodes); + + printf("name\taddress\trole\tslaves\n"); + + while((de = dictNext(di)) != NULL) { + master = dictGetEntryVal(de); + + printf("%s\t%s\t%d\t%s\n",master->name, master->addr, + master->role, master->slaves?"hava":"null"); + + slaves = master->slaves; + if(slaves == NULL) + { + continue; + } + + it = listGetIterator(slaves, AL_START_HEAD); + while((ln = listNext(it)) != NULL) + { + slave = listNodeValue(ln); + printf("%s\t%s\t%d\t%s\n",slave->name, slave->addr, + slave->role, slave->slaves?"hava":"null"); + } + + listReleaseIterator(it); + + printf("\n"); + } +} + + +int test_cluster_update_route(redisClusterContext *cc) +{ + int ret; + + ret = cluster_update_route(cc); + + //print_cluster_node_list(cc); + + return ret; +} + +static redisClusterContext *redisClusterContextInit(void) { + redisClusterContext *cc; + + cc = calloc(1,sizeof(redisClusterContext)); + if (cc == NULL) + return NULL; + + cc->err = 0; + cc->errstr[0] = '\0'; + cc->ip = NULL; + cc->port = 0; + cc->flags = 0; + cc->timeout = NULL; + cc->nodes = NULL; + cc->slots = NULL; + cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT; + cc->retry_count = 0; + cc->requests = NULL; + cc->need_update_route = 0; + cc->update_route_time = 0LL; + + cc->route_version = 0LL; + + memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + return cc; +} + +void redisClusterFree(redisClusterContext *cc) { + + if (cc == NULL) + return; + + if(cc->ip) + { + sdsfree(cc->ip); + cc->ip = NULL; + } + + if (cc->timeout) + { + free(cc->timeout); + } + + memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + if(cc->slots != NULL) + { + cc->slots->nelem = 0; + hiarray_destroy(cc->slots); + cc->slots = NULL; + } + + if(cc->nodes != NULL) + { + dictRelease(cc->nodes); + } + + if(cc->requests != NULL) + { + listRelease(cc->requests); + } + + free(cc); +} + +static int redisClusterAddNode(redisClusterContext *cc, const char *addr) +{ + dictEntry *node_entry; + cluster_node *node; + sds *ip_port = NULL; + int ip_port_count = 0; + sds ip; + int port; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(cc->nodes == NULL) + { + cc->nodes = dictCreate(&clusterNodesDictType, NULL); + if(cc->nodes == NULL) + { + return REDIS_ERR; + } + } + + node_entry = dictFind(cc->nodes, addr); + if(node_entry == NULL) + { + ip_port = sdssplitlen(addr, strlen(addr), + IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &ip_port_count); + if(ip_port == NULL || ip_port_count != 2 || + sdslen(ip_port[0]) <= 0 || sdslen(ip_port[1]) <= 0) + { + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, ip_port_count); + } + __redisClusterSetError(cc,REDIS_ERR_OTHER,"server address is error(correct is like: 127.0.0.1:1234)"); + return REDIS_ERR; + } + + ip = ip_port[0]; + port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + + if(port <= 0) + { + sdsfreesplitres(ip_port, ip_port_count); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"server port is error"); + return REDIS_ERR; + } + + sdsfree(ip_port[1]); + free(ip_port); + ip_port = NULL; + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + sdsfree(ip); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"alloc cluster node error"); + return REDIS_ERR; + } + + cluster_node_init(node); + + node->addr = sdsnew(addr); + if(node->addr == NULL) + { + sdsfree(ip); + hi_free(node); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"new node address error"); + return REDIS_ERR; + } + + node->host = ip; + node->port = port; + + dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); + } + + return REDIS_OK; +} + + +/* Connect to a Redis cluster. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const char *addrs) { + + int ret; + sds *address = NULL; + int address_count = 0; + int i; + + if(cc == NULL) + { + return NULL; + } + + + address = sdssplitlen(addrs, strlen(addrs), CLUSTER_ADDRESS_SEPARATOR, + strlen(CLUSTER_ADDRESS_SEPARATOR), &address_count); + if(address == NULL || address_count <= 0) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address is error(correct is like: 127.0.0.1:1234,127.0.0.2:5678)"); + return cc; + } + + for(i = 0; i < address_count; i ++) + { + ret = redisClusterAddNode(cc, address[i]); + if(ret != REDIS_OK) + { + sdsfreesplitres(address, address_count); + return cc; + } + } + + sdsfreesplitres(address, address_count); + + cluster_update_route(cc); + + return cc; +} + +redisClusterContext *redisClusterConnect(const char *addrs, int flags) +{ + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags |= REDIS_BLOCK; + if(flags) + { + cc->flags |= flags; + } + + return _redisClusterConnect(cc, addrs); +} + +redisClusterContext *redisClusterConnectWithTimeout( + const char *addrs, const struct timeval tv, int flags) +{ + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags |= REDIS_BLOCK; + if(flags) + { + cc->flags |= flags; + } + + if (cc->timeout == NULL) + { + cc->timeout = malloc(sizeof(struct timeval)); + } + + memcpy(cc->timeout, &tv, sizeof(struct timeval)); + + return _redisClusterConnect(cc, addrs); +} + +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { + + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags &= ~REDIS_BLOCK; + if(flags) + { + cc->flags |= flags; + } + + return _redisClusterConnect(cc, addrs); +} + +redisContext *ctx_get_by_node(cluster_node *node, + const struct timeval *timeout, int flags) +{ + redisContext *c = NULL; + if(node == NULL) + { + return NULL; + } + + c = node->con; + if(c != NULL) + { + if(c->err) + { + redisReconnect(c); + } + + return c; + } + + if(node->host == NULL || node->port <= 0) + { + return NULL; + } + + if(flags & REDIS_BLOCK) + { + if(timeout) + { + c = redisConnectWithTimeout(node->host, node->port, *timeout); + } + else + { + c = redisConnect(node->host, node->port); + } + } + else + { + c = redisConnectNonBlock(node->host, node->port); + } + + node->con = c; + + return c; +} + +static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num) +{ + struct hiarray *slots; + uint32_t slot_count; + cluster_slot **slot; + uint32_t middle, start, end; + uint8_t stop = 0; + + if(cc == NULL) + { + return NULL; + } + + if(slot_num >= REDIS_CLUSTER_SLOTS) + { + return NULL; + } + + slots = cc->slots; + if(slots == NULL) + { + return NULL; + } + slot_count = hiarray_n(slots); + + start = 0; + end = slot_count - 1; + middle = 0; + + do{ + if(start >= end) + { + stop = 1; + middle = end; + } + else + { + middle = start + (end - start)/2; + } + + ASSERT(middle < slot_count); + + slot = hiarray_get(slots, middle); + if((*slot)->start > slot_num) + { + end = middle - 1; + } + else if((*slot)->end < slot_num) + { + start = middle + 1; + } + else + { + return (*slot)->node; + } + + + }while(!stop); + + printf("slot_num : %d\n", slot_num); + printf("slot_count : %d\n", slot_count); + printf("start : %d\n", start); + printf("end : %d\n", end); + printf("middle : %d\n", middle); + + return NULL; +} + + +static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num) +{ + if(cc == NULL) + { + return NULL; + } + + if(slot_num >= REDIS_CLUSTER_SLOTS) + { + return NULL; + } + + return cc->table[slot_num]; + +} + +static cluster_node *node_get_witch_connected(redisClusterContext *cc) +{ + dictIterator *di; + dictEntry *de; + struct cluster_node *node; + redisContext *c = NULL; + redisReply *reply = NULL; + + if(cc == NULL || cc->nodes == NULL) + { + return NULL; + } + + di = dictGetIterator(cc->nodes); + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL) + { + continue; + } + + c = ctx_get_by_node(node, cc->timeout, REDIS_BLOCK); + if(c == NULL || c->err) + { + continue; + } + + reply = redisCommand(c, REDIS_COMMAND_PING); + if(reply != NULL && reply->type == REDIS_REPLY_STATUS && + reply->str != NULL && strcmp(reply->str, "PONG") == 0) + { + freeReplyObject(reply); + reply = NULL; + + dictReleaseIterator(di); + + return node; + } + else if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } + } + + dictReleaseIterator(di); + + return NULL; +} + +static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) +{ + struct cmd *command = NULL; + struct keypos *kp; + int key_count; + uint32_t i; + int slot_num = -1; + + if(cc == NULL || cmd == NULL || len <= 0) + { + goto done; + } + + command = command_get(); + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto done; + } + + command->cmd = cmd; + command->clen = len; + redis_parse_cmd(command); + if(command->result != CMD_PARSE_OK) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); + goto done; + } + + key_count = hiarray_n(command->keys); + + if(key_count <= 0) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no keys in command(must have keys for redis cluster mode)"); + goto done; + } + else if(key_count == 1) + { + kp = hiarray_get(command->keys, 0); + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + + goto done; + } + + for(i = 0; i < hiarray_n(command->keys); i ++) + { + kp = hiarray_get(command->keys, i); + + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + } + +done: + + if(command != NULL) + { + command->cmd = NULL; + command_destroy(command); + } + + return slot_num; +} + +/* Get the cluster config from one node. + * Return value: config_value string must free by usr. + */ +static char * cluster_config_get(redisClusterContext *cc, + const char *config_name, int *config_value_len) +{ + redisContext *c; + cluster_node *node; + redisReply *reply = NULL, *sub_reply; + char *config_value = NULL; + + if(cc == NULL || config_name == NULL + || config_value_len == NULL) + { + return NULL; + } + + node = node_get_witch_connected(cc); + if(node == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OTHER, "no reachable node in cluster"); + goto error; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + + reply = redisCommand(c, "config get %s", config_name); + if(reply == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OTHER, "reply for config get is null"); + goto error; + } + + if(reply->type != REDIS_REPLY_ARRAY) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get type is not array"); + goto error; + } + + if(reply->elements != 2) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get elements number is not 2"); + goto error; + } + + sub_reply = reply->element[0]; + if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get config name is not string"); + goto error; + } + + if(strcmp(sub_reply->str, config_name)) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get config name is not we want"); + goto error; + } + + sub_reply = reply->element[1]; + if(sub_reply == NULL || sub_reply->type != REDIS_REPLY_STRING) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get config value type is not string"); + goto error; + } + + config_value = sub_reply->str; + *config_value_len = sub_reply->len; + sub_reply->str= NULL; + + if(reply != NULL) + { + freeReplyObject(reply); + } + + return config_value; + +error: + + if(reply != NULL) + { + freeReplyObject(reply); + } + + return NULL; +} + +/* Helper function for the redisClusterAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +static int __redisClusterAppendCommand(redisClusterContext *cc, + struct cmd *command) { + + cluster_node *node; + redisContext *c = NULL; + + if(cc == NULL || command == NULL) + { + return REDIS_ERR; + } + + node = node_get_by_table(cc, (uint32_t)command->slot_num); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error"); + return REDIS_ERR; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); + return REDIS_ERR; + } + else if(c->err) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + return REDIS_OK; +} + +/* Helper function for the redisClusterGetReply* family of functions. + */ +static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) +{ + cluster_node *node; + redisContext *c; + + if(cc == NULL || slot_num < 0 || reply == NULL) + { + return REDIS_ERR; + } + + node = node_get_by_table(cc, (uint32_t)slot_num); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table is null"); + return REDIS_ERR; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + else if(c->err) + { + if(cc->need_update_route == 0) + { + cc->retry_count ++; + if(cc->retry_count > cc->max_redirect_count) + { + cc->need_update_route = 1; + cc->retry_count = 0; + } + } + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + if(redisGetReply(c, reply) != REDIS_OK) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED) + { + cc->need_update_route = 1; + } + + return REDIS_OK; +} + +static cluster_node *node_get_by_ask_error_reply( + redisClusterContext *cc, redisReply *reply) +{ + sds *part = NULL, *ip_port = NULL; + int part_len = 0, ip_port_len; + dictEntry *de; + cluster_node *node = NULL; + + if(cc == NULL || reply == NULL) + { + return NULL; + } + + if(cluster_reply_error_type(reply) != CLUSTER_ERR_ASK) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply is not ask error!"); + return NULL; + } + + part = sdssplitlen(reply->str, reply->len, " ", 1, &part_len); + + if(part != NULL && part_len == 3) + { + ip_port = sdssplitlen(part[2], sdslen(part[2]), + ":", 1, &ip_port_len); + + if(ip_port != NULL && ip_port_len == 2) + { + de = dictFind(cc->nodes, part[2]); + if(de == NULL) + { + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + __redisClusterSetError(cc, + REDIS_ERR_OOM, "Out of memory"); + + goto done; + } + + cluster_node_init(node); + node->addr = part[1]; + node->host = ip_port[0]; + node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + node->role = REDIS_ROLE_MASTER; + + dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); + + part = NULL; + ip_port = NULL; + } + else + { + node = de->val; + + goto done; + } + } + else + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "ask error reply address part parse error!"); + + goto done; + } + + } + else + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "ask error reply parse error!"); + + goto done; + } + +done: + + if(part != NULL) + { + sdsfreesplitres(part, part_len); + part = NULL; + } + + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, ip_port_len); + ip_port = NULL; + } + + return node; +} + +static void *redis_cluster_command_execute(redisClusterContext *cc, + struct cmd *command) +{ + int ret; + void *reply = NULL; + cluster_node *node; + redisContext *c = NULL; + int error_type; + +retry: + + node = node_get_by_table(cc, (uint32_t)command->slot_num); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error"); + return NULL; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); + return NULL; + } + else if(c->err) + { + node = node_get_witch_connected(cc); + if(node == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "no reachable node in cluster"); + return NULL; + } + + cc->retry_count ++; + if(cc->retry_count > cc->max_redirect_count) + { + __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, + "too many cluster redirect"); + return NULL; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); + return NULL; + } + else if(c->err) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + } + +ask_retry: + + if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + reply = __redisBlockForReply(c); + if(reply == NULL) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + error_type = cluster_reply_error_type(reply); + if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) + { + cc->retry_count ++; + if(cc->retry_count > cc->max_redirect_count) + { + __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, + "too many cluster redirect"); + freeReplyObject(reply); + return NULL; + } + + switch(error_type) + { + case CLUSTER_ERR_MOVED: + freeReplyObject(reply); + reply = NULL; + ret = cluster_update_route(cc); + if(ret != REDIS_OK) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + return NULL; + } + + goto retry; + + break; + case CLUSTER_ERR_ASK: + node = node_get_by_ask_error_reply(cc, reply); + if(node == NULL) + { + freeReplyObject(reply); + return NULL; + } + + freeReplyObject(reply); + reply = NULL; + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); + return NULL; + } + else if(c->err) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + reply = redisCommand(c, REDIS_COMMAND_ASKING); + if(reply == NULL) + { + __redisClusterSetError(cc, c->err, c->errstr); + return NULL; + } + + freeReplyObject(reply); + reply = NULL; + + goto ask_retry; + + break; + case CLUSTER_ERR_TRYAGAIN: + case CLUSTER_ERR_CROSSSLOT: + case CLUSTER_ERR_CLUSTERDOWN: + freeReplyObject(reply); + reply = NULL; + goto retry; + + break; + default: + + break; + } + } + + return reply; +} + +static int command_pre_fragment(redisClusterContext *cc, + struct cmd *command, hilist *commands) +{ + + struct keypos *kp, *sub_kp; + uint32_t key_count; + uint32_t i, j; + uint32_t idx; + uint32_t key_len; + int slot_num = -1; + struct cmd *sub_command; + struct cmd **sub_commands = NULL; + char num_str[12]; + uint8_t num_str_len; + + + if(command == NULL || commands == NULL) + { + goto done; + } + + key_count = hiarray_n(command->keys); + + sub_commands = hi_zalloc(REDIS_CLUSTER_SLOTS * sizeof(*sub_commands)); + if (sub_commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto done; + } + + command->frag_seq = hi_alloc(key_count * sizeof(*command->frag_seq)); + if(command->frag_seq == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto done; + } + + + for(i = 0; i < key_count; i ++) + { + kp = hiarray_get(command->keys, i); + + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + + if(slot_num < 0 || slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"keyHashSlot return error"); + goto done; + } + + if (sub_commands[slot_num] == NULL) { + sub_commands[slot_num] = command_get(); + if (sub_commands[slot_num] == NULL) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + } + + command->frag_seq[i] = sub_command = sub_commands[slot_num]; + + sub_command->narg++; + + sub_kp = hiarray_push(sub_command->keys); + if (sub_kp == NULL) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_kp->start = kp->start; + sub_kp->end = kp->end; + + key_len = (uint32_t)(kp->end - kp->start); + + sub_command->clen += key_len + uint_len(key_len); + + sub_command->slot_num = slot_num; + + if (command->type == CMD_REQ_REDIS_MSET) { + uint32_t len = 0; + char *p; + + for (p = sub_kp->end + 1; !isdigit(*p); p++){} + + p = sub_kp->end + 1; + while(!isdigit(*p)) + { + p ++; + } + + for (; isdigit(*p); p++) { + len = len * 10 + (uint32_t)(*p - '0'); + } + + len += CRLF_LEN * 2; + len += (p - sub_kp->end); + sub_kp->remain_len = len; + sub_command->clen += len; + } + } + + for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ + sub_command = sub_commands[i]; + if (sub_command == NULL) { + continue; + } + + idx = 0; + if (command->type == CMD_REQ_REDIS_MGET) { + //"*%d\r\n$4\r\nmget\r\n" + + sub_command->clen += 5*sub_command->narg; + + sub_command->narg ++; + + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)(strlen(num_str)); + + sub_command->clen += 13 + num_str_len; + + sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); + if(sub_command->cmd == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_command->cmd[idx++] = '*'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, "\r\n$4\r\nmget\r\n", 12); + idx += 12; + + for(j = 0; j < hiarray_n(sub_command->keys); j ++) + { + kp = hiarray_get(sub_command->keys, j); + key_len = (uint32_t)(kp->end - kp->start); + hi_itoa(num_str, key_len); + num_str_len = strlen(num_str); + + sub_command->cmd[idx++] = '$'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + memcpy(sub_command->cmd + idx, kp->start, key_len); + idx += key_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + } + } else if (command->type == CMD_REQ_REDIS_DEL) { + //"*%d\r\n$3\r\ndel\r\n" + + sub_command->clen += 5*sub_command->narg; + + sub_command->narg ++; + + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)strlen(num_str); + + sub_command->clen += 12 + num_str_len; + + sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); + if(sub_command->cmd == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_command->cmd[idx++] = '*'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, "\r\n$3\r\ndel\r\n", 11); + idx += 11; + + for(j = 0; j < hiarray_n(sub_command->keys); j ++) + { + kp = hiarray_get(sub_command->keys, j); + key_len = (uint32_t)(kp->end - kp->start); + hi_itoa(num_str, key_len); + num_str_len = strlen(num_str); + + sub_command->cmd[idx++] = '$'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + memcpy(sub_command->cmd + idx, kp->start, key_len); + idx += key_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + } + } else if (command->type == CMD_REQ_REDIS_MSET) { + //"*%d\r\n$4\r\nmset\r\n" + + sub_command->clen += 3*sub_command->narg; + + sub_command->narg *= 2; + + sub_command->narg ++; + + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)strlen(num_str); + + sub_command->clen += 13 + num_str_len; + + sub_command->cmd = hi_zalloc(sub_command->clen * sizeof(*sub_command->cmd)); + if(sub_command->cmd == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + slot_num = -1; + goto done; + } + + sub_command->cmd[idx++] = '*'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, "\r\n$4\r\nmset\r\n", 12); + idx += 12; + + for(j = 0; j < hiarray_n(sub_command->keys); j ++) + { + kp = hiarray_get(sub_command->keys, j); + key_len = (uint32_t)(kp->end - kp->start); + hi_itoa(num_str, key_len); + num_str_len = strlen(num_str); + + sub_command->cmd[idx++] = '$'; + memcpy(sub_command->cmd + idx, num_str, num_str_len); + idx += num_str_len; + memcpy(sub_command->cmd + idx, CRLF, CRLF_LEN); + idx += CRLF_LEN; + memcpy(sub_command->cmd + idx, kp->start, key_len + kp->remain_len); + idx += key_len + kp->remain_len; + + } + } else { + NOT_REACHED(); + } + + //printf("len : %d\n", sub_command->clen); + //print_string_with_length_fix_CRLF(sub_command->cmd, sub_command->clen); + + sub_command->type = command->type; + + listAddNodeTail(commands, sub_command); + } + +done: + + if(sub_commands != NULL) + { + hi_free(sub_commands); + } + + if(slot_num >= 0 && commands != NULL + && listLength(commands) == 1) + { + listNode *list_node = listFirst(commands); + listDelNode(commands, list_node); + if(command->frag_seq) + { + hi_free(command->frag_seq); + command->frag_seq = NULL; + } + + command->slot_num = slot_num; + } + + return slot_num; +} + +static void *command_post_fragment(redisClusterContext *cc, + struct cmd *command, hilist *commands) +{ + struct cmd *sub_command; + listNode *list_node; + listIter *list_iter; + redisReply *reply, *sub_reply; + long long count = 0; + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_node = listNext(list_iter)) != NULL) + { + sub_command = list_node->value; + reply = sub_command->reply; + if(reply == NULL) + { + return NULL; + } + else if(reply->type == REDIS_REPLY_ERROR) + { + return reply; + } + + if (command->type == CMD_REQ_REDIS_MGET) { + if(reply->type != REDIS_REPLY_ARRAY) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be array)"); + return NULL; + } + }else if(command->type == CMD_REQ_REDIS_DEL){ + if(reply->type != REDIS_REPLY_INTEGER) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be integer)"); + return NULL; + } + + count += reply->integer; + }else if(command->type == CMD_REQ_REDIS_MSET){ + if(reply->type != REDIS_REPLY_STATUS || + reply->len != 2 || strcmp(reply->str, REDIS_STATUS_OK) != 0) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"reply type is error(here only can be status and ok)"); + return NULL; + } + }else { + NOT_REACHED(); + } + } + + reply = hi_calloc(1,sizeof(*reply)); + + if (reply == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + if (command->type == CMD_REQ_REDIS_MGET) { + int i; + uint32_t key_count; + + reply->type = REDIS_REPLY_ARRAY; + + key_count = hiarray_n(command->keys); + + reply->elements = key_count; + reply->element = hi_calloc(key_count, sizeof(*reply)); + if (reply->element == NULL) { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + for (i = key_count - 1; i >= 0; i--) { /* for each key */ + sub_reply = command->frag_seq[i]->reply; /* get it's reply */ + if (sub_reply == NULL) { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply is null"); + return NULL; + } + + if(sub_reply->type == REDIS_REPLY_STRING) + { + reply->element[i] = sub_reply; + } + else if(sub_reply->type == REDIS_REPLY_ARRAY) + { + if(sub_reply->elements == 0) + { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply elements error"); + return NULL; + } + + reply->element[i] = sub_reply->element[sub_reply->elements - 1]; + sub_reply->elements --; + } + } + }else if(command->type == CMD_REQ_REDIS_DEL){ + reply->type = REDIS_REPLY_INTEGER; + reply->integer = count; + }else if(command->type == CMD_REQ_REDIS_MSET){ + reply->type = REDIS_REPLY_STATUS; + uint32_t str_len = strlen(REDIS_STATUS_OK); + reply->str = hi_alloc((str_len + 1) * sizeof(char*)); + if(reply->str == NULL) + { + freeReplyObject(reply); + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + reply->len = str_len; + memcpy(reply->str, REDIS_STATUS_OK, str_len); + reply->str[str_len] = '\0'; + }else { + NOT_REACHED(); + } + + return reply; +} + +/* + * Split the command into subcommands by slot + * + * Returns slot_num + * If slot_num < 0 or slot_num >= REDIS_CLUSTER_SLOTS means this function runs error; + * Otherwise if the commands > 1 , slot_num is the last subcommand slot number. + */ +static int command_format_by_slot(redisClusterContext *cc, + struct cmd *command, hilist *commands) +{ + struct keypos *kp; + int key_count; + int slot_num = -1; + + if(cc == NULL || commands == NULL || + command == NULL || + command->cmd == NULL || command->clen <= 0) + { + goto done; + } + + + redis_parse_cmd(command); + if(command->result == CMD_PARSE_ENOMEM) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "Parse command error: out of memory"); + goto done; + } + else if(command->result != CMD_PARSE_OK) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, command->errstr); + goto done; + } + + key_count = hiarray_n(command->keys); + + if(key_count <= 0) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "No keys in command(must have keys for redis cluster mode)"); + goto done; + } + else if(key_count == 1) + { + kp = hiarray_get(command->keys, 0); + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + command->slot_num = slot_num; + + goto done; + } + + slot_num = command_pre_fragment(cc, command, commands); + +done: + + return slot_num; +} + + +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count) +{ + if(cc == NULL || max_redirect_count <= 0) + { + return; + } + + cc->max_redirect_count = max_redirect_count; +} + +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) { + redisReply *reply = NULL; + int slot_num; + struct cmd *command = NULL, *sub_command; + hilist *commands = NULL; + listNode *list_node; + listIter *list_iter = NULL; + + if(cc == NULL) + { + return NULL; + } + + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + command = command_get(); + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + command->cmd = cmd; + command->clen = len; + + commands = listCreate(); + if(commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + commands->free = listCommandFree; + + slot_num = command_format_by_slot(cc, command, commands); + + if(slot_num < 0) + { + goto error; + } + else if(slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); + goto error; + } + + //all keys belong to one slot + if(listLength(commands) == 0) + { + reply = redis_cluster_command_execute(cc, command); + goto done; + } + + ASSERT(listLength(commands) != 1); + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_node = listNext(list_iter)) != NULL) + { + sub_command = list_node->value; + + reply = redis_cluster_command_execute(cc, sub_command); + if(reply == NULL) + { + goto error; + } + else if(reply->type == REDIS_REPLY_ERROR) + { + goto done; + } + + sub_command->reply = reply; + } + + reply = command_post_fragment(cc, command, commands); + +done: + + command->cmd = NULL; + command_destroy(command); + + if(commands != NULL) + { + listRelease(commands); + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + cc->retry_count = 0; + + return reply; + +error: + + if(command != NULL) + { + command->cmd = NULL; + command_destroy(command); + } + + if(commands != NULL) + { + listRelease(commands); + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + cc->retry_count = 0; + + return NULL; +} + +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) { + redisReply *reply; + char *cmd; + int len; + + if(cc == NULL) + { + return NULL; + } + + len = redisvFormatCommand(&cmd,format,ap); + + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } else if (len == -2) { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + return NULL; + } + + reply = redisClusterFormattedCommand(cc, cmd, len); + + free(cmd); + + return reply; +} + +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { + va_list ap; + redisReply *reply = NULL; + + va_start(ap,format); + reply = redisClustervCommand(cc, format, ap); + va_end(ap); + + return reply; +} + +void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen) { + redisReply *reply = NULL; + char *cmd; + int len; + + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } + + reply = redisClusterFormattedCommand(cc, cmd, len); + + free(cmd); + + return reply; +} + +int redisClusterAppendFormattedCommand(redisClusterContext *cc, + char *cmd, int len) { + int slot_num; + struct cmd *command = NULL, *sub_command; + hilist *commands = NULL; + listNode *list_node; + listIter *list_iter = NULL; + + if(cc->requests == NULL) + { + cc->requests = listCreate(); + if(cc->requests == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cc->requests->free = listCommandFree; + } + + command = command_get(); + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + command->cmd = cmd; + command->clen = len; + + commands = listCreate(); + if(commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + commands->free = listCommandFree; + + slot_num = command_format_by_slot(cc, command, commands); + + if(slot_num < 0) + { + goto error; + } + else if(slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); + goto error; + } + + //all keys belong to one slot + if(listLength(commands) == 0) + { + if(__redisClusterAppendCommand(cc, command) == REDIS_OK) + { + goto done; + } + else + { + goto error; + } + } + + ASSERT(listLength(commands) != 1); + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_node = listNext(list_iter)) != NULL) + { + sub_command = list_node->value; + + if(__redisClusterAppendCommand(cc, sub_command) == REDIS_OK) + { + continue; + } + else + { + goto error; + } + } + +done: + + if(command->cmd != NULL) + { + command->cmd = NULL; + } + else + { + goto error; + } + + if(commands != NULL) + { + if(listLength(commands) > 0) + { + command->sub_commands = commands; + } + else + { + listRelease(commands); + } + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + listAddNodeTail(cc->requests, command); + + return REDIS_OK; + +error: + + if(command != NULL) + { + command->cmd = NULL; + command_destroy(command); + } + + if(commands != NULL) + { + listRelease(commands); + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + /* Attention: mybe here we must pop the + sub_commands that had append to the nodes. + But now we do not handle it. */ + + return REDIS_ERR; +} + + +int redisClustervAppendCommand(redisClusterContext *cc, + const char *format, va_list ap) { + int ret; + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + ret = redisClusterAppendFormattedCommand(cc, cmd, len); + + free(cmd); + + return ret; +} + +int redisClusterAppendCommand(redisClusterContext *cc, + const char *format, ...) { + + int ret; + va_list ap; + + if(cc == NULL || format == NULL) + { + return REDIS_ERR; + } + + va_start(ap,format); + ret = redisClustervAppendCommand(cc, format, ap); + va_end(ap); + + return ret; +} + +int redisClusterAppendCommandArgv(redisClusterContext *cc, + int argc, const char **argv, const size_t *argvlen) { + int ret; + char *cmd; + int len; + + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + ret = redisClusterAppendFormattedCommand(cc, cmd, len); + + free(cmd); + + return ret; +} + +static int redisCLusterSendAll(redisClusterContext *cc) +{ + dictIterator *di; + dictEntry *de; + struct cluster_node *node; + redisContext *c = NULL; + int wdone = 0; + + if(cc == NULL || cc->nodes == NULL) + { + return REDIS_ERR; + } + + di = dictGetIterator(cc->nodes); + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL) + { + continue; + } + + c = ctx_get_by_node(node, cc->timeout, cc->flags); + if(c == NULL) + { + continue; + } + + if (c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + { + dictReleaseIterator(di); + return REDIS_ERR; + } + } while (!wdone); + } + } + + dictReleaseIterator(di); + + return REDIS_OK; +} + +static int redisCLusterClearAll(redisClusterContext *cc) +{ + dictIterator *di; + dictEntry *de; + struct cluster_node *node; + redisContext *c = NULL; + + if (cc == NULL) { + return REDIS_ERR; + } + + if (cc->err) { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + if (cc->nodes == NULL) { + return REDIS_ERR; + } + di = dictGetIterator(cc->nodes); + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + if(node == NULL) + { + continue; + } + + c = node->con; + if(c == NULL) + { + continue; + } + + redisFree(c); + node->con = NULL; + } + + dictReleaseIterator(di); + + return REDIS_OK; +} + +int redisClusterGetReply(redisClusterContext *cc, void **reply) { + + struct cmd *command, *sub_command; + hilist *commands = NULL; + listNode *list_command, *list_sub_command; + listIter *list_iter; + int slot_num; + void *sub_reply; + + if(cc == NULL || reply == NULL) + return REDIS_ERR; + + cc->err = 0; + cc->errstr[0] = '\0'; + + *reply = NULL; + + if (cc->requests == NULL) + return REDIS_ERR; + + list_command = listFirst(cc->requests); + + //no more reply + if(list_command == NULL) + { + *reply = NULL; + return REDIS_OK; + } + + command = list_command->value; + if(command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command in the requests list is null"); + goto error; + } + + slot_num = command->slot_num; + if(slot_num >= 0) + { + listDelNode(cc->requests, list_command); + return __redisClusterGetReply(cc, slot_num, reply); + } + + commands = command->sub_commands; + if(commands == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "sub_commands in command is null"); + goto error; + } + + ASSERT(listLength(commands) != 1); + + list_iter = listGetIterator(commands, AL_START_HEAD); + while((list_sub_command = listNext(list_iter)) != NULL) + { + sub_command = list_sub_command->value; + if(sub_command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "sub_command is null"); + goto error; + } + + slot_num = sub_command->slot_num; + if(slot_num < 0) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "sub_command slot_num is less then zero"); + goto error; + } + + if(__redisClusterGetReply(cc, slot_num, &sub_reply) != REDIS_OK) + { + goto error; + } + + sub_command->reply = sub_reply; + } + + *reply = command_post_fragment(cc, command, commands); + if(*reply == NULL) + { + goto error; + } + + listDelNode(cc->requests, list_command); + return REDIS_OK; + +error: + + listDelNode(cc->requests, list_command); + return REDIS_ERR; +} + +void redisClusterReset(redisClusterContext *cc) +{ + int status; + void *reply; + + if(cc == NULL || cc->nodes == NULL) + { + return; + } + + if (cc->err) { + redisCLusterClearAll(cc); + } else { + redisCLusterSendAll(cc); + + do { + status = redisClusterGetReply(cc, &reply); + if (status == REDIS_OK) { + freeReplyObject(reply); + } else { + redisCLusterClearAll(cc); + break; + } + } while(reply != NULL); + } + + if(cc->requests) + { + listRelease(cc->requests); + cc->requests = NULL; + } + + if(cc->need_update_route) + { + status = cluster_update_route(cc); + if(status != REDIS_OK) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + return; + } + cc->need_update_route = 0; + } +} + +/*############redis cluster async############*/ + +/* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ +static void __redisClusterAsyncCopyError(redisClusterAsyncContext *acc) { + if (!acc) + return; + + redisClusterContext *cc = acc->cc; + acc->err = cc->err; + memcpy(acc->errstr, cc->errstr, 128); +} + +static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, + int type, const char *str) { + + size_t len; + + acc->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(acc->errstr)-1) ? len : (sizeof(acc->errstr)-1); + memcpy(acc->errstr,str,len); + acc->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + __redis_strerror_r(errno, acc->errstr, sizeof(acc->errstr)); + } +} + +static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) { + redisClusterAsyncContext *acc; + + if(cc == NULL) + { + return NULL; + } + + acc = hi_alloc(sizeof(redisClusterAsyncContext)); + if (acc == NULL) + return NULL; + + acc->cc = cc; + + acc->err = 0; + acc->data = NULL; + acc->adapter = NULL; + acc->attach_fn = NULL; + + acc->onConnect = NULL; + acc->onDisconnect = NULL; + + return acc; +} + +static cluster_async_data *cluster_async_data_get(void) +{ + cluster_async_data *cad; + + cad = hi_alloc(sizeof(cluster_async_data)); + if(cad == NULL) + { + return NULL; + } + + cad->acc = NULL; + cad->command = NULL; + cad->callback = NULL; + cad->privdata = NULL; + cad->retry_count = 0; + + return cad; +} + +static void cluster_async_data_free(cluster_async_data *cad) +{ + if(cad == NULL) + { + return; + } + + if(cad->command != NULL) + { + command_destroy(cad->command); + } + + hi_free(cad); + cad = NULL; +} + +static void unlinkAsyncContextAndNode(redisAsyncContext* ac) +{ + cluster_node *node; + + if (ac->data) { + node = (cluster_node *)(ac->data); + node->acon = NULL; + } +} + +redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, + cluster_node *node) +{ + redisAsyncContext *ac; + + if(node == NULL) + { + return NULL; + } + + ac = node->acon; + if(ac != NULL) + { + if (ac->c.err == 0) { + return ac; + } else { + NOT_REACHED(); + } + } + + if(node->host == NULL || node->port <= 0) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); + return NULL; + } + + ac = redisAsyncConnect(node->host, node->port); + if(ac == NULL) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); + return NULL; + } + + if(acc->adapter) + { + acc->attach_fn(ac, acc->adapter); + } + + if(acc->onConnect) + { + redisAsyncSetConnectCallback(ac, acc->onConnect); + } + + if(acc->onDisconnect) + { + redisAsyncSetDisconnectCallback(ac, acc->onDisconnect); + } + + ac->data = node; + ac->dataHandler = unlinkAsyncContextAndNode; + node->acon = ac; + + return ac; +} + +static redisAsyncContext *actx_get_after_update_route_by_slot( + redisClusterAsyncContext *acc, int slot_num) +{ + int ret; + redisClusterContext *cc; + redisAsyncContext *ac; + cluster_node *node; + + if(acc == NULL || slot_num < 0) + { + return NULL; + } + + cc = acc->cc; + if(cc == NULL) + { + return NULL; + } + + ret = cluster_update_route(cc); + if(ret != REDIS_OK) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + return NULL; + } + + node = node_get_by_table(cc, (uint32_t)slot_num); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "node get by table error"); + return NULL; + } + + ac = actx_get_by_node(acc, node); + if(ac == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "actx get by node error"); + return NULL; + } + else if(ac->err) + { + __redisClusterAsyncSetError(acc, ac->err, ac->errstr); + return NULL; + } + + return ac; +} + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) { + + redisClusterContext *cc; + redisClusterAsyncContext *acc; + + cc = redisClusterConnectNonBlock(addrs, flags); + if(cc == NULL) + { + return NULL; + } + + acc = redisClusterAsyncInitialize(cc); + if (acc == NULL) { + redisClusterFree(cc); + return NULL; + } + + __redisClusterAsyncCopyError(acc); + + return acc; +} + + +int redisClusterAsyncSetConnectCallback( + redisClusterAsyncContext *acc, redisConnectCallback *fn) +{ + if (acc->onConnect == NULL) { + acc->onConnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +int redisClusterAsyncSetDisconnectCallback( + redisClusterAsyncContext *acc, redisDisconnectCallback *fn) +{ + if (acc->onDisconnect == NULL) { + acc->onDisconnect = fn; + return REDIS_OK; + } + return REDIS_ERR; +} + +static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *privdata) { + int ret; + redisReply *reply = r; + cluster_async_data *cad = privdata; + redisClusterAsyncContext *acc; + redisClusterContext *cc; + redisAsyncContext *ac_retry = NULL; + int error_type; + cluster_node *node; + struct cmd *command; + int64_t now, next; + + if(cad == NULL) + { + goto error; + } + + acc = cad->acc; + if(acc == NULL) + { + goto error; + } + + cc = acc->cc; + if(cc == NULL) + { + goto error; + } + + command = cad->command; + if(command == NULL) + { + goto error; + } + + if(reply == NULL) + { + //Note: + //I can't decide witch is the best way to deal with connect + //problem for hiredis cluster async api. + //But now the way is : when enough null reply for a node, + //we will update the route after the cluster node timeout. + //If you have a better idea, please contact with me. Thank you. + //My email: diguo58@gmail.com + + node = (cluster_node *)(ac->data); + ASSERT(node != NULL); + + __redisClusterAsyncSetError(acc, + ac->err, ac->errstr); + + if(cc->update_route_time != 0) + { + now = hi_usec_now(); + if(now >= cc->update_route_time) + { + ret = cluster_update_route(cc); + if(ret != REDIS_OK) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + } + + cc->update_route_time = 0LL; + } + + goto done; + } + + node->failure_count ++; + if(node->failure_count > cc->max_redirect_count) + { + char *cluster_timeout_str; + int cluster_timeout_str_len; + int cluster_timeout; + + node->failure_count = 0; + if(cc->update_route_time != 0) + { + goto done; + } + + cluster_timeout_str = cluster_config_get(cc, + "cluster-node-timeout", &cluster_timeout_str_len); + if(cluster_timeout_str == NULL) + { + __redisClusterAsyncSetError(acc, + cc->err, cc->errstr); + goto done; + } + + cluster_timeout = hi_atoi(cluster_timeout_str, + cluster_timeout_str_len); + free(cluster_timeout_str); + if(cluster_timeout <= 0) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, + "cluster_timeout_str convert to integer error"); + goto done; + } + + now = hi_usec_now(); + if (now < 0) { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, + "get now usec time error"); + goto done; + } + + next = now + (cluster_timeout * 1000LL); + + cc->update_route_time = next; + + } + + goto done; + } + + error_type = cluster_reply_error_type(reply); + + if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) + { + cad->retry_count ++; + if(cad->retry_count > cc->max_redirect_count) + { + cad->retry_count = 0; + __redisClusterAsyncSetError(acc, + REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, + "too many cluster redirect"); + goto done; + } + + switch(error_type) + { + case CLUSTER_ERR_MOVED: + ac_retry = actx_get_after_update_route_by_slot(acc, command->slot_num); + if(ac_retry == NULL) + { + goto done; + } + + break; + case CLUSTER_ERR_ASK: + node = node_get_by_ask_error_reply(cc, reply); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + cc->err, cc->errstr); + goto done; + } + + ac_retry = actx_get_by_node(acc, node); + if(ac_retry == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "actx get by node error"); + goto done; + } + else if(ac_retry->err) + { + __redisClusterAsyncSetError(acc, + ac_retry->err, ac_retry->errstr); + goto done; + } + + ret = redisAsyncCommand(ac_retry, + NULL,NULL,REDIS_COMMAND_ASKING); + if(ret != REDIS_OK) + { + goto error; + } + + break; + case CLUSTER_ERR_TRYAGAIN: + case CLUSTER_ERR_CROSSSLOT: + case CLUSTER_ERR_CLUSTERDOWN: + ac_retry = ac; + + break; + default: + + goto done; + break; + } + + goto retry; + } + +done: + + if(acc->err) + { + cad->callback(acc, NULL, cad->privdata); + } + else + { + cad->callback(acc, r, cad->privdata); + } + + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + if(acc->err) + { + acc->err = 0; + memset(acc->errstr, '\0', strlen(acc->errstr)); + } + + if(cad != NULL) + { + cluster_async_data_free(cad); + } + + return; + +retry: + + ret = redisAsyncFormattedCommand(ac_retry, + redisClusterAsyncCallback,cad,command->cmd,command->clen); + if(ret != REDIS_OK) + { + goto error; + } + + return; + +error: + + if(cad != NULL) + { + cluster_async_data_free(cad); + } +} + +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) { + + redisClusterContext *cc; + int status = REDIS_OK; + int slot_num; + cluster_node *node; + redisAsyncContext *ac; + struct cmd *command = NULL; + hilist *commands = NULL; + cluster_async_data *cad; + + if(acc == NULL) + { + return REDIS_ERR; + } + + cc = acc->cc; + + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + if(acc->err) + { + acc->err = 0; + memset(acc->errstr, '\0', strlen(acc->errstr)); + } + + command = command_get(); + if(command == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + command->cmd = malloc(len*sizeof(*command->cmd)); + if(command->cmd == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + memcpy(command->cmd, cmd, len); + command->clen = len; + + commands = listCreate(); + if(commands == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + commands->free = listCommandFree; + + slot_num = command_format_by_slot(cc, command, commands); + + if(slot_num < 0) + { + __redisClusterAsyncSetError(acc, + cc->err, cc->errstr); + goto error; + } + else if(slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER,"slot_num is out of range"); + goto error; + } + + //all keys not belong to one slot + if(listLength(commands) > 0) + { + ASSERT(listLength(commands) != 1); + + __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER, + "Asynchronous API now not support multi-key command"); + goto error; + } + + node = node_get_by_table(cc, (uint32_t) slot_num); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "node get by table error"); + goto error; + } + + ac = actx_get_by_node(acc, node); + if(ac == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "actx get by node error"); + goto error; + } + else if(ac->err) + { + __redisClusterAsyncSetError(acc, ac->err, ac->errstr); + goto error; + } + + cad = cluster_async_data_get(); + if(cad == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + cad->acc = acc; + cad->command = command; + cad->callback = fn; + cad->privdata = privdata; + + status = redisAsyncFormattedCommand(ac, + redisClusterAsyncCallback,cad,cmd,len); + if(status != REDIS_OK) + { + goto error; + } + + if(commands != NULL) + { + listRelease(commands); + } + + return REDIS_OK; + +error: + + if(command != NULL) + { + command_destroy(command); + } + + if(commands != NULL) + { + listRelease(commands); + } + + return REDIS_ERR; +} + + +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) { + int ret; + char *cmd; + int len; + + if(acc == NULL) + { + return REDIS_ERR; + } + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); + + free(cmd); + + return ret; +} + +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { + int ret; + va_list ap; + + va_start(ap,format); + ret = redisClustervAsyncCommand(acc, fn, privdata, format, ap); + va_end(ap); + + return ret; +} + +int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { + int ret; + char *cmd; + int len; + + len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + ret = redisClusterAsyncFormattedCommand(acc, fn, privdata, cmd, len); + + free(cmd); + + return ret; +} + +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { + + redisClusterContext *cc; + redisAsyncContext *ac; + dictIterator *di; + dictEntry *de; + dict *nodes; + struct cluster_node *node; + + if(acc == NULL) + { + return; + } + + cc = acc->cc; + + nodes = cc->nodes; + + if(nodes == NULL) + { + return; + } + + di = dictGetIterator(nodes); + + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); + + ac = node->acon; + + if(ac == NULL || ac->err) + { + continue; + } + + redisAsyncDisconnect(ac); + + node->acon = NULL; + } +} + +void redisClusterAsyncFree(redisClusterAsyncContext *acc) +{ + redisClusterContext *cc; + + if(acc == NULL) + { + return; + } + + cc = acc->cc; + + redisClusterFree(cc); + + hi_free(acc); +} + diff --git a/ext/hiredis-vip-0.3.0/hircluster.h b/ext/hiredis-vip-0.3.0/hircluster.h new file mode 100644 index 000000000..5b9c5a358 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hircluster.h @@ -0,0 +1,178 @@ + +#ifndef __HIRCLUSTER_H +#define __HIRCLUSTER_H + +#include "hiredis.h" +#include "async.h" + +#define HIREDIS_VIP_MAJOR 0 +#define HIREDIS_VIP_MINOR 3 +#define HIREDIS_VIP_PATCH 0 + +#define REDIS_CLUSTER_SLOTS 16384 + +#define REDIS_ROLE_NULL 0 +#define REDIS_ROLE_MASTER 1 +#define REDIS_ROLE_SLAVE 2 + + +#define HIRCLUSTER_FLAG_NULL 0x0 +/* The flag to decide whether add slave node in + * redisClusterContext->nodes. This is set in the + * least significant bit of the flags field in + * redisClusterContext. (1000000000000) */ +#define HIRCLUSTER_FLAG_ADD_SLAVE 0x1000 +/* The flag to decide whether add open slot + * for master node. (10000000000000) */ +#define HIRCLUSTER_FLAG_ADD_OPENSLOT 0x2000 +/* The flag to decide whether get the route + * table by 'cluster slots' command. Default + * is 'cluster nodes' command.*/ +#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 + +struct dict; +struct hilist; + +typedef struct cluster_node +{ + sds name; + sds addr; + sds host; + int port; + uint8_t role; + uint8_t myself; /* myself ? */ + redisContext *con; + redisAsyncContext *acon; + struct hilist *slots; + struct hilist *slaves; + int failure_count; + void *data; /* Not used by hiredis */ + struct hiarray *migrating; /* copen_slot[] */ + struct hiarray *importing; /* copen_slot[] */ +}cluster_node; + +typedef struct cluster_slot +{ + uint32_t start; + uint32_t end; + cluster_node *node; /* master that this slot region belong to */ +}cluster_slot; + +typedef struct copen_slot +{ + uint32_t slot_num; /* slot number */ + int migrate; /* migrating or importing? */ + sds remote_name; /* name for the node that this slot migrating to/importing from */ + cluster_node *node; /* master that this slot belong to */ +}copen_slot; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Context for a connection to Redis cluster */ +typedef struct redisClusterContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + sds ip; + int port; + + int flags; + + enum redisConnectionType connection_type; + struct timeval *timeout; + + struct hiarray *slots; + + struct dict *nodes; + cluster_node *table[REDIS_CLUSTER_SLOTS]; + + uint64_t route_version; + + int max_redirect_count; + int retry_count; + + struct hilist *requests; + + int need_update_route; + int64_t update_route_time; +} redisClusterContext; + +redisClusterContext *redisClusterConnect(const char *addrs, int flags); +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, + const struct timeval tv, int flags); +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); + +void redisClusterFree(redisClusterContext *cc); + +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); + +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); + +redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); + +int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); +int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); +int redisClusterGetReply(redisClusterContext *cc, void **reply); +void redisClusterReset(redisClusterContext *cc); + +int cluster_update_route(redisClusterContext *cc); +int test_cluster_update_route(redisClusterContext *cc); +struct dict *parse_cluster_nodes(redisClusterContext *cc, char *str, int str_len, int flags); +struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int flags); + + +/*############redis cluster async############*/ + +struct redisClusterAsyncContext; + +typedef int (adapterAttachFn)(redisAsyncContext*, void*); + +typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*); + +/* Context for an async connection to Redis */ +typedef struct redisClusterAsyncContext { + + redisClusterContext *cc; + + /* Setup error flags so they can be used directly. */ + int err; + char errstr[128]; /* String representation of error when applicable */ + + /* Not used by hiredis */ + void *data; + + void *adapter; + adapterAttachFn *attach_fn; + + /* Called when either the connection is terminated due to an error or per + * user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ + redisDisconnectCallback *onDisconnect; + + /* Called when the first write event was received. */ + redisConnectCallback *onConnect; + +} redisClusterAsyncContext; + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); +int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); +int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +void redisClusterAsyncFree(redisClusterAsyncContext *acc); + +redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiredis.c b/ext/hiredis-vip-0.3.0/hiredis.c new file mode 100644 index 000000000..73d0251bc --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiredis.c @@ -0,0 +1,1021 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" +#include "sds.h" + +static redisReply *createReplyObject(int type); +static void *createStringObject(const redisReadTask *task, char *str, size_t len); +static void *createArrayObject(const redisReadTask *task, int elements); +static void *createIntegerObject(const redisReadTask *task, long long value); +static void *createNilObject(const redisReadTask *task); + +/* Default set of functions to build the reply. Keep in mind that such a + * function returning NULL is interpreted as OOM. */ +static redisReplyObjectFunctions defaultFunctions = { + createStringObject, + createArrayObject, + createIntegerObject, + createNilObject, + freeReplyObject +}; + +/* Create a reply object */ +static redisReply *createReplyObject(int type) { + redisReply *r = calloc(1,sizeof(*r)); + + if (r == NULL) + return NULL; + + r->type = type; + return r; +} + +/* Free a reply object */ +void freeReplyObject(void *reply) { + redisReply *r = reply; + size_t j; + + if (r == NULL) + return; + + switch(r->type) { + case REDIS_REPLY_INTEGER: + break; /* Nothing to free */ + case REDIS_REPLY_ARRAY: + if (r->element != NULL) { + for (j = 0; j < r->elements; j++) + if (r->element[j] != NULL) + freeReplyObject(r->element[j]); + free(r->element); + } + break; + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_STRING: + if (r->str != NULL) + free(r->str); + break; + } + free(r); +} + +static void *createStringObject(const redisReadTask *task, char *str, size_t len) { + redisReply *r, *parent; + char *buf; + + r = createReplyObject(task->type); + if (r == NULL) + return NULL; + + buf = malloc(len+1); + if (buf == NULL) { + freeReplyObject(r); + return NULL; + } + + assert(task->type == REDIS_REPLY_ERROR || + task->type == REDIS_REPLY_STATUS || + task->type == REDIS_REPLY_STRING); + + /* Copy string value */ + memcpy(buf,str,len); + buf[len] = '\0'; + r->str = buf; + r->len = len; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createArrayObject(const redisReadTask *task, int elements) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_ARRAY); + if (r == NULL) + return NULL; + + if (elements > 0) { + r->element = calloc(elements,sizeof(redisReply*)); + if (r->element == NULL) { + freeReplyObject(r); + return NULL; + } + } + + r->elements = elements; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createIntegerObject(const redisReadTask *task, long long value) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_INTEGER); + if (r == NULL) + return NULL; + + r->integer = value; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +static void *createNilObject(const redisReadTask *task) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_NIL); + if (r == NULL) + return NULL; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY); + parent->element[task->idx] = r; + } + return r; +} + +/* Return the number of digits of 'v' when converted to string in radix 10. + * Implementation borrowed from link in redis/src/util.c:string2ll(). */ +static uint32_t countDigits(uint64_t v) { + uint32_t result = 1; + for (;;) { + if (v < 10) return result; + if (v < 100) return result + 1; + if (v < 1000) return result + 2; + if (v < 10000) return result + 3; + v /= 10000U; + result += 4; + } +} + +/* Helper that calculates the bulk length given a certain string length. */ +static size_t bulklen(size_t len) { + return 1+countDigits(len)+2+len+2; +} + +int redisvFormatCommand(char **target, const char *format, va_list ap) { + const char *c = format; + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + sds curarg, newarg; /* current argument */ + int touched = 0; /* was the current argument touched? */ + char **curargv = NULL, **newargv = NULL; + int argc = 0; + int totlen = 0; + int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */ + int j; + + /* Abort if there is not target to set */ + if (target == NULL) + return -1; + + /* Build the command string accordingly to protocol */ + curarg = sdsempty(); + if (curarg == NULL) + return -1; + + while(*c != '\0') { + if (*c != '%' || c[1] == '\0') { + if (*c == ' ') { + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + + /* curarg is put in argv so it can be overwritten. */ + curarg = sdsempty(); + if (curarg == NULL) goto memory_err; + touched = 0; + } + } else { + newarg = sdscatlen(curarg,c,1); + if (newarg == NULL) goto memory_err; + curarg = newarg; + touched = 1; + } + } else { + char *arg; + size_t size; + + /* Set newarg so it can be checked even if it is not touched. */ + newarg = curarg; + + switch(c[1]) { + case 's': + arg = va_arg(ap,char*); + size = strlen(arg); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case 'b': + arg = va_arg(ap,char*); + size = va_arg(ap,size_t); + if (size > 0) + newarg = sdscatlen(curarg,arg,size); + break; + case '%': + newarg = sdscat(curarg,"%"); + break; + default: + /* Try to detect printf format */ + { + static const char intfmts[] = "diouxX"; + static const char flags[] = "#0-+ "; + char _format[16]; + const char *_p = c+1; + size_t _l = 0; + va_list _cpy; + + /* Flags */ + while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++; + + /* Field width */ + while (*_p != '\0' && isdigit(*_p)) _p++; + + /* Precision */ + if (*_p == '.') { + _p++; + while (*_p != '\0' && isdigit(*_p)) _p++; + } + + /* Copy va_list before consuming with va_arg */ + va_copy(_cpy,ap); + + /* Integer conversion (without modifiers) */ + if (strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); + goto fmt_valid; + } + + /* Double conversion (without modifiers) */ + if (strchr("eEfFgGaA",*_p) != NULL) { + va_arg(ap,double); + goto fmt_valid; + } + + /* Size: char */ + if (_p[0] == 'h' && _p[1] == 'h') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* char gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: short */ + if (_p[0] == 'h') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,int); /* short gets promoted to int */ + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long long */ + if (_p[0] == 'l' && _p[1] == 'l') { + _p += 2; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long long); + goto fmt_valid; + } + goto fmt_invalid; + } + + /* Size: long */ + if (_p[0] == 'l') { + _p += 1; + if (*_p != '\0' && strchr(intfmts,*_p) != NULL) { + va_arg(ap,long); + goto fmt_valid; + } + goto fmt_invalid; + } + + fmt_invalid: + va_end(_cpy); + goto format_err; + + fmt_valid: + _l = (_p+1)-c; + if (_l < sizeof(_format)-2) { + memcpy(_format,c,_l); + _format[_l] = '\0'; + newarg = sdscatvprintf(curarg,_format,_cpy); + + /* Update current position (note: outer blocks + * increment c twice so compensate here) */ + c = _p-1; + } + + va_end(_cpy); + break; + } + } + + if (newarg == NULL) goto memory_err; + curarg = newarg; + + touched = 1; + c++; + } + c++; + } + + /* Add the last argument if needed */ + if (touched) { + newargv = realloc(curargv,sizeof(char*)*(argc+1)); + if (newargv == NULL) goto memory_err; + curargv = newargv; + curargv[argc++] = curarg; + totlen += bulklen(sdslen(curarg)); + } else { + sdsfree(curarg); + } + + /* Clear curarg because it was put in curargv or was free'd. */ + curarg = NULL; + + /* Add bytes needed to hold multi bulk count */ + totlen += 1+countDigits(argc)+2; + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) goto memory_err; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); + memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); + pos += sdslen(curargv[j]); + sdsfree(curargv[j]); + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + free(curargv); + *target = cmd; + return totlen; + +format_err: + error_type = -2; + goto cleanup; + +memory_err: + error_type = -1; + goto cleanup; + +cleanup: + if (curargv) { + while(argc--) + sdsfree(curargv[argc]); + free(curargv); + } + + sdsfree(curarg); + + /* No need to check cmd since it is the last statement that can fail, + * but do it anyway to be as defensive as possible. */ + if (cmd != NULL) + free(cmd); + + return error_type; +} + +/* Format a command according to the Redis protocol. This function + * takes a format similar to printf: + * + * %s represents a C null terminated string you want to interpolate + * %b represents a binary safe string + * + * When using %b you need to provide both the pointer to the string + * and the length in bytes as a size_t. Examples: + * + * len = redisFormatCommand(target, "GET %s", mykey); + * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen); + */ +int redisFormatCommand(char **target, const char *format, ...) { + va_list ap; + int len; + va_start(ap,format); + len = redisvFormatCommand(target,format,ap); + va_end(ap); + + /* The API says "-1" means bad result, but we now also return "-2" in some + * cases. Force the return value to always be -1. */ + if (len < 0) + len = -1; + + return len; +} + +/* Format a command according to the Redis protocol using an sds string and + * sdscatfmt for the processing of arguments. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, + const size_t *argvlen) +{ + sds cmd; + unsigned long long totlen; + int j; + size_t len; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate our total size */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Use an SDS string for command construction */ + cmd = sdsempty(); + if (cmd == NULL) + return -1; + + /* We already know how much storage we need */ + cmd = sdsMakeRoomFor(cmd, totlen); + if (cmd == NULL) + return -1; + + /* Construct command */ + cmd = sdscatfmt(cmd, "*%i\r\n", argc); + for (j=0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + cmd = sdscatfmt(cmd, "$%T\r\n", len); + cmd = sdscatlen(cmd, argv[j], len); + cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); + } + + assert(sdslen(cmd)==totlen); + + *target = cmd; + return totlen; +} + +void redisFreeSdsCommand(sds cmd) { + sdsfree(cmd); +} + +/* Format a command according to the Redis protocol. This function takes the + * number of arguments, an array with arguments and an array with their + * lengths. If the latter is set to NULL, strlen will be used to compute the + * argument lengths. + */ +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen) { + char *cmd = NULL; /* final command */ + int pos; /* position in final command */ + size_t len; + int totlen, j; + + /* Abort on a NULL target */ + if (target == NULL) + return -1; + + /* Calculate number of bytes needed for the command */ + totlen = 1+countDigits(argc)+2; + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + totlen += bulklen(len); + } + + /* Build the command at protocol level */ + cmd = malloc(totlen+1); + if (cmd == NULL) + return -1; + + pos = sprintf(cmd,"*%d\r\n",argc); + for (j = 0; j < argc; j++) { + len = argvlen ? argvlen[j] : strlen(argv[j]); + pos += sprintf(cmd+pos,"$%zu\r\n",len); + memcpy(cmd+pos,argv[j],len); + pos += len; + cmd[pos++] = '\r'; + cmd[pos++] = '\n'; + } + assert(pos == totlen); + cmd[pos] = '\0'; + + *target = cmd; + return totlen; +} + +void redisFreeCommand(char *cmd) { + free(cmd); +} + +void __redisSetError(redisContext *c, int type, const char *str) { + size_t len; + + c->err = type; + if (str != NULL) { + len = strlen(str); + len = len < (sizeof(c->errstr)-1) ? len : (sizeof(c->errstr)-1); + memcpy(c->errstr,str,len); + c->errstr[len] = '\0'; + } else { + /* Only REDIS_ERR_IO may lack a description! */ + assert(type == REDIS_ERR_IO); + __redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); + } +} + +redisReader *redisReaderCreate(void) { + return redisReaderCreateWithFunctions(&defaultFunctions); +} + +static redisContext *redisContextInit(void) { + redisContext *c; + + c = calloc(1,sizeof(redisContext)); + if (c == NULL) + return NULL; + + c->err = 0; + c->errstr[0] = '\0'; + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + c->tcp.host = NULL; + c->tcp.source_addr = NULL; + c->unix_sock.path = NULL; + c->timeout = NULL; + + if (c->obuf == NULL || c->reader == NULL) { + redisFree(c); + return NULL; + } + + return c; +} + +void redisFree(redisContext *c) { + if (c == NULL) + return; + if (c->fd > 0) + close(c->fd); + if (c->obuf != NULL) + sdsfree(c->obuf); + if (c->reader != NULL) + redisReaderFree(c->reader); + if (c->tcp.host) + free(c->tcp.host); + if (c->tcp.source_addr) + free(c->tcp.source_addr); + if (c->unix_sock.path) + free(c->unix_sock.path); + if (c->timeout) + free(c->timeout); + free(c); +} + +int redisFreeKeepFd(redisContext *c) { + int fd = c->fd; + c->fd = -1; + redisFree(c); + return fd; +} + +int redisReconnect(redisContext *c) { + c->err = 0; + memset(c->errstr, '\0', strlen(c->errstr)); + + if (c->fd > 0) { + close(c->fd); + } + + sdsfree(c->obuf); + redisReaderFree(c->reader); + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + + if (c->connection_type == REDIS_CONN_TCP) { + return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, + c->timeout, c->tcp.source_addr); + } else if (c->connection_type == REDIS_CONN_UNIX) { + return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); + } else { + /* Something bad happened here and shouldn't have. There isn't + enough information in the context to reconnect. */ + __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); + } + + return REDIS_ERR; +} + +/* Connect to a Redis instance. On error the field error in the returned + * context will be set to the return value of the error function. + * When no set of reply functions is given, the default set will be used. */ +redisContext *redisConnect(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,&tv); + return c; +} + +redisContext *redisConnectNonBlock(const char *ip, int port) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectTcp(c,ip,port,NULL); + return c; +} + +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + c->flags &= ~REDIS_BLOCK; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr) { + redisContext *c = redisContextInit(); + c->flags &= ~REDIS_BLOCK; + c->flags |= REDIS_REUSEADDR; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + +redisContext *redisConnectUnix(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags |= REDIS_BLOCK; + redisContextConnectUnix(c,path,&tv); + return c; +} + +redisContext *redisConnectUnixNonBlock(const char *path) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->flags &= ~REDIS_BLOCK; + redisContextConnectUnix(c,path,NULL); + return c; +} + +redisContext *redisConnectFd(int fd) { + redisContext *c; + + c = redisContextInit(); + if (c == NULL) + return NULL; + + c->fd = fd; + c->flags |= REDIS_BLOCK | REDIS_CONNECTED; + return c; +} + +/* Set read/write timeout on a blocking socket. */ +int redisSetTimeout(redisContext *c, const struct timeval tv) { + if (c->flags & REDIS_BLOCK) + return redisContextSetTimeout(c,tv); + return REDIS_ERR; +} + +/* Enable connection KeepAlive. */ +int redisEnableKeepAlive(redisContext *c) { + if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK) + return REDIS_ERR; + return REDIS_OK; +} + +/* Use this function to handle a read event on the descriptor. It will try + * and read some bytes from the socket and feed them to the reply parser. + * + * After this function is called, you may use redisContextReadReply to + * see if there is a reply available. */ +int redisBufferRead(redisContext *c) { + char buf[1024*16]; + int nread; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + nread = read(c->fd,buf,sizeof(buf)); + if (nread == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nread == 0) { + __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + return REDIS_ERR; + } else { + if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + } + return REDIS_OK; +} + +/* Write the output buffer to the socket. + * + * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was + * succesfully written to the socket. When the buffer is empty after the + * write operation, "done" is set to 1 (if given). + * + * Returns REDIS_ERR if an error occured trying to write and sets + * c->errstr to hold the appropriate error string. + */ +int redisBufferWrite(redisContext *c, int *done) { + int nwritten; + + /* Return early when the context has seen an error. */ + if (c->err) + return REDIS_ERR; + + if (sdslen(c->obuf) > 0) { + nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); + if (nwritten == -1) { + if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + } else if (nwritten > 0) { + if (nwritten == (signed)sdslen(c->obuf)) { + sdsfree(c->obuf); + c->obuf = sdsempty(); + } else { + sdsrange(c->obuf,nwritten,-1); + } + } + } + if (done != NULL) *done = (sdslen(c->obuf) == 0); + return REDIS_OK; +} + +/* Internal helper function to try and get a reply from the reader, + * or set an error in the context otherwise. */ +int redisGetReplyFromReader(redisContext *c, void **reply) { + if (redisReaderGetReply(c->reader,reply) == REDIS_ERR) { + __redisSetError(c,c->reader->err,c->reader->errstr); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisGetReply(redisContext *c, void **reply) { + int wdone = 0; + void *aux = NULL; + + /* Try to read pending replies */ + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + + /* For the blocking context, flush output buffer and read reply */ + if (aux == NULL && c->flags & REDIS_BLOCK) { + /* Write until done */ + do { + if (redisBufferWrite(c,&wdone) == REDIS_ERR) + return REDIS_ERR; + } while (!wdone); + + /* Read until there is a reply */ + do { + if (redisBufferRead(c) == REDIS_ERR) + return REDIS_ERR; + if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) + return REDIS_ERR; + } while (aux == NULL); + } + + /* Set reply object */ + if (reply != NULL) *reply = aux; + return REDIS_OK; +} + + +/* Helper function for the redisAppendCommand* family of functions. + * + * Write a formatted command to the output buffer. When this family + * is used, you need to call redisGetReply yourself to retrieve + * the reply (or replies in pub/sub). + */ +int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { + sds newbuf; + + newbuf = sdscatlen(c->obuf,cmd,len); + if (newbuf == NULL) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + c->obuf = newbuf; + return REDIS_OK; +} + +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len) { + + if (__redisAppendCommand(c, cmd, len) != REDIS_OK) { + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisvAppendCommand(redisContext *c, const char *format, va_list ap) { + char *cmd; + int len; + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } else if (len == -2) { + __redisSetError(c,REDIS_ERR_OTHER,"Invalid format string"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + free(cmd); + return REDIS_ERR; + } + + free(cmd); + return REDIS_OK; +} + +int redisAppendCommand(redisContext *c, const char *format, ...) { + va_list ap; + int ret; + + va_start(ap,format); + ret = redisvAppendCommand(c,format,ap); + va_end(ap); + return ret; +} + +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + sds cmd; + int len; + + len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len == -1) { + __redisSetError(c,REDIS_ERR_OOM,"Out of memory"); + return REDIS_ERR; + } + + if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { + sdsfree(cmd); + return REDIS_ERR; + } + + sdsfree(cmd); + return REDIS_OK; +} + +/* Helper function for the redisCommand* family of functions. + * + * Write a formatted command to the output buffer. If the given context is + * blocking, immediately read the reply into the "reply" pointer. When the + * context is non-blocking, the "reply" pointer will not be used and the + * command is simply appended to the write buffer. + * + * Returns the reply when a reply was succesfully retrieved. Returns NULL + * otherwise. When NULL is returned in a blocking context, the error field + * in the context will be set. + */ +static void *__redisBlockForReply(redisContext *c) { + void *reply; + + if (c->flags & REDIS_BLOCK) { + if (redisGetReply(c,&reply) != REDIS_OK) + return NULL; + return reply; + } + return NULL; +} + +void *redisvCommand(redisContext *c, const char *format, va_list ap) { + if (redisvAppendCommand(c,format,ap) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} + +void *redisCommand(redisContext *c, const char *format, ...) { + va_list ap; + void *reply = NULL; + va_start(ap,format); + reply = redisvCommand(c,format,ap); + va_end(ap); + return reply; +} + +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { + if (redisAppendCommandArgv(c,argc,argv,argvlen) != REDIS_OK) + return NULL; + return __redisBlockForReply(c); +} diff --git a/ext/hiredis-vip-0.3.0/hiredis.h b/ext/hiredis-vip-0.3.0/hiredis.h new file mode 100644 index 000000000..87f7366f8 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiredis.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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. + */ + +#ifndef __HIREDIS_H +#define __HIREDIS_H +#include "read.h" +#include /* for va_list */ +#include /* for struct timeval */ +#include /* uintXX_t, etc */ +#include "sds.h" /* for sds */ + +#define HIREDIS_MAJOR 0 +#define HIREDIS_MINOR 13 +#define HIREDIS_PATCH 1 + +/* Connection type can be blocking or non-blocking and is set in the + * least significant bit of the flags field in redisContext. */ +#define REDIS_BLOCK 0x1 + +/* Connection may be disconnected before being free'd. The second bit + * in the flags field is set when the context is connected. */ +#define REDIS_CONNECTED 0x2 + +/* The async API might try to disconnect cleanly and flush the output + * buffer and read all subsequent replies before disconnecting. + * This flag means no new commands can come in and the connection + * should be terminated once all replies have been read. */ +#define REDIS_DISCONNECTING 0x4 + +/* Flag specific to the async API which means that the context should be clean + * up as soon as possible. */ +#define REDIS_FREEING 0x8 + +/* Flag that is set when an async callback is executed. */ +#define REDIS_IN_CALLBACK 0x10 + +/* Flag that is set when the async context has one or more subscriptions. */ +#define REDIS_SUBSCRIBED 0x20 + +/* Flag that is set when monitor mode is active */ +#define REDIS_MONITORING 0x40 + +/* Flag that is set when we should set SO_REUSEADDR before calling bind() */ +#define REDIS_REUSEADDR 0x80 + +#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */ + +/* number of times we retry to connect in the case of EADDRNOTAVAIL and + * SO_REUSEADDR is being used. */ +#define REDIS_CONNECT_RETRIES 10 + +/* strerror_r has two completely different prototypes and behaviors + * depending on system issues, so we need to operate on the error buffer + * differently depending on which strerror_r we're using. */ +#ifndef _GNU_SOURCE +/* "regular" POSIX strerror_r that does the right thing. */ +#define __redis_strerror_r(errno, buf, len) \ + do { \ + strerror_r((errno), (buf), (len)); \ + } while (0) +#else +/* "bad" GNU strerror_r we need to clean up after. */ +#define __redis_strerror_r(errno, buf, len) \ + do { \ + char *err_str = strerror_r((errno), (buf), (len)); \ + /* If return value _isn't_ the start of the buffer we passed in, \ + * then GNU strerror_r returned an internal static buffer and we \ + * need to copy the result into our private buffer. */ \ + if (err_str != (buf)) { \ + buf[(len)] = '\0'; \ + strncat((buf), err_str, ((len) - 1)); \ + } \ + } while (0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reply object returned by redisCommand() */ +typedef struct redisReply { + int type; /* REDIS_REPLY_* */ + long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ + int len; /* Length of string */ + char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ + size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ + struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ +} redisReply; + +redisReader *redisReaderCreate(void); + +/* Function to free the reply objects hiredis returns by default. */ +void freeReplyObject(void *reply); + +/* Functions to format a command according to the protocol. */ +int redisvFormatCommand(char **target, const char *format, va_list ap); +int redisFormatCommand(char **target, const char *format, ...); +int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); +int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); +void redisFreeCommand(char *cmd); +void redisFreeSdsCommand(sds cmd); + +enum redisConnectionType { + REDIS_CONN_TCP, + REDIS_CONN_UNIX, +}; + +/* Context for a connection to Redis */ +typedef struct redisContext { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + int fd; + int flags; + char *obuf; /* Write buffer */ + redisReader *reader; /* Protocol reader */ + + enum redisConnectionType connection_type; + struct timeval *timeout; + + struct { + char *host; + char *source_addr; + int port; + } tcp; + + struct { + char *path; + } unix_sock; +} redisContext; + +redisContext *redisConnect(const char *ip, int port); +redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); +redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectBindNonBlock(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, + const char *source_addr); +redisContext *redisConnectUnix(const char *path); +redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); +redisContext *redisConnectUnixNonBlock(const char *path); +redisContext *redisConnectFd(int fd); + +/** + * Reconnect the given context using the saved information. + * + * This re-uses the exact same connect options as in the initial connection. + * host, ip (or path), timeout and bind address are reused, + * flags are used unmodified from the existing context. + * + * Returns REDIS_OK on successfull connect or REDIS_ERR otherwise. + */ +int redisReconnect(redisContext *c); + +int redisSetTimeout(redisContext *c, const struct timeval tv); +int redisEnableKeepAlive(redisContext *c); +void redisFree(redisContext *c); +int redisFreeKeepFd(redisContext *c); +int redisBufferRead(redisContext *c); +int redisBufferWrite(redisContext *c, int *done); + +/* In a blocking context, this function first checks if there are unconsumed + * replies to return and returns one if so. Otherwise, it flushes the output + * buffer to the socket and reads until it has a reply. In a non-blocking + * context, it will return unconsumed replies until there are no more. */ +int redisGetReply(redisContext *c, void **reply); +int redisGetReplyFromReader(redisContext *c, void **reply); + +/* Write a formatted command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisAppendFormattedCommand(redisContext *c, const char *cmd, size_t len); + +/* Write a command to the output buffer. Use these functions in blocking mode + * to get a pipeline of commands. */ +int redisvAppendCommand(redisContext *c, const char *format, va_list ap); +int redisAppendCommand(redisContext *c, const char *format, ...); +int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +/* Issue a command to Redis. In a blocking context, it is identical to calling + * redisAppendCommand, followed by redisGetReply. The function will return + * NULL if there was an error in performing the request, otherwise it will + * return the reply. In a non-blocking context, it is identical to calling + * only redisAppendCommand and will always return NULL. */ +void *redisvCommand(redisContext *c, const char *format, va_list ap); +void *redisCommand(redisContext *c, const char *format, ...); +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/hiutil.c b/ext/hiredis-vip-0.3.0/hiutil.c new file mode 100644 index 000000000..d10cdacea --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiutil.c @@ -0,0 +1,554 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + + +#include "hiutil.h" + +#ifdef HI_HAVE_BACKTRACE +# include +#endif + +int +hi_set_blocking(int sd) +{ + int flags; + + flags = fcntl(sd, F_GETFL, 0); + if (flags < 0) { + return flags; + } + + return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK); +} + +int +hi_set_nonblocking(int sd) +{ + int flags; + + flags = fcntl(sd, F_GETFL, 0); + if (flags < 0) { + return flags; + } + + return fcntl(sd, F_SETFL, flags | O_NONBLOCK); +} + +int +hi_set_reuseaddr(int sd) +{ + int reuse; + socklen_t len; + + reuse = 1; + len = sizeof(reuse); + + return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len); +} + +/* + * Disable Nagle algorithm on TCP socket. + * + * This option helps to minimize transmit latency by disabling coalescing + * of data to fill up a TCP segment inside the kernel. Sockets with this + * option must use readv() or writev() to do data transfer in bulk and + * hence avoid the overhead of small packets. + */ +int +hi_set_tcpnodelay(int sd) +{ + int nodelay; + socklen_t len; + + nodelay = 1; + len = sizeof(nodelay); + + return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); +} + +int +hi_set_linger(int sd, int timeout) +{ + struct linger linger; + socklen_t len; + + linger.l_onoff = 1; + linger.l_linger = timeout; + + len = sizeof(linger); + + return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); +} + +int +hi_set_sndbuf(int sd, int size) +{ + socklen_t len; + + len = sizeof(size); + + return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); +} + +int +hi_set_rcvbuf(int sd, int size) +{ + socklen_t len; + + len = sizeof(size); + + return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); +} + +int +hi_get_soerror(int sd) +{ + int status, err; + socklen_t len; + + err = 0; + len = sizeof(err); + + status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len); + if (status == 0) { + errno = err; + } + + return status; +} + +int +hi_get_sndbuf(int sd) +{ + int status, size; + socklen_t len; + + size = 0; + len = sizeof(size); + + status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len); + if (status < 0) { + return status; + } + + return size; +} + +int +hi_get_rcvbuf(int sd) +{ + int status, size; + socklen_t len; + + size = 0; + len = sizeof(size); + + status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len); + if (status < 0) { + return status; + } + + return size; +} + +int +_hi_atoi(uint8_t *line, size_t n) +{ + int value; + + if (n == 0) { + return -1; + } + + for (value = 0; n--; line++) { + if (*line < '0' || *line > '9') { + return -1; + } + + value = value * 10 + (*line - '0'); + } + + if (value < 0) { + return -1; + } + + return value; +} + +void +_hi_itoa(uint8_t *s, int num) +{ + uint8_t c; + uint8_t sign = 0; + + if(s == NULL) + { + return; + } + + uint32_t len, i; + len = 0; + + if(num < 0) + { + sign = 1; + num = abs(num); + } + else if(num == 0) + { + s[len++] = '0'; + return; + } + + while(num % 10 || num /10) + { + c = num %10 + '0'; + num = num /10; + s[len+1] = s[len]; + s[len] = c; + len ++; + } + + if(sign == 1) + { + s[len++] = '-'; + } + + s[len] = '\0'; + + for(i = 0; i < len/2; i ++) + { + c = s[i]; + s[i] = s[len - i -1]; + s[len - i -1] = c; + } + +} + + +int +hi_valid_port(int n) +{ + if (n < 1 || n > UINT16_MAX) { + return 0; + } + + return 1; +} + +int _uint_len(uint32_t num) +{ + int n = 0; + + if(num == 0) + { + return 1; + } + + while(num != 0) + { + n ++; + num /= 10; + } + + return n; +} + +void * +_hi_alloc(size_t size, const char *name, int line) +{ + void *p; + + ASSERT(size != 0); + + p = malloc(size); + + if(name == NULL && line == 1) + { + + } + + return p; +} + +void * +_hi_zalloc(size_t size, const char *name, int line) +{ + void *p; + + p = _hi_alloc(size, name, line); + if (p != NULL) { + memset(p, 0, size); + } + + return p; +} + +void * +_hi_calloc(size_t nmemb, size_t size, const char *name, int line) +{ + return _hi_zalloc(nmemb * size, name, line); +} + +void * +_hi_realloc(void *ptr, size_t size, const char *name, int line) +{ + void *p; + + ASSERT(size != 0); + + p = realloc(ptr, size); + + if(name == NULL && line == 1) + { + + } + + return p; +} + +void +_hi_free(void *ptr, const char *name, int line) +{ + ASSERT(ptr != NULL); + + if(name == NULL && line == 1) + { + + } + + free(ptr); +} + +void +hi_stacktrace(int skip_count) +{ + if(skip_count > 0) + { + + } + +#ifdef HI_HAVE_BACKTRACE + void *stack[64]; + char **symbols; + int size, i, j; + + size = backtrace(stack, 64); + symbols = backtrace_symbols(stack, size); + if (symbols == NULL) { + return; + } + + skip_count++; /* skip the current frame also */ + + for (i = skip_count, j = 0; i < size; i++, j++) { + printf("[%d] %s\n", j, symbols[i]); + } + + free(symbols); +#endif +} + +void +hi_stacktrace_fd(int fd) +{ + if(fd > 0) + { + + } +#ifdef HI_HAVE_BACKTRACE + void *stack[64]; + int size; + + size = backtrace(stack, 64); + backtrace_symbols_fd(stack, size, fd); +#endif +} + +void +hi_assert(const char *cond, const char *file, int line, int panic) +{ + + printf("File: %s Line: %d: %s\n", file, line, cond); + + if (panic) { + hi_stacktrace(1); + abort(); + } + abort(); +} + +int +_vscnprintf(char *buf, size_t size, const char *fmt, va_list args) +{ + int n; + + n = vsnprintf(buf, size, fmt, args); + + /* + * The return value is the number of characters which would be written + * into buf not including the trailing '\0'. If size is == 0 the + * function returns 0. + * + * On error, the function also returns 0. This is to allow idiom such + * as len += _vscnprintf(...) + * + * See: http://lwn.net/Articles/69419/ + */ + if (n <= 0) { + return 0; + } + + if (n < (int) size) { + return n; + } + + return (int)(size - 1); +} + +int +_scnprintf(char *buf, size_t size, const char *fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = _vscnprintf(buf, size, fmt, args); + va_end(args); + + return n; +} + +/* + * Send n bytes on a blocking descriptor + */ +ssize_t +_hi_sendn(int sd, const void *vptr, size_t n) +{ + size_t nleft; + ssize_t nsend; + const char *ptr; + + ptr = vptr; + nleft = n; + while (nleft > 0) { + nsend = send(sd, ptr, nleft, 0); + if (nsend < 0) { + if (errno == EINTR) { + continue; + } + return nsend; + } + if (nsend == 0) { + return -1; + } + + nleft -= (size_t)nsend; + ptr += nsend; + } + + return (ssize_t)n; +} + +/* + * Recv n bytes from a blocking descriptor + */ +ssize_t +_hi_recvn(int sd, void *vptr, size_t n) +{ + size_t nleft; + ssize_t nrecv; + char *ptr; + + ptr = vptr; + nleft = n; + while (nleft > 0) { + nrecv = recv(sd, ptr, nleft, 0); + if (nrecv < 0) { + if (errno == EINTR) { + continue; + } + return nrecv; + } + if (nrecv == 0) { + break; + } + + nleft -= (size_t)nrecv; + ptr += nrecv; + } + + return (ssize_t)(n - nleft); +} + +/* + * Return the current time in microseconds since Epoch + */ +int64_t +hi_usec_now(void) +{ + struct timeval now; + int64_t usec; + int status; + + status = gettimeofday(&now, NULL); + if (status < 0) { + return -1; + } + + usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; + + return usec; +} + +/* + * Return the current time in milliseconds since Epoch + */ +int64_t +hi_msec_now(void) +{ + return hi_usec_now() / 1000LL; +} + +void print_string_with_length(char *s, size_t len) +{ + char *token; + for(token = s; token <= s + len; token ++) + { + printf("%c", *token); + } + printf("\n"); +} + +void print_string_with_length_fix_CRLF(char *s, size_t len) +{ + char *token; + for(token = s; token < s + len; token ++) + { + if(*token == CR) + { + printf("\\r"); + } + else if(*token == LF) + { + printf("\\n"); + } + else + { + printf("%c", *token); + } + } + printf("\n"); +} + diff --git a/ext/hiredis-vip-0.3.0/hiutil.h b/ext/hiredis-vip-0.3.0/hiutil.h new file mode 100644 index 000000000..d9e1ddb0b --- /dev/null +++ b/ext/hiredis-vip-0.3.0/hiutil.h @@ -0,0 +1,265 @@ +#ifndef __HIUTIL_H_ +#define __HIUTIL_H_ + +#include +#include +#include + +#define HI_OK 0 +#define HI_ERROR -1 +#define HI_EAGAIN -2 +#define HI_ENOMEM -3 + +typedef int rstatus_t; /* return type */ + +#define LF (uint8_t) 10 +#define CR (uint8_t) 13 +#define CRLF "\x0d\x0a" +#define CRLF_LEN (sizeof("\x0d\x0a") - 1) + +#define NELEMS(a) ((sizeof(a)) / sizeof((a)[0])) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define SQUARE(d) ((d) * (d)) +#define VAR(s, s2, n) (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1)) +#define STDDEV(s, s2, n) (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n)))) + +#define HI_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1) +#define HI_INET6_ADDRSTRLEN \ + (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1) +#define HI_INET_ADDRSTRLEN MAX(HI_INET4_ADDRSTRLEN, HI_INET6_ADDRSTRLEN) +#define HI_UNIX_ADDRSTRLEN \ + (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) + +#define HI_MAXHOSTNAMELEN 256 + +/* + * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral + * type (uintmax_t) in ascii, including the null terminator '\0' + * + * From stdint.h, we have: + * # define UINT8_MAX (255) + * # define UINT16_MAX (65535) + * # define UINT32_MAX (4294967295U) + * # define UINT64_MAX (__UINT64_C(18446744073709551615)) + */ +#define HI_UINT8_MAXLEN (3 + 1) +#define HI_UINT16_MAXLEN (5 + 1) +#define HI_UINT32_MAXLEN (10 + 1) +#define HI_UINT64_MAXLEN (20 + 1) +#define HI_UINTMAX_MAXLEN HI_UINT64_MAXLEN + +/* + * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2 + * of 2. + */ +#define HI_ALIGNMENT sizeof(unsigned long) /* platform word */ +#define HI_ALIGN(d, n) (((d) + (n - 1)) & ~(n - 1)) +#define HI_ALIGN_PTR(p, n) \ + (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1)) + + + +#define str3icmp(m, c0, c1, c2) \ + ((m[0] == c0 || m[0] == (c0 ^ 0x20)) && \ + (m[1] == c1 || m[1] == (c1 ^ 0x20)) && \ + (m[2] == c2 || m[2] == (c2 ^ 0x20))) + +#define str4icmp(m, c0, c1, c2, c3) \ + (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20))) + +#define str5icmp(m, c0, c1, c2, c3, c4) \ + (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20))) + +#define str6icmp(m, c0, c1, c2, c3, c4, c5) \ + (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20))) + +#define str7icmp(m, c0, c1, c2, c3, c4, c5, c6) \ + (str6icmp(m, c0, c1, c2, c3, c4, c5) && \ + (m[6] == c6 || m[6] == (c6 ^ 0x20))) + +#define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ + (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) && \ + (m[7] == c7 || m[7] == (c7 ^ 0x20))) + +#define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ + (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ + (m[8] == c8 || m[8] == (c8 ^ 0x20))) + +#define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ + (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && \ + (m[9] == c9 || m[9] == (c9 ^ 0x20))) + +#define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ + (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && \ + (m[10] == c10 || m[10] == (c10 ^ 0x20))) + +#define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ + (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && \ + (m[11] == c11 || m[11] == (c11 ^ 0x20))) + +#define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) \ + (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) && \ + (m[12] == c12 || m[12] == (c12 ^ 0x20))) + +#define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) \ + (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) && \ + (m[13] == c13 || m[13] == (c13 ^ 0x20))) + +#define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) \ + (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) && \ + (m[14] == c14 || m[14] == (c14 ^ 0x20))) + +#define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15) \ + (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) && \ + (m[15] == c15 || m[15] == (c15 ^ 0x20))) + + + +/* + * Wrapper to workaround well known, safe, implicit type conversion when + * invoking system calls. + */ +#define hi_gethostname(_name, _len) \ + gethostname((char *)_name, (size_t)_len) + +#define hi_atoi(_line, _n) \ + _hi_atoi((uint8_t *)_line, (size_t)_n) +#define hi_itoa(_line, _n) \ + _hi_itoa((uint8_t *)_line, (int)_n) + +#define uint_len(_n) \ + _uint_len((uint32_t)_n) + + +int hi_set_blocking(int sd); +int hi_set_nonblocking(int sd); +int hi_set_reuseaddr(int sd); +int hi_set_tcpnodelay(int sd); +int hi_set_linger(int sd, int timeout); +int hi_set_sndbuf(int sd, int size); +int hi_set_rcvbuf(int sd, int size); +int hi_get_soerror(int sd); +int hi_get_sndbuf(int sd); +int hi_get_rcvbuf(int sd); + +int _hi_atoi(uint8_t *line, size_t n); +void _hi_itoa(uint8_t *s, int num); + +int hi_valid_port(int n); + +int _uint_len(uint32_t num); + + +/* + * Memory allocation and free wrappers. + * + * These wrappers enables us to loosely detect double free, dangling + * pointer access and zero-byte alloc. + */ +#define hi_alloc(_s) \ + _hi_alloc((size_t)(_s), __FILE__, __LINE__) + +#define hi_zalloc(_s) \ + _hi_zalloc((size_t)(_s), __FILE__, __LINE__) + +#define hi_calloc(_n, _s) \ + _hi_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__) + +#define hi_realloc(_p, _s) \ + _hi_realloc(_p, (size_t)(_s), __FILE__, __LINE__) + +#define hi_free(_p) do { \ + _hi_free(_p, __FILE__, __LINE__); \ + (_p) = NULL; \ +} while (0) + +void *_hi_alloc(size_t size, const char *name, int line); +void *_hi_zalloc(size_t size, const char *name, int line); +void *_hi_calloc(size_t nmemb, size_t size, const char *name, int line); +void *_hi_realloc(void *ptr, size_t size, const char *name, int line); +void _hi_free(void *ptr, const char *name, int line); + + +#define hi_strndup(_s, _n) \ + strndup((char *)(_s), (size_t)(_n)); + +/* + * Wrappers to send or receive n byte message on a blocking + * socket descriptor. + */ +#define hi_sendn(_s, _b, _n) \ + _hi_sendn(_s, _b, (size_t)(_n)) + +#define hi_recvn(_s, _b, _n) \ + _hi_recvn(_s, _b, (size_t)(_n)) + +/* + * Wrappers to read or write data to/from (multiple) buffers + * to a file or socket descriptor. + */ +#define hi_read(_d, _b, _n) \ + read(_d, _b, (size_t)(_n)) + +#define hi_readv(_d, _b, _n) \ + readv(_d, _b, (int)(_n)) + +#define hi_write(_d, _b, _n) \ + write(_d, _b, (size_t)(_n)) + +#define hi_writev(_d, _b, _n) \ + writev(_d, _b, (int)(_n)) + +ssize_t _hi_sendn(int sd, const void *vptr, size_t n); +ssize_t _hi_recvn(int sd, void *vptr, size_t n); + +/* + * Wrappers for defining custom assert based on whether macro + * HI_ASSERT_PANIC or HI_ASSERT_LOG was defined at the moment + * ASSERT was called. + */ +#ifdef HI_ASSERT_PANIC + +#define ASSERT(_x) do { \ + if (!(_x)) { \ + hi_assert(#_x, __FILE__, __LINE__, 1); \ + } \ +} while (0) + +#define NOT_REACHED() ASSERT(0) + +#elif HI_ASSERT_LOG + +#define ASSERT(_x) do { \ + if (!(_x)) { \ + hi_assert(#_x, __FILE__, __LINE__, 0); \ + } \ +} while (0) + +#define NOT_REACHED() ASSERT(0) + +#else + +#define ASSERT(_x) + +#define NOT_REACHED() + +#endif + +void hi_assert(const char *cond, const char *file, int line, int panic); +void hi_stacktrace(int skip_count); +void hi_stacktrace_fd(int fd); + +int _scnprintf(char *buf, size_t size, const char *fmt, ...); +int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args); +int64_t hi_usec_now(void); +int64_t hi_msec_now(void); + +void print_string_with_length(char *s, size_t len); +void print_string_with_length_fix_CRLF(char *s, size_t len); + +uint16_t crc16(const char *buf, int len); + +#endif diff --git a/ext/hiredis-vip-0.3.0/net.c b/ext/hiredis-vip-0.3.0/net.c new file mode 100644 index 000000000..60a2dc754 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/net.c @@ -0,0 +1,458 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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 "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "sds.h" + +/* Defined in hiredis.c */ +void __redisSetError(redisContext *c, int type, const char *str); + +static void redisContextCloseFd(redisContext *c) { + if (c && c->fd >= 0) { + close(c->fd); + c->fd = -1; + } +} + +static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + char buf[128] = { 0 }; + size_t len = 0; + + if (prefix != NULL) + len = snprintf(buf,sizeof(buf),"%s: ",prefix); + __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len); + __redisSetError(c,type,buf); +} + +static int redisSetReuseAddr(redisContext *c) { + int on = 1; + if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int redisCreateSocket(redisContext *c, int type) { + int s; + if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + c->fd = s; + if (type == AF_INET) { + if (redisSetReuseAddr(c) == REDIS_ERR) { + return REDIS_ERR; + } + } + return REDIS_OK; +} + +static int redisSetBlocking(redisContext *c, int blocking) { + int flags; + + /* Set the socket nonblocking. + * Note that fcntl(2) for F_GETFL and F_SETFL can't be + * interrupted by a signal. */ + if ((flags = fcntl(c->fd, F_GETFL)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (blocking) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(c->fd, F_SETFL, flags) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +int redisKeepAlive(redisContext *c, int interval) { + int val = 1; + int fd = c->fd; + + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval; + +#ifdef _OSX + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#else +#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) + val = interval; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = interval/3; + if (val == 0) val = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } + + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { + __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); + return REDIS_ERR; + } +#endif +#endif + + return REDIS_OK; +} + +static int redisSetTcpNoDelay(redisContext *c) { + int yes = 1; + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); + redisContextCloseFd(c); + return REDIS_ERR; + } + return REDIS_OK; +} + +#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) + +static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) { + struct pollfd wfd[1]; + long msec; + + msec = -1; + wfd[0].fd = c->fd; + wfd[0].events = POLLOUT; + + /* Only use timeout when not NULL. */ + if (timeout != NULL) { + if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + + msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); + + if (msec < 0 || msec > INT_MAX) { + msec = INT_MAX; + } + } + + if (errno == EINPROGRESS) { + int res; + + if ((res = poll(wfd, 1, msec)) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); + redisContextCloseFd(c); + return REDIS_ERR; + } else if (res == 0) { + errno = ETIMEDOUT; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; + } + + if (redisCheckSocketError(c) != REDIS_OK) + return REDIS_ERR; + + return REDIS_OK; + } + + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + redisContextCloseFd(c); + return REDIS_ERR; +} + +int redisCheckSocketError(redisContext *c) { + int err = 0; + socklen_t errlen = sizeof(err); + + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); + return REDIS_ERR; + } + + if (err) { + errno = err; + __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); + return REDIS_ERR; + } + + return REDIS_OK; +} + +int redisContextSetTimeout(redisContext *c, const struct timeval tv) { + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); + return REDIS_ERR; + } + if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { + __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); + return REDIS_ERR; + } + return REDIS_OK; +} + +static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + int s, rv, n; + char _port[6]; /* strlen("65535"); */ + struct addrinfo hints, *servinfo, *bservinfo, *p, *b; + int blocking = (c->flags & REDIS_BLOCK); + int reuseaddr = (c->flags & REDIS_REUSEADDR); + int reuses = 0; + + c->connection_type = REDIS_CONN_TCP; + c->tcp.port = port; + + /* We need to take possession of the passed parameters + * to make them reusable for a reconnect. + * We also carefully check we don't free data we already own, + * as in the case of the reconnect method. + * + * This is a bit ugly, but atleast it works and doesn't leak memory. + **/ + if (c->tcp.host != addr) { + if (c->tcp.host) + free(c->tcp.host); + + c->tcp.host = strdup(addr); + } + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + if (c->timeout) + free(c->timeout); + c->timeout = NULL; + } + + if (source_addr == NULL) { + free(c->tcp.source_addr); + c->tcp.source_addr = NULL; + } else if (c->tcp.source_addr != source_addr) { + free(c->tcp.source_addr); + c->tcp.source_addr = strdup(source_addr); + } + + snprintf(_port, 6, "%d", port); + memset(&hints,0,sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + /* Try with IPv6 if no IPv4 address was found. We do it in this order since + * in a Redis client you can't afford to test if you have IPv6 connectivity + * as this would add latency to every connect. Otherwise a more sensible + * route could be: Use IPv6 if both addresses are available and there is IPv6 + * connectivity. */ + if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) { + hints.ai_family = AF_INET6; + if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) { + __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv)); + return REDIS_ERR; + } + } + for (p = servinfo; p != NULL; p = p->ai_next) { +addrretry: + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + continue; + + c->fd = s; + if (redisSetBlocking(c,0) != REDIS_OK) + goto error; + if (c->tcp.source_addr) { + int bound = 0; + /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ + if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + + if (reuseaddr) { + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, + sizeof(n)) < 0) { + goto error; + } + } + + for (b = bservinfo; b != NULL; b = b->ai_next) { + if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { + bound = 1; + break; + } + } + freeaddrinfo(bservinfo); + if (!bound) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + } + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { + if (errno == EHOSTUNREACH) { + redisContextCloseFd(c); + continue; + } else if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else if (errno == EADDRNOTAVAIL && reuseaddr) { + if (++reuses >= REDIS_CONNECT_RETRIES) { + goto error; + } else { + goto addrretry; + } + } else { + if (redisContextWaitReady(c,c->timeout) != REDIS_OK) + goto error; + } + } + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + goto error; + if (redisSetTcpNoDelay(c) != REDIS_OK) + goto error; + + c->flags |= REDIS_CONNECTED; + rv = REDIS_OK; + goto end; + } + if (p == NULL) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + +error: + rv = REDIS_ERR; +end: + freeaddrinfo(servinfo); + return rv; // Need to return REDIS_OK if alright +} + +int redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout) { + return _redisContextConnectTcp(c, addr, port, timeout, NULL); +} + +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr) { + return _redisContextConnectTcp(c, addr, port, timeout, source_addr); +} + +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { + int blocking = (c->flags & REDIS_BLOCK); + struct sockaddr_un sa; + + if (redisCreateSocket(c,AF_LOCAL) < 0) + return REDIS_ERR; + if (redisSetBlocking(c,0) != REDIS_OK) + return REDIS_ERR; + + c->connection_type = REDIS_CONN_UNIX; + if (c->unix_sock.path != path) + c->unix_sock.path = strdup(path); + + if (timeout) { + if (c->timeout != timeout) { + if (c->timeout == NULL) + c->timeout = malloc(sizeof(struct timeval)); + + memcpy(c->timeout, timeout, sizeof(struct timeval)); + } + } else { + if (c->timeout) + free(c->timeout); + c->timeout = NULL; + } + + sa.sun_family = AF_LOCAL; + strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); + if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + if (errno == EINPROGRESS && !blocking) { + /* This is ok. */ + } else { + if (redisContextWaitReady(c,c->timeout) != REDIS_OK) + return REDIS_ERR; + } + } + + /* Reset socket to be blocking after connect(2). */ + if (blocking && redisSetBlocking(c,1) != REDIS_OK) + return REDIS_ERR; + + c->flags |= REDIS_CONNECTED; + return REDIS_OK; +} diff --git a/ext/hiredis-vip-0.3.0/net.h b/ext/hiredis-vip-0.3.0/net.h new file mode 100644 index 000000000..2f1a0bf85 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/net.h @@ -0,0 +1,53 @@ +/* Extracted from anet.c to work properly with Hiredis error reporting. + * + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2014, Pieter Noordhuis + * Copyright (c) 2015, Matt Stancliff , + * Jan-Erik Rediger + * + * 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. + */ + +#ifndef __NET_H +#define __NET_H + +#include "hiredis.h" + +#if defined(__sun) +#define AF_LOCAL AF_UNIX +#endif + +int redisCheckSocketError(redisContext *c); +int redisContextSetTimeout(redisContext *c, const struct timeval tv); +int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + const char *source_addr); +int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); +int redisKeepAlive(redisContext *c, int interval); + +#endif diff --git a/ext/hiredis-vip-0.3.0/read.c b/ext/hiredis-vip-0.3.0/read.c new file mode 100644 index 000000000..df1a467a9 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/read.c @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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 "fmacros.h" +#include +#include +#ifndef _MSC_VER +#include +#endif +#include +#include +#include + +#include "read.h" +#include "sds.h" + +static void __redisReaderSetError(redisReader *r, int type, const char *str) { + size_t len; + + if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + r->reply = NULL; + } + + /* Clear input buffer on errors. */ + if (r->buf != NULL) { + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; + } + + /* Reset task stack. */ + r->ridx = -1; + + /* Set error. */ + r->err = type; + len = strlen(str); + len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1); + memcpy(r->errstr,str,len); + r->errstr[len] = '\0'; +} + +static size_t chrtos(char *buf, size_t size, char byte) { + size_t len = 0; + + switch(byte) { + case '\\': + case '"': + len = snprintf(buf,size,"\"\\%c\"",byte); + break; + case '\n': len = snprintf(buf,size,"\"\\n\""); break; + case '\r': len = snprintf(buf,size,"\"\\r\""); break; + case '\t': len = snprintf(buf,size,"\"\\t\""); break; + case '\a': len = snprintf(buf,size,"\"\\a\""); break; + case '\b': len = snprintf(buf,size,"\"\\b\""); break; + default: + if (isprint(byte)) + len = snprintf(buf,size,"\"%c\"",byte); + else + len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte); + break; + } + + return len; +} + +static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) { + char cbuf[8], sbuf[128]; + + chrtos(cbuf,sizeof(cbuf),byte); + snprintf(sbuf,sizeof(sbuf), + "Protocol error, got %s as reply type byte", cbuf); + __redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf); +} + +static void __redisReaderSetErrorOOM(redisReader *r) { + __redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory"); +} + +static char *readBytes(redisReader *r, unsigned int bytes) { + char *p; + if (r->len-r->pos >= bytes) { + p = r->buf+r->pos; + r->pos += bytes; + return p; + } + return NULL; +} + +/* Find pointer to \r\n. */ +static char *seekNewline(char *s, size_t len) { + int pos = 0; + int _len = len-1; + + /* Position should be < len-1 because the character at "pos" should be + * followed by a \n. Note that strchr cannot be used because it doesn't + * allow to search a limited length and the buffer that is being searched + * might not have a trailing NULL character. */ + while (pos < _len) { + while(pos < _len && s[pos] != '\r') pos++; + if (s[pos] != '\r') { + /* Not found. */ + return NULL; + } else { + if (s[pos+1] == '\n') { + /* Found. */ + return s+pos; + } else { + /* Continue searching. */ + pos++; + } + } + } + return NULL; +} + +/* Read a long long value starting at *s, under the assumption that it will be + * terminated by \r\n. Ambiguously returns -1 for unexpected input. */ +static long long readLongLong(char *s) { + long long v = 0; + int dec, mult = 1; + char c; + + if (*s == '-') { + mult = -1; + s++; + } else if (*s == '+') { + mult = 1; + s++; + } + + while ((c = *(s++)) != '\r') { + dec = c - '0'; + if (dec >= 0 && dec < 10) { + v *= 10; + v += dec; + } else { + /* Should not happen... */ + return -1; + } + } + + return mult*v; +} + +static char *readLine(redisReader *r, int *_len) { + char *p, *s; + int len; + + p = r->buf+r->pos; + s = seekNewline(p,(r->len-r->pos)); + if (s != NULL) { + len = s-(r->buf+r->pos); + r->pos += len+2; /* skip \r\n */ + if (_len) *_len = len; + return p; + } + return NULL; +} + +static void moveToNextTask(redisReader *r) { + redisReadTask *cur, *prv; + while (r->ridx >= 0) { + /* Return a.s.a.p. when the stack is now empty. */ + if (r->ridx == 0) { + r->ridx--; + return; + } + + cur = &(r->rstack[r->ridx]); + prv = &(r->rstack[r->ridx-1]); + assert(prv->type == REDIS_REPLY_ARRAY); + if (cur->idx == prv->elements-1) { + r->ridx--; + } else { + /* Reset the type because the next item can be anything */ + assert(cur->idx < prv->elements); + cur->type = -1; + cur->elements = -1; + cur->idx++; + return; + } + } +} + +static int processLineItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + int len; + + if ((p = readLine(r,&len)) != NULL) { + if (cur->type == REDIS_REPLY_INTEGER) { + if (r->fn && r->fn->createInteger) + obj = r->fn->createInteger(cur,readLongLong(p)); + else + obj = (void*)REDIS_REPLY_INTEGER; + } else { + /* Type will be error or status. */ + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,p,len); + else + obj = (void*)(size_t)(cur->type); + } + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj = NULL; + char *p, *s; + long len; + unsigned long bytelen; + int success = 0; + + p = r->buf+r->pos; + s = seekNewline(p,r->len-r->pos); + if (s != NULL) { + p = r->buf+r->pos; + bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ + len = readLongLong(p); + + if (len < 0) { + /* The nil object can always be created. */ + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + success = 1; + } else { + /* Only continue when the buffer contains the entire bulk item. */ + bytelen += len+2; /* include \r\n */ + if (r->pos+bytelen <= r->len) { + if (r->fn && r->fn->createString) + obj = r->fn->createString(cur,s+2,len); + else + obj = (void*)REDIS_REPLY_STRING; + success = 1; + } + } + + /* Proceed when obj was created. */ + if (success) { + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->pos += bytelen; + + /* Set reply if this is the root object. */ + if (r->ridx == 0) r->reply = obj; + moveToNextTask(r); + return REDIS_OK; + } + } + + return REDIS_ERR; +} + +static int processMultiBulkItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + void *obj; + char *p; + long elements; + int root = 0; + + /* Set error for nested multi bulks with depth > 7 */ + if (r->ridx == 8) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "No support for nested multi bulk replies with depth > 7"); + return REDIS_ERR; + } + + if ((p = readLine(r,NULL)) != NULL) { + elements = readLongLong(p); + root = (r->ridx == 0); + + if (elements == -1) { + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + moveToNextTask(r); + } else { + if (r->fn && r->fn->createArray) + obj = r->fn->createArray(cur,elements); + else + obj = (void*)REDIS_REPLY_ARRAY; + + if (obj == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + /* Modify task stack when there are more than 0 elements. */ + if (elements > 0) { + cur->elements = elements; + cur->obj = obj; + r->ridx++; + r->rstack[r->ridx].type = -1; + r->rstack[r->ridx].elements = -1; + r->rstack[r->ridx].idx = 0; + r->rstack[r->ridx].obj = NULL; + r->rstack[r->ridx].parent = cur; + r->rstack[r->ridx].privdata = r->privdata; + } else { + moveToNextTask(r); + } + } + + /* Set reply if this is the root object. */ + if (root) r->reply = obj; + return REDIS_OK; + } + + return REDIS_ERR; +} + +static int processItem(redisReader *r) { + redisReadTask *cur = &(r->rstack[r->ridx]); + char *p; + + /* check if we need to read type */ + if (cur->type < 0) { + if ((p = readBytes(r,1)) != NULL) { + switch (p[0]) { + case '-': + cur->type = REDIS_REPLY_ERROR; + break; + case '+': + cur->type = REDIS_REPLY_STATUS; + break; + case ':': + cur->type = REDIS_REPLY_INTEGER; + break; + case '$': + cur->type = REDIS_REPLY_STRING; + break; + case '*': + cur->type = REDIS_REPLY_ARRAY; + break; + default: + __redisReaderSetErrorProtocolByte(r,*p); + return REDIS_ERR; + } + } else { + /* could not consume 1 byte */ + return REDIS_ERR; + } + } + + /* process typed item */ + switch(cur->type) { + case REDIS_REPLY_ERROR: + case REDIS_REPLY_STATUS: + case REDIS_REPLY_INTEGER: + return processLineItem(r); + case REDIS_REPLY_STRING: + return processBulkItem(r); + case REDIS_REPLY_ARRAY: + return processMultiBulkItem(r); + default: + assert(NULL); + return REDIS_ERR; /* Avoid warning. */ + } +} + +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { + redisReader *r; + + r = calloc(sizeof(redisReader),1); + if (r == NULL) + return NULL; + + r->err = 0; + r->errstr[0] = '\0'; + r->fn = fn; + r->buf = sdsempty(); + r->maxbuf = REDIS_READER_MAX_BUF; + if (r->buf == NULL) { + free(r); + return NULL; + } + + r->ridx = -1; + return r; +} + +void redisReaderFree(redisReader *r) { + if (r->reply != NULL && r->fn && r->fn->freeObject) + r->fn->freeObject(r->reply); + if (r->buf != NULL) + sdsfree(r->buf); + free(r); +} + +int redisReaderFeed(redisReader *r, const char *buf, size_t len) { + sds newbuf; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* Copy the provided buffer. */ + if (buf != NULL && len >= 1) { + /* Destroy internal buffer when it is empty and is quite large. */ + if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { + sdsfree(r->buf); + r->buf = sdsempty(); + r->pos = 0; + + /* r->buf should not be NULL since we just free'd a larger one. */ + assert(r->buf != NULL); + } + + newbuf = sdscatlen(r->buf,buf,len); + if (newbuf == NULL) { + __redisReaderSetErrorOOM(r); + return REDIS_ERR; + } + + r->buf = newbuf; + r->len = sdslen(r->buf); + } + + return REDIS_OK; +} + +int redisReaderGetReply(redisReader *r, void **reply) { + /* Default target pointer to NULL. */ + if (reply != NULL) + *reply = NULL; + + /* Return early when this reader is in an erroneous state. */ + if (r->err) + return REDIS_ERR; + + /* When the buffer is empty, there will never be a reply. */ + if (r->len == 0) + return REDIS_OK; + + /* Set first item to process when the stack is empty. */ + if (r->ridx == -1) { + r->rstack[0].type = -1; + r->rstack[0].elements = -1; + r->rstack[0].idx = -1; + r->rstack[0].obj = NULL; + r->rstack[0].parent = NULL; + r->rstack[0].privdata = r->privdata; + r->ridx = 0; + } + + /* Process items in reply. */ + while (r->ridx >= 0) + if (processItem(r) != REDIS_OK) + break; + + /* Return ASAP when an error occurred. */ + if (r->err) + return REDIS_ERR; + + /* Discard part of the buffer when we've consumed at least 1k, to avoid + * doing unnecessary calls to memmove() in sds.c. */ + if (r->pos >= 1024) { + sdsrange(r->buf,r->pos,-1); + r->pos = 0; + r->len = sdslen(r->buf); + } + + /* Emit a reply when there is one. */ + if (r->ridx == -1) { + if (reply != NULL) + *reply = r->reply; + r->reply = NULL; + } + return REDIS_OK; +} diff --git a/ext/hiredis-vip-0.3.0/read.h b/ext/hiredis-vip-0.3.0/read.h new file mode 100644 index 000000000..088c97903 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/read.h @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * + * 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. + */ + + +#ifndef __HIREDIS_READ_H +#define __HIREDIS_READ_H +#include /* for size_t */ + +#define REDIS_ERR -1 +#define REDIS_OK 0 + +/* When an error occurs, the err flag in a context is set to hold the type of + * error that occured. REDIS_ERR_IO means there was an I/O error and you + * should use the "errno" variable to find out what is wrong. + * For other values, the "errstr" field will hold a description. */ +#define REDIS_ERR_IO 1 /* Error in read or write */ +#define REDIS_ERR_EOF 3 /* End of file */ +#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ +#define REDIS_ERR_OOM 5 /* Out of memory */ +#define REDIS_ERR_OTHER 2 /* Everything else... */ +#if 1 //shenzheng 2015-8-10 redis cluster +#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6 +#endif //shenzheng 2015-8-10 redis cluster + +#define REDIS_REPLY_STRING 1 +#define REDIS_REPLY_ARRAY 2 +#define REDIS_REPLY_INTEGER 3 +#define REDIS_REPLY_NIL 4 +#define REDIS_REPLY_STATUS 5 +#define REDIS_REPLY_ERROR 6 + +#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ + +#if 1 //shenzheng 2015-8-22 redis cluster +#define REDIS_ERROR_MOVED "MOVED" +#define REDIS_ERROR_ASK "ASK" +#define REDIS_ERROR_TRYAGAIN "TRYAGAIN" +#define REDIS_ERROR_CROSSSLOT "CROSSSLOT" +#define REDIS_ERROR_CLUSTERDOWN "CLUSTERDOWN" + +#define REDIS_STATUS_OK "OK" +#endif //shenzheng 2015-9-24 redis cluster + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct redisReadTask { + int type; + int elements; /* number of elements in multibulk container */ + int idx; /* index in parent (array) object */ + void *obj; /* holds user-generated value for a read task */ + struct redisReadTask *parent; /* parent task */ + void *privdata; /* user-settable arbitrary field */ +} redisReadTask; + +typedef struct redisReplyObjectFunctions { + void *(*createString)(const redisReadTask*, char*, size_t); + void *(*createArray)(const redisReadTask*, int); + void *(*createInteger)(const redisReadTask*, long long); + void *(*createNil)(const redisReadTask*); + void (*freeObject)(void*); +} redisReplyObjectFunctions; + +typedef struct redisReader { + int err; /* Error flags, 0 when there is no error */ + char errstr[128]; /* String representation of error when applicable */ + + char *buf; /* Read buffer */ + size_t pos; /* Buffer cursor */ + size_t len; /* Buffer length */ + size_t maxbuf; /* Max length of unused buffer */ + + redisReadTask rstack[9]; + int ridx; /* Index of current read task */ + void *reply; /* Temporary reply pointer */ + + redisReplyObjectFunctions *fn; + void *privdata; +} redisReader; + +/* Public API for the protocol parser. */ +redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); +void redisReaderFree(redisReader *r); +int redisReaderFeed(redisReader *r, const char *buf, size_t len); +int redisReaderGetReply(redisReader *r, void **reply); + +/* Backwards compatibility, can be removed on big version bump. */ +#define redisReplyReaderCreate redisReaderCreate +#define redisReplyReaderFree redisReaderFree +#define redisReplyReaderFeed redisReaderFeed +#define redisReplyReaderGetReply redisReaderGetReply +#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ext/hiredis-vip-0.3.0/sds.c b/ext/hiredis-vip-0.3.0/sds.c new file mode 100644 index 000000000..5d55b7792 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/sds.c @@ -0,0 +1,1095 @@ +/* SDS (Simple Dynamic Strings), A C dynamic strings library. + * + * Copyright (c) 2006-2014, Salvatore Sanfilippo + * 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 +#include +#include +#include +#include + +#include "sds.h" + +/* Create a new sds string with the content specified by the 'init' pointer + * and 'initlen'. + * If NULL is used for 'init' the string is initialized with zero bytes. + * + * The string is always null-termined (all the sds strings are, always) so + * even if you create an sds string with: + * + * mystring = sdsnewlen("abc",3"); + * + * You can print the string with printf() as there is an implicit \0 at the + * end of the string. However the string is binary safe and can contain + * \0 characters in the middle, as the length is stored in the sds header. */ +sds sdsnewlen(const void *init, size_t initlen) { + struct sdshdr *sh; + + if (init) { + sh = malloc(sizeof *sh+initlen+1); + } else { + sh = calloc(sizeof *sh+initlen+1,1); + } + if (sh == NULL) return NULL; + sh->len = initlen; + sh->free = 0; + if (initlen && init) + memcpy(sh->buf, init, initlen); + sh->buf[initlen] = '\0'; + return (char*)sh->buf; +} + +/* Create an empty (zero length) sds string. Even in this case the string + * always has an implicit null term. */ +sds sdsempty(void) { + return sdsnewlen("",0); +} + +/* Create a new sds string starting from a null termined C string. */ +sds sdsnew(const char *init) { + size_t initlen = (init == NULL) ? 0 : strlen(init); + return sdsnewlen(init, initlen); +} + +/* Duplicate an sds string. */ +sds sdsdup(const sds s) { + return sdsnewlen(s, sdslen(s)); +} + +/* Free an sds string. No operation is performed if 's' is NULL. */ +void sdsfree(sds s) { + if (s == NULL) return; + free(s-sizeof(struct sdshdr)); +} + +/* Set the sds string length to the length as obtained with strlen(), so + * considering as content only up to the first null term character. + * + * This function is useful when the sds string is hacked manually in some + * way, like in the following example: + * + * s = sdsnew("foobar"); + * s[2] = '\0'; + * sdsupdatelen(s); + * printf("%d\n", sdslen(s)); + * + * The output will be "2", but if we comment out the call to sdsupdatelen() + * the output will be "6" as the string was modified but the logical length + * remains 6 bytes. */ +void sdsupdatelen(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + int reallen = strlen(s); + sh->free += (sh->len-reallen); + sh->len = reallen; +} + +/* Modify an sds string on-place to make it empty (zero length). + * However all the existing buffer is not discarded but set as free space + * so that next append operations will not require allocations up to the + * number of bytes previously available. */ +void sdsclear(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + sh->free += sh->len; + sh->len = 0; + sh->buf[0] = '\0'; +} + +/* Enlarge the free space at the end of the sds string so that the caller + * is sure that after calling this function can overwrite up to addlen + * bytes after the end of the string, plus one more byte for nul term. + * + * Note: this does not change the *length* of the sds string as returned + * by sdslen(), but only the free buffer space we have. */ +sds sdsMakeRoomFor(sds s, size_t addlen) { + struct sdshdr *sh, *newsh; + size_t free = sdsavail(s); + size_t len, newlen; + + if (free >= addlen) return s; + len = sdslen(s); + sh = (void*) (s-sizeof *sh); + newlen = (len+addlen); + if (newlen < SDS_MAX_PREALLOC) + newlen *= 2; + else + newlen += SDS_MAX_PREALLOC; + newsh = realloc(sh, sizeof *newsh+newlen+1); + if (newsh == NULL) return NULL; + + newsh->free = newlen - len; + return newsh->buf; +} + +/* Reallocate the sds string so that it has no free space at the end. The + * contained string remains not altered, but next concatenation operations + * will require a reallocation. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdsRemoveFreeSpace(sds s) { + struct sdshdr *sh; + + sh = (void*) (s-sizeof *sh); + sh = realloc(sh, sizeof *sh+sh->len+1); + sh->free = 0; + return sh->buf; +} + +/* Return the total size of the allocation of the specifed sds string, + * including: + * 1) The sds header before the pointer. + * 2) The string. + * 3) The free buffer at the end if any. + * 4) The implicit null term. + */ +size_t sdsAllocSize(sds s) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + + return sizeof(*sh)+sh->len+sh->free+1; +} + +/* Increment the sds length and decrements the left free space at the + * end of the string according to 'incr'. Also set the null term + * in the new end of the string. + * + * This function is used in order to fix the string length after the + * user calls sdsMakeRoomFor(), writes something after the end of + * the current string, and finally needs to set the new length. + * + * Note: it is possible to use a negative increment in order to + * right-trim the string. + * + * Usage example: + * + * Using sdsIncrLen() and sdsMakeRoomFor() it is possible to mount the + * following schema, to cat bytes coming from the kernel to the end of an + * sds string without copying into an intermediate buffer: + * + * oldlen = sdslen(s); + * s = sdsMakeRoomFor(s, BUFFER_SIZE); + * nread = read(fd, s+oldlen, BUFFER_SIZE); + * ... check for nread <= 0 and handle it ... + * sdsIncrLen(s, nread); + */ +void sdsIncrLen(sds s, int incr) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + + assert(sh->free >= incr); + sh->len += incr; + sh->free -= incr; + assert(sh->free >= 0); + s[sh->len] = '\0'; +} + +/* Grow the sds to have the specified length. Bytes that were not part of + * the original length of the sds will be set to zero. + * + * if the specified length is smaller than the current length, no operation + * is performed. */ +sds sdsgrowzero(sds s, size_t len) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + size_t totlen, curlen = sh->len; + + if (len <= curlen) return s; + s = sdsMakeRoomFor(s,len-curlen); + if (s == NULL) return NULL; + + /* Make sure added region doesn't contain garbage */ + sh = (void*)(s-sizeof *sh); + memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ + totlen = sh->len+sh->free; + sh->len = len; + sh->free = totlen-sh->len; + return s; +} + +/* Append the specified binary-safe string pointed by 't' of 'len' bytes to the + * end of the specified sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatlen(sds s, const void *t, size_t len) { + struct sdshdr *sh; + size_t curlen = sdslen(s); + + s = sdsMakeRoomFor(s,len); + if (s == NULL) return NULL; + sh = (void*) (s-sizeof *sh); + memcpy(s+curlen, t, len); + sh->len = curlen+len; + sh->free = sh->free-len; + s[curlen+len] = '\0'; + return s; +} + +/* Append the specified null termianted C string to the sds string 's'. + * + * After the call, the passed sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscat(sds s, const char *t) { + return sdscatlen(s, t, strlen(t)); +} + +/* Append the specified sds 't' to the existing sds 's'. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); +} + +/* Destructively modify the sds string 's' to hold the specified binary + * safe string pointed by 't' of length 'len' bytes. */ +sds sdscpylen(sds s, const char *t, size_t len) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + size_t totlen = sh->free+sh->len; + + if (totlen < len) { + s = sdsMakeRoomFor(s,len-sh->len); + if (s == NULL) return NULL; + sh = (void*) (s-sizeof *sh); + totlen = sh->free+sh->len; + } + memcpy(s, t, len); + s[len] = '\0'; + sh->len = len; + sh->free = totlen-len; + return s; +} + +/* Like sdscpylen() but 't' must be a null-termined string so that the length + * of the string is obtained with strlen(). */ +sds sdscpy(sds s, const char *t) { + return sdscpylen(s, t, strlen(t)); +} + +/* Helper for sdscatlonglong() doing the actual number -> string + * conversion. 's' must point to a string with room for at least + * SDS_LLSTR_SIZE bytes. + * + * The function returns the lenght of the null-terminated string + * representation stored at 's'. */ +#define SDS_LLSTR_SIZE 21 +int sdsll2str(char *s, long long value) { + char *p, aux; + unsigned long long v; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + v = (value < 0) ? -value : value; + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p++ = '-'; + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Identical sdsll2str(), but for unsigned long long type. */ +int sdsull2str(char *s, unsigned long long v) { + char *p, aux; + size_t l; + + /* Generate the string representation, this method produces + * an reversed string. */ + p = s; + do { + *p++ = '0'+(v%10); + v /= 10; + } while(v); + + /* Compute length and add null term. */ + l = p-s; + *p = '\0'; + + /* Reverse the string. */ + p--; + while(s < p) { + aux = *s; + *s = *p; + *p = aux; + s++; + p--; + } + return l; +} + +/* Like sdscatpritf() but gets va_list instead of being variadic. */ +sds sdscatvprintf(sds s, const char *fmt, va_list ap) { + va_list cpy; + char *buf, *t; + size_t buflen = 16; + + while(1) { + buf = malloc(buflen); + if (buf == NULL) return NULL; + buf[buflen-2] = '\0'; + va_copy(cpy,ap); + vsnprintf(buf, buflen, fmt, cpy); + if (buf[buflen-2] != '\0') { + free(buf); + buflen *= 2; + continue; + } + break; + } + t = sdscat(s, buf); + free(buf); + return t; +} + +/* Append to the sds string 's' a string obtained using printf-alike format + * specifier. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("Sum is: "); + * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b); + * + * Often you need to create a string from scratch with the printf-alike + * format. When this is the need, just use sdsempty() as the target string: + * + * s = sdscatprintf(sdsempty(), "... your format ...", args); + */ +sds sdscatprintf(sds s, const char *fmt, ...) { + va_list ap; + char *t; + va_start(ap, fmt); + t = sdscatvprintf(s,fmt,ap); + va_end(ap); + return t; +} + +/* This function is similar to sdscatprintf, but much faster as it does + * not rely on sprintf() family functions implemented by the libc that + * are often very slow. Moreover directly handling the sds string as + * new data is concatenated provides a performance improvement. + * + * However this function only handles an incompatible subset of printf-alike + * format specifiers: + * + * %s - C String + * %S - SDS string + * %i - signed int + * %I - 64 bit signed integer (long long, int64_t) + * %u - unsigned int + * %U - 64 bit unsigned integer (unsigned long long, uint64_t) + * %T - A size_t variable. + * %% - Verbatim "%" character. + */ +sds sdscatfmt(sds s, char const *fmt, ...) { + struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr))); + size_t initlen = sdslen(s); + const char *f = fmt; + int i; + va_list ap; + + va_start(ap,fmt); + f = fmt; /* Next format specifier byte to process. */ + i = initlen; /* Position of the next byte to write to dest str. */ + while(*f) { + char next, *str; + int l; + long long num; + unsigned long long unum; + + /* Make sure there is always space for at least 1 char. */ + if (sh->free == 0) { + s = sdsMakeRoomFor(s,1); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + + switch(*f) { + case '%': + next = *(f+1); + f++; + switch(next) { + case 's': + case 'S': + str = va_arg(ap,char*); + l = (next == 's') ? strlen(str) : sdslen(str); + if (sh->free < l) { + s = sdsMakeRoomFor(s,l); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + memcpy(s+i,str,l); + sh->len += l; + sh->free -= l; + i += l; + break; + case 'i': + case 'I': + if (next == 'i') + num = va_arg(ap,int); + else + num = va_arg(ap,long long); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsll2str(buf,num); + if (sh->free < l) { + s = sdsMakeRoomFor(s,l); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + memcpy(s+i,buf,l); + sh->len += l; + sh->free -= l; + i += l; + } + break; + case 'u': + case 'U': + case 'T': + if (next == 'u') + unum = va_arg(ap,unsigned int); + else if(next == 'U') + unum = va_arg(ap,unsigned long long); + else + unum = (unsigned long long)va_arg(ap,size_t); + { + char buf[SDS_LLSTR_SIZE]; + l = sdsull2str(buf,unum); + if (sh->free < l) { + s = sdsMakeRoomFor(s,l); + sh = (void*) (s-(sizeof(struct sdshdr))); + } + memcpy(s+i,buf,l); + sh->len += l; + sh->free -= l; + i += l; + } + break; + default: /* Handle %% and generally %. */ + s[i++] = next; + sh->len += 1; + sh->free -= 1; + break; + } + break; + default: + s[i++] = *f; + sh->len += 1; + sh->free -= 1; + break; + } + f++; + } + va_end(ap); + + /* Add null-term */ + s[i] = '\0'; + return s; +} + + +/* Remove the part of the string from left and from right composed just of + * contiguous characters found in 'cset', that is a null terminted C string. + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. + * + * Example: + * + * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); + * s = sdstrim(s,"A. :"); + * printf("%s\n", s); + * + * Output will be just "Hello World". + */ +void sdstrim(sds s, const char *cset) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + char *start, *end, *sp, *ep; + size_t len; + + sp = start = s; + ep = end = s+sdslen(s)-1; + while(sp <= end && strchr(cset, *sp)) sp++; + while(ep > start && strchr(cset, *ep)) ep--; + len = (sp > ep) ? 0 : ((ep-sp)+1); + if (sh->buf != sp) memmove(sh->buf, sp, len); + sh->buf[len] = '\0'; + sh->free = sh->free+(sh->len-len); + sh->len = len; +} + +/* Turn the string into a smaller (or equal) string containing only the + * substring specified by the 'start' and 'end' indexes. + * + * start and end can be negative, where -1 means the last character of the + * string, -2 the penultimate character, and so forth. + * + * The interval is inclusive, so the start and end characters will be part + * of the resulting string. + * + * The string is modified in-place. + * + * Example: + * + * s = sdsnew("Hello World"); + * sdsrange(s,1,-1); => "ello World" + */ +void sdsrange(sds s, int start, int end) { + struct sdshdr *sh = (void*) (s-sizeof *sh); + size_t newlen, len = sdslen(s); + + if (len == 0) return; + if (start < 0) { + start = len+start; + if (start < 0) start = 0; + } + if (end < 0) { + end = len+end; + if (end < 0) end = 0; + } + newlen = (start > end) ? 0 : (end-start)+1; + if (newlen != 0) { + if (start >= (signed)len) { + newlen = 0; + } else if (end >= (signed)len) { + end = len-1; + newlen = (start > end) ? 0 : (end-start)+1; + } + } else { + start = 0; + } + if (start && newlen) memmove(sh->buf, sh->buf+start, newlen); + sh->buf[newlen] = 0; + sh->free = sh->free+(sh->len-newlen); + sh->len = newlen; +} + +/* Apply tolower() to every character of the sds string 's'. */ +void sdstolower(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = tolower(s[j]); +} + +/* Apply toupper() to every character of the sds string 's'. */ +void sdstoupper(sds s) { + int len = sdslen(s), j; + + for (j = 0; j < len; j++) s[j] = toupper(s[j]); +} + +/* Compare two sds strings s1 and s2 with memcmp(). + * + * Return value: + * + * 1 if s1 > s2. + * -1 if s1 < s2. + * 0 if s1 and s2 are exactly the same binary string. + * + * If two strings share exactly the same prefix, but one of the two has + * additional characters, the longer string is considered to be greater than + * the smaller one. */ +int sdscmp(const sds s1, const sds s2) { + size_t l1, l2, minlen; + int cmp; + + l1 = sdslen(s1); + l2 = sdslen(s2); + minlen = (l1 < l2) ? l1 : l2; + cmp = memcmp(s1,s2,minlen); + if (cmp == 0) return l1-l2; + return cmp; +} + +/* Split 's' with separator in 'sep'. An array + * of sds strings is returned. *count will be set + * by reference to the number of tokens returned. + * + * On out of memory, zero length string, zero length + * separator, NULL is returned. + * + * Note that 'sep' is able to split a string using + * a multi-character separator. For example + * sdssplit("foo_-_bar","_-_"); will return two + * elements "foo" and "bar". + * + * This version of the function is binary-safe but + * requires length arguments. sdssplit() is just the + * same function but for zero-terminated strings. + */ +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count) { + int elements = 0, slots = 5, start = 0, j; + sds *tokens; + + if (seplen < 1 || len < 0) return NULL; + + tokens = malloc(sizeof(sds)*slots); + if (tokens == NULL) return NULL; + + if (len == 0) { + *count = 0; + return tokens; + } + for (j = 0; j < (len-(seplen-1)); j++) { + /* make sure there is room for the next element and the final one */ + if (slots < elements+2) { + sds *newtokens; + + slots *= 2; + newtokens = realloc(tokens,sizeof(sds)*slots); + if (newtokens == NULL) goto cleanup; + tokens = newtokens; + } + /* search the separator */ + if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) { + tokens[elements] = sdsnewlen(s+start,j-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + start = j+seplen; + j = j+seplen-1; /* skip the separator */ + } + } + /* Add the final element. We are sure there is room in the tokens array. */ + tokens[elements] = sdsnewlen(s+start,len-start); + if (tokens[elements] == NULL) goto cleanup; + elements++; + *count = elements; + return tokens; + +cleanup: + { + int i; + for (i = 0; i < elements; i++) sdsfree(tokens[i]); + free(tokens); + *count = 0; + return NULL; + } +} + +/* Free the result returned by sdssplitlen(), or do nothing if 'tokens' is NULL. */ +void sdsfreesplitres(sds *tokens, int count) { + if (!tokens) return; + while(count--) + sdsfree(tokens[count]); + free(tokens); +} + +/* Create an sds string from a long long value. It is much faster than: + * + * sdscatprintf(sdsempty(),"%lld\n", value); + */ +sds sdsfromlonglong(long long value) { + char buf[32], *p; + unsigned long long v; + + v = (value < 0) ? -value : value; + p = buf+31; /* point to the last character */ + do { + *p-- = '0'+(v%10); + v /= 10; + } while(v); + if (value < 0) *p-- = '-'; + p++; + return sdsnewlen(p,32-(p-buf)); +} + +/* Append to the sds string "s" an escaped string representation where + * all the non-printable characters (tested with isprint()) are turned into + * escapes in the form "\n\r\a...." or "\x". + * + * After the call, the modified sds string is no longer valid and all the + * references must be substituted with the new pointer returned by the call. */ +sds sdscatrepr(sds s, const char *p, size_t len) { + s = sdscatlen(s,"\"",1); + while(len--) { + switch(*p) { + case '\\': + case '"': + s = sdscatprintf(s,"\\%c",*p); + break; + case '\n': s = sdscatlen(s,"\\n",2); break; + case '\r': s = sdscatlen(s,"\\r",2); break; + case '\t': s = sdscatlen(s,"\\t",2); break; + case '\a': s = sdscatlen(s,"\\a",2); break; + case '\b': s = sdscatlen(s,"\\b",2); break; + default: + if (isprint(*p)) + s = sdscatprintf(s,"%c",*p); + else + s = sdscatprintf(s,"\\x%02x",(unsigned char)*p); + break; + } + p++; + } + return sdscatlen(s,"\"",1); +} + +/* Helper function for sdssplitargs() that returns non zero if 'c' + * is a valid hex digit. */ +int is_hex_digit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); +} + +/* Helper function for sdssplitargs() that converts a hex digit into an + * integer from 0 to 15 */ +int hex_digit_to_int(char c) { + switch(c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': case 'A': return 10; + case 'b': case 'B': return 11; + case 'c': case 'C': return 12; + case 'd': case 'D': return 13; + case 'e': case 'E': return 14; + case 'f': case 'F': return 15; + default: return 0; + } +} + +/* Split a line into arguments, where every argument can be in the + * following programming-language REPL-alike form: + * + * foo bar "newline are supported\n" and "\xff\x00otherstuff" + * + * The number of arguments is stored into *argc, and an array + * of sds is returned. + * + * The caller should free the resulting array of sds strings with + * sdsfreesplitres(). + * + * Note that sdscatrepr() is able to convert back a string into + * a quoted string in the same format sdssplitargs() is able to parse. + * + * The function returns the allocated tokens on success, even when the + * input string is empty, or NULL if the input contains unbalanced + * quotes or closed quotes followed by non space characters + * as in: "foo"bar or "foo' + */ +sds *sdssplitargs(const char *line, int *argc) { + const char *p = line; + char *current = NULL; + char **vector = NULL; + + *argc = 0; + while(1) { + /* skip blanks */ + while(*p && isspace(*p)) p++; + if (*p) { + /* get a token */ + int inq=0; /* set to 1 if we are in "quotes" */ + int insq=0; /* set to 1 if we are in 'single quotes' */ + int done=0; + + if (current == NULL) current = sdsempty(); + while(!done) { + if (inq) { + if (*p == '\\' && *(p+1) == 'x' && + is_hex_digit(*(p+2)) && + is_hex_digit(*(p+3))) + { + unsigned char byte; + + byte = (hex_digit_to_int(*(p+2))*16)+ + hex_digit_to_int(*(p+3)); + current = sdscatlen(current,(char*)&byte,1); + p += 3; + } else if (*p == '\\' && *(p+1)) { + char c; + + p++; + switch(*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'b': c = '\b'; break; + case 'a': c = '\a'; break; + default: c = *p; break; + } + current = sdscatlen(current,&c,1); + } else if (*p == '"') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else if (insq) { + if (*p == '\\' && *(p+1) == '\'') { + p++; + current = sdscatlen(current,"'",1); + } else if (*p == '\'') { + /* closing quote must be followed by a space or + * nothing at all. */ + if (*(p+1) && !isspace(*(p+1))) goto err; + done=1; + } else if (!*p) { + /* unterminated quotes */ + goto err; + } else { + current = sdscatlen(current,p,1); + } + } else { + switch(*p) { + case ' ': + case '\n': + case '\r': + case '\t': + case '\0': + done=1; + break; + case '"': + inq=1; + break; + case '\'': + insq=1; + break; + default: + current = sdscatlen(current,p,1); + break; + } + } + if (*p) p++; + } + /* add the token to the vector */ + vector = realloc(vector,((*argc)+1)*sizeof(char*)); + vector[*argc] = current; + (*argc)++; + current = NULL; + } else { + /* Even on empty input string return something not NULL. */ + if (vector == NULL) vector = malloc(sizeof(void*)); + return vector; + } + } + +err: + while((*argc)--) + sdsfree(vector[*argc]); + free(vector); + if (current) sdsfree(current); + *argc = 0; + return NULL; +} + +/* Modify the string substituting all the occurrences of the set of + * characters specified in the 'from' string to the corresponding character + * in the 'to' array. + * + * For instance: sdsmapchars(mystring, "ho", "01", 2) + * will have the effect of turning the string "hello" into "0ell1". + * + * The function returns the sds string pointer, that is always the same + * as the input pointer since no resize is needed. */ +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { + size_t j, i, l = sdslen(s); + + for (j = 0; j < l; j++) { + for (i = 0; i < setlen; i++) { + if (s[j] == from[i]) { + s[j] = to[i]; + break; + } + } + } + return s; +} + +/* Join an array of C strings using the specified separator (also a C string). + * Returns the result as an sds string. */ +sds sdsjoin(char **argv, int argc, char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscat(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +/* Like sdsjoin, but joins an array of SDS strings. */ +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { + sds join = sdsempty(); + int j; + + for (j = 0; j < argc; j++) { + join = sdscatsds(join, argv[j]); + if (j != argc-1) join = sdscatlen(join,sep,seplen); + } + return join; +} + +#ifdef SDS_TEST_MAIN +#include +#include "testhelp.h" + +int main(void) { + { + struct sdshdr *sh; + sds x = sdsnew("foo"), y; + + test_cond("Create a string and obtain the length", + sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0) + + sdsfree(x); + x = sdsnewlen("foo",2); + test_cond("Create a string with specified length", + sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0) + + x = sdscat(x,"bar"); + test_cond("Strings concatenation", + sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0); + + x = sdscpy(x,"a"); + test_cond("sdscpy() against an originally longer string", + sdslen(x) == 1 && memcmp(x,"a\0",2) == 0) + + x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk"); + test_cond("sdscpy() against an originally shorter string", + sdslen(x) == 33 && + memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0) + + sdsfree(x); + x = sdscatprintf(sdsempty(),"%d",123); + test_cond("sdscatprintf() seems working in the base case", + sdslen(x) == 3 && memcmp(x,"123\0",4) ==0) + + sdsfree(x); + x = sdsnew("xxciaoyyy"); + sdstrim(x,"xy"); + test_cond("sdstrim() correctly trims characters", + sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0) + + y = sdsdup(x); + sdsrange(y,1,1); + test_cond("sdsrange(...,1,1)", + sdslen(y) == 1 && memcmp(y,"i\0",2) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,-1); + test_cond("sdsrange(...,1,-1)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,-2,-1); + test_cond("sdsrange(...,-2,-1)", + sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,2,1); + test_cond("sdsrange(...,2,1)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,1,100); + test_cond("sdsrange(...,1,100)", + sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0) + + sdsfree(y); + y = sdsdup(x); + sdsrange(y,100,100); + test_cond("sdsrange(...,100,100)", + sdslen(y) == 0 && memcmp(y,"\0",1) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("foo"); + y = sdsnew("foa"); + test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("bar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0) + + sdsfree(y); + sdsfree(x); + x = sdsnew("aar"); + y = sdsnew("bar"); + test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0) + + sdsfree(y); + sdsfree(x); + x = sdsnewlen("\a\n\0foo\r",7); + y = sdscatrepr(sdsempty(),x,sdslen(x)); + test_cond("sdscatrepr(...data...)", + memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) + + { + int oldfree; + + sdsfree(x); + x = sdsnew("0"); + sh = (void*) (x-(sizeof(struct sdshdr))); + test_cond("sdsnew() free/len buffers", sh->len == 1 && sh->free == 0); + x = sdsMakeRoomFor(x,1); + sh = (void*) (x-(sizeof(struct sdshdr))); + test_cond("sdsMakeRoomFor()", sh->len == 1 && sh->free > 0); + oldfree = sh->free; + x[1] = '1'; + sdsIncrLen(x,1); + test_cond("sdsIncrLen() -- content", x[0] == '0' && x[1] == '1'); + test_cond("sdsIncrLen() -- len", sh->len == 2); + test_cond("sdsIncrLen() -- free", sh->free == oldfree-1); + } + } + test_report() + return 0; +} +#endif diff --git a/ext/hiredis-vip-0.3.0/sds.h b/ext/hiredis-vip-0.3.0/sds.h new file mode 100644 index 000000000..19a2abd31 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/sds.h @@ -0,0 +1,105 @@ +/* SDS (Simple Dynamic Strings), A C dynamic strings library. + * + * Copyright (c) 2006-2014, Salvatore Sanfilippo + * 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. + */ + +#ifndef __SDS_H +#define __SDS_H + +#define SDS_MAX_PREALLOC (1024*1024) + +#include +#include +#ifdef _MSC_VER +#include "win32.h" +#endif + +typedef char *sds; + +struct sdshdr { + int len; + int free; + char buf[]; +}; + +static inline size_t sdslen(const sds s) { + struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); + return sh->len; +} + +static inline size_t sdsavail(const sds s) { + struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); + return sh->free; +} + +sds sdsnewlen(const void *init, size_t initlen); +sds sdsnew(const char *init); +sds sdsempty(void); +size_t sdslen(const sds s); +sds sdsdup(const sds s); +void sdsfree(sds s); +size_t sdsavail(const sds s); +sds sdsgrowzero(sds s, size_t len); +sds sdscatlen(sds s, const void *t, size_t len); +sds sdscat(sds s, const char *t); +sds sdscatsds(sds s, const sds t); +sds sdscpylen(sds s, const char *t, size_t len); +sds sdscpy(sds s, const char *t); + +sds sdscatvprintf(sds s, const char *fmt, va_list ap); +#ifdef __GNUC__ +sds sdscatprintf(sds s, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +sds sdscatprintf(sds s, const char *fmt, ...); +#endif + +sds sdscatfmt(sds s, char const *fmt, ...); +void sdstrim(sds s, const char *cset); +void sdsrange(sds s, int start, int end); +void sdsupdatelen(sds s); +void sdsclear(sds s); +int sdscmp(const sds s1, const sds s2); +sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); +void sdsfreesplitres(sds *tokens, int count); +void sdstolower(sds s); +void sdstoupper(sds s); +sds sdsfromlonglong(long long value); +sds sdscatrepr(sds s, const char *p, size_t len); +sds *sdssplitargs(const char *line, int *argc); +sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); +sds sdsjoin(char **argv, int argc, char *sep, size_t seplen); +sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); + +/* Low level functions exposed to the user API */ +sds sdsMakeRoomFor(sds s, size_t addlen); +void sdsIncrLen(sds s, int incr); +sds sdsRemoveFreeSpace(sds s); +size_t sdsAllocSize(sds s); + +#endif diff --git a/ext/hiredis-vip-0.3.0/test.c b/ext/hiredis-vip-0.3.0/test.c new file mode 100644 index 000000000..8fde55446 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/test.c @@ -0,0 +1,806 @@ +#include "fmacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "net.h" + +enum connection_type { + CONN_TCP, + CONN_UNIX, + CONN_FD +}; + +struct config { + enum connection_type type; + + struct { + const char *host; + int port; + struct timeval timeout; + } tcp; + + struct { + const char *path; + } unix; +}; + +/* The following lines make up our testing "framework" :) */ +static int tests = 0, fails = 0; +#define test(_s) { printf("#%02d ", ++tests); printf(_s); } +#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} + +static long long usec(void) { + struct timeval tv; + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; +} + +/* The assert() calls below have side effects, so we need assert() + * even if we are compiling without asserts (-DNDEBUG). */ +#ifdef NDEBUG +#undef assert +#define assert(e) (void)(e) +#endif + +static redisContext *select_database(redisContext *c) { + redisReply *reply; + + /* Switch to DB 9 for testing, now that we know we can chat. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Make sure the DB is emtpy */ + reply = redisCommand(c,"DBSIZE"); + assert(reply != NULL); + if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) { + /* Awesome, DB 9 is empty and we can continue. */ + freeReplyObject(reply); + } else { + printf("Database #9 is not empty, test can not continue\n"); + exit(1); + } + + return c; +} + +static int disconnect(redisContext *c, int keep_fd) { + redisReply *reply; + + /* Make sure we're on DB 9. */ + reply = redisCommand(c,"SELECT 9"); + assert(reply != NULL); + freeReplyObject(reply); + reply = redisCommand(c,"FLUSHDB"); + assert(reply != NULL); + freeReplyObject(reply); + + /* Free the context as well, but keep the fd if requested. */ + if (keep_fd) + return redisFreeKeepFd(c); + redisFree(c); + return -1; +} + +static redisContext *connect(struct config config) { + redisContext *c = NULL; + + if (config.type == CONN_TCP) { + c = redisConnect(config.tcp.host, config.tcp.port); + } else if (config.type == CONN_UNIX) { + c = redisConnectUnix(config.unix.path); + } else if (config.type == CONN_FD) { + /* Create a dummy connection just to get an fd to inherit */ + redisContext *dummy_ctx = redisConnectUnix(config.unix.path); + if (dummy_ctx) { + int fd = disconnect(dummy_ctx, 1); + printf("Connecting to inherited fd %d\n", fd); + c = redisConnectFd(fd); + } + } else { + assert(NULL); + } + + if (c == NULL) { + printf("Connection error: can't allocate redis context\n"); + exit(1); + } else if (c->err) { + printf("Connection error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + return select_database(c); +} + +static void test_format_commands(void) { + char *cmd; + int len; + + test("Format command without interpolation: "); + len = redisFormatCommand(&cmd,"SET foo bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s string interpolation: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%s and an empty string: "); + len = redisFormatCommand(&cmd,"SET %s %s","foo",""); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with an empty string in between proper interpolations: "); + len = redisFormatCommand(&cmd,"SET %s %s","","foo"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && + len == 4+4+(3+2)+4+(0+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b string interpolation: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command with %%b and an empty string: "); + len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(0+2)); + free(cmd); + + test("Format command with literal %%: "); + len = redisFormatCommand(&cmd,"SET %% %%"); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && + len == 4+4+(3+2)+4+(1+2)+4+(1+2)); + free(cmd); + + /* Vararg width depends on the type. These tests make sure that the + * width is correctly determined using the format and subsequent varargs + * can correctly be interpolated. */ +#define INTEGER_WIDTH_TEST(fmt, type) do { \ + type value = 123; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + +#define FLOAT_WIDTH_TEST(type) do { \ + type value = 123.0; \ + test("Format command with printf-delegation (" #type "): "); \ + len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ + test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ + len == 4+5+(12+2)+4+(9+2)); \ + free(cmd); \ +} while(0) + + INTEGER_WIDTH_TEST("d", int); + INTEGER_WIDTH_TEST("hhd", char); + INTEGER_WIDTH_TEST("hd", short); + INTEGER_WIDTH_TEST("ld", long); + INTEGER_WIDTH_TEST("lld", long long); + INTEGER_WIDTH_TEST("u", unsigned int); + INTEGER_WIDTH_TEST("hhu", unsigned char); + INTEGER_WIDTH_TEST("hu", unsigned short); + INTEGER_WIDTH_TEST("lu", unsigned long); + INTEGER_WIDTH_TEST("llu", unsigned long long); + FLOAT_WIDTH_TEST(float); + FLOAT_WIDTH_TEST(double); + + test("Format command with invalid printf format: "); + len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); + test_cond(len == -1); + + const char *argv[3]; + argv[0] = "SET"; + argv[1] = "foo\0xxx"; + argv[2] = "bar"; + size_t lens[3] = { 3, 7, 3 }; + int argc = 3; + + test("Format command by passing argc/argv without lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(3+2)+4+(3+2)); + free(cmd); + + test("Format command by passing argc/argv with lengths: "); + len = redisFormatCommandArgv(&cmd,argc,argv,lens); + test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && + len == 4+4+(3+2)+4+(7+2)+4+(3+2)); + free(cmd); +} + +static void test_append_formatted_commands(struct config config) { + redisContext *c; + redisReply *reply; + char *cmd; + int len; + + c = connect(config); + + test("Append format command: "); + + len = redisFormatCommand(&cmd, "SET foo bar"); + + test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK); + + assert(redisGetReply(c, (void*)&reply) == REDIS_OK); + + free(cmd); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_reply_reader(void) { + redisReader *reader; + void *reply; + int ret; + int i; + + test("Error handling in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + /* when the reply already contains multiple items, they must be free'd + * on an error. valgrind will bark when this doesn't happen. */ + test("Memory cleanup in reply parser: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*2\r\n",4); + redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11); + redisReaderFeed(reader,(char*)"@foo\r\n",6); + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); + redisReaderFree(reader); + + test("Set error on nested multi bulks with depth > 7: "); + reader = redisReaderCreate(); + + for (i = 0; i < 9; i++) { + redisReaderFeed(reader,(char*)"*1\r\n",4); + } + + ret = redisReaderGetReply(reader,NULL); + test_cond(ret == REDIS_ERR && + strncasecmp(reader->errstr,"No support for",14) == 0); + redisReaderFree(reader); + + test("Works with NULL functions for reply: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r\n",5); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Works when a single newline (\\r\\n) covers two calls to feed: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"+OK\r",4); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_OK && reply == NULL); + redisReaderFeed(reader,(char*)"\n",1); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS); + redisReaderFree(reader); + + test("Don't reset state after protocol error: "); + reader = redisReaderCreate(); + reader->fn = NULL; + redisReaderFeed(reader,(char*)"x",1); + ret = redisReaderGetReply(reader,&reply); + assert(ret == REDIS_ERR); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && reply == NULL); + redisReaderFree(reader); + + /* Regression test for issue #45 on GitHub. */ + test("Don't do empty allocation for empty multi bulk: "); + reader = redisReaderCreate(); + redisReaderFeed(reader,(char*)"*0\r\n",4); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_ARRAY && + ((redisReply*)reply)->elements == 0); + freeReplyObject(reply); + redisReaderFree(reader); +} + +static void test_free_null(void) { + void *redisContext = NULL; + void *reply = NULL; + + test("Don't fail when redisFree is passed a NULL value: "); + redisFree(redisContext); + test_cond(redisContext == NULL); + + test("Don't fail when freeReplyObject is passed a NULL value: "); + freeReplyObject(reply); + test_cond(reply == NULL); +} + +static void test_blocking_connection_errors(void) { + redisContext *c; + + test("Returns error when host cannot be resolved: "); + c = redisConnect((char*)"idontexist.test", 6379); + test_cond(c->err == REDIS_ERR_OTHER && + (strcmp(c->errstr,"Name or service not known") == 0 || + strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || + strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || + strcmp(c->errstr,"No address associated with hostname") == 0 || + strcmp(c->errstr,"Temporary failure in name resolution") == 0 || + strcmp(c->errstr,"no address associated with name") == 0)); + redisFree(c); + + test("Returns error when the port is not open: "); + c = redisConnect((char*)"localhost", 1); + test_cond(c->err == REDIS_ERR_IO && + strcmp(c->errstr,"Connection refused") == 0); + redisFree(c); + + test("Returns error when the unix socket path doesn't accept connections: "); + c = redisConnectUnix((char*)"/tmp/idontexist.sock"); + test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ + redisFree(c); +} + +static void test_blocking_connection(struct config config) { + redisContext *c; + redisReply *reply; + + c = connect(config); + + test("Is able to deliver commands: "); + reply = redisCommand(c,"PING"); + test_cond(reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"pong") == 0) + freeReplyObject(reply); + + test("Is a able to send commands verbatim: "); + reply = redisCommand(c,"SET foo bar"); + test_cond (reply->type == REDIS_REPLY_STATUS && + strcasecmp(reply->str,"ok") == 0) + freeReplyObject(reply); + + test("%%s String interpolation works: "); + reply = redisCommand(c,"SET %s %s","foo","hello world"); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + strcmp(reply->str,"hello world") == 0); + freeReplyObject(reply); + + test("%%b String interpolation works: "); + reply = redisCommand(c,"SET %b %b","foo",(size_t)3,"hello\x00world",(size_t)11); + freeReplyObject(reply); + reply = redisCommand(c,"GET foo"); + test_cond(reply->type == REDIS_REPLY_STRING && + memcmp(reply->str,"hello\x00world",11) == 0) + + test("Binary reply length is correct: "); + test_cond(reply->len == 11) + freeReplyObject(reply); + + test("Can parse nil replies: "); + reply = redisCommand(c,"GET nokey"); + test_cond(reply->type == REDIS_REPLY_NIL) + freeReplyObject(reply); + + /* test 7 */ + test("Can parse integer replies: "); + reply = redisCommand(c,"INCR mycounter"); + test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1) + freeReplyObject(reply); + + test("Can parse multi bulk replies: "); + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + freeReplyObject(redisCommand(c,"LPUSH mylist bar")); + reply = redisCommand(c,"LRANGE mylist 0 -1"); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + !memcmp(reply->element[0]->str,"bar",3) && + !memcmp(reply->element[1]->str,"foo",3)) + freeReplyObject(reply); + + /* m/e with multi bulk reply *before* other reply. + * specifically test ordering of reply items to parse. */ + test("Can handle nested multi bulk replies: "); + freeReplyObject(redisCommand(c,"MULTI")); + freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1")); + freeReplyObject(redisCommand(c,"PING")); + reply = (redisCommand(c,"EXEC")); + test_cond(reply->type == REDIS_REPLY_ARRAY && + reply->elements == 2 && + reply->element[0]->type == REDIS_REPLY_ARRAY && + reply->element[0]->elements == 2 && + !memcmp(reply->element[0]->element[0]->str,"bar",3) && + !memcmp(reply->element[0]->element[1]->str,"foo",3) && + reply->element[1]->type == REDIS_REPLY_STATUS && + strcasecmp(reply->element[1]->str,"pong") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_connection_timeouts(struct config config) { + redisContext *c; + redisReply *reply; + ssize_t s; + const char *cmd = "DEBUG SLEEP 3\r\n"; + struct timeval tv; + + c = connect(config); + test("Successfully completes a command when the timeout is not exceeded: "); + reply = redisCommand(c,"SET foo fast"); + freeReplyObject(reply); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0); + freeReplyObject(reply); + disconnect(c, 0); + + c = connect(config); + test("Does not return a reply when the command times out: "); + s = write(c->fd, cmd, strlen(cmd)); + tv.tv_sec = 0; + tv.tv_usec = 10000; + redisSetTimeout(c, tv); + reply = redisCommand(c, "GET foo"); + test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); + freeReplyObject(reply); + + test("Reconnect properly reconnects after a timeout: "); + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + test("Reconnect properly uses owned parameters: "); + config.tcp.host = "foo"; + config.unix.path = "foo"; + redisReconnect(c); + reply = redisCommand(c, "PING"); + test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); + freeReplyObject(reply); + + disconnect(c, 0); +} + +static void test_blocking_io_errors(struct config config) { + redisContext *c; + redisReply *reply; + void *_reply; + int major, minor; + + /* Connect to target given by config. */ + c = connect(config); + { + /* Find out Redis version to determine the path for the next test */ + const char *field = "redis_version:"; + char *p, *eptr; + + reply = redisCommand(c,"INFO"); + p = strstr(reply->str,field); + major = strtol(p+strlen(field),&eptr,10); + p = eptr+1; /* char next to the first "." */ + minor = strtol(p,&eptr,10); + freeReplyObject(reply); + } + + test("Returns I/O error when the connection is lost: "); + reply = redisCommand(c,"QUIT"); + if (major > 2 || (major == 2 && minor > 0)) { + /* > 2.0 returns OK on QUIT and read() should be issued once more + * to know the descriptor is at EOF. */ + test_cond(strcasecmp(reply->str,"OK") == 0 && + redisGetReply(c,&_reply) == REDIS_ERR); + freeReplyObject(reply); + } else { + test_cond(reply == NULL); + } + + /* On 2.0, QUIT will cause the connection to be closed immediately and + * the read(2) for the reply on QUIT will set the error to EOF. + * On >2.0, QUIT will return with OK and another read(2) needed to be + * issued to find out the socket was closed by the server. In both + * conditions, the error will be set to EOF. */ + assert(c->err == REDIS_ERR_EOF && + strcmp(c->errstr,"Server closed the connection") == 0); + redisFree(c); + + c = connect(config); + test("Returns I/O error on socket timeout: "); + struct timeval tv = { 0, 1000 }; + assert(redisSetTimeout(c,tv) == REDIS_OK); + test_cond(redisGetReply(c,&_reply) == REDIS_ERR && + c->err == REDIS_ERR_IO && errno == EAGAIN); + redisFree(c); +} + +static void test_invalid_timeout_errors(struct config config) { + redisContext *c; + + test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = 0; + config.tcp.timeout.tv_usec = 10000001; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO); + redisFree(c); + + test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); + + config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; + config.tcp.timeout.tv_usec = 0; + + c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); + + test_cond(c->err == REDIS_ERR_IO); + redisFree(c); +} + +static void test_throughput(struct config config) { + redisContext *c = connect(config); + redisReply **replies; + int i, num; + long long t1, t2; + + test("Throughput:\n"); + for (i = 0; i < 500; i++) + freeReplyObject(redisCommand(c,"LPUSH mylist foo")); + + num = 1000; + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"PING"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c,"LRANGE mylist 0 499"); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); + + num = 10000; + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"PING"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"LRANGE mylist 0 499"); + t1 = usec(); + for (i = 0; i < num; i++) { + assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY); + assert(replies[i] != NULL && replies[i]->elements == 500); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + + disconnect(c, 0); +} + +// static long __test_callback_flags = 0; +// static void __test_callback(redisContext *c, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// } +// +// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) { +// ((void)c); +// /* Shift to detect execution order */ +// __test_callback_flags <<= 8; +// __test_callback_flags |= (long)privdata; +// if (reply) freeReplyObject(reply); +// } +// +// static redisContext *__connect_nonblock() { +// /* Reset callback flags */ +// __test_callback_flags = 0; +// return redisConnectNonBlock("127.0.0.1", port, NULL); +// } +// +// static void test_nonblocking_connection() { +// redisContext *c; +// int wdone = 0; +// +// test("Calls command callback when command is issued: "); +// c = __connect_nonblock(); +// redisSetCommandCallback(c,__test_callback,(void*)1); +// redisCommand(c,"PING"); +// test_cond(__test_callback_flags == 1); +// redisFree(c); +// +// test("Calls disconnect callback on redisDisconnect: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 2); +// redisFree(c); +// +// test("Calls disconnect callback and free callback on redisFree: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)2); +// redisSetFreeCallback(c,__test_callback,(void*)4); +// redisFree(c); +// test_cond(__test_callback_flags == ((2 << 8) | 4)); +// +// test("redisBufferWrite against empty write buffer: "); +// c = __connect_nonblock(); +// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1); +// redisFree(c); +// +// test("redisBufferWrite against not yet connected fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("redisBufferWrite against closed fd: "); +// c = __connect_nonblock(); +// redisCommand(c,"PING"); +// redisDisconnect(c); +// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR && +// strncmp(c->error,"write:",6) == 0); +// redisFree(c); +// +// test("Process callbacks in the right sequence: "); +// c = __connect_nonblock(); +// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING"); +// +// /* Write output buffer */ +// wdone = 0; +// while(!wdone) { +// usleep(500); +// redisBufferWrite(c,&wdone); +// } +// +// /* Read until at least one callback is executed (the 3 replies will +// * arrive in a single packet, causing all callbacks to be executed in +// * a single pass). */ +// while(__test_callback_flags == 0) { +// assert(redisBufferRead(c) == REDIS_OK); +// redisProcessCallbacks(c); +// } +// test_cond(__test_callback_flags == 0x010203); +// redisFree(c); +// +// test("redisDisconnect executes pending callbacks with NULL reply: "); +// c = __connect_nonblock(); +// redisSetDisconnectCallback(c,__test_callback,(void*)1); +// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING"); +// redisDisconnect(c); +// test_cond(__test_callback_flags == 0x0201); +// redisFree(c); +// } + +int main(int argc, char **argv) { + struct config cfg = { + .tcp = { + .host = "127.0.0.1", + .port = 6379 + }, + .unix = { + .path = "/tmp/redis.sock" + } + }; + int throughput = 1; + int test_inherit_fd = 1; + + /* Ignore broken pipe signal (for I/O error tests). */ + signal(SIGPIPE, SIG_IGN); + + /* Parse command line options. */ + argv++; argc--; + while (argc) { + if (argc >= 2 && !strcmp(argv[0],"-h")) { + argv++; argc--; + cfg.tcp.host = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"-p")) { + argv++; argc--; + cfg.tcp.port = atoi(argv[0]); + } else if (argc >= 2 && !strcmp(argv[0],"-s")) { + argv++; argc--; + cfg.unix.path = argv[0]; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) { + throughput = 0; + } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { + test_inherit_fd = 0; + } else { + fprintf(stderr, "Invalid argument: %s\n", argv[0]); + exit(1); + } + argv++; argc--; + } + + test_format_commands(); + test_reply_reader(); + test_blocking_connection_errors(); + test_free_null(); + + printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); + cfg.type = CONN_TCP; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + test_invalid_timeout_errors(cfg); + test_append_formatted_commands(cfg); + if (throughput) test_throughput(cfg); + + printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); + cfg.type = CONN_UNIX; + test_blocking_connection(cfg); + test_blocking_connection_timeouts(cfg); + test_blocking_io_errors(cfg); + if (throughput) test_throughput(cfg); + + if (test_inherit_fd) { + printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); + cfg.type = CONN_FD; + test_blocking_connection(cfg); + } + + + if (fails) { + printf("*** %d TESTS FAILED ***\n", fails); + return 1; + } + + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/ext/hiredis-vip-0.3.0/win32.h b/ext/hiredis-vip-0.3.0/win32.h new file mode 100644 index 000000000..1a27c18f2 --- /dev/null +++ b/ext/hiredis-vip-0.3.0/win32.h @@ -0,0 +1,42 @@ +#ifndef _WIN32_HELPER_INCLUDE +#define _WIN32_HELPER_INCLUDE +#ifdef _MSC_VER + +#ifndef inline +#define inline __inline +#endif + +#ifndef va_copy +#define va_copy(d,s) ((d) = (s)) +#endif + +#ifndef snprintf +#define snprintf c99_snprintf + +__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap) +{ + int count = -1; + + if (size != 0) + count = _vsnprintf_s(str, size, _TRUNCATE, format, ap); + if (count == -1) + count = _vscprintf(format, ap); + + return count; +} + +__inline int c99_snprintf(char* str, size_t size, const char* format, ...) +{ + int count; + va_list ap; + + va_start(ap, format); + count = c99_vsnprintf(str, size, format, ap); + va_end(ap); + + return count; +} +#endif + +#endif +#endif \ No newline at end of file diff --git a/make-linux.mk b/make-linux.mk index e74f19303..dd8c5c520 100644 --- a/make-linux.mk +++ b/make-linux.mk @@ -260,6 +260,13 @@ ifeq ($(ZT_OFFICIAL),1) override LDFLAGS+=-Wl,--wrap=memcpy -static-libstdc++ endif +ifeq ($(ZT_CONTROLLER),1) + LDLIBS+=-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq + DEFS+=-DZT_CONTROLLER_USE_LIBPQ + INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/pgsql-10/include -Iext/hiredis-vip-0.3.0 + ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o +endif + # ARM32 hell -- use conservative CFLAGS ifeq ($(ZT_ARCHITECTURE),3) ifeq ($(shell if [ -e /usr/bin/dpkg ]; then dpkg --print-architecture; fi),armel) @@ -335,7 +342,7 @@ docker: FORCE docker build --no-cache -f ext/installfiles/linux/zerotier-containerized/Dockerfile -t zerotier-containerized . central-controller: FORCE - make -j4 LDLIBS="-L/usr/pgsql-10/lib/ -lpq -Lext/librabbitmq/centos_x64/lib/ -lrabbitmq" CXXFLAGS="-I/usr/pgsql-10/include -I./ext/librabbitmq/centos_x64/include -fPIC" DEFS="-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER" ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one + make -j4 ZT_CONTROLLER=1 ZT_OFFICIAL=1 ZT_USE_X64_ASM_ED25519=1 one central-controller-docker: FORCE docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=`git name-rev --name-only HEAD` . diff --git a/make-mac.mk b/make-mac.mk index 35ca4a486..2dd232ff2 100644 --- a/make-mac.mk +++ b/make-mac.mk @@ -1,3 +1,4 @@ + CC=clang CXX=clang++ INCLUDES= @@ -29,7 +30,8 @@ ONE_OBJS+=osdep/MacEthernetTap.o osdep/MacKextEthernetTap.o ext/http-parser/http ifeq ($(ZT_CONTROLLER),1) LIBS+=-L/usr/local/opt/libpq/lib -lpq -Lext/librabbitmq/macos/lib -lrabbitmq DEFS+=-DZT_CONTROLLER_USE_LIBPQ -DZT_CONTROLLER - INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/local/opt/libpq/include + INCLUDES+=-Iext/librabbitmq/macos/include -I/usr/local/opt/libpq/include -Iext/hiredis-vip-0.3.0 + ONE_OBJS+=ext/hiredis-vip-0.3.0/adlist.o ext/hiredis-vip-0.3.0/async.o ext/hiredis-vip-0.3.0/command.o ext/hiredis-vip-0.3.0/crc16.o ext/hiredis-vip-0.3.0/dict.o ext/hiredis-vip-0.3.0/hiarray.o ext/hiredis-vip-0.3.0/hircluster.o ext/hiredis-vip-0.3.0/hiredis.o ext/hiredis-vip-0.3.0/hiutil.o ext/hiredis-vip-0.3.0/net.o ext/hiredis-vip-0.3.0/read.o ext/hiredis-vip-0.3.0/sds.o endif # Official releases are signed with our Apple cert and apply software updates by default @@ -150,7 +152,8 @@ official: FORCE make ZT_OFFICIAL_RELEASE=1 mac-dist-pkg central-controller-docker: FORCE - docker build --no-cache -t docker.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + #docker build --no-cache -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . + docker build -t registry.zerotier.com/zerotier-central/ztcentral-controller:${TIMESTAMP} -f ext/central-controller-docker/Dockerfile --build-arg git_branch=$(shell git name-rev --name-only HEAD) . clean: rm -rf MacEthernetTapAgent *.dSYM build-* *.a *.pkg *.dmg *.o node/*.o controller/*.o service/*.o osdep/*.o ext/http-parser/*.o $(CORE_OBJS) $(ONE_OBJS) zerotier-one zerotier-idtool zerotier-selftest zerotier-cli zerotier doc/node_modules macui/build zt1_update_$(ZT_BUILD_PLATFORM)_$(ZT_BUILD_ARCHITECTURE)_*