From 284c5c0dbef7b2a0b896d73565d97a5a000ea2ca Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 30 Sep 2015 14:30:24 +0800 Subject: [PATCH 001/273] add hiredis-0.13.1 --- .gitignore | 7 + .travis.yml | 16 + CHANGELOG.md | 90 +++ COPYING | 29 + Makefile | 198 +++++++ README.md | 392 +++++++++++++ adapters/ae.h | 127 ++++ adapters/glib.h | 153 +++++ adapters/libev.h | 147 +++++ adapters/libevent.h | 108 ++++ adapters/libuv.h | 121 ++++ async.c | 686 ++++++++++++++++++++++ async.h | 129 +++++ dict.c | 338 +++++++++++ dict.h | 126 ++++ examples/example-ae.c | 62 ++ examples/example-glib.c | 73 +++ examples/example-libev.c | 52 ++ examples/example-libevent.c | 53 ++ examples/example-libuv.c | 53 ++ examples/example.c | 78 +++ fmacros.h | 21 + hiredis.c | 1021 ++++++++++++++++++++++++++++++++ hiredis.h | 222 +++++++ net.c | 458 +++++++++++++++ net.h | 53 ++ read.c | 525 +++++++++++++++++ read.h | 116 ++++ sds.c | 1095 +++++++++++++++++++++++++++++++++++ sds.h | 105 ++++ test.c | 806 ++++++++++++++++++++++++++ win32.h | 42 ++ 32 files changed, 7502 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README.md create mode 100644 adapters/ae.h create mode 100644 adapters/glib.h create mode 100644 adapters/libev.h create mode 100644 adapters/libevent.h create mode 100644 adapters/libuv.h create mode 100644 async.c create mode 100644 async.h create mode 100644 dict.c create mode 100644 dict.h create mode 100644 examples/example-ae.c create mode 100644 examples/example-glib.c create mode 100644 examples/example-libev.c create mode 100644 examples/example-libevent.c create mode 100644 examples/example-libuv.c create mode 100644 examples/example.c create mode 100644 fmacros.h create mode 100644 hiredis.c create mode 100644 hiredis.h create mode 100644 net.c create mode 100644 net.h create mode 100644 read.c create mode 100644 read.h create mode 100644 sds.c create mode 100644 sds.h create mode 100644 test.c create mode 100644 win32.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c44b5c53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/hiredis-test +/examples/hiredis-example* +/*.o +/*.so +/*.dylib +/*.a +/*.pc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..1df63b0b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: c +compiler: + - gcc + - clang + +env: + - CFLAGS="-Werror" + - PRE="valgrind --track-origins=yes --leak-check=full" + - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" + - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + +install: + - sudo apt-get update -qq + - sudo apt-get install libc6-dbg libc6-dev libc6-i686:i386 libc6-dev-i386 libc6-dbg:i386 valgrind -y + +script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..29d7cf33 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,90 @@ +### 0.13.1 - May 03, 2015 + +This is a bug fix release. +The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. +Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. +Other non-C99 code can now use hiredis as usual again. +Sorry for the inconvenience. + +* Fix memory leak in async reply handling (Salvatore Sanfilippo) +* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) + +### 0.13.0 - April 16, 2015 + +This release adds a minimal Windows compatibility layer. +The parser, standalone since v0.12.0, can now be compiled on Windows +(and thus used in other client libraries as well) + +* Windows compatibility layer for parser code (tzickel) +* Properly escape data printed to PKGCONF file (Dan Skorupski) +* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) +* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) + +### 0.12.1 - January 26, 2015 + +* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location +* Fix `make test` as 32 bit build on 64 bit platform + +### 0.12.0 - January 22, 2015 + +* Add optional KeepAlive support + +* Try again on EINTR errors + +* Add libuv adapter + +* Add IPv6 support + +* Remove possiblity of multiple close on same fd + +* Add ability to bind source address on connect + +* Add redisConnectFd() and redisFreeKeepFd() + +* Fix getaddrinfo() memory leak + +* Free string if it is unused (fixes memory leak) + +* Improve redisAppendCommandArgv performance 2.5x + +* Add support for SO_REUSEADDR + +* Fix redisvFormatCommand format parsing + +* Add GLib 2.0 adapter + +* Refactor reading code into read.c + +* Fix errno error buffers to not clobber errors + +* Generate pkgconf during build + +* Silence _BSD_SOURCE warnings + +* Improve digit counting for multibulk creation + + +### 0.11.0 + +* Increase the maximum multi-bulk reply depth to 7. + +* Increase the read buffer size from 2k to 16k. + +* Use poll(2) instead of select(2) to support large fds (>= 1024). + +### 0.10.1 + +* Makefile overhaul. Important to check out if you override one or more + variables using environment variables or via arguments to the "make" tool. + +* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements + being created by the default reply object functions. + +* Issue #43: Don't crash in an asynchronous context when Redis returns an error + reply after the connection has been made (this happens when the maximum + number of connections is reached). + +### 0.10.0 + +* See commit log. + diff --git a/COPYING b/COPYING new file mode 100644 index 00000000..a5fc9739 --- /dev/null +++ b/COPYING @@ -0,0 +1,29 @@ +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. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8b0f0c24 --- /dev/null +++ b/Makefile @@ -0,0 +1,198 @@ +# 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 +EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +TESTS=hiredis-test +LIBNAME=libhiredis +PKGCONFNAME=hiredis.pc + +HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') +HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') +HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') + +# Installation related variables and target +PREFIX?=/usr/local +INCLUDE_PATH?=include/hiredis +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_MAJOR).$(HIREDIS_MINOR) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_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_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX) + DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_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) +async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h +dict.o: dict.c fmacros.h dict.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.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 + +$(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_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_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 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/README.md b/README.md new file mode 100644 index 00000000..4f1a58d2 --- /dev/null +++ b/README.md @@ -0,0 +1,392 @@ +[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) + +# HIREDIS + +Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. + +It is minimalistic because it just adds minimal support for the protocol, but +at the same time it uses a high level printf-alike API in order to make it +much higher level than otherwise suggested by its minimal code base and the +lack of explicit bindings for every Redis command. + +Apart from supporting sending commands and receiving replies, it comes with +a reply parser that is decoupled from the I/O layer. It +is a stream parser designed for easy reusability, which can for instance be used +in higher level language bindings for efficient reply parsing. + +Hiredis only supports the binary-safe Redis protocol, so you can use it with any +Redis version >= 1.2.0. + +The library comes with multiple APIs. There is the +*synchronous API*, the *asynchronous API* and the *reply parsing API*. + +## UPGRADING + +Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing +code using hiredis should not be a big pain. The key thing to keep in mind when +upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to +the stateless 0.0.1 that only has a file descriptor to work with. + +## Synchronous API + +To consume the synchronous API, there are only a few function calls that need to be introduced: + +```c +redisContext *redisConnect(const char *ip, int port); +void *redisCommand(redisContext *c, const char *format, ...); +void freeReplyObject(void *reply); +``` + +### Connecting + +The function `redisConnect` is used to create a so-called `redisContext`. The +context is where Hiredis holds state for a connection. The `redisContext` +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. More information on errors can be found in the **Errors** section. +After trying to connect to Redis using `redisConnect` you should +check the `err` field to see if establishing the connection was successful: +```c +redisContext *c = redisConnect("127.0.0.1", 6379); +if (c != NULL && c->err) { + printf("Error: %s\n", c->errstr); + // handle error +} +``` + +### Sending commands + +There are several ways to issue commands to Redis. The first that will be introduced is +`redisCommand`. This function takes a format similar to printf. In the simplest form, +it is used like this: +```c +reply = redisCommand(context, "SET foo bar"); +``` + +The specifier `%s` interpolates a string in the command, and uses `strlen` to +determine the length of the string: +```c +reply = redisCommand(context, "SET foo %s", value); +``` +When you need to pass binary safe strings in a command, the `%b` specifier can be +used. Together with a pointer to the string, it requires a `size_t` length argument +of the string: +```c +reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); +``` +Internally, Hiredis 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 = redisCommand(context, "SET key:%s %s", myid, value); +``` + +### Using replies + +The return value of `redisCommand` holds a reply when the command was +successfully executed. When an error occurs, the return value is `NULL` and +the `err` field in the context will be set (see section on **Errors**). +Once an error is returned the context cannot be reused and you should set up +a new connection. + +The standard replies that `redisCommand` are of the type `redisReply`. The +`type` field in the `redisReply` should be used to test what kind of reply +was received: + +* **`REDIS_REPLY_STATUS`**: + * The command replied with a status reply. The status string can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ERROR`**: + * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. + +* **`REDIS_REPLY_INTEGER`**: + * The command replied with an integer. The integer value can be accessed using the + `reply->integer` field of type `long long`. + +* **`REDIS_REPLY_NIL`**: + * The command replied with a **nil** object. There is no data to access. + +* **`REDIS_REPLY_STRING`**: + * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ARRAY`**: + * A multi bulk reply. The number of elements in the multi bulk reply is stored in + `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well + and can be accessed via `reply->element[..index..]`. + Redis may reply with nested arrays but this is fully supported. + +Replies should be freed using the `freeReplyObject()` function. +Note that this function will take care of freeing sub-reply objects +contained in arrays and nested arrays, so there is no need for the user to +free the sub replies (it is actually harmful and will corrupt the memory). + +**Important:** the current version of hiredis (0.10.0) frees replies when the +asynchronous API is used. This means you should not call `freeReplyObject` when +you use this API. The reply is cleaned up by hiredis _after_ the callback +returns. This behavior will probably change in future releases, so make sure to +keep an eye on the changelog when upgrading (see issue #39). + +### Cleaning up + +To disconnect and free the context the following function can be used: +```c +void redisFree(redisContext *c); +``` +This function immediately closes the socket and then frees the allocations done in +creating the context. + +### Sending commands (cont'd) + +Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. +It has the following prototype: +```c +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the +arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will +use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments +need to be binary safe, the entire array of lengths `argvlen` should be provided. + +The return value has the same semantic as `redisCommand`. + +### Pipelining + +To explain how Hiredis supports pipelining in a blocking connection, there needs to be +understanding of the internal execution flow. + +When any of the functions in the `redisCommand` family is called, Hiredis first formats the +command according to the Redis protocol. The formatted command is then put in the output buffer +of the context. This output buffer is dynamic, so it can hold any number of commands. +After the command is put in the output buffer, `redisGetReply` is called. This function has the +following two execution paths: + +1. The input buffer is non-empty: + * Try to parse a single reply from the input buffer and return it + * If no reply could be parsed, continue at *2* +2. The input buffer is empty: + * Write the **entire** output buffer to the socket + * Read from the socket until a single reply could be parsed + +The function `redisGetReply` 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 `redisCommand` family, apart from not returning a reply: +```c +void redisAppendCommand(redisContext *c, const char *format, ...); +void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +After calling either function one or more times, `redisGetReply` 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. + +The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and +a single call to `read(2)`): +```c +redisReply *reply; +redisAppendCommand(context,"SET foo bar"); +redisAppendCommand(context,"GET foo"); +redisGetReply(context,&reply); // reply for SET +freeReplyObject(reply); +redisGetReply(context,&reply); // reply for GET +freeReplyObject(reply); +``` +This API can also be used to implement a blocking subscriber: +```c +reply = redisCommand(context,"SUBSCRIBE foo"); +freeReplyObject(reply); +while(redisGetReply(context,&reply) == REDIS_OK) { + // consume message + freeReplyObject(reply); +} +``` +### Errors + +When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is +returned. The `err` field inside the context will be non-zero and set to one of the +following constants: + +* **`REDIS_ERR_IO`**: + There was an I/O error while creating the connection, trying to write + to the socket or read from the socket. If you included `errno.h` in your + application, you can use the global `errno` variable to find out what is + wrong. + +* **`REDIS_ERR_EOF`**: + The server closed the connection which resulted in an empty read. + +* **`REDIS_ERR_PROTOCOL`**: + There was an error while parsing the protocol. + +* **`REDIS_ERR_OTHER`**: + Any other error. Currently, it is only used when a specified hostname to connect + to cannot be resolved. + +In every case, the `errstr` field in the context will be set to hold a string representation +of the error. + +## Asynchronous API + +Hiredis comes with an asynchronous API that works easily with any event library. +Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) +and [libevent](http://monkey.org/~provos/libevent/). + +### 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 +redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); +if (c->err) { + printf("Error: %s\n", c->errstr); + // handle error +} +``` + +The 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. + +The context object is always freed after the disconnect callback fired. When a reconnect is needed, +the disconnect callback is a good point to do so. + +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 redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +``` +### Sending commands and their callbacks + +In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the synchronous API, there is only a single way to send commands. +Because commands are sent to Redis 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(redisAsyncContext *c, 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 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); +``` +Both functions 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 `redisAsyncCommand` 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 asynchronous connection can be terminated using: +```c +void redisAsyncDisconnect(redisAsyncContext *ac); +``` +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 context object after it is created. +See the `adapters/` directory for bindings to *libev* and *libevent*. + +## Reply parsing API + +Hiredis comes with a reply parsing API that makes it easy for writing higher +level language bindings. + +The reply parsing API consists of the following functions: +```c +redisReader *redisReaderCreate(void); +void redisReaderFree(redisReader *reader); +int redisReaderFeed(redisReader *reader, const char *buf, size_t len); +int redisReaderGetReply(redisReader *reader, void **reply); +``` +The same set of functions are used internally by hiredis when creating a +normal Redis context, the above API just exposes it to the user for a direct +usage. + +### Usage + +The function `redisReaderCreate` creates a `redisReader` structure that holds a +buffer with unparsed data and state for the protocol parser. + +Incoming data -- most likely from a socket -- can be placed in the internal +buffer of the `redisReader` using `redisReaderFeed`. This function will make a +copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed +when `redisReaderGetReply` is called. This function returns an integer status +and a reply object (as described above) via `void **reply`. The returned status +can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went +wrong (either a protocol error, or an out of memory error). + +The parser limits the level of nesting for multi bulk payloads to 7. If the +multi bulk nesting level is higher than this, the parser returns an error. + +### Customizing replies + +The function `redisReaderGetReply` creates `redisReply` and makes the function +argument `reply` point to the created `redisReply` variable. For instance, if +the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` +will hold the status as a vanilla C string. However, the functions that are +responsible for creating instances of the `redisReply` can be customized by +setting the `fn` field on the `redisReader` struct. This should be done +immediately after creating the `redisReader`. + +For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) +uses customized reply object functions to create Ruby objects. + +### Reader max buffer + +Both when using the Reader API directly or when using it indirectly via a +normal Redis context, the redisReader structure uses a buffer in order to +accumulate data from the server. +Usually this buffer is destroyed when it is empty and is larger than 16 +KiB in order to avoid wasting memory in unused buffers + +However when working with very big payloads destroying the buffer may slow +down performances considerably, so it is possible to modify the max size of +an idle buffer changing the value of the `maxbuf` field of the reader structure +to the desired value. The special value of 0 means that there is no maximum +value for an idle buffer, so the buffer will never get freed. + +For instance if you have a normal Redis context you can set the maximum idle +buffer to zero (unlimited) just with: +```c +context->reader->maxbuf = 0; +``` +This should be done only in order to maximize performances when working with +large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again +as soon as possible in order to prevent allocation of useless memory. + +## AUTHORS + +Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and +Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. +Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and +Jan-Erik Rediger (janerik at fnordig dot com) diff --git a/adapters/ae.h b/adapters/ae.h new file mode 100644 index 00000000..5c551c2e --- /dev/null +++ b/adapters/ae.h @@ -0,0 +1,127 @@ +/* + * 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" + +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; +} +#endif diff --git a/adapters/glib.h b/adapters/glib.h new file mode 100644 index 00000000..e13eee73 --- /dev/null +++ b/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/adapters/libev.h b/adapters/libev.h new file mode 100644 index 00000000..2bf8d521 --- /dev/null +++ b/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/adapters/libevent.h b/adapters/libevent.h new file mode 100644 index 00000000..1c2b271b --- /dev/null +++ b/adapters/libevent.h @@ -0,0 +1,108 @@ +/* + * 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" + +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; +} +#endif diff --git a/adapters/libuv.h b/adapters/libuv.h new file mode 100644 index 00000000..3cdf3d39 --- /dev/null +++ b/adapters/libuv.h @@ -0,0 +1,121 @@ +#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/async.c b/async.c new file mode 100644 index 00000000..28aa76f5 --- /dev/null +++ b/async.c @@ -0,0 +1,686 @@ +/* + * 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->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); + } + } + + /* 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/async.h b/async.h new file mode 100644 index 00000000..59cbf469 --- /dev/null +++ b/async.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_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; + + /* 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/dict.c b/dict.c new file mode 100644 index 00000000..79b1041c --- /dev/null +++ b/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/dict.h b/dict.h new file mode 100644 index 00000000..95fcd280 --- /dev/null +++ b/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/examples/example-ae.c b/examples/example-ae.c new file mode 100644 index 00000000..8efa7306 --- /dev/null +++ b/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/examples/example-glib.c b/examples/example-glib.c new file mode 100644 index 00000000..d6e10f8e --- /dev/null +++ b/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/examples/example-libev.c b/examples/example-libev.c new file mode 100644 index 00000000..cc8b166e --- /dev/null +++ b/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/examples/example-libevent.c b/examples/example-libevent.c new file mode 100644 index 00000000..d333c22b --- /dev/null +++ b/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/examples/example-libuv.c b/examples/example-libuv.c new file mode 100644 index 00000000..a5462d41 --- /dev/null +++ b/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/examples/example.c b/examples/example.c new file mode 100644 index 00000000..25226a80 --- /dev/null +++ b/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/fmacros.h b/fmacros.h new file mode 100644 index 00000000..19d7b219 --- /dev/null +++ b/fmacros.h @@ -0,0 +1,21 @@ +#ifndef __HIREDIS_FMACRO_H +#define __HIREDIS_FMACRO_H + +#if defined(__linux__) +#define _BSD_SOURCE +#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/hiredis.c b/hiredis.c new file mode 100644 index 00000000..73d0251b --- /dev/null +++ b/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/hiredis.h b/hiredis.h new file mode 100644 index 00000000..d4bf9e52 --- /dev/null +++ b/hiredis.h @@ -0,0 +1,222 @@ +/* + * 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/net.c b/net.c new file mode 100644 index 00000000..60a2dc75 --- /dev/null +++ b/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/net.h b/net.h new file mode 100644 index 00000000..2f1a0bf8 --- /dev/null +++ b/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/read.c b/read.c new file mode 100644 index 00000000..df1a467a --- /dev/null +++ b/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/read.h b/read.h new file mode 100644 index 00000000..635a872c --- /dev/null +++ b/read.h @@ -0,0 +1,116 @@ +/* + * 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... */ + +#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. */ + +#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/sds.c b/sds.c new file mode 100644 index 00000000..5d55b779 --- /dev/null +++ b/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/sds.h b/sds.h new file mode 100644 index 00000000..19a2abd3 --- /dev/null +++ b/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/test.c b/test.c new file mode 100644 index 00000000..8fde5544 --- /dev/null +++ b/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/win32.h b/win32.h new file mode 100644 index 00000000..1a27c18f --- /dev/null +++ b/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 From 8ba4b096659e099fdad868c5a948a7e569ecac6d Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 30 Sep 2015 14:34:35 +0800 Subject: [PATCH 002/273] support redis cluster --- Makefile | 13 +- README.md | 39 + adapters/libevent.h | 27 + adlist.c | 341 +++++ adlist.h | 93 ++ command.c | 1688 +++++++++++++++++++++++++ command.h | 178 +++ crc16.c | 87 ++ fmacros.h | 2 + hiarray.c | 187 +++ hiarray.h | 58 + hircluster.c | 2916 +++++++++++++++++++++++++++++++++++++++++++ hircluster.h | 121 ++ hiutil.c | 554 ++++++++ hiutil.h | 265 ++++ read.h | 10 + 16 files changed, 6576 insertions(+), 3 deletions(-) create mode 100644 adlist.c create mode 100644 adlist.h create mode 100644 command.c create mode 100644 command.h create mode 100644 crc16.c create mode 100644 hiarray.c create mode 100644 hiarray.h create mode 100644 hircluster.c create mode 100644 hircluster.h create mode 100644 hiutil.c create mode 100644 hiutil.h diff --git a/Makefile b/Makefile index 8b0f0c24..93287468 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # 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 +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 @@ -68,13 +68,20 @@ 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 +test.o: test.c fmacros.h hiredis.h read.h sds.h net.h $(DYLIBNAME): $(OBJ) $(DYLIB_MAKE_CMD) $(OBJ) @@ -162,7 +169,7 @@ $(PKGCONFNAME): hiredis.h install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_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) diff --git a/README.md b/README.md index 4f1a58d2..8219efc3 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,45 @@ Redis version >= 1.2.0. The library comes with multiple APIs. There is the *synchronous API*, the *asynchronous API* and the *reply parsing API*. +## 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); +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv); +redisClusterContext *redisClusterConnectNonBlock(const char *addrs); +void redisClusterFree(redisClusterContext *cc); +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); +int redisClusterGetReply(redisClusterContext *cc, void **reply); +void redisCLusterReset(redisClusterContext *cc); + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs); +int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, const char *format, ...); +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +void redisClusterAsyncFree(redisClusterAsyncContext *acc); +``` + ## UPGRADING Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing diff --git a/adapters/libevent.h b/adapters/libevent.h index 1c2b271b..6bc911c7 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -34,6 +34,10 @@ #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; @@ -105,4 +109,27 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { 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/adlist.c b/adlist.c new file mode 100644 index 00000000..1a3bff02 --- /dev/null +++ b/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. */ +list *listCreate(void) +{ + struct list *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(list *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. */ +list *listAddNodeHead(list *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. */ +list *listAddNodeTail(list *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; +} + +list *listInsertNode(list *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(list *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(list *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(list *list, listIter *li) { + li->next = list->head; + li->direction = AL_START_HEAD; +} + +void listRewindTail(list *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. */ +list *listDup(list *orig) +{ + list *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(list *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(list *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(list *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/adlist.h b/adlist.h new file mode 100644 index 00000000..be322552 --- /dev/null +++ b/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 list { + listNode *head; + listNode *tail; + void *(*dup)(void *ptr); + void (*free)(void *ptr); + int (*match)(void *ptr, void *key); + unsigned long len; +} list; + +/* 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 */ +list *listCreate(void); +void listRelease(list *list); +list *listAddNodeHead(list *list, void *value); +list *listAddNodeTail(list *list, void *value); +list *listInsertNode(list *list, listNode *old_node, void *value, int after); +void listDelNode(list *list, listNode *node); +listIter *listGetIterator(list *list, int direction); +listNode *listNext(listIter *iter); +void listReleaseIterator(listIter *iter); +list *listDup(list *orig); +listNode *listSearchKey(list *list, void *key); +listNode *listIndex(list *list, long index); +void listRewind(list *list, listIter *li); +void listRewindTail(list *list, listIter *li); +void listRotate(list *list); + +/* Directions for iterators */ +#define AL_START_HEAD 0 +#define AL_START_TAIL 1 + +#endif /* __ADLIST_H__ */ diff --git a/command.c b/command.c new file mode 100644 index 00000000..97a3c82c --- /dev/null +++ b/command.c @@ -0,0 +1,1688 @@ +#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. + * + * Nutcracker only supports the Redis unified protocol for requests. + */ +void +redis_parse_req(struct cmd *r) +{ + 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 = array_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_ERROR; + + return; + +error: + + r->result = CMD_PARSE_ERROR; + errno = EINVAL; + +} + +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->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 = array_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->keys != NULL) + { + command->keys->nelem = 0; + array_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/command.h b/command.h new file mode 100644 index 00000000..a891cd24 --- /dev/null +++ b/command.h @@ -0,0 +1,178 @@ +#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_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 */ + + cmd_type_t type; /* command type */ + + char *cmd; + uint32_t clen; /* command length */ + + struct array *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; + + list *sub_commands; /* just for pipeline and multi-key commands */ + +}; + +void redis_parse_req(struct cmd *r); + +struct cmd *command_get(void); +void command_destroy(struct cmd *command); + +#endif diff --git a/crc16.c b/crc16.c new file mode 100644 index 00000000..0f304f6e --- /dev/null +++ b/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/fmacros.h b/fmacros.h index 19d7b219..a3b1df03 100644 --- a/fmacros.h +++ b/fmacros.h @@ -2,7 +2,9 @@ #define __HIREDIS_FMACRO_H #if defined(__linux__) +#ifndef _BSD_SOURCE #define _BSD_SOURCE +#endif #define _DEFAULT_SOURCE #endif diff --git a/hiarray.c b/hiarray.c new file mode 100644 index 00000000..d8fe6e88 --- /dev/null +++ b/hiarray.c @@ -0,0 +1,187 @@ +#include + +#include "hiarray.h" + +struct array * +array_create(uint32_t n, size_t size) +{ + struct array *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 +array_destroy(struct array *a) +{ + array_deinit(a); + hi_free(a); +} + +rstatus_t +array_init(struct array *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 +array_deinit(struct array *a) +{ + ASSERT(a->nelem == 0); + + if (a->elem != NULL) { + hi_free(a->elem); + } +} + +uint32_t +array_idx(struct array *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 * +array_push(struct array *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 * +array_pop(struct array *a) +{ + void *elem; + + ASSERT(a->nelem != 0); + + a->nelem--; + elem = (uint8_t *)a->elem + a->size * a->nelem; + + return elem; +} + +void * +array_get(struct array *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 * +array_top(struct array *a) +{ + ASSERT(a->nelem != 0); + + return array_get(a, a->nelem - 1); +} + +void +array_swap(struct array *a, struct array *b) +{ + struct array tmp; + + tmp = *a; + *a = *b; + *b = tmp; +} + +/* + * Sort nelem elements of the array in ascending order based on the + * compare comparator. + */ +void +array_sort(struct array *a, array_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. + */ +rstatus_t +array_each(struct array *a, array_each_t func, void *data) +{ + uint32_t i, nelem; + + ASSERT(array_n(a) != 0); + ASSERT(func != NULL); + + for (i = 0, nelem = array_n(a); i < nelem; i++) { + void *elem = array_get(a, i); + rstatus_t status; + + status = func(elem, data); + if (status != HI_OK) { + return status; + } + } + + return HI_OK; +} diff --git a/hiarray.h b/hiarray.h new file mode 100644 index 00000000..5c965538 --- /dev/null +++ b/hiarray.h @@ -0,0 +1,58 @@ +#ifndef __HIARRAY_H_ +#define __HIARRAY_H_ + +#include + +#include "hiutil.h" + +typedef int (*array_compare_t)(const void *, const void *); +typedef rstatus_t (*array_each_t)(void *, void *); + +struct array { + uint32_t nelem; /* # element */ + void *elem; /* element */ + size_t size; /* element size */ + uint32_t nalloc; /* # allocated element */ +}; + +#define null_array { 0, NULL, 0, 0 } + +static inline void +array_null(struct array *a) +{ + a->nelem = 0; + a->elem = NULL; + a->size = 0; + a->nalloc = 0; +} + +static inline void +array_set(struct array *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 +array_n(const struct array *a) +{ + return a->nelem; +} + +struct array *array_create(uint32_t n, size_t size); +void array_destroy(struct array *a); +rstatus_t array_init(struct array *a, uint32_t n, size_t size); +void array_deinit(struct array *a); + +uint32_t array_idx(struct array *a, void *elem); +void *array_push(struct array *a); +void *array_pop(struct array *a); +void *array_get(struct array *a, uint32_t idx); +void *array_top(struct array *a); +void array_swap(struct array *a, struct array *b); +void array_sort(struct array *a, array_compare_t compare); +rstatus_t array_each(struct array *a, array_each_t func, void *data); + +#endif diff --git a/hircluster.c b/hircluster.c new file mode 100644 index 00000000..5e75c944 --- /dev/null +++ b/hircluster.c @@ -0,0 +1,2916 @@ + +#include "fmacros.h" +#include +#include +#include +#include +#include + +#include "hircluster.h" +#include "hiutil.h" +#include "hiarray.h" +#include "command.h" +#include "dict.c" +#include "async.h" + +#define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" +#define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" + +#define IP_PORT_SEPARATOR ":" + +#define CLUSTER_ADDRESS_SEPARATOR "," + +#define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5 + +static void cluster_node_deinit(cluster_node *node); + +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); +} + +/* Cluster nodes hash table, mapping nodes addresses 1.2.3.4:6379 to + * clusterNode structures. */ +dictType clusterNodesDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + dictClusterNodeDestructor /* 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; + + 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_node_init(cluster_node *node) +{ + node->name = NULL; + node->addr = NULL; + node->host = NULL; + node->port = 0; + node->master = 1; + node->count = 0; + node->slave_of = NULL; + node->con = NULL; + node->acon = NULL; + node->slots = NULL; + + return REDIS_OK; +} + +static void cluster_node_deinit(cluster_node *node) +{ + if(node == NULL) + { + return; + } + + if(node->count > 0) + { + return; + } + + sdsfree(node->name); + sdsfree(node->addr); + sdsfree(node->host); + node->port = 0; + node->master = 1; + node->slave_of = NULL; + + if(node->con != NULL) + { + redisFree(node->con); + } + + if(node->acon != NULL) + { + redisAsyncFree(node->acon); + } + + if(node->slots != NULL) + { + listRelease(node->slots); + } + +} + +static int cluster_slot_init(cluster_slot *slot, cluster_node *node) +{ + slot->start = 0; + slot->end = 0; + if(node != NULL) + { + node->count ++; + } + slot->node = node; + + return REDIS_OK; +} + +static int cluster_slot_deinit(cluster_slot *slot) +{ + cluster_node *node; + slot->start = 0; + slot->end = 0; + if(slot->node != NULL) + { + node = slot->node; + node->count --; + slot->node = NULL; + } + + hi_free(slot); + + return REDIS_OK; +} + +static int cluster_slot_ref_node(cluster_slot *slot, cluster_node *node) +{ + cluster_node *node_old; + + if(slot->node != NULL) + { + node_old = slot->node; + node_old->count --; + } + + if(node != NULL) + { + node->count ++; + listAddNodeTail(node->slots, slot); + } + + slot->node = node; + + return REDIS_OK; +} + + + +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_update_route_with_slots(redisClusterContext *cc, + const char *ip, int port) +{ + redisContext *c; + redisReply *reply = NULL; + redisReply *elem; + redisReply *elem_slots_begin, *elem_slots_end; + redisReply *elem_node_master; + redisReply *elem_ip, *elem_port; + struct array *slots = NULL; + cluster_slot *slot; + cluster_node *node; + const char *errstr = NULL; + int err = 0; + unsigned int i, idx; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(cc->flags & REDIS_BLOCK) + { + if(cc->timeout) + { + c = redisConnectWithTimeout(ip, port, *cc->timeout); + } + else + { + c = redisConnect(ip, port); + } + } + else + { + c = redisConnectNonBlock(ip, port); + } + + if (c == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "init redis context error(return NULL)!\0"; + goto error; + } + else if(c->err) + { + err = c->err; + errstr = c->errstr; + goto error; + } + + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); + + if(reply == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply error(NULL)!\0"; + goto error; + } + + if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(level 0 type is not array)!\0"; + goto error; + } + + slots = array_create(reply->elements, sizeof(cluster_slot)); + if(slots == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "array create error!\0"; + goto error; + } + + for(i = 0; i < reply->elements; i ++) + { + elem = reply->element[i]; + if(elem->type != REDIS_REPLY_ARRAY || elem->elements <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(level 1 type is not array)!\0"; + goto error; + } + + slot = array_push(slots); + if(slot == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "slot push in array error!\0"; + goto error; + } + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "alloc cluster node error!\0"; + goto error; + } + + cluster_node_init(node); + cluster_slot_ref_node(slot, node); + + for(idx = 0; idx < elem->elements; idx ++) + { + if(idx == 0) + { + elem_slots_begin = elem->element[idx]; + if(elem_slots_begin->type != REDIS_REPLY_INTEGER) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(slot begin is not integer)!\0"; + goto error; + } + slot->start = (int)(elem_slots_begin->integer); + } + else if(idx == 1) + { + elem_slots_end = elem->element[idx]; + if(elem_slots_end->type != REDIS_REPLY_INTEGER) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(slot end is not integer)!\0"; + goto error; + } + slot->end = (int)(elem_slots_end->integer); + + if(slot->start > slot->end) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(slot begin is bigger than slot end)!\0"; + goto error; + } + } + else if(idx == 2) + { + elem_node_master = elem->element[idx]; + if(elem_node_master->type != REDIS_REPLY_ARRAY || + elem_node_master->elements != 2) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(master line is not array)!\0"; + goto error; + } + + elem_ip = elem_node_master->element[0]; + elem_port = elem_node_master->element[1]; + + if(elem_ip->type != REDIS_REPLY_STRING || + elem_ip->len <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(master ip is not string)!\0"; + goto error; + } + + if(elem_port->type != REDIS_REPLY_INTEGER || + elem_port->integer <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(master port is not integer)!\0"; + goto error; + } + + node->host = sdsnewlen(elem_ip->str, elem_ip->len); + node->port = (int)(elem_port->integer); + + node->addr = sdsnewlen(elem_ip->str, elem_ip->len); + sdscatlen(node->addr, ":", 1); + node->addr = sdscatfmt(node->addr, "%I", node->port); + } + else + { + continue; + } + } + } + + cc->slots = slots; + + array_sort(cc->slots, cluster_slot_start_cmp); + + freeReplyObject(reply); + + if (c != NULL) + { + redisFree(c); + } + + return REDIS_OK; + +error: + + cc->err = err; + memcpy(cc->errstr, errstr, strlen(errstr)); + + if(slots != NULL) + { + while(array_n(slots)) + { + slot = array_pop(slots); + cluster_slot_deinit(slot); + } + + array_destroy(slots); + } + + if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } + + if (c != NULL) + { + redisFree(c); + } + return REDIS_ERR; +} + +static int +cluster_update_route_with_nodes(redisClusterContext *cc, + const char *ip, int port) +{ + redisContext *c = NULL; + redisReply *reply = NULL; + struct array *slots = NULL; + dict *nodes = NULL; + cluster_node *node; + 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; + const char *errstr = NULL; + int err = 0; + sds *part = NULL, *ip_port = NULL, *slot_start_end = NULL; + int count_part = 0, count_ip_port = 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) + { + err = REDIS_ERR_OTHER; + errstr = "ip or port error!\0"; + goto error; + } + + if(cc->timeout) + { + c = redisConnectWithTimeout(ip, port, *cc->timeout); + } + else + { + c = redisConnect(ip, port); + } + + if (c == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "init redis context error(return NULL)!\0"; + goto error; + } + else if(c->err) + { + err = c->err; + errstr = c->errstr; + goto error; + } + + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); + + if(reply == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster nodes) reply error(NULL)!\0"; + goto error; + } + else if(reply->type != REDIS_REPLY_STRING) + { + err = REDIS_ERR_OTHER; + if(reply->type == REDIS_REPLY_ERROR) + { + errstr = reply->str; + } + else + { + errstr = "command(cluster nodes) reply error(type is not string)!\0"; + } + + goto error; + } + + nodes = dictCreate(&clusterNodesDictType, NULL); + + slots = array_create(10, sizeof(cluster_slot*)); + if(slots == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "array create error!\0"; + 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) + { + err = REDIS_ERR_OTHER; + errstr = "split cluster nodes error!\0"; + goto error; + } + + 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]; + } + + if(role_len >= 6 && memcmp(role, "master", 6) == 0) + { + if(count_part < 8) + { + err = REDIS_ERR_OTHER; + errstr = "master node part number error!\0"; + goto error; + } + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "alloc cluster node error!\0"; + goto error; + } + + cluster_node_init(node); + + node->slots = listCreate(); + if(node->slots == NULL) + { + hi_free(node); + err = REDIS_ERR_OTHER; + errstr = "slots for node listCreate error!\0"; + goto error; + } + + node->name = part[0]; + + node->addr = part[1]; + + dictAdd(nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); + + ip_port = sdssplitlen(part[1], sdslen(part[1]), + IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port); + if(ip_port == NULL || count_ip_port != 2) + { + err = REDIS_ERR_OTHER; + errstr = "split ip port error!\0"; + goto error; + } + node->host = ip_port[0]; + node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + node->master = 1; + if(myself == 1) + { + node->con = c; + c = NULL; + myself = 0; + } + + sdsfree(ip_port[1]); + free(ip_port); + count_ip_port = 0; + ip_port = 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) + { + err = REDIS_ERR_OTHER; + errstr = "split slot start end error(NULL)!\0"; + 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) + { + err = REDIS_ERR_OTHER; + errstr = "diffent node hold a same slot!\0"; + goto error; + } + table[j] = node; + } + + slot = array_push(slots); + if(slot == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "slot push in array error!\0"; + goto error; + } + + *slot = hi_alloc(sizeof(**slot)); + if(*slot == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "alloc slot error!\0"; + goto error; + } + + cluster_slot_init(*slot, NULL); + + (*slot)->start = (uint32_t)slot_start; + (*slot)->end = (uint32_t)slot_end; + cluster_slot_ref_node(*slot, node); + + } + + for(k = 2; k < count_part; k ++) + { + sdsfree(part[k]); + } + free(part); + count_part = 0; + part = NULL; + } + else + { + 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) + { + while(array_n(cc->slots)) + { + slot = array_pop(cc->slots); + cluster_slot_deinit(*slot); + } + + array_destroy(cc->slots); + cc->slots = NULL; + } + cc->slots = slots; + + if(cc->nodes != NULL) + { + dictRelease(cc->nodes); + } + cc->nodes = nodes; + + array_sort(cc->slots, cluster_slot_start_cmp); + + memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + freeReplyObject(reply); + + if(c != NULL) + { + redisFree(c); + } + + return REDIS_OK; + +error: + + __redisClusterSetError(cc, err, errstr); + + if(part != NULL) + { + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + } + + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, count_ip_port); + count_ip_port = 0; + ip_port = 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; + } + + while(array_n(slots)) + { + slot = array_pop(slots); + cluster_slot_deinit(*slot); + } + + array_destroy(slots); + } + + if(nodes != NULL) + { + if(nodes == cc->nodes) + { + cc->nodes = NULL; + } + + dictRelease(cc->nodes); + } + + if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } + + if(c != NULL) + { + redisFree(c); + } + + return REDIS_ERR; +} + +static 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_with_nodes(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_with_nodes(cc, node->host, node->port); + if(ret == REDIS_OK) + { + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + 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 redisClusterContext *redisClusterContextInit(void) { + redisClusterContext *cc; + unsigned int i; + + 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->nodes = NULL; + for(i = 0; i < REDIS_CLUSTER_SLOTS; i ++) + { + cc->table[i] = NULL; + } + + return cc; +} + +void redisClusterFree(redisClusterContext *cc) { + + unsigned int i; + cluster_slot **slot; + + if (cc == NULL) + return; + + if(cc->ip) + { + sdsfree(cc->ip); + cc->ip = NULL; + } + + if (cc->timeout) + { + free(cc->timeout); + } + + for(i = 0; i < REDIS_CLUSTER_SLOTS; i ++) + { + cc->table[i] = NULL; + } + + if(cc->slots != NULL) + { + while(array_n(cc->slots)) + { + slot = array_pop(cc->slots); + cluster_slot_deinit(*slot); + } + + array_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) +{ + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags |= REDIS_BLOCK; + + return _redisClusterConnect(cc, addrs); +} + +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv) +{ + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags |= REDIS_BLOCK; + + 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) { + + redisClusterContext *cc; + + cc = redisClusterContextInit(); + + if(cc == NULL) + { + return NULL; + } + + cc->flags &= ~REDIS_BLOCK; + + 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) + { + __redisSetError(c, REDIS_ERR_OTHER, "node host or port is error"); + return c; + } + + 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 array *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 = array_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 >= 0 && middle < slot_count); + + slot = array_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, cc->flags); + if(c == NULL || c->err) + { + continue; + } + + reply = redisCommand(c, "ping"); + //printf("reply->type: %d\n", reply->type); + //printf("reply->str: %s\n", reply->str==NULL?"NULL":reply->str); + + 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_req(command); + if(command->result != CMD_PARSE_OK) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); + goto done; + } + + key_count = array_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 = array_get(command->keys, 0); + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + + goto done; + } + + for(i = 0; i < array_n(command->keys); i ++) + { + kp = array_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; +} + +/* 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. + */ + +int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) +{ + cluster_node *node; + redisContext *c; + + if(cc == NULL || slot_num < 0) + { + 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) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + if(redisGetReply(c, reply) != REDIS_OK) + { + __redisClusterSetError(cc, c->err, c->errstr); + return REDIS_ERR; + } + + return REDIS_OK; +} + +static void *redis_cluster_command_execute(redisClusterContext *cc, + struct cmd *command) +{ + int ret; + void *reply = NULL; + redisReply *reply_check = NULL; + cluster_node *node; + redisContext *c = NULL; + +moved_retry: + + 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 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 || 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; + } + + reply_check = reply; + if(reply_check->type == REDIS_REPLY_ERROR) + { + if((int)strlen(REDIS_ERROR_MOVED) < reply_check->len && + strncmp(reply_check->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) + { + 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; + } + + 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; + } + + goto moved_retry; + } + else if((int)strlen(REDIS_ERROR_ASK) < reply_check->len && + strncmp(reply_check->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0) + { + sds *part = NULL, *ip_port = NULL; + int part_len = 0, ip_port_len; + dictEntry *de; + + part = sdssplitlen(reply_check->str, reply_check->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_OTHER, + "alloc cluster node error!"); + sdsfreesplitres(part, part_len); + part = NULL; + sdsfreesplitres(ip_port, ip_port_len); + ip_port = NULL; + + return NULL; + } + + 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->master = 1; + + dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); + + part = NULL; + ip_port = NULL; + } + else + { + node = de->val; + + sdsfreesplitres(part, part_len); + part = NULL; + sdsfreesplitres(ip_port, ip_port_len); + ip_port = 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; + } + + } + + if(part != NULL) + { + sdsfreesplitres(part, part_len); + part = NULL; + } + + if(ip_port != NULL) + { + sdsfreesplitres(ip_port, ip_port_len); + ip_port = NULL; + } + } + + freeReplyObject(reply); + reply = NULL; + + reply = redisCommand(c, "asking"); + + freeReplyObject(reply); + reply = 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; + } + + goto ask_retry; + } + } + + + return reply; +} + +static int command_pre_fragment(redisClusterContext *cc, + struct cmd *command, list *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 = array_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 = array_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 = array_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 < array_n(sub_command->keys); j ++) + { + kp = array_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 < array_n(sub_command->keys); j ++) + { + kp = array_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 < array_n(sub_command->keys); j ++) + { + kp = array_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); + command_destroy(list_node->value); + 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, list *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 = array_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; +} + +static int command_format_by_slot(redisClusterContext *cc, + struct cmd *command, list *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_req(command); + if(command->result != CMD_PARSE_OK) + { + __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); + goto done; + } + + key_count = array_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 = array_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 *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { + + va_list ap; + redisReply *reply = NULL; + char *cmd = NULL; + int slot_num; + int len; + struct cmd *command = NULL, *sub_command; + list *commands = NULL; + listNode *list_node; + listIter *list_iter = NULL; + + va_start(ap,format); + + if(cc == NULL) + { + return NULL; + } + + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } + + len = redisvFormatCommand(&cmd,format,ap); + + va_end(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; + } + + 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_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_destroy(command); + } + else if(cmd != NULL) + { + free(cmd); + } + + if(commands != NULL) + { + listRelease(commands); + } + + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } + + cc->retry_count = 0; + + return NULL; +} + +int redisClusterAppendCommand(redisClusterContext *cc, + const char *format, ...) { + + va_list ap; + int len; + int slot_num; + struct cmd *command = NULL, *sub_command; + list *commands = NULL; + + char *cmd; + listNode *list_node; + listIter *list_iter = NULL; + + if(cc == NULL || format == NULL) + { + return REDIS_ERR; + } + + 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; + } + + va_start(ap,format); + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } else if (len == -2) { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + goto error; + } + + 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: + + va_end(ap); + + if(command->cmd != NULL) + { + free(command->cmd); + command->cmd = NULL; + } + else + { + goto error; + } + + if(commands != NULL && listLength(commands) > 0) + { + 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: + + va_end(ap); + + if(command != NULL) + { + command_destroy(command); + } + else if(cmd != NULL) + { + free(cmd); + } + + 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 redisClusterAppendCommandArgv(redisClusterContext *cc, + int argc, const char **argv) { + + int j; + + for (j=0; j < argc; j++) { + if(redisClusterAppendCommand(cc, argv[j]) != REDIS_OK) + { + return REDIS_ERR; + } + } + + return REDIS_OK; +} + + +int redisClusterGetReply(redisClusterContext *cc, void **reply) { + + struct cmd *command, *sub_command; + list *commands = NULL; + listNode *list_command, *list_sub_command; + listIter *list_iter; + int slot_num; + void *sub_reply; + + if(cc == NULL || cc->requests == NULL) + { + return REDIS_ERR; + } + + list_command = listFirst(cc->requests); + if(list_command == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "no more reply"); + return REDIS_ERR; + } + + 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) +{ + dictIterator *di; + dictEntry *de; + struct cluster_node *node; + redisContext *c = NULL; + + if(cc == NULL || cc->nodes == NULL) + { + return; + } + + 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; + } + + sdsfree(c->obuf); + redisReaderFree(c->reader); + + c->obuf = sdsempty(); + c->reader = redisReaderCreate(); + } + + dictReleaseIterator(di); + + if(cc->requests) + { + listRelease(cc->requests); + cc->requests = NULL; + } +} + +/*############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 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) + { + redisReconnect(&ac->c); + } + + return ac; + } + + 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); + } + + node->acon = ac; + + return ac; +} + +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs) { + + redisClusterContext *cc; + redisClusterAsyncContext *acc; + + cc = redisClusterConnectNonBlock(addrs); + 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; +} + +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, + redisCallbackFn *fn, void *privdata, const char *format, ...) { + + va_list ap; + redisClusterContext *cc; + int status = REDIS_OK; + char *cmd = NULL; + int len; + int slot_num; + cluster_node *node; + redisAsyncContext *ac; + + va_start(ap,format); + + if(acc == NULL) + { + return REDIS_ERR; + } + + cc = acc->cc; + + 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; + } + + slot_num = slot_get_by_command(cc, cmd, len); + + if(slot_num < 0) + { + status = REDIS_ERR; + goto done; + } + else if(slot_num >= REDIS_CLUSTER_SLOTS) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"slot_num is out of range"); + status = REDIS_ERR; + goto done; + } + + node = node_get_by_table(cc, (uint32_t) slot_num); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node get by slot error"); + status = REDIS_ERR; + goto done; + } + + ac = actx_get_by_node(acc, node); + if(ac == NULL) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "actx get by slot error"); + status = REDIS_ERR; + goto done; + } + else if(ac->err) + { + __redisClusterAsyncSetError(acc, ac->err, ac->errstr); + status = REDIS_ERR; + goto done; + } + + va_end(ap); + va_start(ap,format); + status = redisvAsyncCommand(ac,fn,privdata,format,ap); + +done: + + va_end(ap); + + if(cmd != NULL) + { + free(cmd); + } + + return status; +} + +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) + { + 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/hircluster.h b/hircluster.h new file mode 100644 index 00000000..5d1313c1 --- /dev/null +++ b/hircluster.h @@ -0,0 +1,121 @@ + +#ifndef __HIRCLUSTER_H +#define __HIRCLUSTER_H + +#include "hiredis.h" +#include "adlist.h" +#include "hiarray.h" +#include "async.h" + +#define REDIS_CLUSTER_SLOTS 16384 + +struct dict; + +typedef struct cluster_node +{ + sds name; + sds addr; + sds host; + int port; + int count; + uint8_t master; + redisContext *con; + redisAsyncContext *acon; + list *slots; + struct cluster_node *slave_of; +}cluster_node; + +typedef struct cluster_slot +{ + uint32_t start; + uint32_t end; + cluster_node *node; +}cluster_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 array *slots; + + struct dict *nodes; + cluster_node *table[REDIS_CLUSTER_SLOTS]; + + int max_redirect_count; + int retry_count; + + list *requests; +} redisClusterContext; + +redisClusterContext *redisClusterConnect(const char *addrs); +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, + const struct timeval tv); +redisClusterContext *redisClusterConnectNonBlock(const char *addrs); + + +void redisClusterFree(redisClusterContext *cc); + +void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); + +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); + +redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); + +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); +int redisClusterGetReply(redisClusterContext *cc, void **reply); +void redisCLusterReset(redisClusterContext *cc); + + +/*############redis cluster async############*/ + +typedef int (adapterAttachFn)(struct redisAsyncContext*, 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 redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, const char *format, ...); +void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +void redisClusterAsyncFree(redisClusterAsyncContext *acc); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hiutil.c b/hiutil.c new file mode 100644 index 00000000..cce5ccd5 --- /dev/null +++ b/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/hiutil.h b/hiutil.h new file mode 100644 index 00000000..6598f07f --- /dev/null +++ b/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/read.h b/read.h index 635a872c..c15e789f 100644 --- a/read.h +++ b/read.h @@ -46,6 +46,9 @@ #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 @@ -56,6 +59,13 @@ #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_STATUS_OK "OK" +#endif //shenzheng 2015-9-24 redis cluster + #ifdef __cplusplus extern "C" { #endif From 37e20e5409d60166e8e23505a310b03487d43b1d Mon Sep 17 00:00:00 2001 From: deep011 Date: Tue, 27 Oct 2015 17:32:49 +0800 Subject: [PATCH 003/273] fix a bug that cluster pipeline can not update route --- command.c | 10 +-- command.h | 4 +- hiarray.c | 38 +++++----- hiarray.h | 40 +++++----- hircluster.c | 209 ++++++++++++++++++++++++++++++++++----------------- hircluster.h | 5 +- 6 files changed, 187 insertions(+), 119 deletions(-) diff --git a/command.c b/command.c index 97a3c82c..f52f0fda 100644 --- a/command.c +++ b/command.c @@ -310,10 +310,10 @@ redis_argeval(struct cmd *r) * the last argument is handled in a special way in order to allow for * a binary-safe last argument. * - * Nutcracker only supports the Redis unified protocol for requests. + * only supports the Redis unified protocol for requests. */ void -redis_parse_req(struct cmd *r) +redis_parse_cmd(struct cmd *r) { char *p, *m, *token = NULL; char *cmd_end; @@ -1110,7 +1110,7 @@ redis_parse_req(struct cmd *r) m = token; token = NULL; - kpos = array_push(r->keys); + kpos = hiarray_push(r->keys); if (kpos == NULL) { goto enomem; } @@ -1638,7 +1638,7 @@ struct cmd *command_get() command->reply = NULL; command->sub_commands = NULL; - command->keys = array_create(1, sizeof(struct keypos)); + command->keys = hiarray_create(1, sizeof(struct keypos)); if (command->keys == NULL) { hi_free(command); @@ -1663,7 +1663,7 @@ void command_destroy(struct cmd *command) if(command->keys != NULL) { command->keys->nelem = 0; - array_destroy(command->keys); + hiarray_destroy(command->keys); } if(command->frag_seq != NULL) diff --git a/command.h b/command.h index a891cd24..0ca359e3 100644 --- a/command.h +++ b/command.h @@ -151,7 +151,7 @@ struct cmd { char *cmd; uint32_t clen; /* command length */ - struct array *keys; /* array of keypos, for req */ + struct hiarray *keys; /* array of keypos, for req */ char *narg_start; /* narg start (redis) */ char *narg_end; /* narg end (redis) */ @@ -170,7 +170,7 @@ struct cmd { }; -void redis_parse_req(struct cmd *r); +void redis_parse_cmd(struct cmd *r); struct cmd *command_get(void); void command_destroy(struct cmd *command); diff --git a/hiarray.c b/hiarray.c index d8fe6e88..8d47fe6a 100644 --- a/hiarray.c +++ b/hiarray.c @@ -2,10 +2,10 @@ #include "hiarray.h" -struct array * -array_create(uint32_t n, size_t size) +struct hiarray * +hiarray_create(uint32_t n, size_t size) { - struct array *a; + struct hiarray *a; ASSERT(n != 0 && size != 0); @@ -28,14 +28,14 @@ array_create(uint32_t n, size_t size) } void -array_destroy(struct array *a) +hiarray_destroy(struct hiarray *a) { - array_deinit(a); + hiarray_deinit(a); hi_free(a); } rstatus_t -array_init(struct array *a, uint32_t n, size_t size) +hiarray_init(struct hiarray *a, uint32_t n, size_t size) { ASSERT(n != 0 && size != 0); @@ -52,7 +52,7 @@ array_init(struct array *a, uint32_t n, size_t size) } void -array_deinit(struct array *a) +hiarray_deinit(struct hiarray *a) { ASSERT(a->nelem == 0); @@ -62,7 +62,7 @@ array_deinit(struct array *a) } uint32_t -array_idx(struct array *a, void *elem) +hiarray_idx(struct hiarray *a, void *elem) { uint8_t *p, *q; uint32_t off, idx; @@ -81,7 +81,7 @@ array_idx(struct array *a, void *elem) } void * -array_push(struct array *a) +hiarray_push(struct hiarray *a) { void *elem, *new; size_t size; @@ -106,7 +106,7 @@ array_push(struct array *a) } void * -array_pop(struct array *a) +hiarray_pop(struct hiarray *a) { void *elem; @@ -119,7 +119,7 @@ array_pop(struct array *a) } void * -array_get(struct array *a, uint32_t idx) +hiarray_get(struct hiarray *a, uint32_t idx) { void *elem; @@ -132,17 +132,17 @@ array_get(struct array *a, uint32_t idx) } void * -array_top(struct array *a) +hiarray_top(struct hiarray *a) { ASSERT(a->nelem != 0); - return array_get(a, a->nelem - 1); + return hiarray_get(a, a->nelem - 1); } void -array_swap(struct array *a, struct array *b) +hiarray_swap(struct hiarray *a, struct hiarray *b) { - struct array tmp; + struct hiarray tmp; tmp = *a; *a = *b; @@ -154,7 +154,7 @@ array_swap(struct array *a, struct array *b) * compare comparator. */ void -array_sort(struct array *a, array_compare_t compare) +hiarray_sort(struct hiarray *a, hiarray_compare_t compare) { ASSERT(a->nelem != 0); @@ -166,15 +166,15 @@ array_sort(struct array *a, array_compare_t compare) * success. On failure short-circuits and returns the error status. */ rstatus_t -array_each(struct array *a, array_each_t func, void *data) +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 = array_n(a); i < nelem; i++) { - void *elem = array_get(a, i); + for (i = 0, nelem = hiarray_n(a); i < nelem; i++) { + void *elem = hiarray_get(a, i); rstatus_t status; status = func(elem, data); diff --git a/hiarray.h b/hiarray.h index 5c965538..8e68b068 100644 --- a/hiarray.h +++ b/hiarray.h @@ -5,20 +5,20 @@ #include "hiutil.h" -typedef int (*array_compare_t)(const void *, const void *); -typedef rstatus_t (*array_each_t)(void *, void *); +typedef int (*hiarray_compare_t)(const void *, const void *); +typedef rstatus_t (*hiarray_each_t)(void *, void *); -struct array { +struct hiarray { uint32_t nelem; /* # element */ void *elem; /* element */ size_t size; /* element size */ uint32_t nalloc; /* # allocated element */ }; -#define null_array { 0, NULL, 0, 0 } +#define null_hiarray { 0, NULL, 0, 0 } static inline void -array_null(struct array *a) +hiarray_null(struct hiarray *a) { a->nelem = 0; a->elem = NULL; @@ -27,7 +27,7 @@ array_null(struct array *a) } static inline void -array_set(struct array *a, void *elem, size_t size, uint32_t nalloc) +hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc) { a->nelem = 0; a->elem = elem; @@ -36,23 +36,23 @@ array_set(struct array *a, void *elem, size_t size, uint32_t nalloc) } static inline uint32_t -array_n(const struct array *a) +hiarray_n(const struct hiarray *a) { return a->nelem; } -struct array *array_create(uint32_t n, size_t size); -void array_destroy(struct array *a); -rstatus_t array_init(struct array *a, uint32_t n, size_t size); -void array_deinit(struct array *a); - -uint32_t array_idx(struct array *a, void *elem); -void *array_push(struct array *a); -void *array_pop(struct array *a); -void *array_get(struct array *a, uint32_t idx); -void *array_top(struct array *a); -void array_swap(struct array *a, struct array *b); -void array_sort(struct array *a, array_compare_t compare); -rstatus_t array_each(struct array *a, array_each_t func, void *data); +struct hiarray *hiarray_create(uint32_t n, size_t size); +void hiarray_destroy(struct hiarray *a); +rstatus_t 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); +rstatus_t hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); #endif diff --git a/hircluster.c b/hircluster.c index 5e75c944..ff76863c 100644 --- a/hircluster.c +++ b/hircluster.c @@ -270,7 +270,7 @@ cluster_update_route_with_slots(redisClusterContext *cc, redisReply *elem_slots_begin, *elem_slots_end; redisReply *elem_node_master; redisReply *elem_ip, *elem_port; - struct array *slots = NULL; + struct hiarray *slots = NULL; cluster_slot *slot; cluster_node *node; const char *errstr = NULL; @@ -328,7 +328,7 @@ cluster_update_route_with_slots(redisClusterContext *cc, goto error; } - slots = array_create(reply->elements, sizeof(cluster_slot)); + slots = hiarray_create(reply->elements, sizeof(cluster_slot)); if(slots == NULL) { err = REDIS_ERR_OTHER; @@ -347,7 +347,7 @@ cluster_update_route_with_slots(redisClusterContext *cc, goto error; } - slot = array_push(slots); + slot = hiarray_push(slots); if(slot == NULL) { err = REDIS_ERR_OTHER; @@ -449,7 +449,7 @@ cluster_update_route_with_slots(redisClusterContext *cc, cc->slots = slots; - array_sort(cc->slots, cluster_slot_start_cmp); + hiarray_sort(cc->slots, cluster_slot_start_cmp); freeReplyObject(reply); @@ -467,13 +467,13 @@ cluster_update_route_with_slots(redisClusterContext *cc, if(slots != NULL) { - while(array_n(slots)) + while(hiarray_n(slots)) { - slot = array_pop(slots); + slot = hiarray_pop(slots); cluster_slot_deinit(slot); } - array_destroy(slots); + hiarray_destroy(slots); } if(reply != NULL) @@ -495,7 +495,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, { redisContext *c = NULL; redisReply *reply = NULL; - struct array *slots = NULL; + struct hiarray *slots = NULL; dict *nodes = NULL; cluster_node *node; cluster_slot **slot; @@ -571,7 +571,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, nodes = dictCreate(&clusterNodesDictType, NULL); - slots = array_create(10, sizeof(cluster_slot*)); + slots = hiarray_create(10, sizeof(cluster_slot*)); if(slots == NULL) { err = REDIS_ERR_OTHER; @@ -717,7 +717,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, table[j] = node; } - slot = array_push(slots); + slot = hiarray_push(slots); if(slot == NULL) { err = REDIS_ERR_OTHER; @@ -768,13 +768,13 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(cc->slots != NULL) { - while(array_n(cc->slots)) + while(hiarray_n(cc->slots)) { - slot = array_pop(cc->slots); + slot = hiarray_pop(cc->slots); cluster_slot_deinit(*slot); } - array_destroy(cc->slots); + hiarray_destroy(cc->slots); cc->slots = NULL; } cc->slots = slots; @@ -785,7 +785,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, } cc->nodes = nodes; - array_sort(cc->slots, cluster_slot_start_cmp); + hiarray_sort(cc->slots, cluster_slot_start_cmp); memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); @@ -830,13 +830,13 @@ cluster_update_route_with_nodes(redisClusterContext *cc, cc->slots = NULL; } - while(array_n(slots)) + while(hiarray_n(slots)) { - slot = array_pop(slots); + slot = hiarray_pop(slots); cluster_slot_deinit(*slot); } - array_destroy(slots); + hiarray_destroy(slots); } if(nodes != NULL) @@ -951,6 +951,7 @@ static redisClusterContext *redisClusterContextInit(void) { cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT; cc->retry_count = 0; cc->requests = NULL; + cc->need_update_route = 0; cc->nodes = NULL; for(i = 0; i < REDIS_CLUSTER_SLOTS; i ++) @@ -987,13 +988,13 @@ void redisClusterFree(redisClusterContext *cc) { if(cc->slots != NULL) { - while(array_n(cc->slots)) + while(hiarray_n(cc->slots)) { - slot = array_pop(cc->slots); + slot = hiarray_pop(cc->slots); cluster_slot_deinit(*slot); } - array_destroy(cc->slots); + hiarray_destroy(cc->slots); cc->slots = NULL; } @@ -1237,7 +1238,7 @@ redisContext *ctx_get_by_node(cluster_node *node, static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num) { - struct array *slots; + struct hiarray *slots; uint32_t slot_count; cluster_slot **slot; uint32_t middle, start, end; @@ -1258,7 +1259,7 @@ static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num { return NULL; } - slot_count = array_n(slots); + slot_count = hiarray_n(slots); start = 0; end = slot_count - 1; @@ -1277,7 +1278,7 @@ static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num ASSERT(middle >= 0 && middle < slot_count); - slot = array_get(slots, middle); + slot = hiarray_get(slots, middle); if((*slot)->start > slot_num) { end = middle - 1; @@ -1396,14 +1397,14 @@ static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) command->cmd = cmd; command->clen = len; - redis_parse_req(command); + redis_parse_cmd(command); if(command->result != CMD_PARSE_OK) { __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); goto done; } - key_count = array_n(command->keys); + key_count = hiarray_n(command->keys); if(key_count <= 0) { @@ -1412,15 +1413,15 @@ static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) } else if(key_count == 1) { - kp = array_get(command->keys, 0); + kp = hiarray_get(command->keys, 0); slot_num = keyHashSlot(kp->start, kp->end - kp->start); goto done; } - for(i = 0; i < array_n(command->keys); i ++) + for(i = 0; i < hiarray_n(command->keys); i ++) { - kp = array_get(command->keys, i); + kp = hiarray_get(command->keys, i); slot_num = keyHashSlot(kp->start, kp->end - kp->start); } @@ -1472,7 +1473,7 @@ static int __redisClusterAppendCommand(redisClusterContext *cc, return REDIS_ERR; } - if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) + if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) { __redisClusterSetError(cc, c->err, c->errstr); return REDIS_ERR; @@ -1483,13 +1484,13 @@ static int __redisClusterAppendCommand(redisClusterContext *cc, /* Helper function for the redisClusterGetReply* family of functions. */ - int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) { cluster_node *node; redisContext *c; + redisReply *r; - if(cc == NULL || slot_num < 0) + if(cc == NULL || slot_num < 0 || reply == NULL) { return REDIS_ERR; } @@ -1509,6 +1510,15 @@ int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) } 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; } @@ -1519,6 +1529,14 @@ int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) return REDIS_ERR; } + r = *reply; + if(r->type == REDIS_REPLY_ERROR && + (int)strlen(REDIS_ERROR_MOVED) < r->len && + strncmp(r->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) + { + cc->need_update_route = 1; + } + return REDIS_OK; } @@ -1733,7 +1751,7 @@ static int command_pre_fragment(redisClusterContext *cc, goto done; } - key_count = array_n(command->keys); + key_count = hiarray_n(command->keys); sub_commands = hi_zalloc(REDIS_CLUSTER_SLOTS * sizeof(*sub_commands)); if (sub_commands == NULL) @@ -1752,7 +1770,7 @@ static int command_pre_fragment(redisClusterContext *cc, for(i = 0; i < key_count; i ++) { - kp = array_get(command->keys, i); + kp = hiarray_get(command->keys, i); slot_num = keyHashSlot(kp->start, kp->end - kp->start); @@ -1775,7 +1793,7 @@ static int command_pre_fragment(redisClusterContext *cc, sub_command->narg++; - sub_kp = array_push(sub_command->keys); + sub_kp = hiarray_push(sub_command->keys); if (sub_kp == NULL) { __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); slot_num = -1; @@ -1847,9 +1865,9 @@ static int command_pre_fragment(redisClusterContext *cc, memcpy(sub_command->cmd + idx, "\r\n$4\r\nmget\r\n", 12); idx += 12; - for(j = 0; j < array_n(sub_command->keys); j ++) + for(j = 0; j < hiarray_n(sub_command->keys); j ++) { - kp = array_get(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); @@ -1890,9 +1908,9 @@ static int command_pre_fragment(redisClusterContext *cc, memcpy(sub_command->cmd + idx, "\r\n$3\r\ndel\r\n", 11); idx += 11; - for(j = 0; j < array_n(sub_command->keys); j ++) + for(j = 0; j < hiarray_n(sub_command->keys); j ++) { - kp = array_get(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); @@ -1935,9 +1953,9 @@ static int command_pre_fragment(redisClusterContext *cc, memcpy(sub_command->cmd + idx, "\r\n$4\r\nmset\r\n", 12); idx += 12; - for(j = 0; j < array_n(sub_command->keys); j ++) + for(j = 0; j < hiarray_n(sub_command->keys); j ++) { - kp = array_get(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); @@ -2051,7 +2069,7 @@ static void *command_post_fragment(redisClusterContext *cc, reply->type = REDIS_REPLY_ARRAY; - key_count = array_n(command->keys); + key_count = hiarray_n(command->keys); reply->elements = key_count; reply->element = hi_calloc(key_count, sizeof(*reply)); @@ -2125,14 +2143,14 @@ static int command_format_by_slot(redisClusterContext *cc, } - redis_parse_req(command); + redis_parse_cmd(command); if(command->result != CMD_PARSE_OK) { __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); goto done; } - key_count = array_n(command->keys); + key_count = hiarray_n(command->keys); if(key_count <= 0) { @@ -2141,7 +2159,7 @@ static int command_format_by_slot(redisClusterContext *cc, } else if(key_count == 1) { - kp = array_get(command->keys, 0); + kp = hiarray_get(command->keys, 0); slot_num = keyHashSlot(kp->start, kp->end - kp->start); command->slot_num = slot_num; @@ -2423,7 +2441,7 @@ int redisClusterAppendCommand(redisClusterContext *cc, goto error; } - if(commands != NULL && listLength(commands) > 0) + if(commands != NULL) { if(listLength(commands) > 0) { @@ -2489,6 +2507,50 @@ int redisClusterAppendCommandArgv(redisClusterContext *cc, return REDIS_OK; } +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; +} int redisClusterGetReply(redisClusterContext *cc, void **reply) { @@ -2499,17 +2561,18 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { int slot_num; void *sub_reply; - if(cc == NULL || cc->requests == NULL) + if(cc == NULL || cc->requests == NULL || reply == NULL) { return REDIS_ERR; } list_command = listFirst(cc->requests); + + //no more reply if(list_command == NULL) { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "no more reply"); - return REDIS_ERR; + *reply = NULL; + return REDIS_OK; } command = list_command->value; @@ -2583,45 +2646,49 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { void redisCLusterReset(redisClusterContext *cc) { - dictIterator *di; - dictEntry *de; - struct cluster_node *node; redisContext *c = NULL; + int status; + void *reply; if(cc == NULL || cc->nodes == NULL) { return; } - di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); - if(node == NULL) + redisCLusterSendAll(cc); + + do{ + status = redisClusterGetReply(cc, &reply); + if(status == REDIS_OK) { - continue; + freeReplyObject(reply); } - - c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL) + else { - continue; + redisReaderFree(c->reader); + c->reader = redisReaderCreate(); + break; } - - sdsfree(c->obuf); - redisReaderFree(c->reader); - - c->obuf = sdsempty(); - c->reader = redisReaderCreate(); } - - dictReleaseIterator(di); - + 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############*/ diff --git a/hircluster.h b/hircluster.h index 5d1313c1..ef8470c4 100644 --- a/hircluster.h +++ b/hircluster.h @@ -4,7 +4,6 @@ #include "hiredis.h" #include "adlist.h" -#include "hiarray.h" #include "async.h" #define REDIS_CLUSTER_SLOTS 16384 @@ -48,7 +47,7 @@ typedef struct redisClusterContext { enum redisConnectionType connection_type; struct timeval *timeout; - struct array *slots; + struct hiarray *slots; struct dict *nodes; cluster_node *table[REDIS_CLUSTER_SLOTS]; @@ -57,6 +56,8 @@ typedef struct redisClusterContext { int retry_count; list *requests; + + int need_update_route; } redisClusterContext; redisClusterContext *redisClusterConnect(const char *addrs); From b960bbf833523f0e0a284ecd8b15be5f457a9aaa Mon Sep 17 00:00:00 2001 From: deep011 Date: Mon, 2 Nov 2015 16:57:49 +0800 Subject: [PATCH 004/273] support cluster asynchronous api update route table when slots are migrating --- hircluster.c | 653 +++++++++++++++++++++++++++++++++++++++++++++++---- hircluster.h | 8 +- read.h | 9 +- 3 files changed, 621 insertions(+), 49 deletions(-) diff --git a/hircluster.c b/hircluster.c index ff76863c..08696aa0 100644 --- a/hircluster.c +++ b/hircluster.c @@ -16,12 +16,37 @@ #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 cluster_node *node; + 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); unsigned int dictSdsHash(const void *key) { @@ -147,6 +172,50 @@ static void __redisClusterSetError(redisClusterContext *cc, int type, const char } } +static CLUSTER_ERR_TYPE 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) { node->name = NULL; @@ -250,7 +319,48 @@ static int cluster_slot_ref_node(cluster_slot *slot, cluster_node *node) return REDIS_OK; } +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; + 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; + } + } + + dictReleaseIterator(di); + +} static int cluster_slot_start_cmp(const void *t1, const void *t2) @@ -260,6 +370,10 @@ cluster_slot_start_cmp(const void *t1, const void *t2) return (*s1)->start > (*s2)->start?1:-1; } +/* + * Not used and not correct, maybe delete in the future. + * + */ static int cluster_update_route_with_slots(redisClusterContext *cc, const char *ip, int port) @@ -779,6 +893,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, } cc->slots = slots; + cluster_nodes_swap_ctx(cc->nodes, nodes); + if(cc->nodes != NULL) { dictRelease(cc->nodes); @@ -788,6 +904,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, hiarray_sort(cc->slots, cluster_slot_start_cmp); memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + cc->route_version ++; freeReplyObject(reply); @@ -934,7 +1051,6 @@ cluster_update_route(redisClusterContext *cc) static redisClusterContext *redisClusterContextInit(void) { redisClusterContext *cc; - unsigned int i; cc = calloc(1,sizeof(redisClusterContext)); if (cc == NULL) @@ -954,17 +1070,16 @@ static redisClusterContext *redisClusterContextInit(void) { cc->need_update_route = 0; cc->nodes = NULL; - for(i = 0; i < REDIS_CLUSTER_SLOTS; i ++) - { - cc->table[i] = NULL; - } + + cc->route_version = 0; + + memset(cc->table, 0, REDIS_CLUSTER_SLOTS); return cc; } void redisClusterFree(redisClusterContext *cc) { - unsigned int i; cluster_slot **slot; if (cc == NULL) @@ -981,10 +1096,7 @@ void redisClusterFree(redisClusterContext *cc) { free(cc->timeout); } - for(i = 0; i < REDIS_CLUSTER_SLOTS; i ++) - { - cc->table[i] = NULL; - } + memset(cc->table, 0, REDIS_CLUSTER_SLOTS); if(cc->slots != NULL) { @@ -1211,8 +1323,7 @@ redisContext *ctx_get_by_node(cluster_node *node, if(node->host == NULL || node->port <= 0) { - __redisSetError(c, REDIS_ERR_OTHER, "node host or port is error"); - return c; + return NULL; } if(flags & REDIS_BLOCK) @@ -1349,9 +1460,7 @@ static cluster_node *node_get_witch_connected(redisClusterContext *cc) continue; } - reply = redisCommand(c, "ping"); - //printf("reply->type: %d\n", reply->type); - //printf("reply->str: %s\n", reply->str==NULL?"NULL":reply->str); + reply = redisCommand(c, REDIS_COMMAND_PING); if(reply != NULL && reply->type == REDIS_REPLY_STATUS && reply->str != NULL && strcmp(reply->str, "PONG") == 0) @@ -1540,6 +1649,99 @@ int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) 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->master = 1; + + 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) { @@ -1554,7 +1756,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, node = node_get_by_table(cc, (uint32_t)command->slot_num); if(node == NULL) { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error"); return NULL; } @@ -1709,7 +1911,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, freeReplyObject(reply); reply = NULL; - reply = redisCommand(c, "asking"); + reply = redisCommand(c, REDIS_COMMAND_ASKING); freeReplyObject(reply); reply = NULL; @@ -1726,7 +1928,6 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, } } - return reply; } @@ -2128,6 +2329,13 @@ static void *command_post_fragment(redisClusterContext *cc, 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, list *commands) { @@ -2747,6 +2955,43 @@ static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext 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->node = 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 redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node) { @@ -2840,8 +3085,246 @@ int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisD 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; + CLUSTER_ERR_TYPE error_type; + cluster_node *node; + struct cmd *command; + + 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 do not know how to deal with connect problem for hiredis cluster async api, + //so i leave it temporarily. + /* + if(ac->c.flags & REDIS_DISCONNECTING) + { + 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; + } + + node = cad->node; + node->acon = NULL; + + node = node_get_witch_connected(cc); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "no reachable node in cluster"); + 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; + } + + cad->node = node; + + goto retry; + } + */ + __redisClusterAsyncSetError(acc, ac->err, + ac->errstr); + + 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: + ret = cluster_update_route(cc); + if(ret != REDIS_OK) + { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "route update error, please recreate redisClusterContext!"); + goto done; + } + + node = node_get_by_table(cc, (uint32_t)command->slot_num); + if(node == NULL) + { + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "node get by table error"); + 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; + } + + cad->node = node; + + 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; + } + + cad->node = node; + + 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 redisClusterAsyncCommand(redisClusterAsyncContext *acc, - redisCallbackFn *fn, void *privdata, const char *format, ...) { + redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { va_list ap; redisClusterContext *cc; @@ -2851,76 +3334,156 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, int slot_num; cluster_node *node; redisAsyncContext *ac; - - va_start(ap,format); + struct cmd *command = NULL; + list *commands = NULL; + cluster_async_data *cad; if(acc == NULL) { return REDIS_ERR; } + va_start(ap,format); + 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)); + } + len = redisvFormatCommand(&cmd,format,ap); if (len == -1) { __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - return REDIS_ERR; + goto error; } else if (len == -2) { __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string"); - return REDIS_ERR; + goto error; } - slot_num = slot_get_by_command(cc, cmd, len); + command = command_get(); + if(command == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + command->cmd = cmd; + 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); + + //slot_num = slot_get_by_command(cc, cmd, len); if(slot_num < 0) { - status = REDIS_ERR; - goto done; + __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"); - status = REDIS_ERR; - goto done; + __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 slot error"); - status = REDIS_ERR; - goto done; + __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 slot error"); - status = REDIS_ERR; - goto done; + __redisClusterAsyncSetError(acc, + REDIS_ERR_OTHER, "actx get by node error"); + goto error; } else if(ac->err) { __redisClusterAsyncSetError(acc, ac->err, ac->errstr); - status = REDIS_ERR; - goto done; + goto error; } - va_end(ap); - va_start(ap,format); - status = redisvAsyncCommand(ac,fn,privdata,format,ap); + cad = cluster_async_data_get(); + if(cad == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } -done: + cad->acc = acc; + cad->node = node; + cad->command = command; + cad->callback = fn; + cad->privdata = privdata; + + status = redisAsyncFormattedCommand(ac, + redisClusterAsyncCallback,cad,cmd,len); + if(status != REDIS_OK) + { + goto error; + } va_end(ap); - if(cmd != NULL) + if(commands != NULL) { - free(cmd); + listRelease(commands); } + + return REDIS_OK; + +error: + + va_end(ap); - return status; + if(command != NULL) + { + command_destroy(command); + } + else if(cmd != NULL) + { + free(cmd); + } + + if(commands != NULL) + { + listRelease(commands); + } + + return REDIS_ERR; } void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { @@ -2954,7 +3517,7 @@ void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { ac = node->acon; - if(ac == NULL) + if(ac == NULL || ac->err) { continue; } diff --git a/hircluster.h b/hircluster.h index ef8470c4..8e50448c 100644 --- a/hircluster.h +++ b/hircluster.h @@ -52,6 +52,8 @@ typedef struct redisClusterContext { struct dict *nodes; cluster_node *table[REDIS_CLUSTER_SLOTS]; + uint64_t route_version; + int max_redirect_count; int retry_count; @@ -82,8 +84,12 @@ void redisCLusterReset(redisClusterContext *cc); /*############redis cluster async############*/ +struct redisClusterAsyncContext; + typedef int (adapterAttachFn)(struct redisAsyncContext*, void*); +typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*); + /* Context for an async connection to Redis */ typedef struct redisClusterAsyncContext { @@ -111,7 +117,7 @@ typedef struct redisClusterAsyncContext { redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); void redisClusterAsyncFree(redisClusterAsyncContext *acc); diff --git a/read.h b/read.h index c15e789f..088c9790 100644 --- a/read.h +++ b/read.h @@ -60,10 +60,13 @@ #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_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" +#define REDIS_STATUS_OK "OK" #endif //shenzheng 2015-9-24 redis cluster #ifdef __cplusplus From 442d7c8b9fd4b311843d250a28bd011275c139f9 Mon Sep 17 00:00:00 2001 From: deep011 Date: Thu, 5 Nov 2015 09:10:42 +0800 Subject: [PATCH 005/273] support ae cluster asynchronous api --- adapters/ae.h | 27 +++++++++++++++++++++++++++ async.c | 2 +- hiredis.h | 1 - 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/adapters/ae.h b/adapters/ae.h index 5c551c2e..f861cf28 100644 --- a/adapters/ae.h +++ b/adapters/ae.h @@ -35,6 +35,10 @@ #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; @@ -124,4 +128,27 @@ static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { 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/async.c b/async.c index 28aa76f5..fde90675 100644 --- a/async.c +++ b/async.c @@ -651,7 +651,7 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat 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; diff --git a/hiredis.h b/hiredis.h index d4bf9e52..87f7366f 100644 --- a/hiredis.h +++ b/hiredis.h @@ -156,7 +156,6 @@ typedef struct redisContext { struct { char *path; } unix_sock; - } redisContext; redisContext *redisConnect(const char *ip, int port); From 8d88c63695763bad94ebc7c23b60cc93d44c3dfc Mon Sep 17 00:00:00 2001 From: deep011 Date: Fri, 6 Nov 2015 23:32:45 +0800 Subject: [PATCH 006/273] add slave nodes to node list --- command.c | 21 --- hircluster.c | 411 +++++++++++++++++++++++++++------------------------ hircluster.h | 2 + 3 files changed, 220 insertions(+), 214 deletions(-) diff --git a/command.c b/command.c index f52f0fda..00e9962f 100644 --- a/command.c +++ b/command.c @@ -1116,7 +1116,6 @@ redis_parse_cmd(struct cmd *r) } kpos->start = m; kpos->end = p; - //kpos->v_len = 0; state = SW_KEY_LF; } @@ -1196,26 +1195,6 @@ redis_parse_cmd(struct cmd *r) } 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; diff --git a/hircluster.c b/hircluster.c index 08696aa0..8450fe3f 100644 --- a/hircluster.c +++ b/hircluster.c @@ -13,6 +13,12 @@ #include "dict.c" #include "async.h" +/* 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. */ +#define CLUSTER_NEED_ADD_SLAVE 0x10000000 + + #define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" #define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" @@ -77,6 +83,8 @@ void dictClusterNodeDestructor(void *privdata, void *val) DICT_NOTUSED(privdata); cluster_node_deinit(val); + + hi_free(val); } /* Cluster nodes hash table, mapping nodes addresses 1.2.3.4:6379 to @@ -319,6 +327,91 @@ static int cluster_slot_ref_node(cluster_slot *slot, cluster_node *node) return REDIS_OK; } +void redisClusterSetOptionAddSlave(redisClusterContext *cc) +{ + if(cc == NULL) + { + return; + } + + cc->flags |= CLUSTER_NEED_ADD_SLAVE; +} + +/** + * 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, int 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) + { + node->slots = listCreate(); + if(node->slots == NULL) + { + hi_free(node); + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slots for node listCreate error"); + goto error; + } + } + + 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->master = 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; @@ -603,10 +696,14 @@ cluster_update_route_with_slots(redisClusterContext *cc, return REDIS_ERR; } +/** + * Update route with the "cluster nodes" command reply. + */ static int cluster_update_route_with_nodes(redisClusterContext *cc, const char *ip, int port) { + int ret; redisContext *c = NULL; redisReply *reply = NULL; struct hiarray *slots = NULL; @@ -618,10 +715,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, int role_len; uint8_t myself = 0; int slot_start, slot_end; - const char *errstr = NULL; - int err = 0; - sds *part = NULL, *ip_port = NULL, *slot_start_end = NULL; - int count_part = 0, count_ip_port = 0, count_slot_start_end = 0; + 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}; @@ -633,8 +728,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(ip == NULL || port <= 0) { - err = REDIS_ERR_OTHER; - errstr = "ip or port error!\0"; + __redisClusterSetError(cc, + REDIS_ERR_OTHER,"ip or port error!"); goto error; } @@ -649,14 +744,13 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if (c == NULL) { - err = REDIS_ERR_OTHER; - errstr = "init redis context error(return NULL)!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "init redis context error(return NULL)"); goto error; } else if(c->err) { - err = c->err; - errstr = c->errstr; + __redisClusterSetError(cc,c->err,c->errstr); goto error; } @@ -664,20 +758,21 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(reply == NULL) { - err = REDIS_ERR_OTHER; - errstr = "command(cluster nodes) reply error(NULL)!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command(cluster nodes) reply error(NULL)"); goto error; } else if(reply->type != REDIS_REPLY_STRING) { - err = REDIS_ERR_OTHER; if(reply->type == REDIS_REPLY_ERROR) { - errstr = reply->str; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + reply->str); } else { - errstr = "command(cluster nodes) reply error(type is not string)!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "command(cluster nodes) reply error(type is not string)"); } goto error; @@ -688,8 +783,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, slots = hiarray_create(10, sizeof(cluster_slot*)); if(slots == NULL) { - err = REDIS_ERR_OTHER; - errstr = "array create error!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "array create error"); goto error; } @@ -709,8 +804,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(part == NULL || count_part < 8) { - err = REDIS_ERR_OTHER; - errstr = "split cluster nodes error!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split cluster nodes error"); goto error; } @@ -726,63 +821,39 @@ cluster_update_route_with_nodes(redisClusterContext *cc, role = part[2]; } + //add master node if(role_len >= 6 && memcmp(role, "master", 6) == 0) { if(count_part < 8) { - err = REDIS_ERR_OTHER; - errstr = "master node part number error!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "master node part number error"); goto error; } - - node = hi_alloc(sizeof(cluster_node)); + + node = node_get_with_nodes(cc, part, count_part, 1); if(node == NULL) { - err = REDIS_ERR_OTHER; - errstr = "alloc cluster node error!\0"; goto error; } - cluster_node_init(node); - - node->slots = listCreate(); - if(node->slots == NULL) + ret = dictAdd(nodes, + sdsnewlen(node->addr, sdslen(node->addr)), node); + if(ret != DICT_OK) { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "the address already exists in the nodes"); + cluster_node_deinit(node); hi_free(node); - err = REDIS_ERR_OTHER; - errstr = "slots for node listCreate error!\0"; goto error; } - node->name = part[0]; - - node->addr = part[1]; - - dictAdd(nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); - - ip_port = sdssplitlen(part[1], sdslen(part[1]), - IP_PORT_SEPARATOR, strlen(IP_PORT_SEPARATOR), &count_ip_port); - if(ip_port == NULL || count_ip_port != 2) - { - err = REDIS_ERR_OTHER; - errstr = "split ip port error!\0"; - goto error; - } - node->host = ip_port[0]; - node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - node->master = 1; if(myself == 1) { node->con = c; c = NULL; - myself = 0; } - sdsfree(ip_port[1]); - free(ip_port); - count_ip_port = 0; - ip_port = NULL; - for(k = 8; k < count_part; k ++) { slot_start_end = sdssplitlen(part[k], @@ -790,19 +861,22 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(slot_start_end == NULL) { - err = REDIS_ERR_OTHER; - errstr = "split slot start end error(NULL)!\0"; + __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_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]));; + 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 { @@ -824,8 +898,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, { if(table[j] != NULL) { - err = REDIS_ERR_OTHER; - errstr = "diffent node hold a same slot!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "diffent node hold a same slot"); goto error; } table[j] = node; @@ -834,16 +908,16 @@ cluster_update_route_with_nodes(redisClusterContext *cc, slot = hiarray_push(slots); if(slot == NULL) { - err = REDIS_ERR_OTHER; - errstr = "slot push in array error!\0"; + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "slot push in array error"); goto error; } *slot = hi_alloc(sizeof(**slot)); if(*slot == NULL) { - err = REDIS_ERR_OTHER; - errstr = "alloc slot error!\0"; + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); goto error; } @@ -855,24 +929,43 @@ cluster_update_route_with_nodes(redisClusterContext *cc, } - for(k = 2; k < count_part; k ++) - { - sdsfree(part[k]); - } - free(part); - count_part = 0; - part = NULL; } - else + //add slave node + else if((cc->flags & CLUSTER_NEED_ADD_SLAVE) && + (role_len >= 5 && memcmp(role, "slave", 5) == 0)) { + node = node_get_with_nodes(cc, part, count_part, 0); + if(node == NULL) + { + goto error; + } + + ret = dictAdd(nodes, + sdsnewlen(node->addr, sdslen(node->addr)), node); + if(ret != DICT_OK) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "the address already exists in the nodes"); + cluster_node_deinit(node); + hi_free(node); + goto error; + } + if(myself == 1) { - myself = 0; + node->con = c; + c = NULL; } - sdsfreesplitres(part, count_part); - count_part = 0; - part = NULL; } + + if(myself == 1) + { + myself = 0; + } + + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; start = pos + 1; line_start = start; @@ -916,8 +1009,6 @@ cluster_update_route_with_nodes(redisClusterContext *cc, return REDIS_OK; error: - - __redisClusterSetError(cc, err, errstr); if(part != NULL) { @@ -926,13 +1017,6 @@ cluster_update_route_with_nodes(redisClusterContext *cc, part = NULL; } - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, count_ip_port); - count_ip_port = 0; - ip_port = NULL; - } - if(slot_start_end != NULL) { sdsfreesplitres(slot_start_end, count_slot_start_end); @@ -1597,7 +1681,6 @@ int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) { cluster_node *node; redisContext *c; - redisReply *r; if(cc == NULL || slot_num < 0 || reply == NULL) { @@ -1638,10 +1721,7 @@ int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) return REDIS_ERR; } - r = *reply; - if(r->type == REDIS_REPLY_ERROR && - (int)strlen(REDIS_ERROR_MOVED) < r->len && - strncmp(r->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) + if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED) { cc->need_update_route = 1; } @@ -1747,11 +1827,11 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, { int ret; void *reply = NULL; - redisReply *reply_check = NULL; cluster_node *node; redisContext *c = NULL; + CLUSTER_ERR_TYPE error_type; -moved_retry: +retry: node = node_get_by_table(cc, (uint32_t)command->slot_num); if(node == NULL) @@ -1806,12 +1886,20 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, return NULL; } - reply_check = reply; - if(reply_check->type == REDIS_REPLY_ERROR) + error_type = cluster_reply_error_type(reply); + if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) { - if((int)strlen(REDIS_ERROR_MOVED) < reply_check->len && - strncmp(reply_check->str, REDIS_ERROR_MOVED, strlen(REDIS_ERROR_MOVED)) == 0) + 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; + } + + switch(error_type) + { + case CLUSTER_ERR_MOVED: freeReplyObject(reply); reply = NULL; ret = cluster_update_route(cc); @@ -1821,91 +1909,15 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, "route update error, please recreate redisClusterContext!"); 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; - } - goto moved_retry; - } - else if((int)strlen(REDIS_ERROR_ASK) < reply_check->len && - strncmp(reply_check->str, REDIS_ERROR_ASK, strlen(REDIS_ERROR_ASK)) == 0) - { - sds *part = NULL, *ip_port = NULL; - int part_len = 0, ip_port_len; - dictEntry *de; + goto retry; - part = sdssplitlen(reply_check->str, reply_check->len, - " ", 1, &part_len); - - if(part != NULL && part_len == 3) + break; + case CLUSTER_ERR_ASK: + node = node_get_by_ask_error_reply(cc, reply); + if(node == NULL) { - 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_OTHER, - "alloc cluster node error!"); - sdsfreesplitres(part, part_len); - part = NULL; - sdsfreesplitres(ip_port, ip_port_len); - ip_port = NULL; - - return NULL; - } - - 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->master = 1; - - dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); - - part = NULL; - ip_port = NULL; - } - else - { - node = de->val; - - sdsfreesplitres(part, part_len); - part = NULL; - sdsfreesplitres(ip_port, ip_port_len); - ip_port = 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; - } - - } - - if(part != NULL) - { - sdsfreesplitres(part, part_len); - part = NULL; - } - - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, ip_port_len); - ip_port = NULL; - } + return NULL; } freeReplyObject(reply); @@ -1915,19 +1927,24 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, freeReplyObject(reply); reply = 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; - } 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; } @@ -3005,12 +3022,18 @@ static redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, ac = node->acon; if(ac != NULL) { + /* if(ac->c.err) { redisReconnect(&ac->c); } return ac; + */ + if(ac->c.err == 0) + { + return ac; + } } if(node->host == NULL || node->port <= 0) @@ -3167,15 +3190,16 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv goto retry; } */ - __redisClusterAsyncSetError(acc, ac->err, - ac->errstr); + //__redisClusterAsyncSetError(acc, ac->err, + // ac->errstr); - goto done; + //goto done; } error_type = cluster_reply_error_type(reply); - if(error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) + if((error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) + || error_type == REDIS_ERR) { cad->retry_count ++; @@ -3190,6 +3214,7 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv switch(error_type) { + case REDIS_ERR: case CLUSTER_ERR_MOVED: ret = cluster_update_route(cc); if(ret != REDIS_OK) diff --git a/hircluster.h b/hircluster.h index 8e50448c..c617dcd6 100644 --- a/hircluster.h +++ b/hircluster.h @@ -62,6 +62,8 @@ typedef struct redisClusterContext { int need_update_route; } redisClusterContext; +void redisClusterSetOptionAddSlave(redisClusterContext *cc); + redisClusterContext *redisClusterConnect(const char *addrs); redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv); From 81ed03d010448692be6cef296f9f68ed5d1da08b Mon Sep 17 00:00:00 2001 From: deep011 Date: Mon, 9 Nov 2015 23:47:06 +0800 Subject: [PATCH 007/273] add slave nodes mapping to master nodes --- README.md | 23 ++-- adapters/libuv.h | 1 + hircluster.c | 271 +++++++++++++++++++++++++++++++++++++---------- hircluster.h | 26 +++-- 4 files changed, 237 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 8219efc3..3a14e33b 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,10 @@ Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. -It is minimalistic because it just adds minimal support for the protocol, but -at the same time it uses a high level printf-alike API in order to make it -much higher level than otherwise suggested by its minimal code base and the -lack of explicit bindings for every Redis command. - -Apart from supporting sending commands and receiving replies, it comes with -a reply parser that is decoupled from the I/O layer. It -is a stream parser designed for easy reusability, which can for instance be used -in higher level language bindings for efficient reply parsing. - -Hiredis only supports the binary-safe Redis protocol, so you can use it with any -Redis version >= 1.2.0. +We supported redis cluster for hiredis. The library comes with multiple APIs. There is the -*synchronous API*, the *asynchronous API* and the *reply parsing API*. +*cluster API*, *synchronous API*, the *asynchronous API* and the *reply parsing API*. ## CLUSTER SUPPORT @@ -39,9 +28,9 @@ The library comes with multiple APIs. There is the ### CLUSTER API: ```c -redisClusterContext *redisClusterConnect(const char *addrs); -redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv); -redisClusterContext *redisClusterConnectNonBlock(const char *addrs); +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 *redisClusterCommand(redisClusterContext *cc, const char *format, ...); @@ -51,7 +40,7 @@ int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char int redisClusterGetReply(redisClusterContext *cc, void **reply); void redisCLusterReset(redisClusterContext *cc); -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs); +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, const char *format, ...); diff --git a/adapters/libuv.h b/adapters/libuv.h index 3cdf3d39..3c9a49f5 100644 --- a/adapters/libuv.h +++ b/adapters/libuv.h @@ -118,4 +118,5 @@ static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { return REDIS_OK; } + #endif diff --git a/hircluster.c b/hircluster.c index 8450fe3f..ede9101f 100644 --- a/hircluster.c +++ b/hircluster.c @@ -13,12 +13,6 @@ #include "dict.c" #include "async.h" -/* 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. */ -#define CLUSTER_NEED_ADD_SLAVE 0x10000000 - - #define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" #define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" @@ -55,6 +49,13 @@ typedef enum CLUSTER_ERR_TYPE{ static void cluster_node_deinit(cluster_node *node); +void listClusterNodeDestructor(void *val) +{ + cluster_node_deinit(val); + + hi_free(val); +} + unsigned int dictSdsHash(const void *key) { return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); } @@ -87,8 +88,11 @@ void dictClusterNodeDestructor(void *privdata, void *val) hi_free(val); } -/* Cluster nodes hash table, mapping nodes addresses 1.2.3.4:6379 to - * clusterNode structures. */ +/* 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 */ @@ -98,6 +102,21 @@ dictType clusterNodesDictType = { 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; @@ -180,7 +199,7 @@ static void __redisClusterSetError(redisClusterContext *cc, int type, const char } } -static CLUSTER_ERR_TYPE cluster_reply_error_type(redisReply *reply) +static int cluster_reply_error_type(redisReply *reply) { if(reply == NULL) @@ -230,9 +249,9 @@ static int cluster_node_init(cluster_node *node) node->addr = NULL; node->host = NULL; node->port = 0; - node->master = 1; + node->role = REDIS_ROLE_NULL; node->count = 0; - node->slave_of = NULL; + node->slaves = NULL; node->con = NULL; node->acon = NULL; node->slots = NULL; @@ -256,8 +275,8 @@ static void cluster_node_deinit(cluster_node *node) sdsfree(node->addr); sdsfree(node->host); node->port = 0; - node->master = 1; - node->slave_of = NULL; + node->role = 1; + node->slaves = NULL; if(node->con != NULL) { @@ -273,7 +292,11 @@ static void cluster_node_deinit(cluster_node *node) { listRelease(node->slots); } - + + if(node->slaves != NULL) + { + listRelease(node->slaves); + } } static int cluster_slot_init(cluster_slot *slot, cluster_node *node) @@ -327,22 +350,12 @@ static int cluster_slot_ref_node(cluster_slot *slot, cluster_node *node) return REDIS_OK; } -void redisClusterSetOptionAddSlave(redisClusterContext *cc) -{ - if(cc == NULL) - { - return; - } - - cc->flags |= CLUSTER_NEED_ADD_SLAVE; -} - /** * 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, int role) + sds *node_infos, int info_count, uint8_t role) { sds *ip_port = NULL; int count_ip_port = 0; @@ -388,7 +401,7 @@ static cluster_node *node_get_with_nodes( } node->host = ip_port[0]; node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - node->master = role; + node->role = role; sdsfree(ip_port[1]); free(ip_port); @@ -463,6 +476,117 @@ cluster_slot_start_cmp(const void *t1, const void *t2) return (*s1)->start > (*s2)->start?1:-1; } +static int +cluster_master_slave_mapping(redisClusterContext *cc, + dict **nodes, cluster_node *node, sds master_name) +{ + int ret; + dictEntry *di; + cluster_node *node_old; + listNode *lnode; + + if(cc == NULL || 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) + { + 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; +} + /* * Not used and not correct, maybe delete in the future. * @@ -708,7 +832,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, redisReply *reply = NULL; struct hiarray *slots = NULL; dict *nodes = NULL; - cluster_node *node; + dict *nodes_name = NULL; + cluster_node *master, *slave; cluster_slot **slot; char *pos, *start, *end, *line_start, *line_end; char *role; @@ -831,26 +956,39 @@ cluster_update_route_with_nodes(redisClusterContext *cc, goto error; } - node = node_get_with_nodes(cc, part, count_part, 1); - if(node == NULL) + master = node_get_with_nodes(cc, + part, count_part, REDIS_ROLE_MASTER); + if(master == NULL) { goto error; } ret = dictAdd(nodes, - sdsnewlen(node->addr, sdslen(node->addr)), node); + 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(node); - hi_free(node); + cluster_node_deinit(master); + hi_free(master); goto error; } + + if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) + { + ret = cluster_master_slave_mapping(cc, + &nodes_name, master, master->name); + if(ret != REDIS_OK) + { + cluster_node_deinit(master); + hi_free(master); + goto error; + } + } if(myself == 1) { - node->con = c; + master->con = c; c = NULL; } @@ -902,7 +1040,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, "diffent node hold a same slot"); goto error; } - table[j] = node; + table[j] = master; } slot = hiarray_push(slots); @@ -925,35 +1063,34 @@ cluster_update_route_with_nodes(redisClusterContext *cc, (*slot)->start = (uint32_t)slot_start; (*slot)->end = (uint32_t)slot_end; - cluster_slot_ref_node(*slot, node); + cluster_slot_ref_node(*slot, master); } } //add slave node - else if((cc->flags & CLUSTER_NEED_ADD_SLAVE) && + else if((cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) && (role_len >= 5 && memcmp(role, "slave", 5) == 0)) { - node = node_get_with_nodes(cc, part, count_part, 0); - if(node == NULL) + slave = node_get_with_nodes(cc, part, + count_part, REDIS_ROLE_SLAVE); + if(slave == NULL) { goto error; } - ret = dictAdd(nodes, - sdsnewlen(node->addr, sdslen(node->addr)), node); - if(ret != DICT_OK) + ret = cluster_master_slave_mapping(cc, + &nodes_name, slave, part[3]); + if(ret != REDIS_OK) { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "the address already exists in the nodes"); - cluster_node_deinit(node); - hi_free(node); + cluster_node_deinit(slave); + hi_free(slave); goto error; } - + if(myself == 1) { - node->con = c; + slave->con = c; c = NULL; } } @@ -1050,6 +1187,11 @@ cluster_update_route_with_nodes(redisClusterContext *cc, dictRelease(cc->nodes); } + if(nodes_name != NULL) + { + dictRelease(nodes_name); + } + if(reply != NULL) { freeReplyObject(reply); @@ -1330,7 +1472,7 @@ static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const return cc; } -redisClusterContext *redisClusterConnect(const char *addrs) +redisClusterContext *redisClusterConnect(const char *addrs, int flags) { redisClusterContext *cc; @@ -1342,11 +1484,16 @@ redisClusterContext *redisClusterConnect(const char *addrs) } cc->flags |= REDIS_BLOCK; - + if(flags) + { + cc->flags |= flags; + } + return _redisClusterConnect(cc, addrs); } -redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv) +redisClusterContext *redisClusterConnectWithTimeout( + const char *addrs, const struct timeval tv, int flags) { redisClusterContext *cc; @@ -1358,7 +1505,11 @@ redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const str } cc->flags |= REDIS_BLOCK; - + if(flags) + { + cc->flags |= flags; + } + if (cc->timeout == NULL) { cc->timeout = malloc(sizeof(struct timeval)); @@ -1369,7 +1520,7 @@ redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const str return _redisClusterConnect(cc, addrs); } -redisClusterContext *redisClusterConnectNonBlock(const char *addrs) { +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { redisClusterContext *cc; @@ -1381,7 +1532,11 @@ redisClusterContext *redisClusterConnectNonBlock(const char *addrs) { } cc->flags &= ~REDIS_BLOCK; - + if(flags) + { + cc->flags |= flags; + } + return _redisClusterConnect(cc, addrs); } @@ -1774,7 +1929,7 @@ static cluster_node *node_get_by_ask_error_reply( node->addr = part[1]; node->host = ip_port[0]; node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); - node->master = 1; + node->role = REDIS_ROLE_MASTER; dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); @@ -1829,7 +1984,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, void *reply = NULL; cluster_node *node; redisContext *c = NULL; - CLUSTER_ERR_TYPE error_type; + int error_type; retry: @@ -3069,12 +3224,12 @@ static redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, return ac; } -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs) { +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) { redisClusterContext *cc; redisClusterAsyncContext *acc; - cc = redisClusterConnectNonBlock(addrs); + cc = redisClusterConnectNonBlock(addrs, flags); if(cc == NULL) { return NULL; @@ -3115,7 +3270,7 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv redisClusterAsyncContext *acc; redisClusterContext *cc; redisAsyncContext *ac_retry = NULL; - CLUSTER_ERR_TYPE error_type; + int error_type; cluster_node *node; struct cmd *command; diff --git a/hircluster.h b/hircluster.h index c617dcd6..e624a926 100644 --- a/hircluster.h +++ b/hircluster.h @@ -8,6 +8,17 @@ #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. */ +#define HIRCLUSTER_FLAG_ADD_SLAVE 0x10000000 + struct dict; typedef struct cluster_node @@ -17,11 +28,11 @@ typedef struct cluster_node sds host; int port; int count; - uint8_t master; + uint8_t role; redisContext *con; redisAsyncContext *acon; list *slots; - struct cluster_node *slave_of; + list *slaves; }cluster_node; typedef struct cluster_slot @@ -62,13 +73,10 @@ typedef struct redisClusterContext { int need_update_route; } redisClusterContext; -void redisClusterSetOptionAddSlave(redisClusterContext *cc); - -redisClusterContext *redisClusterConnect(const char *addrs); +redisClusterContext *redisClusterConnect(const char *addrs, int flags); redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, - const struct timeval tv); -redisClusterContext *redisClusterConnectNonBlock(const char *addrs); - + const struct timeval tv, int flags); +redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); void redisClusterFree(redisClusterContext *cc); @@ -116,7 +124,7 @@ typedef struct redisClusterAsyncContext { } redisClusterAsyncContext; -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs); +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); From 3e6a4ee2228ebc3fcdb28a7f919534eeaa3eea4f Mon Sep 17 00:00:00 2001 From: deep011 Date: Thu, 12 Nov 2015 00:46:23 +0800 Subject: [PATCH 008/273] fix memory leak and change library name to hiredis_vip --- Makefile | 20 ++++++++-------- hircluster.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++-- hircluster.h | 6 +++++ 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 93287468..58494bfc 100644 --- a/Makefile +++ b/Makefile @@ -6,16 +6,16 @@ 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 +LIBNAME=libhiredis_vip PKGCONFNAME=hiredis.pc -HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') -HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') -HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') +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 +INCLUDE_PATH?=include/hiredis-vip LIBRARY_PATH?=lib PKGCONF_PATH?=pkgconfig INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) @@ -44,8 +44,8 @@ REAL_LDFLAGS=$(LDFLAGS) $(ARCH) DYLIBSUFFIX=so STLIBSUFFIX=a -DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR) -DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) +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) @@ -60,8 +60,8 @@ ifeq ($(uname_S),SunOS) endif ifeq ($(uname_S),Darwin) DYLIBSUFFIX=dylib - DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX) - DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX) + 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 @@ -163,7 +163,7 @@ $(PKGCONFNAME): hiredis.h @echo >> $@ @echo Name: hiredis >> $@ @echo Description: Minimalistic C client library for Redis. >> $@ - @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ + @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 >> $@ diff --git a/hircluster.c b/hircluster.c index ede9101f..e0511284 100644 --- a/hircluster.c +++ b/hircluster.c @@ -276,7 +276,6 @@ static void cluster_node_deinit(cluster_node *node) sdsfree(node->host); node->port = 0; node->role = 1; - node->slaves = NULL; if(node->con != NULL) { @@ -376,7 +375,7 @@ static cluster_node *node_get_with_nodes( cluster_node_init(node); - if(role) + if(role == REDIS_ROLE_MASTER) { node->slots = listCreate(); if(node->slots == NULL) @@ -546,6 +545,7 @@ cluster_master_slave_mapping(redisClusterContext *cc, if(node_old->slaves != NULL) { + node_old->slaves->free = NULL; while(listLength(node_old->slaves) > 0) { lnode = listFirst(node_old->slaves); @@ -1142,6 +1142,11 @@ cluster_update_route_with_nodes(redisClusterContext *cc, { redisFree(c); } + + if(nodes_name != NULL) + { + dictRelease(nodes_name); + } return REDIS_OK; @@ -1258,6 +1263,8 @@ cluster_update_route(redisClusterContext *cc) cc->err = 0; memset(cc->errstr, '\0', strlen(cc->errstr)); } + + dictReleaseIterator(it); return REDIS_OK; } @@ -1274,6 +1281,59 @@ cluster_update_route(redisClusterContext *cc) return REDIS_ERR; } +static void print_cluster_node_list(redisClusterContext *cc) +{ + dictIterator *di = NULL; + dictEntry *de; + listIter *it; + listNode *ln; + cluster_node *master, *slave; + list *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"); + } + + 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; diff --git a/hircluster.h b/hircluster.h index e624a926..1329b2ee 100644 --- a/hircluster.h +++ b/hircluster.h @@ -6,6 +6,10 @@ #include "adlist.h" #include "async.h" +#define HIREDIS_VIP_MAJOR 0 +#define HIREDIS_VIP_MINOR 2 +#define HIREDIS_VIP_PATCH 0 + #define REDIS_CLUSTER_SLOTS 16384 #define REDIS_ROLE_NULL 0 @@ -91,6 +95,8 @@ int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char int redisClusterGetReply(redisClusterContext *cc, void **reply); void redisCLusterReset(redisClusterContext *cc); +int test_cluster_update_route(redisClusterContext *cc); + /*############redis cluster async############*/ From 3667bec2e784df51b42d70cf6acd643fadd25b17 Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 18 Nov 2015 15:25:30 +0800 Subject: [PATCH 009/273] change include file for hiarray --- hiarray.c | 1 + hiarray.h | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/hiarray.c b/hiarray.c index 8d47fe6a..15ffe08d 100644 --- a/hiarray.c +++ b/hiarray.c @@ -1,5 +1,6 @@ #include +#include "hiutil.h" #include "hiarray.h" struct hiarray * diff --git a/hiarray.h b/hiarray.h index 8e68b068..fda3a4b8 100644 --- a/hiarray.h +++ b/hiarray.h @@ -3,10 +3,8 @@ #include -#include "hiutil.h" - typedef int (*hiarray_compare_t)(const void *, const void *); -typedef rstatus_t (*hiarray_each_t)(void *, void *); +typedef int (*hiarray_each_t)(void *, void *); struct hiarray { uint32_t nelem; /* # element */ @@ -43,7 +41,7 @@ hiarray_n(const struct hiarray *a) struct hiarray *hiarray_create(uint32_t n, size_t size); void hiarray_destroy(struct hiarray *a); -rstatus_t hiarray_init(struct hiarray *a, uint32_t n, size_t size); +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); @@ -53,6 +51,6 @@ 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); -rstatus_t hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); +int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); #endif From 2cfb27f78cf5bca38292cbc634b07612a265fac5 Mon Sep 17 00:00:00 2001 From: deep011 Date: Fri, 20 Nov 2015 09:25:08 +0800 Subject: [PATCH 010/273] 1. Change tabs to spaces; 2. update the solution to deal with redis down in cluster asynchronous api --- hircluster.c | 6463 ++++++++++++++++++++++++++------------------------ hircluster.h | 68 +- 2 files changed, 3339 insertions(+), 3192 deletions(-) diff --git a/hircluster.c b/hircluster.c index e0511284..f35f1248 100644 --- a/hircluster.c +++ b/hircluster.c @@ -29,22 +29,22 @@ typedef struct cluster_async_data { - redisClusterAsyncContext *acc; - struct cluster_node *node; - struct cmd *command; - redisClusterCallbackFn *callback; - int retry_count; - void *privdata; + redisClusterAsyncContext *acc; + struct cluster_node *node; + 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_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); @@ -53,7 +53,7 @@ void listClusterNodeDestructor(void *val) { cluster_node_deinit(val); - hi_free(val); + hi_free(val); } unsigned int dictSdsHash(const void *key) { @@ -85,7 +85,7 @@ void dictClusterNodeDestructor(void *privdata, void *val) cluster_node_deinit(val); - hi_free(val); + hi_free(val); } /* Cluster nodes hash table, mapping nodes @@ -113,14 +113,14 @@ dictType clusterNodesRefDictType = { NULL, /* val dup */ dictSdsKeyCompare, /* key compare */ dictSdsDestructor, /* key destructor */ - NULL /* val destructor */ + NULL /* val destructor */ }; void listCommandFree(void *command) { - struct cmd *cmd = command; - command_destroy(cmd); + struct cmd *cmd = command; + command_destroy(cmd); } /* Defined in hiredis.c */ @@ -202,269 +202,270 @@ static void __redisClusterSetError(redisClusterContext *cc, int type, const char 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; + 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) { - node->name = NULL; - node->addr = NULL; - node->host = NULL; - node->port = 0; - node->role = REDIS_ROLE_NULL; - node->count = 0; - node->slaves = NULL; - node->con = NULL; - node->acon = NULL; - node->slots = NULL; - - return REDIS_OK; + node->name = NULL; + node->addr = NULL; + node->host = NULL; + node->port = 0; + node->role = REDIS_ROLE_NULL; + node->count = 0; + node->slaves = NULL; + node->con = NULL; + node->acon = NULL; + node->slots = NULL; + node->failure_count = 0; + + return REDIS_OK; } static void cluster_node_deinit(cluster_node *node) -{ - if(node == NULL) - { - return; - } - - if(node->count > 0) - { - return; - } - - sdsfree(node->name); - sdsfree(node->addr); - sdsfree(node->host); - node->port = 0; - node->role = 1; - - 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 == NULL) + { + return; + } + + if(node->count > 0) + { + return; + } + + sdsfree(node->name); + sdsfree(node->addr); + sdsfree(node->host); + node->port = 0; + node->role = 1; + + 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); + } } static int cluster_slot_init(cluster_slot *slot, cluster_node *node) { - slot->start = 0; - slot->end = 0; - if(node != NULL) - { - node->count ++; - } - slot->node = node; - - return REDIS_OK; + slot->start = 0; + slot->end = 0; + if(node != NULL) + { + node->count ++; + } + slot->node = node; + + return REDIS_OK; } static int cluster_slot_deinit(cluster_slot *slot) { - cluster_node *node; - slot->start = 0; - slot->end = 0; - if(slot->node != NULL) - { - node = slot->node; - node->count --; - slot->node = NULL; - } - - hi_free(slot); - - return REDIS_OK; + cluster_node *node; + slot->start = 0; + slot->end = 0; + if(slot->node != NULL) + { + node = slot->node; + node->count --; + slot->node = NULL; + } + + hi_free(slot); + + return REDIS_OK; } static int cluster_slot_ref_node(cluster_slot *slot, cluster_node *node) { - cluster_node *node_old; - - if(slot->node != NULL) - { - node_old = slot->node; - node_old->count --; - } + cluster_node *node_old; + + if(slot->node != NULL) + { + node_old = slot->node; + node_old->count --; + } - if(node != NULL) - { - node->count ++; - listAddNodeTail(node->slots, slot); - } + if(node != NULL) + { + node->count ++; + listAddNodeTail(node->slots, slot); + } - slot->node = node; - - return REDIS_OK; + slot->node = node; + + return REDIS_OK; } /** * 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) + 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->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; + 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->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(ip_port != NULL) + { + sdsfreesplitres(ip_port, count_ip_port); + } - if(node != NULL) - { - hi_free(node); - } + if(node != NULL) + { + hi_free(node); + } - return NULL; + 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; - - 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; - } - } - - dictReleaseIterator(di); - + dictIterator *di; + dictEntry *de_f, *de_t; + cluster_node *node_f, *node_t; + redisContext *c; + redisAsyncContext *ac; + + 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; + } + } + + dictReleaseIterator(di); + } static int @@ -477,114 +478,114 @@ cluster_slot_start_cmp(const void *t1, const void *t2) static int cluster_master_slave_mapping(redisClusterContext *cc, - dict **nodes, cluster_node *node, sds master_name) + dict **nodes, cluster_node *node, sds master_name) { - int ret; - dictEntry *di; - cluster_node *node_old; - listNode *lnode; - - if(cc == NULL || 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; + int ret; + dictEntry *di; + cluster_node *node_old; + listNode *lnode; + + if(cc == NULL || 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; } /* @@ -593,231 +594,224 @@ cluster_master_slave_mapping(redisClusterContext *cc, */ static int cluster_update_route_with_slots(redisClusterContext *cc, - const char *ip, int port) + const char *ip, int port) { - redisContext *c; - redisReply *reply = NULL; - redisReply *elem; - redisReply *elem_slots_begin, *elem_slots_end; - redisReply *elem_node_master; - redisReply *elem_ip, *elem_port; - struct hiarray *slots = NULL; - cluster_slot *slot; - cluster_node *node; - const char *errstr = NULL; - int err = 0; - unsigned int i, idx; - - if(cc == NULL) - { - return REDIS_ERR; - } - - if(cc->flags & REDIS_BLOCK) - { - if(cc->timeout) - { - c = redisConnectWithTimeout(ip, port, *cc->timeout); - } - else - { - c = redisConnect(ip, port); - } - } - else - { - c = redisConnectNonBlock(ip, port); - } - - if (c == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "init redis context error(return NULL)!\0"; - goto error; - } - else if(c->err) - { - err = c->err; - errstr = c->errstr; - goto error; - } - - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); - - if(reply == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply error(NULL)!\0"; - goto error; - } - - if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(level 0 type is not array)!\0"; - goto error; - } - - slots = hiarray_create(reply->elements, sizeof(cluster_slot)); - if(slots == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "array create error!\0"; - goto error; - } - - for(i = 0; i < reply->elements; i ++) - { - elem = reply->element[i]; - if(elem->type != REDIS_REPLY_ARRAY || elem->elements <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(level 1 type is not array)!\0"; - goto error; - } - - slot = hiarray_push(slots); - if(slot == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "slot push in array error!\0"; - goto error; - } - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "alloc cluster node error!\0"; - goto error; - } - - cluster_node_init(node); - cluster_slot_ref_node(slot, node); - - for(idx = 0; idx < elem->elements; idx ++) - { - if(idx == 0) - { - elem_slots_begin = elem->element[idx]; - if(elem_slots_begin->type != REDIS_REPLY_INTEGER) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(slot begin is not integer)!\0"; - goto error; - } - slot->start = (int)(elem_slots_begin->integer); - } - else if(idx == 1) - { - elem_slots_end = elem->element[idx]; - if(elem_slots_end->type != REDIS_REPLY_INTEGER) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(slot end is not integer)!\0"; - goto error; - } - slot->end = (int)(elem_slots_end->integer); - - if(slot->start > slot->end) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(slot begin is bigger than slot end)!\0"; - goto error; - } - } - else if(idx == 2) - { - elem_node_master = elem->element[idx]; - if(elem_node_master->type != REDIS_REPLY_ARRAY || - elem_node_master->elements != 2) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(master line is not array)!\0"; - goto error; - } - - elem_ip = elem_node_master->element[0]; - elem_port = elem_node_master->element[1]; - - if(elem_ip->type != REDIS_REPLY_STRING || - elem_ip->len <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(master ip is not string)!\0"; - goto error; - } - - if(elem_port->type != REDIS_REPLY_INTEGER || - elem_port->integer <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(master port is not integer)!\0"; - goto error; - } - - node->host = sdsnewlen(elem_ip->str, elem_ip->len); - node->port = (int)(elem_port->integer); - - node->addr = sdsnewlen(elem_ip->str, elem_ip->len); - sdscatlen(node->addr, ":", 1); - node->addr = sdscatfmt(node->addr, "%I", node->port); - } - else - { - continue; - } - } - } - - cc->slots = slots; - - hiarray_sort(cc->slots, cluster_slot_start_cmp); - - freeReplyObject(reply); - - if (c != NULL) - { - redisFree(c); - } - - return REDIS_OK; + redisContext *c; + redisReply *reply = NULL; + redisReply *elem; + redisReply *elem_slots_begin, *elem_slots_end; + redisReply *elem_node_master; + redisReply *elem_ip, *elem_port; + struct hiarray *slots = NULL; + cluster_slot *slot; + cluster_node *node; + const char *errstr = NULL; + int err = 0; + unsigned int i, idx; + + if(cc == NULL) + { + return REDIS_ERR; + } + + if(cc->timeout) + { + c = redisConnectWithTimeout(ip, port, *cc->timeout); + } + else + { + c = redisConnect(ip, port); + } + + if (c == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "init redis context error(return NULL)!\0"; + goto error; + } + else if(c->err) + { + err = c->err; + errstr = c->errstr; + goto error; + } + + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); + + if(reply == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply error(NULL)!\0"; + goto error; + } + + if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(level 0 type is not array)!\0"; + goto error; + } + + slots = hiarray_create(reply->elements, sizeof(cluster_slot)); + if(slots == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "array create error!\0"; + goto error; + } + + for(i = 0; i < reply->elements; i ++) + { + elem = reply->element[i]; + if(elem->type != REDIS_REPLY_ARRAY || elem->elements <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(level 1 type is not array)!\0"; + goto error; + } + + slot = hiarray_push(slots); + if(slot == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "slot push in array error!\0"; + goto error; + } + + node = hi_alloc(sizeof(cluster_node)); + if(node == NULL) + { + err = REDIS_ERR_OTHER; + errstr = "alloc cluster node error!\0"; + goto error; + } + + cluster_node_init(node); + cluster_slot_ref_node(slot, node); + + for(idx = 0; idx < elem->elements; idx ++) + { + if(idx == 0) + { + elem_slots_begin = elem->element[idx]; + if(elem_slots_begin->type != REDIS_REPLY_INTEGER) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(slot begin is not integer)!\0"; + goto error; + } + slot->start = (int)(elem_slots_begin->integer); + } + else if(idx == 1) + { + elem_slots_end = elem->element[idx]; + if(elem_slots_end->type != REDIS_REPLY_INTEGER) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(slot end is not integer)!\0"; + goto error; + } + slot->end = (int)(elem_slots_end->integer); + + if(slot->start > slot->end) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(slot begin is bigger than slot end)!\0"; + goto error; + } + } + else if(idx == 2) + { + elem_node_master = elem->element[idx]; + if(elem_node_master->type != REDIS_REPLY_ARRAY || + elem_node_master->elements != 2) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(master line is not array)!\0"; + goto error; + } + + elem_ip = elem_node_master->element[0]; + elem_port = elem_node_master->element[1]; + + if(elem_ip->type != REDIS_REPLY_STRING || + elem_ip->len <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(master ip is not string)!\0"; + goto error; + } + + if(elem_port->type != REDIS_REPLY_INTEGER || + elem_port->integer <= 0) + { + err = REDIS_ERR_OTHER; + errstr = "command(cluster slots) reply" + " error(master port is not integer)!\0"; + goto error; + } + + node->host = sdsnewlen(elem_ip->str, elem_ip->len); + node->port = (int)(elem_port->integer); + + node->addr = sdsnewlen(elem_ip->str, elem_ip->len); + sdscatlen(node->addr, ":", 1); + node->addr = sdscatfmt(node->addr, "%I", node->port); + } + else + { + continue; + } + } + } + + cc->slots = slots; + + hiarray_sort(cc->slots, cluster_slot_start_cmp); + + freeReplyObject(reply); + + if (c != NULL) + { + redisFree(c); + } + + return REDIS_OK; error: - cc->err = err; - memcpy(cc->errstr, errstr, strlen(errstr)); + cc->err = err; + memcpy(cc->errstr, errstr, strlen(errstr)); - if(slots != NULL) - { - while(hiarray_n(slots)) - { - slot = hiarray_pop(slots); - cluster_slot_deinit(slot); - } - - hiarray_destroy(slots); - } + if(slots != NULL) + { + while(hiarray_n(slots)) + { + slot = hiarray_pop(slots); + cluster_slot_deinit(slot); + } + + hiarray_destroy(slots); + } - if(reply != NULL) - { - freeReplyObject(reply); - reply = NULL; - } + if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } - if (c != NULL) + if (c != NULL) { - redisFree(c); - } - return REDIS_ERR; + redisFree(c); + } + return REDIS_ERR; } /** @@ -825,1024 +819,1117 @@ cluster_update_route_with_slots(redisClusterContext *cc, */ static int cluster_update_route_with_nodes(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; - } - - 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(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 = hi_alloc(sizeof(**slot)); - if(*slot == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); - goto error; - } - - cluster_slot_init(*slot, NULL); - - (*slot)->start = (uint32_t)slot_start; - (*slot)->end = (uint32_t)slot_end; - cluster_slot_ref_node(*slot, master); - - } - - } - //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(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) - { - while(hiarray_n(cc->slots)) - { - slot = hiarray_pop(cc->slots); - cluster_slot_deinit(*slot); - } - - 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 = 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; - } - - while(hiarray_n(slots)) - { - slot = hiarray_pop(slots); - cluster_slot_deinit(*slot); - } - - hiarray_destroy(slots); - } - - if(nodes != NULL) - { - if(nodes == cc->nodes) - { - cc->nodes = NULL; - } - - dictRelease(cc->nodes); - } - - if(nodes_name != NULL) - { - dictRelease(nodes_name); - } - - if(reply != NULL) - { - freeReplyObject(reply); - reply = NULL; - } - - if(c != NULL) - { - redisFree(c); - } - - return REDIS_ERR; -} - -static 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_with_nodes(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_with_nodes(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) + const char *ip, int port) { - dictIterator *di = NULL; - dictEntry *de; - listIter *it; - listNode *ln; - cluster_node *master, *slave; - list *slaves; + 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(cc == NULL) - { - return; - } + if(ip == NULL || port <= 0) + { + __redisClusterSetError(cc, + REDIS_ERR_OTHER,"ip or port error!"); + goto error; + } - di = dictGetIterator(cc->nodes); + 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; + } - printf("name\taddress\trole\tslaves\n"); - - while((de = dictNext(di)) != NULL) { - master = dictGetEntryVal(de); + reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); - printf("%s\t%s\t%d\t%s\n",master->name, master->addr, - master->role, master->slaves?"hava":"null"); + 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; + } - 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"); - } + nodes = dictCreate(&clusterNodesDictType, NULL); + + slots = hiarray_create(10, sizeof(cluster_slot*)); + if(slots == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "array create error"); + goto error; + } - printf("\n"); - } -} + 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); -int test_cluster_update_route(redisClusterContext *cc) -{ - int ret; - - ret = cluster_update_route(cc); + if(part == NULL || count_part < 8) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "split cluster nodes error"); + goto error; + } - //print_cluster_node_list(cc); - - return ret; -} + 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]; + } -static redisClusterContext *redisClusterContextInit(void) { - redisClusterContext *cc; + //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(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 = hi_alloc(sizeof(**slot)); + if(*slot == NULL) + { + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Out of memory"); + goto error; + } + + cluster_slot_init(*slot, NULL); + + (*slot)->start = (uint32_t)slot_start; + (*slot)->end = (uint32_t)slot_end; + cluster_slot_ref_node(*slot, master); + + } - cc = calloc(1,sizeof(redisClusterContext)); - if (cc == NULL) - return NULL; + } + //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(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; + } + } - 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; + if(myself == 1) + { + myself = 0; + } - cc->nodes = NULL; + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; + } + } - cc->route_version = 0; + if(cc->slots != NULL) + { + while(hiarray_n(cc->slots)) + { + slot = hiarray_pop(cc->slots); + cluster_slot_deinit(*slot); + } + + hiarray_destroy(cc->slots); + cc->slots = NULL; + } + cc->slots = slots; - memset(cc->table, 0, REDIS_CLUSTER_SLOTS); - - return cc; -} + cluster_nodes_swap_ctx(cc->nodes, nodes); -void redisClusterFree(redisClusterContext *cc) { + if(cc->nodes != NULL) + { + dictRelease(cc->nodes); + } + cc->nodes = nodes; - cluster_slot **slot; - - if (cc == NULL) - return; + hiarray_sort(cc->slots, cluster_slot_start_cmp); - if(cc->ip) - { - sdsfree(cc->ip); - cc->ip = NULL; - } + memcpy(cc->table, table, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + cc->route_version ++; + + freeReplyObject(reply); - if (cc->timeout) + if(c != NULL) { - free(cc->timeout); + redisFree(c); } - memset(cc->table, 0, REDIS_CLUSTER_SLOTS); - - if(cc->slots != NULL) - { - while(hiarray_n(cc->slots)) - { - slot = hiarray_pop(cc->slots); - cluster_slot_deinit(*slot); - } - - hiarray_destroy(cc->slots); - cc->slots = NULL; - } - - if(cc->nodes != NULL) - { - dictRelease(cc->nodes); - } - - if(cc->requests != NULL) - { - listRelease(cc->requests); - } - - free(cc); + 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; + } + + while(hiarray_n(slots)) + { + slot = hiarray_pop(slots); + cluster_slot_deinit(*slot); + } + + hiarray_destroy(slots); + } + + if(nodes != NULL) + { + if(nodes == cc->nodes) + { + cc->nodes = NULL; + } + + dictRelease(cc->nodes); + } + + if(nodes_name != NULL) + { + dictRelease(nodes_name); + } + + if(reply != NULL) + { + freeReplyObject(reply); + reply = NULL; + } + + if(c != NULL) + { + redisFree(c); + } + + return REDIS_ERR; +} + +static 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_with_nodes(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_with_nodes(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; + list *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"); + } + + 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->nodes = NULL; + + cc->route_version = 0LL; + + memset(cc->table, 0, REDIS_CLUSTER_SLOTS); + + return cc; +} + +void redisClusterFree(redisClusterContext *cc) { + + cluster_slot **slot; + + 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); + + if(cc->slots != NULL) + { + while(hiarray_n(cc->slots)) + { + slot = hiarray_pop(cc->slots); + cluster_slot_deinit(*slot); + } + + 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; + 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 >= 0 && 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; + } -/* 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) { + 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; + } + } - 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); + dictReleaseIterator(di); - return cc; + return NULL; } -redisClusterContext *redisClusterConnect(const char *addrs, int flags) +static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) { - redisClusterContext *cc; + struct cmd *command = NULL; + struct keypos *kp; + int key_count; + uint32_t i; + int slot_num = -1; - cc = redisClusterContextInit(); + if(cc == NULL || cmd == NULL || len <= 0) + { + goto done; + } - if(cc == NULL) - { - return NULL; - } + 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; + } - cc->flags |= REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - return _redisClusterConnect(cc, addrs); -} + key_count = hiarray_n(command->keys); -redisClusterContext *redisClusterConnectWithTimeout( - const char *addrs, const struct timeval tv, int flags) -{ - redisClusterContext *cc; + 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); - cc = redisClusterContextInit(); + goto done; + } + + for(i = 0; i < hiarray_n(command->keys); i ++) + { + kp = hiarray_get(command->keys, i); - if(cc == NULL) - { - return NULL; - } + slot_num = keyHashSlot(kp->start, kp->end - kp->start); + } - cc->flags |= REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - if (cc->timeout == NULL) +done: + + if(command != NULL) { - cc->timeout = malloc(sizeof(struct timeval)); + command->cmd = NULL; + command_destroy(command); } - - memcpy(cc->timeout, &tv, sizeof(struct timeval)); - - return _redisClusterConnect(cc, addrs); + + return slot_num; } -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { +/* 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; - redisClusterContext *cc; + 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; + } - cc = redisClusterContextInit(); + 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(cc == NULL) - { - return NULL; - } + if(reply->type != REDIS_REPLY_ARRAY) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get type is not array"); + goto error; + } - cc->flags &= ~REDIS_BLOCK; - if(flags) - { - cc->flags |= flags; - } - - return _redisClusterConnect(cc, addrs); -} + if(reply->elements != 2) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "reply for config get elements number is not 2"); + goto error; + } -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; -} + 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; + } -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 >= 0 && 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; -} + 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; + } -static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num) -{ - if(cc == NULL) - { - return NULL; - } + config_value = sub_reply->str; + *config_value_len = sub_reply->len; + sub_reply->str= NULL; - if(slot_num >= REDIS_CLUSTER_SLOTS) - { - return NULL; - } + if(reply != NULL) + { + freeReplyObject(reply); + } - return cc->table[slot_num]; - -} + return config_value; -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, cc->flags); - 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; -} +error: -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); - } + if(reply != NULL) + { + freeReplyObject(reply); + } -done: - - if(command != NULL) - { - command->cmd = NULL; - command_destroy(command); - } - - return slot_num; + return NULL; } /* Helper function for the redisClusterAppendCommand* family of functions. @@ -1852,41 +1939,41 @@ static int slot_get_by_command(redisClusterContext *cc, char *cmd, int len) * 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; - } - + 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; } @@ -1894,671 +1981,671 @@ static int __redisClusterAppendCommand(redisClusterContext *cc, */ 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; + 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) + 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; - } + 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(part != NULL) + { + sdsfreesplitres(part, part_len); + part = NULL; + } - if(ip_port != NULL) - { - sdsfreesplitres(ip_port, ip_port_len); - ip_port = NULL; - } - - return node; + 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) + struct cmd *command) { - int ret; - void *reply = NULL; - cluster_node *node; - redisContext *c = NULL; - int error_type; + 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 || c->err) - { - __redisClusterSetError(cc, c->err, c->errstr); - return NULL; - } - } + + 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 || 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"); - 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) - { - return NULL; - } - - freeReplyObject(reply); - reply = NULL; - - reply = redisCommand(c, REDIS_COMMAND_ASKING); - - 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; + 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"); + 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) + { + return NULL; + } + + freeReplyObject(reply); + reply = NULL; + + reply = redisCommand(c, REDIS_COMMAND_ASKING); + + 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, list *commands) + struct cmd *command, list *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)); + + 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) { + { + __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; + slot_num = -1; + goto done; } } - command->frag_seq[i] = sub_command = sub_commands[slot_num]; + command->frag_seq[i] = sub_command = sub_commands[slot_num]; - sub_command->narg++; + sub_command->narg++; - sub_kp = hiarray_push(sub_command->keys); + sub_kp = hiarray_push(sub_command->keys); if (sub_kp == NULL) { __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - slot_num = -1; - goto done; + slot_num = -1; + goto done; } - + sub_kp->start = kp->start; sub_kp->end = kp->end; - key_len = (uint32_t)(kp->end - kp->start); + 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 ++; - sub_command->clen += key_len + uint_len(key_len); + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)(strlen(num_str)); - sub_command->slot_num = slot_num; + 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; - if (command->type == CMD_REQ_REDIS_MSET) { - uint32_t len = 0; - char *p; + sub_command->narg *= 2; - for (p = sub_kp->end + 1; !isdigit(*p); p++){} - - p = sub_kp->end + 1; - while(!isdigit(*p)) - { - p ++; - } + sub_command->narg ++; - 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; - } - } + hi_itoa(num_str, sub_command->narg); + num_str_len = (uint8_t)strlen(num_str); + + sub_command->clen += 13 + num_str_len; - for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ - sub_command = sub_commands[i]; - if (sub_command == NULL) { - continue; - } + 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; + } - 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->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; - 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 { + 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); - + //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); + listAddNodeTail(commands, sub_command); } done: - if(sub_commands != NULL) - { - hi_free(sub_commands); - } + if(sub_commands != NULL) + { + hi_free(sub_commands); + } - if(slot_num >= 0 && commands != NULL - && listLength(commands) == 1) - { - listNode *list_node = listFirst(commands); - command_destroy(list_node->value); - listDelNode(commands, list_node); - if(command->frag_seq) - { - hi_free(command->frag_seq); - command->frag_seq = NULL; - } + if(slot_num >= 0 && commands != NULL + && listLength(commands) == 1) + { + listNode *list_node = listFirst(commands); + command_destroy(list_node->value); + listDelNode(commands, list_node); + if(command->frag_seq) + { + hi_free(command->frag_seq); + command->frag_seq = NULL; + } - command->slot_num = slot_num; - } + command->slot_num = slot_num; + } - return slot_num; + return slot_num; } static void *command_post_fragment(redisClusterContext *cc, - struct cmd *command, list *commands) + struct cmd *command, list *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)); + 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; - } + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + return NULL; + } - if (command->type == CMD_REQ_REDIS_MGET) { - int i; - uint32_t key_count; + if (command->type == CMD_REQ_REDIS_MGET) { + int i; + uint32_t key_count; - reply->type = REDIS_REPLY_ARRAY; + reply->type = REDIS_REPLY_ARRAY; - key_count = hiarray_n(command->keys); + key_count = hiarray_n(command->keys); - reply->elements = key_count; - reply->element = hi_calloc(key_count, sizeof(*reply)); - if (reply->element == NULL) { - freeReplyObject(reply); + 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; - } - - 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; + return NULL; + } + + reply->len = str_len; + memcpy(reply->str, REDIS_STATUS_OK, str_len); + reply->str[str_len] = '\0'; + }else { + NOT_REACHED(); + } + + return reply; } /* @@ -2569,566 +2656,566 @@ static void *command_post_fragment(redisClusterContext *cc, * Otherwise if the commands > 1 , slot_num is the last subcommand slot number. */ static int command_format_by_slot(redisClusterContext *cc, - struct cmd *command, list *commands) + struct cmd *command, list *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_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); - command->slot_num = slot_num; - - goto done; - } - - slot_num = command_pre_fragment(cc, command, 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_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); + command->slot_num = slot_num; + + goto done; + } + + slot_num = command_pre_fragment(cc, command, commands); done: - - return slot_num; + + return slot_num; } void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count) { - if(cc == NULL || max_redirect_count <= 0) - { - return; - } + if(cc == NULL || max_redirect_count <= 0) + { + return; + } - cc->max_redirect_count = max_redirect_count; + cc->max_redirect_count = max_redirect_count; } void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { - - va_list ap; + + va_list ap; redisReply *reply = NULL; - char *cmd = NULL; - int slot_num; - int len; - struct cmd *command = NULL, *sub_command; - list *commands = NULL; - listNode *list_node; - listIter *list_iter = NULL; - + char *cmd = NULL; + int slot_num; + int len; + struct cmd *command = NULL, *sub_command; + list *commands = NULL; + listNode *list_node; + listIter *list_iter = NULL; + va_start(ap,format); - if(cc == NULL) - { - return NULL; - } + if(cc == NULL) + { + return NULL; + } - if(cc->err) - { - cc->err = 0; - memset(cc->errstr, '\0', strlen(cc->errstr)); - } + if(cc->err) + { + cc->err = 0; + memset(cc->errstr, '\0', strlen(cc->errstr)); + } len = redisvFormatCommand(&cmd,format,ap); - va_end(ap); + va_end(ap); - if (len == -1) { + 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; - } - - command = command_get(); - if(command == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + } + + 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); - + } + + 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_destroy(command); + command_destroy(command); - if(commands != NULL) - { - listRelease(commands); - } + if(commands != NULL) + { + listRelease(commands); + } - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } - cc->retry_count = 0; - + cc->retry_count = 0; + return reply; error: - if(command != NULL) - { - command_destroy(command); - } - else if(cmd != NULL) - { - free(cmd); - } + if(command != NULL) + { + command_destroy(command); + } + else if(cmd != NULL) + { + free(cmd); + } - if(commands != NULL) - { - listRelease(commands); - } + if(commands != NULL) + { + listRelease(commands); + } - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } + if(list_iter != NULL) + { + listReleaseIterator(list_iter); + } - cc->retry_count = 0; - - return NULL; + cc->retry_count = 0; + + return NULL; } int redisClusterAppendCommand(redisClusterContext *cc, - const char *format, ...) { - - va_list ap; - int len; - int slot_num; - struct cmd *command = NULL, *sub_command; - list *commands = NULL; - - char *cmd; - listNode *list_node; - listIter *list_iter = NULL; - - if(cc == NULL || format == NULL) - { - return REDIS_ERR; - } - - 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; - } - - va_start(ap,format); - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); - goto error; - } - - 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; - } - } + const char *format, ...) { + + va_list ap; + int len; + int slot_num; + struct cmd *command = NULL, *sub_command; + list *commands = NULL; + + char *cmd; + listNode *list_node; + listIter *list_iter = NULL; + + if(cc == NULL || format == NULL) + { + return REDIS_ERR; + } + + 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; + } + + va_start(ap,format); + + len = redisvFormatCommand(&cmd,format,ap); + if (len == -1) { + __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } else if (len == -2) { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + goto error; + } + + 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: - va_end(ap); - - if(command->cmd != NULL) - { - free(command->cmd); - 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; + va_end(ap); + + if(command->cmd != NULL) + { + free(command->cmd); + 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: - va_end(ap); + va_end(ap); - if(command != NULL) - { - command_destroy(command); - } - else if(cmd != NULL) - { - free(cmd); - } + if(command != NULL) + { + command_destroy(command); + } + else if(cmd != NULL) + { + free(cmd); + } - if(commands != NULL) - { - listRelease(commands); - } + if(commands != NULL) + { + listRelease(commands); + } - if(list_iter != NULL) - { - listReleaseIterator(list_iter); - } + 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; + /* 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 redisClusterAppendCommandArgv(redisClusterContext *cc, - int argc, const char **argv) { + int argc, const char **argv) { - int j; + int j; - for (j=0; j < argc; j++) { + for (j=0; j < argc; j++) { if(redisClusterAppendCommand(cc, argv[j]) != REDIS_OK) { - return REDIS_ERR; - } + return REDIS_ERR; + } + } + + return REDIS_OK; +} + +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 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; -} +int redisClusterGetReply(redisClusterContext *cc, void **reply) { + + struct cmd *command, *sub_command; + list *commands = NULL; + listNode *list_command, *list_sub_command; + listIter *list_iter; + int slot_num; + void *sub_reply; + + if(cc == NULL || cc->requests == NULL || reply == 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; + } -int redisClusterGetReply(redisClusterContext *cc, void **reply) { + + + *reply = command_post_fragment(cc, command, commands); + if(*reply == NULL) + { + goto error; + } - struct cmd *command, *sub_command; - list *commands = NULL; - listNode *list_command, *list_sub_command; - listIter *list_iter; - int slot_num; - void *sub_reply; - - if(cc == NULL || cc->requests == NULL || reply == 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; + listDelNode(cc->requests, list_command); + return REDIS_OK; error: - listDelNode(cc->requests, list_command); - return REDIS_ERR; + listDelNode(cc->requests, list_command); + return REDIS_ERR; } void redisCLusterReset(redisClusterContext *cc) { - redisContext *c = NULL; - int status; - void *reply; - - if(cc == NULL || cc->nodes == NULL) - { - return; - } - - redisCLusterSendAll(cc); - - do{ - status = redisClusterGetReply(cc, &reply); - if(status == REDIS_OK) - { - freeReplyObject(reply); - } - else - { - redisReaderFree(c->reader); - c->reader = redisReaderCreate(); - 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; - } + redisContext *c = NULL; + int status; + void *reply; + + if(cc == NULL || cc->nodes == NULL) + { + return; + } + + redisCLusterSendAll(cc); + + do{ + status = redisClusterGetReply(cc, &reply); + if(status == REDIS_OK) + { + freeReplyObject(reply); + } + else + { + redisReaderFree(c->reader); + c->reader = redisReaderCreate(); + 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############*/ @@ -3141,12 +3228,12 @@ static void __redisClusterAsyncCopyError(redisClusterAsyncContext *acc) { redisClusterContext *cc = acc->cc; acc->err = cc->err; - memcpy(acc->errstr, cc->errstr, 128); + memcpy(acc->errstr, cc->errstr, 128); } static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, - int type, const char *str) { - + int type, const char *str) { + size_t len; acc->err = type; @@ -3165,21 +3252,21 @@ static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) { redisClusterAsyncContext *acc; - if(cc == NULL) - { - return NULL; - } + if(cc == NULL) + { + return NULL; + } acc = hi_alloc(sizeof(redisClusterAsyncContext)); if (acc == NULL) return NULL; - acc->cc = cc; + acc->cc = cc; acc->err = 0; acc->data = NULL; - acc->adapter = NULL; - acc->attach_fn = NULL; + acc->adapter = NULL; + acc->attach_fn = NULL; acc->onConnect = NULL; acc->onDisconnect = NULL; @@ -3189,99 +3276,142 @@ static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext static cluster_async_data *cluster_async_data_get(void) { - cluster_async_data *cad; + cluster_async_data *cad; - cad = hi_alloc(sizeof(cluster_async_data)); - if(cad == NULL) - { - return NULL; - } + cad = hi_alloc(sizeof(cluster_async_data)); + if(cad == NULL) + { + return NULL; + } - cad->acc = NULL; - cad->node = NULL; - cad->command = NULL; - cad->callback = NULL; - cad->privdata = NULL; - cad->retry_count = 0; + cad->acc = NULL; + cad->node = NULL; + cad->command = NULL; + cad->callback = NULL; + cad->privdata = NULL; + cad->retry_count = 0; - return cad; + return cad; } static void cluster_async_data_free(cluster_async_data *cad) { - if(cad == NULL) - { - return; - } + if(cad == NULL) + { + return; + } - if(cad->command != NULL) - { - command_destroy(cad->command); - } - - hi_free(cad); - cad = NULL; + if(cad->command != NULL) + { + command_destroy(cad->command); + } + + hi_free(cad); + cad = NULL; } static redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, - cluster_node *node) + cluster_node *node) +{ + redisAsyncContext *ac; + + if(node == NULL) + { + return NULL; + } + + ac = node->acon; + if(ac != NULL) + { + if(ac->c.err == 0) + { + return ac; + } + } + + 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); + } + + node->acon = ac; + + return ac; +} + +static redisAsyncContext *actx_get_after_update_route_by_slot( + redisClusterAsyncContext *acc, int slot_num) { - redisAsyncContext *ac; - - if(node == NULL) - { - return NULL; - } - - ac = node->acon; - if(ac != NULL) - { - /* - if(ac->c.err) - { - redisReconnect(&ac->c); - } - - return ac; - */ - if(ac->c.err == 0) - { - return ac; - } - } - - 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); - } - - node->acon = ac; - - return ac; + 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) { @@ -3289,25 +3419,27 @@ redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) redisClusterContext *cc; redisClusterAsyncContext *acc; - cc = redisClusterConnectNonBlock(addrs, flags); - if(cc == NULL) - { - return NULL; - } + cc = redisClusterConnectNonBlock(addrs, flags); + if(cc == NULL) + { + return NULL; + } - acc = redisClusterAsyncInitialize(cc); + acc = redisClusterAsyncInitialize(cc); if (acc == NULL) { redisClusterFree(cc); return NULL; } - - __redisClusterAsyncCopyError(acc); - + + __redisClusterAsyncCopyError(acc); + return acc; } -int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn) { +int redisClusterAsyncSetConnectCallback( + redisClusterAsyncContext *acc, redisConnectCallback *fn) +{ if (acc->onConnect == NULL) { acc->onConnect = fn; return REDIS_OK; @@ -3315,7 +3447,9 @@ int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConn return REDIS_ERR; } -int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn) { +int redisClusterAsyncSetDisconnectCallback( + redisClusterAsyncContext *acc, redisDisconnectCallback *fn) +{ if (acc->onDisconnect == NULL) { acc->onDisconnect = fn; return REDIS_OK; @@ -3324,284 +3458,295 @@ int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisD } 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; - - 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 do not know how to deal with connect problem for hiredis cluster async api, - //so i leave it temporarily. - /* - if(ac->c.flags & REDIS_DISCONNECTING) - { - 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; - } - - node = cad->node; - node->acon = NULL; - - node = node_get_witch_connected(cc); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "no reachable node in cluster"); - 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; - } - - cad->node = node; - - goto retry; - } - */ - //__redisClusterAsyncSetError(acc, ac->err, - // ac->errstr); - - //goto done; - } - - error_type = cluster_reply_error_type(reply); - - if((error_type > CLUSTER_NOT_ERR && error_type < CLUSTER_ERR_SENTINEL) - || error_type == REDIS_ERR) - { - 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 REDIS_ERR: - case CLUSTER_ERR_MOVED: - ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, - "route update error, please recreate redisClusterContext!"); - goto done; - } - - node = node_get_by_table(cc, (uint32_t)command->slot_num); - if(node == NULL) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, "node get by table error"); - 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; - } - - cad->node = node; - - 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; - } - - cad->node = node; - - break; - } - case CLUSTER_ERR_TRYAGAIN: - case CLUSTER_ERR_CROSSSLOT: - case CLUSTER_ERR_CLUSTERDOWN: - - ac_retry = ac; - break; - default: - - goto done; - break; - } - - goto retry; - } + 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 = cad->node; + if(node->acon != NULL) + { + node->acon = 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; + } + + cad->node = node_get_by_table(cc, (uint32_t)command->slot_num); + + 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; + } + + cad->node = node; + + 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; + 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; + 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); - } + if(cad != NULL) + { + cluster_async_data_free(cad); + } } int redisClusterAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { - - va_list ap; - redisClusterContext *cc; - int status = REDIS_OK; - char *cmd = NULL; - int len; - int slot_num; - cluster_node *node; - redisAsyncContext *ac; - struct cmd *command = NULL; - list *commands = NULL; - cluster_async_data *cad; - - if(acc == NULL) - { - return REDIS_ERR; - } - - va_start(ap,format); - - 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)); - } - - len = redisvFormatCommand(&cmd,format,ap); - - if (len == -1) { + redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { + + va_list ap; + redisClusterContext *cc; + int status = REDIS_OK; + char *cmd = NULL; + int len; + int slot_num; + cluster_node *node; + redisAsyncContext *ac; + struct cmd *command = NULL; + list *commands = NULL; + cluster_async_data *cad; + + if(acc == NULL) + { + return REDIS_ERR; + } + + va_start(ap,format); + + 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)); + } + + len = redisvFormatCommand(&cmd,format,ap); + + if (len == -1) { __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); goto error; } else if (len == -2) { @@ -3609,178 +3754,178 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, goto error; } - command = command_get(); - if(command == NULL) - { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + command = command_get(); + if(command == NULL) + { + __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + goto error; + } + + command->cmd = cmd; + 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); + + //slot_num = slot_get_by_command(cc, cmd, len); + + 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->node = node; + cad->command = command; + cad->callback = fn; + cad->privdata = privdata; + + status = redisAsyncFormattedCommand(ac, + redisClusterAsyncCallback,cad,cmd,len); + if(status != REDIS_OK) + { goto error; - } - - command->cmd = cmd; - 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); - - //slot_num = slot_get_by_command(cc, cmd, len); - - 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->node = node; - cad->command = command; - cad->callback = fn; - cad->privdata = privdata; - - status = redisAsyncFormattedCommand(ac, - redisClusterAsyncCallback,cad,cmd,len); - if(status != REDIS_OK) - { - goto error; - } - - va_end(ap); - - if(commands != NULL) - { - listRelease(commands); - } + } + + va_end(ap); + + if(commands != NULL) + { + listRelease(commands); + } return REDIS_OK; error: - va_end(ap); - - if(command != NULL) - { - command_destroy(command); - } - else if(cmd != NULL) - { - free(cmd); - } + va_end(ap); + + if(command != NULL) + { + command_destroy(command); + } + else if(cmd != NULL) + { + free(cmd); + } - if(commands != NULL) - { - listRelease(commands); - } + if(commands != NULL) + { + listRelease(commands); + } - return REDIS_ERR; + return REDIS_ERR; } void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { - redisClusterContext *cc; - redisAsyncContext *ac; - dictIterator *di; - dictEntry *de; - dict *nodes; - struct cluster_node *node; + redisClusterContext *cc; + redisAsyncContext *ac; + dictIterator *di; + dictEntry *de; + dict *nodes; + struct cluster_node *node; - if(acc == NULL) - { - return; - } + if(acc == NULL) + { + return; + } - cc = acc->cc; + cc = acc->cc; - nodes = cc->nodes; + nodes = cc->nodes; - if(nodes == NULL) - { - return; - } - - di = dictGetIterator(nodes); + if(nodes == NULL) + { + return; + } + + di = dictGetIterator(nodes); - while((de = dictNext(di)) != NULL) - { - node = dictGetEntryVal(de); + while((de = dictNext(di)) != NULL) + { + node = dictGetEntryVal(de); - ac = node->acon; + ac = node->acon; - if(ac == NULL || ac->err) - { - continue; - } + if(ac == NULL || ac->err) + { + continue; + } - redisAsyncDisconnect(ac); + redisAsyncDisconnect(ac); - node->acon = NULL; - } + node->acon = NULL; + } } void redisClusterAsyncFree(redisClusterAsyncContext *acc) { - redisClusterContext *cc; - - if(acc == NULL) - { - return; - } + redisClusterContext *cc; + + if(acc == NULL) + { + return; + } - cc = acc->cc; + cc = acc->cc; - redisClusterFree(cc); + redisClusterFree(cc); - hi_free(acc); + hi_free(acc); } diff --git a/hircluster.h b/hircluster.h index 1329b2ee..e1a9a1c8 100644 --- a/hircluster.h +++ b/hircluster.h @@ -12,38 +12,39 @@ #define REDIS_CLUSTER_SLOTS 16384 -#define REDIS_ROLE_NULL 0 -#define REDIS_ROLE_MASTER 1 -#define REDIS_ROLE_SLAVE 2 +#define REDIS_ROLE_NULL 0 +#define REDIS_ROLE_MASTER 1 +#define REDIS_ROLE_SLAVE 2 -#define HIRCLUSTER_FLAG_NULL 0x0 +#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. */ -#define HIRCLUSTER_FLAG_ADD_SLAVE 0x10000000 +#define HIRCLUSTER_FLAG_ADD_SLAVE 0x10000000 struct dict; typedef struct cluster_node { - sds name; - sds addr; - sds host; - int port; - int count; - uint8_t role; - redisContext *con; - redisAsyncContext *acon; - list *slots; - list *slaves; + sds name; + sds addr; + sds host; + int port; + int count; + uint8_t role; + redisContext *con; + redisAsyncContext *acon; + list *slots; + list *slaves; + int failure_count; }cluster_node; typedef struct cluster_slot { - uint32_t start; - uint32_t end; - cluster_node *node; + uint32_t start; + uint32_t end; + cluster_node *node; }cluster_slot; #ifdef __cplusplus @@ -54,32 +55,33 @@ extern "C" { 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; + sds ip; + int port; - int flags; + int flags; enum redisConnectionType connection_type; struct timeval *timeout; - - struct hiarray *slots; + + struct hiarray *slots; - struct dict *nodes; - cluster_node *table[REDIS_CLUSTER_SLOTS]; + struct dict *nodes; + cluster_node *table[REDIS_CLUSTER_SLOTS]; - uint64_t route_version; + uint64_t route_version; - int max_redirect_count; - int retry_count; + int max_redirect_count; + int retry_count; - list *requests; + list *requests; - int need_update_route; + 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); + const struct timeval tv, int flags); redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); void redisClusterFree(redisClusterContext *cc); @@ -118,8 +120,8 @@ typedef struct redisClusterAsyncContext { /* Not used by hiredis */ void *data; - void *adapter; - adapterAttachFn *attach_fn; + 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). */ From 44b615b2b9ce895c6690e76e7c7437ea911fa7d2 Mon Sep 17 00:00:00 2001 From: deep011 Date: Fri, 20 Nov 2015 16:52:30 +0800 Subject: [PATCH 011/273] filtrate ':0' address in the cluster nodes command reply --- command.c | 161 +++++++++++++++++++++++++------------------- command.h | 30 ++++----- hiarray.c | 4 +- hircluster.c | 17 ++++- hiutil.c | 186 +++++++++++++++++++++++++-------------------------- hiutil.h | 14 ++-- 6 files changed, 224 insertions(+), 188 deletions(-) diff --git a/command.c b/command.c index 00e9962f..8ceeaa55 100644 --- a/command.c +++ b/command.c @@ -316,10 +316,10 @@ void redis_parse_cmd(struct cmd *r) { char *p, *m, *token = NULL; - char *cmd_end; + char *cmd_end; char ch; - uint32_t rlen = 0; /* running length in parsing fsa */ - uint32_t rnarg = 0; /* running # arg used by parsing fsa */ + uint32_t rlen = 0; /* running length in parsing fsa */ + uint32_t rnarg = 0; /* running # arg used by parsing fsa */ enum { SW_START, SW_NARG, @@ -352,7 +352,7 @@ redis_parse_cmd(struct cmd *r) } state; state = SW_START; - cmd_end = r->cmd + r->clen; + cmd_end = r->cmd + r->clen; ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(r->cmd != NULL && r->clen > 0); @@ -445,7 +445,7 @@ redis_parse_cmd(struct cmd *r) //m = cmd_end - 1; //p = m; //break; - goto error; + goto error; } if (*m != CR) { @@ -1062,7 +1062,7 @@ redis_parse_cmd(struct cmd *r) } else if (isdigit(ch)) { rlen = rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { - + if (rnarg == 0) { goto error; } @@ -1116,6 +1116,7 @@ redis_parse_cmd(struct cmd *r) } kpos->start = m; kpos->end = p; + //kpos->v_len = 0; state = SW_KEY_LF; } @@ -1195,6 +1196,26 @@ redis_parse_cmd(struct cmd *r) } 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; @@ -1573,20 +1594,20 @@ redis_parse_cmd(struct cmd *r) done: - ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL); - + ASSERT(r->type > CMD_UNKNOWN && r->type < CMD_SENTINEL); + r->result = CMD_PARSE_OK; return; enomem: - + r->result = CMD_PARSE_ERROR; return; error: - + r->result = CMD_PARSE_ERROR; errno = EINVAL; @@ -1594,74 +1615,74 @@ redis_parse_cmd(struct cmd *r) 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->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)); + struct cmd *command; + command = hi_alloc(sizeof(struct cmd)); + if(command == NULL) + { + return NULL; + } + + command->id = ++cmd_id; + command->result = CMD_PARSE_OK; + 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); + { + hi_free(command); return NULL; } - return command; + return command; } void command_destroy(struct cmd *command) { - if(command == NULL) - { - return; - } - - if(command->cmd != NULL) - { - free(command->cmd); - } - - 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); + if(command == NULL) + { + return; + } + + if(command->cmd != NULL) + { + free(command->cmd); + } + + 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/command.h b/command.h index 0ca359e3..c8d888a5 100644 --- a/command.h +++ b/command.h @@ -137,36 +137,36 @@ typedef enum cmd_type { 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 */ + 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 */ - cmd_type_t type; /* command type */ + cmd_type_t type; /* command type */ - char *cmd; - uint32_t clen; /* command length */ - + 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) */ + 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) */ + 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*/ + 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; + redisReply *reply; - list *sub_commands; /* just for pipeline and multi-key commands */ + list *sub_commands; /* just for pipeline and multi-key commands */ }; diff --git a/hiarray.c b/hiarray.c index 15ffe08d..cf742ecf 100644 --- a/hiarray.c +++ b/hiarray.c @@ -35,7 +35,7 @@ hiarray_destroy(struct hiarray *a) hi_free(a); } -rstatus_t +int hiarray_init(struct hiarray *a, uint32_t n, size_t size) { ASSERT(n != 0 && size != 0); @@ -166,7 +166,7 @@ hiarray_sort(struct hiarray *a, hiarray_compare_t 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. */ -rstatus_t +int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) { uint32_t i, nelem; diff --git a/hircluster.c b/hircluster.c index f35f1248..57a3ff51 100644 --- a/hircluster.c +++ b/hircluster.c @@ -928,6 +928,20 @@ cluster_update_route_with_nodes(redisClusterContext *cc, 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; @@ -1122,6 +1136,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(cc->nodes != NULL) { dictRelease(cc->nodes); + cc->nodes = NULL; } cc->nodes = nodes; @@ -1183,7 +1198,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, cc->nodes = NULL; } - dictRelease(cc->nodes); + dictRelease(nodes); } if(nodes_name != NULL) diff --git a/hiutil.c b/hiutil.c index cce5ccd5..d10cdace 100644 --- a/hiutil.c +++ b/hiutil.c @@ -189,50 +189,50 @@ _hi_atoi(uint8_t *line, size_t n) void _hi_itoa(uint8_t *s, int num) { - uint8_t c; - uint8_t sign = 0; + uint8_t c; + uint8_t sign = 0; - if(s == NULL) - { - return; - } + if(s == NULL) + { + return; + } - uint32_t len, i; + uint32_t len, i; len = 0; - if(num < 0) - { - sign = 1; - num = abs(num); - } - else if(num == 0) - { - s[len++] = '0'; - return; - } + 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'; + c = num %10 + '0'; num = num /10; s[len+1] = s[len]; s[len] = c; len ++; } - if(sign == 1) - { - s[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; - } + s[len] = '\0'; + + for(i = 0; i < len/2; i ++) + { + c = s[i]; + s[i] = s[len - i -1]; + s[len - i -1] = c; + } } @@ -249,20 +249,20 @@ hi_valid_port(int n) int _uint_len(uint32_t num) { - int n = 0; + int n = 0; - if(num == 0) - { - return 1; - } + if(num == 0) + { + return 1; + } - while(num != 0) - { - n ++; - num /= 10; - } + while(num != 0) + { + n ++; + num /= 10; + } - return n; + return n; } void * @@ -274,10 +274,10 @@ _hi_alloc(size_t size, const char *name, int line) p = malloc(size); - if(name == NULL && line == 1) - { + if(name == NULL && line == 1) + { - } + } return p; } @@ -310,11 +310,11 @@ _hi_realloc(void *ptr, size_t size, const char *name, int line) p = realloc(ptr, size); - if(name == NULL && line == 1) - { + if(name == NULL && line == 1) + { - } - + } + return p; } @@ -323,21 +323,21 @@ _hi_free(void *ptr, const char *name, int line) { ASSERT(ptr != NULL); - if(name == NULL && line == 1) - { + if(name == NULL && line == 1) + { - } + } - free(ptr); + free(ptr); } void hi_stacktrace(int skip_count) { - if(skip_count > 0) - { + if(skip_count > 0) + { - } + } #ifdef HI_HAVE_BACKTRACE void *stack[64]; @@ -363,10 +363,10 @@ hi_stacktrace(int skip_count) void hi_stacktrace_fd(int fd) { - if(fd > 0) - { - - } + if(fd > 0) + { + + } #ifdef HI_HAVE_BACKTRACE void *stack[64]; int size; @@ -379,14 +379,14 @@ hi_stacktrace_fd(int fd) void hi_assert(const char *cond, const char *file, int line, int panic) { - - printf("File: %s Line: %d: %s\n", file, line, cond); - + + printf("File: %s Line: %d: %s\n", file, line, cond); + if (panic) { hi_stacktrace(1); abort(); } - abort(); + abort(); } int @@ -437,7 +437,7 @@ ssize_t _hi_sendn(int sd, const void *vptr, size_t n) { size_t nleft; - ssize_t nsend; + ssize_t nsend; const char *ptr; ptr = vptr; @@ -467,13 +467,13 @@ _hi_sendn(int sd, const void *vptr, size_t n) ssize_t _hi_recvn(int sd, void *vptr, size_t n) { - size_t nleft; - ssize_t nrecv; - char *ptr; + size_t nleft; + ssize_t nrecv; + char *ptr; - ptr = vptr; - nleft = n; - while (nleft > 0) { + ptr = vptr; + nleft = n; + while (nleft > 0) { nrecv = recv(sd, ptr, nleft, 0); if (nrecv < 0) { if (errno == EINTR) { @@ -523,32 +523,32 @@ hi_msec_now(void) void print_string_with_length(char *s, size_t len) { - char *token; - for(token = s; token <= s + len; token ++) - { - printf("%c", *token); - } - printf("\n"); + 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"); + 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/hiutil.h b/hiutil.h index 6598f07f..d9e1ddb0 100644 --- a/hiutil.h +++ b/hiutil.h @@ -40,10 +40,10 @@ typedef int rstatus_t; /* return type */ * 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 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) @@ -128,10 +128,10 @@ typedef int rstatus_t; /* return type */ #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) + _hi_itoa((uint8_t *)_line, (int)_n) -#define uint_len(_n) \ - _uint_len((uint32_t)_n) +#define uint_len(_n) \ + _uint_len((uint32_t)_n) int hi_set_blocking(int sd); From 3a33ad4007ea2790acf294d60f19609477f44de2 Mon Sep 17 00:00:00 2001 From: deep011 Date: Mon, 23 Nov 2015 17:53:28 +0800 Subject: [PATCH 012/273] change version to 0.2.1 --- hircluster.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hircluster.h b/hircluster.h index e1a9a1c8..6a3159ab 100644 --- a/hircluster.h +++ b/hircluster.h @@ -8,7 +8,7 @@ #define HIREDIS_VIP_MAJOR 0 #define HIREDIS_VIP_MINOR 2 -#define HIREDIS_VIP_PATCH 0 +#define HIREDIS_VIP_PATCH 1 #define REDIS_CLUSTER_SLOTS 16384 From 699a702febb7dfd4e616c1987ece92ebc7dee840 Mon Sep 17 00:00:00 2001 From: deep011 Date: Tue, 24 Nov 2015 11:21:36 +0800 Subject: [PATCH 013/273] update readme --- README.md | 352 ++++++++++++++++-------------------------------------- 1 file changed, 102 insertions(+), 250 deletions(-) diff --git a/README.md b/README.md index 3a14e33b..98d80a1d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ -[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) -# HIREDIS +# HIREDIS-VIP -Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. +Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. -We supported redis cluster for hiredis. +Hiredis-vip supported redis cluster. -The library comes with multiple APIs. There is the -*cluster API*, *synchronous API*, the *asynchronous API* and the *reply parsing API*. +Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . ## CLUSTER SUPPORT @@ -43,224 +41,132 @@ void redisCLusterReset(redisClusterContext *cc); redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisCallbackFn *fn, void *privdata, const char *format, ...); +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); void redisClusterAsyncFree(redisClusterAsyncContext *acc); ``` - -## UPGRADING -Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing -code using hiredis should not be a big pain. The key thing to keep in mind when -upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to -the stateless 0.0.1 that only has a file descriptor to work with. +## Quick usage + +If you want used but not read the follow, please reference the examples: +https://github.com/vipshop/hiredis-vip/wiki -## Synchronous API +## Cluster synchronous API To consume the synchronous API, there are only a few function calls that need to be introduced: ```c -redisContext *redisConnect(const char *ip, int port); -void *redisCommand(redisContext *c, const char *format, ...); -void freeReplyObject(void *reply); +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); ``` -### Connecting +### Cluster connecting -The function `redisConnect` is used to create a so-called `redisContext`. The -context is where Hiredis holds state for a connection. The `redisContext` +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. More information on errors can be found in the **Errors** section. -After trying to connect to Redis using `redisConnect` you should +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 -redisContext *c = redisConnect("127.0.0.1", 6379); -if (c != NULL && c->err) { - printf("Error: %s\n", c->errstr); +redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); +if (cc != NULL && cc->err) { + printf("Error: %s\n", cc->errstr); // handle error } ``` -### Sending commands +### Cluster sending commands -There are several ways to issue commands to Redis. The first that will be introduced is -`redisCommand`. This function takes a format similar to printf. In the simplest form, +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 = redisCommand(context, "SET foo bar"); +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 = redisCommand(context, "SET foo %s", value); -``` -When you need to pass binary safe strings in a command, the `%b` specifier can be -used. Together with a pointer to the string, it requires a `size_t` length argument -of the string: -```c -reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); +reply = redisClusterCommand(clustercontext, "SET foo %s", value); ``` -Internally, Hiredis splits the command in different arguments and will +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 = redisCommand(context, "SET key:%s %s", myid, value); +reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); ``` -### Using replies - -The return value of `redisCommand` holds a reply when the command was -successfully executed. When an error occurs, the return value is `NULL` and -the `err` field in the context will be set (see section on **Errors**). -Once an error is returned the context cannot be reused and you should set up -a new connection. - -The standard replies that `redisCommand` are of the type `redisReply`. The -`type` field in the `redisReply` should be used to test what kind of reply -was received: +### Cluster multi-key commands -* **`REDIS_REPLY_STATUS`**: - * The command replied with a status reply. The status string can be accessed using `reply->str`. - The length of this string can be accessed using `reply->len`. +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. -* **`REDIS_REPLY_ERROR`**: - * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. - -* **`REDIS_REPLY_INTEGER`**: - * The command replied with an integer. The integer value can be accessed using the - `reply->integer` field of type `long long`. - -* **`REDIS_REPLY_NIL`**: - * The command replied with a **nil** object. There is no data to access. - -* **`REDIS_REPLY_STRING`**: - * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. - The length of this string can be accessed using `reply->len`. - -* **`REDIS_REPLY_ARRAY`**: - * A multi bulk reply. The number of elements in the multi bulk reply is stored in - `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well - and can be accessed via `reply->element[..index..]`. - Redis may reply with nested arrays but this is fully supported. - -Replies should be freed using the `freeReplyObject()` function. -Note that this function will take care of freeing sub-reply objects -contained in arrays and nested arrays, so there is no need for the user to -free the sub replies (it is actually harmful and will corrupt the memory). - -**Important:** the current version of hiredis (0.10.0) frees replies when the -asynchronous API is used. This means you should not call `freeReplyObject` when -you use this API. The reply is cleaned up by hiredis _after_ the callback -returns. This behavior will probably change in future releases, so make sure to -keep an eye on the changelog when upgrading (see issue #39). +Example: +```c +reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); +``` -### Cleaning up +### Cluster cleaning up To disconnect and free the context the following function can be used: ```c -void redisFree(redisContext *c); +void redisClusterFree(redisClusterContext *cc); ``` This function immediately closes the socket and then frees the allocations done in creating the context. -### Sending commands (cont'd) +### Cluster pipelining -Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. -It has the following prototype: -```c -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); -``` -It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the -arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will -use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments -need to be binary safe, the entire array of lengths `argvlen` should be provided. - -The return value has the same semantic as `redisCommand`. - -### Pipelining - -To explain how Hiredis supports pipelining in a blocking connection, there needs to be -understanding of the internal execution flow. - -When any of the functions in the `redisCommand` family is called, Hiredis first formats the -command according to the Redis protocol. The formatted command is then put in the output buffer -of the context. This output buffer is dynamic, so it can hold any number of commands. -After the command is put in the output buffer, `redisGetReply` is called. This function has the -following two execution paths: - -1. The input buffer is non-empty: - * Try to parse a single reply from the input buffer and return it - * If no reply could be parsed, continue at *2* -2. The input buffer is empty: - * Write the **entire** output buffer to the socket - * Read from the socket until a single reply could be parsed - -The function `redisGetReply` 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 `redisCommand` family, apart from not returning a reply: +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 -void redisAppendCommand(redisContext *c, const char *format, ...); -void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); ``` -After calling either function one or more times, `redisGetReply` can be used to receive the +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 pipeline (resulting in only a single call to `write(2)` and -a single call to `read(2)`): +The following examples shows a simple cluster pipeline: ```c redisReply *reply; -redisAppendCommand(context,"SET foo bar"); -redisAppendCommand(context,"GET foo"); -redisGetReply(context,&reply); // reply for SET +redisClusterAppendCommand(clusterContext,"SET foo bar"); +redisClusterAppendCommand(clusterContext,"GET foo"); +redisClusterGetReply(clusterContext,&reply); // reply for SET freeReplyObject(reply); -redisGetReply(context,&reply); // reply for GET +redisClusterGetReply(clusterContext,&reply); // reply for GET freeReplyObject(reply); +redisCLusterReset(clusterContext); ``` This API can also be used to implement a blocking subscriber: ```c -reply = redisCommand(context,"SUBSCRIBE foo"); +reply = redisClusterCommand(clusterContext,"SUBSCRIBE foo"); freeReplyObject(reply); -while(redisGetReply(context,&reply) == REDIS_OK) { +while(redisClusterGetReply(clusterContext,&reply) == REDIS_OK) { // consume message freeReplyObject(reply); } +redisCLusterReset(clusterContext); ``` -### Errors - -When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is -returned. The `err` field inside the context will be non-zero and set to one of the -following constants: - -* **`REDIS_ERR_IO`**: - There was an I/O error while creating the connection, trying to write - to the socket or read from the socket. If you included `errno.h` in your - application, you can use the global `errno` variable to find out what is - wrong. - -* **`REDIS_ERR_EOF`**: - The server closed the connection which resulted in an empty read. - -* **`REDIS_ERR_PROTOCOL`**: - There was an error while parsing the protocol. - -* **`REDIS_ERR_OTHER`**: - Any other error. Currently, it is only used when a specified hostname to connect - to cannot be resolved. - -In every case, the `errstr` field in the context will be set to hold a string representation -of the error. -## Asynchronous API +## Cluster asynchronous API -Hiredis comes with an asynchronous API that works easily with any event library. -Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) -and [libevent](http://monkey.org/~provos/libevent/). +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 @@ -270,14 +176,14 @@ should be checked after creation to see if there were errors creating the connec 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 -redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); -if (c->err) { - printf("Error: %s\n", c->errstr); +redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); +if (acc->err) { + printf("Error: %s\n", acc->errstr); // handle error } ``` -The asynchronous context can hold a disconnect callback function that is called when the +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 @@ -287,39 +193,37 @@ On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection w 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. -The context object is always freed after the disconnect callback fired. When a reconnect is needed, -the disconnect callback is a good point to do so. +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 redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); ``` ### Sending commands and their callbacks -In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. -Therefore, unlike the synchronous API, there is only a single way to send commands. -Because commands are sent to Redis asynchronously, issuing a command requires a callback function +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(redisAsyncContext *c, void *reply, void *privdata); +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 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 redisClusterAsyncCommand( + redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, + void *privdata, const char *format, ...); ``` -Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command +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 `redisAsyncCommand` family. +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 @@ -329,9 +233,9 @@ All pending callbacks are called with a `NULL` reply when the context encountere ### Disconnecting -An asynchronous connection can be terminated using: +An cluster asynchronous connection can be terminated using: ```c -void redisAsyncDisconnect(redisAsyncContext *ac); +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 @@ -341,80 +245,28 @@ callbacks have been executed. After this, the disconnection callback is executed ### Hooking it up to event library *X* -There are a few hooks that need to be set on the context object after it is created. -See the `adapters/` directory for bindings to *libev* and *libevent*. +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*. -## Reply parsing API +## Package -Hiredis comes with a reply parsing API that makes it easy for writing higher -level language bindings. +If you only want get library, you can "yum install hiredis-vip" or "apt-get install hiredis-vip" instead of building from source code. -The reply parsing API consists of the following functions: -```c -redisReader *redisReaderCreate(void); -void redisReaderFree(redisReader *reader); -int redisReaderFeed(redisReader *reader, const char *buf, size_t len); -int redisReaderGetReply(redisReader *reader, void **reply); -``` -The same set of functions are used internally by hiredis when creating a -normal Redis context, the above API just exposes it to the user for a direct -usage. - -### Usage - -The function `redisReaderCreate` creates a `redisReader` structure that holds a -buffer with unparsed data and state for the protocol parser. - -Incoming data -- most likely from a socket -- can be placed in the internal -buffer of the `redisReader` using `redisReaderFeed`. This function will make a -copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed -when `redisReaderGetReply` is called. This function returns an integer status -and a reply object (as described above) via `void **reply`. The returned status -can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went -wrong (either a protocol error, or an out of memory error). - -The parser limits the level of nesting for multi bulk payloads to 7. If the -multi bulk nesting level is higher than this, the parser returns an error. - -### Customizing replies - -The function `redisReaderGetReply` creates `redisReply` and makes the function -argument `reply` point to the created `redisReply` variable. For instance, if -the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` -will hold the status as a vanilla C string. However, the functions that are -responsible for creating instances of the `redisReply` can be customized by -setting the `fn` field on the `redisReader` struct. This should be done -immediately after creating the `redisReader`. - -For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) -uses customized reply object functions to create Ruby objects. - -### Reader max buffer - -Both when using the Reader API directly or when using it indirectly via a -normal Redis context, the redisReader structure uses a buffer in order to -accumulate data from the server. -Usually this buffer is destroyed when it is empty and is larger than 16 -KiB in order to avoid wasting memory in unused buffers - -However when working with very big payloads destroying the buffer may slow -down performances considerably, so it is possible to modify the max size of -an idle buffer changing the value of the `maxbuf` field of the reader structure -to the desired value. The special value of 0 means that there is no maximum -value for an idle buffer, so the buffer will never get freed. - -For instance if you have a normal Redis context you can set the maximum idle -buffer to zero (unlimited) just with: -```c -context->reader->maxbuf = 0; -``` -This should be done only in order to maximize performances when working with -large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again -as soon as possible in order to prevent allocation of useless memory. +If you used hiredis-vip in your project, you can install "hiredis-vip-devel" package for development. + +Before install the package, execute the follow command first: + +**deb package** : `curl -s https://packagecloud.io/install/repositories/deep/packages/script.deb.sh | sudo bash` + +**rpm package** : `curl -s https://packagecloud.io/install/repositories/deep/packages/script.rpm.sh | sudo bash` + +You can also download the packages from "https://packagecloud.io/deep/packages" and install by yourself. + +If you want to support other OS packages, please contact with me. ## AUTHORS -Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and -Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. -Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and -Jan-Erik Rediger (janerik at fnordig dot com) +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. From a6b9034f746aed5f44e574e830a9eb831ce730ce Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 23 Dec 2015 16:51:03 +0800 Subject: [PATCH 014/273] add three function redisClustervCommand,redisClustervAppendCommand,redisClustervAsyncCommand --- README.md | 3 ++ hircluster.c | 83 +++++++++++++++++++++++++++++++--------------------- hircluster.h | 5 +++- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 98d80a1d..294946eb 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,10 @@ redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const str redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); void redisClusterFree(redisClusterContext *cc); void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); +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); int redisClusterGetReply(redisClusterContext *cc, void **reply); @@ -41,6 +43,7 @@ void redisCLusterReset(redisClusterContext *cc); redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +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, ...); void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); void redisClusterAsyncFree(redisClusterAsyncContext *acc); diff --git a/hircluster.c b/hircluster.c index 57a3ff51..53a34256 100644 --- a/hircluster.c +++ b/hircluster.c @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include "hircluster.h" #include "hiutil.h" @@ -2726,9 +2726,7 @@ void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count) cc->max_redirect_count = max_redirect_count; } -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { - - va_list ap; +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) { redisReply *reply = NULL; char *cmd = NULL; int slot_num; @@ -2737,8 +2735,6 @@ void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { list *commands = NULL; listNode *list_node; listIter *list_iter = NULL; - - va_start(ap,format); if(cc == NULL) { @@ -2753,8 +2749,6 @@ void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { len = redisvFormatCommand(&cmd,format,ap); - va_end(ap); - if (len == -1) { __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); return NULL; @@ -2867,10 +2861,21 @@ void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { return NULL; } -int redisClusterAppendCommand(redisClusterContext *cc, - const char *format, ...) { - +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; +} + +int redisClustervAppendCommand(redisClusterContext *cc, + const char *format, va_list ap){ + int len; int slot_num; struct cmd *command = NULL, *sub_command; @@ -2880,11 +2885,6 @@ int redisClusterAppendCommand(redisClusterContext *cc, listNode *list_node; listIter *list_iter = NULL; - if(cc == NULL || format == NULL) - { - return REDIS_ERR; - } - if(cc->requests == NULL) { cc->requests = listCreate(); @@ -2897,8 +2897,6 @@ int redisClusterAppendCommand(redisClusterContext *cc, cc->requests->free = listCommandFree; } - va_start(ap,format); - len = redisvFormatCommand(&cmd,format,ap); if (len == -1) { __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); @@ -2971,8 +2969,6 @@ int redisClusterAppendCommand(redisClusterContext *cc, done: - va_end(ap); - if(command->cmd != NULL) { free(command->cmd); @@ -3006,8 +3002,6 @@ int redisClusterAppendCommand(redisClusterContext *cc, error: - va_end(ap); - if(command != NULL) { command_destroy(command); @@ -3032,6 +3026,25 @@ int redisClusterAppendCommand(redisClusterContext *cc, But now we do not handle it. */ return REDIS_ERR; + +} + +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, @@ -3722,11 +3735,9 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv } } - -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { - - va_list ap; +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) { + redisClusterContext *cc; int status = REDIS_OK; char *cmd = NULL; @@ -3743,8 +3754,6 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, return REDIS_ERR; } - va_start(ap,format); - cc = acc->cc; if(cc->err) @@ -3856,8 +3865,6 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, goto error; } - va_end(ap); - if(commands != NULL) { listRelease(commands); @@ -3866,8 +3873,6 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, return REDIS_OK; error: - - va_end(ap); if(command != NULL) { @@ -3886,6 +3891,18 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, return REDIS_ERR; } +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; +} + void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { redisClusterContext *cc; diff --git a/hircluster.h b/hircluster.h index 6a3159ab..f2d9a8b3 100644 --- a/hircluster.h +++ b/hircluster.h @@ -8,7 +8,7 @@ #define HIREDIS_VIP_MAJOR 0 #define HIREDIS_VIP_MINOR 2 -#define HIREDIS_VIP_PATCH 1 +#define HIREDIS_VIP_PATCH 2 #define REDIS_CLUSTER_SLOTS 16384 @@ -88,10 +88,12 @@ void redisClusterFree(redisClusterContext *cc); void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); +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); int redisClusterGetReply(redisClusterContext *cc, void **reply); @@ -135,6 +137,7 @@ typedef struct redisClusterAsyncContext { redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); +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, ...); void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); void redisClusterAsyncFree(redisClusterAsyncContext *acc); From 8ef71d5c4f191f262201d8fb18b92abd12584e2a Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 20 Jan 2016 17:23:38 +0800 Subject: [PATCH 015/273] add actx_get_by_node() in hircluster.h --- async.c | 2 +- hircluster.c | 4 ++-- hircluster.h | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/async.c b/async.c index fde90675..28aa76f5 100644 --- a/async.c +++ b/async.c @@ -651,7 +651,7 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat 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; diff --git a/hircluster.c b/hircluster.c index 53a34256..04e61e79 100644 --- a/hircluster.c +++ b/hircluster.c @@ -256,6 +256,7 @@ static int cluster_node_init(cluster_node *node) node->acon = NULL; node->slots = NULL; node->failure_count = 0; + node->data = NULL; return REDIS_OK; } @@ -3338,8 +3339,7 @@ static void cluster_async_data_free(cluster_async_data *cad) cad = NULL; } - -static redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, +redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node) { redisAsyncContext *ac; diff --git a/hircluster.h b/hircluster.h index f2d9a8b3..5a4453df 100644 --- a/hircluster.h +++ b/hircluster.h @@ -38,6 +38,7 @@ typedef struct cluster_node list *slots; list *slaves; int failure_count; + void *data; /* Not used by hiredis */ }cluster_node; typedef struct cluster_slot @@ -142,6 +143,8 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallback void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); void redisClusterAsyncFree(redisClusterAsyncContext *acc); +redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node); + #ifdef __cplusplus } #endif From 1f622e384727ae33289eda962684cb5c6a448752 Mon Sep 17 00:00:00 2001 From: deep011 Date: Mon, 25 Jan 2016 12:22:29 +0800 Subject: [PATCH 016/273] Add info for command parse failed. --- command.c | 14 +++++++++++++- command.h | 7 ++++--- hircluster.c | 11 ++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/command.c b/command.c index 8ceeaa55..e32091b4 100644 --- a/command.c +++ b/command.c @@ -315,6 +315,7 @@ redis_argeval(struct cmd *r) void redis_parse_cmd(struct cmd *r) { + int len; char *p, *m, *token = NULL; char *cmd_end; char ch; @@ -1602,7 +1603,7 @@ redis_parse_cmd(struct cmd *r) enomem: - r->result = CMD_PARSE_ERROR; + r->result = CMD_PARSE_ENOMEM; return; @@ -1610,7 +1611,13 @@ redis_parse_cmd(struct cmd *r) 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() @@ -1624,6 +1631,7 @@ struct cmd *command_get() command->id = ++cmd_id; command->result = CMD_PARSE_OK; + command->errstr = NULL; command->type = CMD_UNKNOWN; command->cmd = NULL; command->clen = 0; @@ -1660,6 +1668,10 @@ void command_destroy(struct cmd *command) free(command->cmd); } + if(command->errstr != NULL){ + hi_free(command->errstr); + } + if(command->keys != NULL) { command->keys->nelem = 0; diff --git a/command.h b/command.h index c8d888a5..9509630b 100644 --- a/command.h +++ b/command.h @@ -8,6 +8,7 @@ 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 */ @@ -145,13 +146,14 @@ 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 */ + struct hiarray *keys; /* array of keypos, for req */ char *narg_start; /* narg start (redis) */ char *narg_end; /* narg end (redis) */ @@ -161,13 +163,12 @@ struct cmd { 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*/ + * -1:the keys in this command cross different slots*/ struct cmd **frag_seq; /* sequence of fragment command, map from keys to fragments*/ redisReply *reply; list *sub_commands; /* just for pipeline and multi-key commands */ - }; void redis_parse_cmd(struct cmd *r); diff --git a/hircluster.c b/hircluster.c index 04e61e79..8cfdb5b4 100644 --- a/hircluster.c +++ b/hircluster.c @@ -2687,9 +2687,14 @@ static int command_format_by_slot(redisClusterContext *cc, redis_parse_cmd(command); - if(command->result != CMD_PARSE_OK) + if(command->result == CMD_PARSE_ENOMEM) { - __redisClusterSetError(cc, REDIS_ERR_PROTOCOL, "parse command error"); + __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; } @@ -2697,7 +2702,7 @@ static int command_format_by_slot(redisClusterContext *cc, if(key_count <= 0) { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no keys in command(must have keys for redis cluster mode)"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "No keys in command(must have keys for redis cluster mode)"); goto done; } else if(key_count == 1) From 3f80b9979ef4f0174fd9d0df456a1887f3c697b2 Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 27 Jan 2016 09:50:17 +0800 Subject: [PATCH 017/273] fix a bug that can't handle ask error correctly for synchronous API --- hircluster.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/hircluster.c b/hircluster.c index 8cfdb5b4..c55ae3f6 100644 --- a/hircluster.c +++ b/hircluster.c @@ -2182,7 +2182,12 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, } c = ctx_get_by_node(node, cc->timeout, cc->flags); - if(c == NULL || c->err) + 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; @@ -2212,6 +2217,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, { __redisClusterSetError(cc, REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, "too many cluster redirect"); + freeReplyObject(reply); return NULL; } @@ -2235,13 +2241,31 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, 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; From a6be66f79bb4a63587788d6dd31917e9a4ae9346 Mon Sep 17 00:00:00 2001 From: deep011 Date: Thu, 28 Jan 2016 18:21:36 +0800 Subject: [PATCH 018/273] Add two flags for cluster: HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS --- hircluster.c | 1187 ++++++++++++++++++++++++++++++++++++++------------ hircluster.h | 32 +- 2 files changed, 931 insertions(+), 288 deletions(-) diff --git a/hircluster.c b/hircluster.c index c55ae3f6..46cd1d69 100644 --- a/hircluster.c +++ b/hircluster.c @@ -48,6 +48,8 @@ typedef enum CLUSTER_ERR_TYPE{ }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) { @@ -56,6 +58,11 @@ void listClusterNodeDestructor(void *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)); } @@ -186,6 +193,10 @@ static unsigned int keyHashSlot(char *key, int keylen) { 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); @@ -245,39 +256,41 @@ static int cluster_reply_error_type(redisReply *reply) 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->count = 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; } - if(node->count > 0) - { - return; - } - sdsfree(node->name); sdsfree(node->addr); sdsfree(node->host); node->port = 0; - node->role = 1; + node->role = REDIS_ROLE_NULL; if(node->con != NULL) { @@ -298,57 +311,218 @@ static void cluster_node_deinit(cluster_node *node) { 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; - if(node != NULL) - { - node->count ++; + 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 int cluster_slot_deinit(cluster_slot *slot) +static void cluster_slot_destroy(cluster_slot *slot) { - cluster_node *node; slot->start = 0; slot->end = 0; - if(slot->node != NULL) - { - node = slot->node; - node->count --; - slot->node = NULL; - } + slot->node = NULL; hi_free(slot); - - return REDIS_OK; } -static int cluster_slot_ref_node(cluster_slot *slot, cluster_node *node) +static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, + sds remote_name, cluster_node *node) { - cluster_node *node_old; + 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; + } - if(slot->node != NULL) - { - node_old = slot->node; - node_old->count --; + 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(node != NULL) - { - node->count ++; - listAddNodeTail(node->slots, slot); + 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; } - slot->node = node; + 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; + } - return REDIS_OK; + 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; } /** @@ -387,6 +561,8 @@ static cluster_node *node_get_with_nodes( "slots for node listCreate error"); goto error; } + + node->slots->free = listClusterSlotDestructor; } node->name = node_infos[0]; @@ -434,31 +610,30 @@ static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) redisContext *c; redisAsyncContext *ac; + if(nodes_f == NULL || nodes_t == NULL){ + return; + } + di = dictGetIterator(nodes_t); - while((de_t = dictNext(di)) != NULL) - { + while((de_t = dictNext(di)) != NULL){ node_t = dictGetEntryVal(de_t); - if(node_t == NULL) - { + if(node_t == NULL){ continue; } de_f = dictFind(nodes_f, node_t->addr); - if(de_f == NULL) - { + if(de_f == NULL){ continue; } node_f = dictGetEntryVal(de_f); - if(node_f->con != NULL) - { + if(node_f->con != NULL){ c = node_f->con; node_f->con = node_t->con; node_t->con = c; } - if(node_f->acon != NULL) - { + if(node_f->acon != NULL){ ac = node_f->acon; node_f->acon = node_t->acon; node_t->acon = ac; @@ -478,7 +653,7 @@ cluster_slot_start_cmp(const void *t1, const void *t2) } static int -cluster_master_slave_mapping(redisClusterContext *cc, +cluster_master_slave_mapping_with_name(redisClusterContext *cc, dict **nodes, cluster_node *node, sds master_name) { int ret; @@ -486,8 +661,7 @@ cluster_master_slave_mapping(redisClusterContext *cc, cluster_node *node_old; listNode *lnode; - if(cc == NULL || node == NULL - || master_name == NULL) + if(node == NULL || master_name == NULL) { return REDIS_ERR; } @@ -541,285 +715,761 @@ cluster_master_slave_mapping(redisClusterContext *cc, return REDIS_ERR; } - node->slaves->free = - listClusterNodeDestructor; + 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 != 2){ + __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; + } + } + + 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; + } + } - - 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); + //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; } - 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; + 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; } + } - node_old->slaves->free = - listClusterNodeDestructor; + if(myself == 1){ + myself = 0; } - listAddNodeTail(node_old->slaves, node); - } - else - { - NOT_REACHED(); + sdsfreesplitres(part, count_part); + count_part = 0; + part = NULL; + + start = pos + 1; + line_start = start; + pos = start; } } - - return REDIS_OK; + + 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; } -/* - * Not used and not correct, maybe delete in the future. - * +/** + * Update route with the "cluster nodes" or "cluster slots" command reply. */ static int -cluster_update_route_with_slots(redisClusterContext *cc, +cluster_update_route_by_addr(redisClusterContext *cc, const char *ip, int port) { - redisContext *c; + redisContext *c = NULL; redisReply *reply = NULL; - redisReply *elem; - redisReply *elem_slots_begin, *elem_slots_end; - redisReply *elem_node_master; - redisReply *elem_ip, *elem_port; + dict *nodes = NULL; struct hiarray *slots = NULL; - cluster_slot *slot; - cluster_node *node; - const char *errstr = NULL; - int err = 0; - unsigned int i, idx; + 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) - { + if(cc == NULL){ return REDIS_ERR; } - - if(cc->timeout) - { - c = redisConnectWithTimeout(ip, port, *cc->timeout); + + if(ip == NULL || port <= 0){ + __redisClusterSetError(cc, + REDIS_ERR_OTHER,"Ip or port error!"); + goto error; } - else - { - c = redisConnect(ip, port); + + if(cc->timeout){ + c = redisConnectWithTimeout(ip, port, *cc->timeout); + }else{ + c = redisConnect(ip, port); } - - if (c == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "init redis context error(return NULL)!\0"; + + if (c == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OTHER, + "Init redis context error(return NULL)"); goto error; - } - else if(c->err) - { - err = c->err; - errstr = c->errstr; + }else if(c->err){ + __redisClusterSetError(cc,c->err,c->errstr); goto error; } - reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); + 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(reply == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply error(NULL)!\0"; + if(nodes == NULL){ goto error; } - if(reply->type != REDIS_REPLY_ARRAY || reply->elements <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(level 0 type is not array)!\0"; + 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; } - slots = hiarray_create(reply->elements, sizeof(cluster_slot)); - if(slots == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "array create error!\0"; + dit = dictGetIterator(nodes); + if(dit == NULL){ + __redisClusterSetError(cc,REDIS_ERR_OOM, + "Dict get iterator failed: out of memory"); goto error; } - - for(i = 0; i < reply->elements; i ++) - { - elem = reply->element[i]; - if(elem->type != REDIS_REPLY_ARRAY || elem->elements <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(level 1 type is not array)!\0"; + + 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; } - slot = hiarray_push(slots); - if(slot == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "slot push in array error!\0"; - goto error; + if(master->slots == NULL){ + continue; } - - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - err = REDIS_ERR_OTHER; - errstr = "alloc cluster node error!\0"; + + lit = listGetIterator(master->slots, AL_START_HEAD); + if(lit == NULL){ + __redisClusterSetError(cc, REDIS_ERR_OOM, + "List get iterator failed: out of memory"); goto error; } - cluster_node_init(node); - cluster_slot_ref_node(slot, node); - - for(idx = 0; idx < elem->elements; idx ++) - { - if(idx == 0) - { - elem_slots_begin = elem->element[idx]; - if(elem_slots_begin->type != REDIS_REPLY_INTEGER) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(slot begin is not integer)!\0"; - goto error; - } - slot->start = (int)(elem_slots_begin->integer); - } - else if(idx == 1) - { - elem_slots_end = elem->element[idx]; - if(elem_slots_end->type != REDIS_REPLY_INTEGER) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(slot end is not integer)!\0"; - goto error; - } - slot->end = (int)(elem_slots_end->integer); - - if(slot->start > slot->end) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(slot begin is bigger than slot end)!\0"; - 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; } - else if(idx == 2) - { - elem_node_master = elem->element[idx]; - if(elem_node_master->type != REDIS_REPLY_ARRAY || - elem_node_master->elements != 2) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(master line is not array)!\0"; - goto error; - } - - elem_ip = elem_node_master->element[0]; - elem_port = elem_node_master->element[1]; - - if(elem_ip->type != REDIS_REPLY_STRING || - elem_ip->len <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(master ip is not string)!\0"; - goto error; - } + + slot_elem = hiarray_push(slots); + *slot_elem = slot; + } - if(elem_port->type != REDIS_REPLY_INTEGER || - elem_port->integer <= 0) - { - err = REDIS_ERR_OTHER; - errstr = "command(cluster slots) reply" - " error(master port is not integer)!\0"; - goto error; - } + listReleaseIterator(lit); + } - node->host = sdsnewlen(elem_ip->str, elem_ip->len); - node->port = (int)(elem_port->integer); + dictReleaseIterator(dit); - node->addr = sdsnewlen(elem_ip->str, elem_ip->len); - sdscatlen(node->addr, ":", 1); - node->addr = sdscatfmt(node->addr, "%I", node->port); - } - else - { - continue; + 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; } } - 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); + 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) - { + if(c != NULL){ redisFree(c); } - + return REDIS_OK; error: - cc->err = err; - memcpy(cc->errstr, errstr, strlen(errstr)); + if(dit != NULL){ + dictReleaseIterator(dit); + } + + if(lit != NULL){ + listReleaseIterator(lit); + } if(slots != NULL) - { - while(hiarray_n(slots)) + { + if(slots == cc->slots) { - slot = hiarray_pop(slots); - cluster_slot_deinit(slot); + cc->slots = NULL; } + slots->nelem = 0; hiarray_destroy(slots); } - if(reply != NULL) - { + if(nodes != NULL){ + if(nodes == cc->nodes){ + cc->nodes = NULL; + } + + dictRelease(nodes); + } + + if(reply != NULL){ freeReplyObject(reply); reply = NULL; } - if (c != NULL) - { + if(c != NULL){ redisFree(c); } + return REDIS_ERR; } + /** * Update route with the "cluster nodes" command reply. */ static int -cluster_update_route_with_nodes(redisClusterContext *cc, +cluster_update_route_with_nodes_old(redisClusterContext *cc, const char *ip, int port) { int ret; @@ -985,7 +1635,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(cc->flags & HIRCLUSTER_FLAG_ADD_SLAVE) { - ret = cluster_master_slave_mapping(cc, + ret = cluster_master_slave_mapping_with_name(cc, &nodes_name, master, master->name); if(ret != REDIS_OK) { @@ -1060,20 +1710,16 @@ cluster_update_route_with_nodes(redisClusterContext *cc, goto error; } - *slot = hi_alloc(sizeof(**slot)); + *slot = cluster_slot_create(master); if(*slot == NULL) { __redisClusterSetError(cc,REDIS_ERR_OOM, "Out of memory"); goto error; } - - cluster_slot_init(*slot, NULL); (*slot)->start = (uint32_t)slot_start; - (*slot)->end = (uint32_t)slot_end; - cluster_slot_ref_node(*slot, master); - + (*slot)->end = (uint32_t)slot_end; } } @@ -1088,7 +1734,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, goto error; } - ret = cluster_master_slave_mapping(cc, + ret = cluster_master_slave_mapping_with_name(cc, &nodes_name, slave, part[3]); if(ret != REDIS_OK) { @@ -1121,12 +1767,7 @@ cluster_update_route_with_nodes(redisClusterContext *cc, if(cc->slots != NULL) { - while(hiarray_n(cc->slots)) - { - slot = hiarray_pop(cc->slots); - cluster_slot_deinit(*slot); - } - + cc->slots->nelem = 0; hiarray_destroy(cc->slots); cc->slots = NULL; } @@ -1182,13 +1823,8 @@ cluster_update_route_with_nodes(redisClusterContext *cc, { cc->slots = NULL; } - - while(hiarray_n(slots)) - { - slot = hiarray_pop(slots); - cluster_slot_deinit(*slot); - } - + + slots->nelem = 0; hiarray_destroy(slots); } @@ -1237,7 +1873,7 @@ cluster_update_route(redisClusterContext *cc) if(cc->ip != NULL && cc->port > 0) { - ret = cluster_update_route_with_nodes(cc, cc->ip, cc->port); + ret = cluster_update_route_by_addr(cc, cc->ip, cc->port); if(ret == REDIS_OK) { return REDIS_OK; @@ -1265,7 +1901,7 @@ cluster_update_route(redisClusterContext *cc) continue; } - ret = cluster_update_route_with_nodes(cc, node->host, node->port); + ret = cluster_update_route_by_addr(cc, node->host, node->port); if(ret == REDIS_OK) { if(cc->err) @@ -1366,18 +2002,14 @@ static redisClusterContext *redisClusterContextInit(void) { cc->need_update_route = 0; cc->update_route_time = 0LL; - cc->nodes = NULL; - cc->route_version = 0LL; - memset(cc->table, 0, REDIS_CLUSTER_SLOTS); + memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); return cc; } void redisClusterFree(redisClusterContext *cc) { - - cluster_slot **slot; if (cc == NULL) return; @@ -1393,16 +2025,11 @@ void redisClusterFree(redisClusterContext *cc) { free(cc->timeout); } - memset(cc->table, 0, REDIS_CLUSTER_SLOTS); + memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); if(cc->slots != NULL) { - while(hiarray_n(cc->slots)) - { - slot = hiarray_pop(cc->slots); - cluster_slot_deinit(*slot); - } - + cc->slots->nelem = 0; hiarray_destroy(cc->slots); cc->slots = NULL; } @@ -2259,7 +2886,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, __redisClusterSetError(cc, c->err, c->errstr); return NULL; } - + reply = redisCommand(c, REDIS_COMMAND_ASKING); if(reply == NULL) { @@ -3586,7 +4213,6 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv goto done; } - node->failure_count ++; if(node->failure_count > cc->max_redirect_count) { @@ -3642,7 +4268,6 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv 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; diff --git a/hircluster.h b/hircluster.h index 5a4453df..8765c0e3 100644 --- a/hircluster.h +++ b/hircluster.h @@ -17,11 +17,18 @@ #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. */ -#define HIRCLUSTER_FLAG_ADD_SLAVE 0x10000000 +#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 add open slot + * for master node. (100000000000000) */ +#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 struct dict; @@ -31,7 +38,6 @@ typedef struct cluster_node sds addr; sds host; int port; - int count; uint8_t role; redisContext *con; redisAsyncContext *acon; @@ -39,15 +45,25 @@ typedef struct cluster_node list *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; + 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 @@ -101,6 +117,8 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply); void redisCLusterReset(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############*/ From 244f9bf9611e73079e7d1f65314f7c1b4f69eb3f Mon Sep 17 00:00:00 2001 From: deep011 Date: Sat, 6 Feb 2016 12:25:54 +0800 Subject: [PATCH 019/273] open cluster_update_route function --- hircluster.c | 10 +++++++++- hircluster.h | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/hircluster.c b/hircluster.c index 46cd1d69..2295a02e 100644 --- a/hircluster.c +++ b/hircluster.c @@ -265,6 +265,7 @@ static int cluster_node_init(cluster_node *node) node->host = NULL; node->port = 0; node->role = REDIS_ROLE_NULL; + node->myself = 0; node->slaves = NULL; node->con = NULL; node->acon = NULL; @@ -291,6 +292,7 @@ static void cluster_node_deinit(cluster_node *node) sdsfree(node->host); node->port = 0; node->role = REDIS_ROLE_NULL; + node->myself = 0; if(node->con != NULL) { @@ -1057,6 +1059,8 @@ parse_cluster_nodes(redisClusterContext *cc, goto error; } } + + if(myself) master->myself = 1; for(k = 8; k < count_part; k ++){ slot_start_end = sdssplitlen(part[k], @@ -1193,6 +1197,8 @@ parse_cluster_nodes(redisClusterContext *cc, hi_free(slave); goto error; } + + if(myself) slave->myself = 1; } if(myself == 1){ @@ -1857,7 +1863,7 @@ cluster_update_route_with_nodes_old(redisClusterContext *cc, return REDIS_ERR; } -static int +int cluster_update_route(redisClusterContext *cc) { int ret; @@ -1965,6 +1971,8 @@ static void print_cluster_node_list(redisClusterContext *cc) slave->role, slave->slaves?"hava":"null"); } + listReleaseIterator(it); + printf("\n"); } } diff --git a/hircluster.h b/hircluster.h index 8765c0e3..349e0a89 100644 --- a/hircluster.h +++ b/hircluster.h @@ -39,6 +39,7 @@ typedef struct cluster_node sds host; int port; uint8_t role; + uint8_t myself; /* myself ? */ redisContext *con; redisAsyncContext *acon; list *slots; @@ -116,6 +117,7 @@ int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char 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); From edd87df00af56fc5cffaee1383f9ef7050cdc248 Mon Sep 17 00:00:00 2001 From: deep011 Date: Sat, 5 Mar 2016 15:16:33 +0800 Subject: [PATCH 020/273] Add redisClusterCommandArgv liked function --- README.md | 8 +- hircluster.c | 212 ++++++++++++++++++++++++++++++++++----------------- hircluster.h | 7 +- 3 files changed, 155 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 294946eb..bc69f55d 100644 --- a/README.md +++ b/README.md @@ -31,20 +31,26 @@ redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const str 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); +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); ``` diff --git a/hircluster.c b/hircluster.c index 2295a02e..8d0678b9 100644 --- a/hircluster.c +++ b/hircluster.c @@ -3391,11 +3391,9 @@ void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count) cc->max_redirect_count = max_redirect_count; } -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) { +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) { redisReply *reply = NULL; - char *cmd = NULL; int slot_num; - int len; struct cmd *command = NULL, *sub_command; list *commands = NULL; listNode *list_node; @@ -3410,17 +3408,7 @@ void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list { cc->err = 0; memset(cc->errstr, '\0', strlen(cc->errstr)); - } - - 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; - } + } command = command_get(); if(command == NULL) @@ -3484,6 +3472,7 @@ void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list done: + command->cmd = NULL; command_destroy(command); if(commands != NULL) @@ -3504,12 +3493,9 @@ void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list if(command != NULL) { + command->cmd = NULL; command_destroy(command); } - else if(cmd != NULL) - { - free(cmd); - } if(commands != NULL) { @@ -3526,8 +3512,34 @@ void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list 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; @@ -3538,15 +3550,29 @@ void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { return reply; } -int redisClustervAppendCommand(redisClusterContext *cc, - const char *format, va_list ap){ - +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; list *commands = NULL; - - char *cmd; listNode *list_node; listIter *list_iter = NULL; @@ -3560,15 +3586,6 @@ int redisClustervAppendCommand(redisClusterContext *cc, } cc->requests->free = listCommandFree; - } - - len = redisvFormatCommand(&cmd,format,ap); - if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); - goto error; } command = command_get(); @@ -3636,7 +3653,6 @@ int redisClustervAppendCommand(redisClusterContext *cc, if(command->cmd != NULL) { - free(command->cmd); command->cmd = NULL; } else @@ -3669,12 +3685,9 @@ int redisClustervAppendCommand(redisClusterContext *cc, if(command != NULL) { + command->cmd = NULL; command_destroy(command); } - else if(cmd != NULL) - { - free(cmd); - } if(commands != NULL) { @@ -3691,7 +3704,29 @@ int redisClustervAppendCommand(redisClusterContext *cc, 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, @@ -3713,18 +3748,22 @@ int redisClusterAppendCommand(redisClusterContext *cc, } int redisClusterAppendCommandArgv(redisClusterContext *cc, - int argc, const char **argv) { - - int j; + int argc, const char **argv, const size_t *argvlen) { + int ret; + char *cmd; + int len; - for (j=0; j < argc; j++) { - if(redisClusterAppendCommand(cc, argv[j]) != REDIS_OK) - { - return REDIS_ERR; - } + 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 REDIS_OK; + return ret; } static int redisCLusterSendAll(redisClusterContext *cc) @@ -3847,8 +3886,6 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { sub_command->reply = sub_reply; } - - *reply = command_post_fragment(cc, command, commands); if(*reply == NULL) { @@ -4397,13 +4434,11 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv } } -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) { +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) { redisClusterContext *cc; int status = REDIS_OK; - char *cmd = NULL; - int len; int slot_num; cluster_node *node; redisAsyncContext *ac; @@ -4430,16 +4465,6 @@ int redisClustervAsyncCommand(redisClusterAsyncContext *acc, memset(acc->errstr, '\0', strlen(acc->errstr)); } - len = redisvFormatCommand(&cmd,format,ap); - - if (len == -1) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); - goto error; - } else if (len == -2) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string"); - goto error; - } - command = command_get(); if(command == NULL) { @@ -4447,7 +4472,13 @@ int redisClustervAsyncCommand(redisClusterAsyncContext *acc, goto error; } - command->cmd = cmd; + 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(); @@ -4461,8 +4492,6 @@ int redisClustervAsyncCommand(redisClusterAsyncContext *acc, slot_num = command_format_by_slot(cc, command, commands); - //slot_num = slot_get_by_command(cc, cmd, len); - if(slot_num < 0) { __redisClusterAsyncSetError(acc, @@ -4540,10 +4569,6 @@ int redisClustervAsyncCommand(redisClusterAsyncContext *acc, { command_destroy(command); } - else if(cmd != NULL) - { - free(cmd); - } if(commands != NULL) { @@ -4553,6 +4578,34 @@ int redisClustervAsyncCommand(redisClusterAsyncContext *acc, 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; @@ -4565,6 +4618,25 @@ int redisClusterAsyncCommand(redisClusterAsyncContext *acc, 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; diff --git a/hircluster.h b/hircluster.h index 349e0a89..6b76cc59 100644 --- a/hircluster.h +++ b/hircluster.h @@ -106,14 +106,17 @@ 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); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); int redisClusterGetReply(redisClusterContext *cc, void **reply); void redisCLusterReset(redisClusterContext *cc); @@ -158,8 +161,10 @@ typedef struct 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); From 013e38c18e0d38034f9b1c135d776e5139c6e7c6 Mon Sep 17 00:00:00 2001 From: deep011 Date: Tue, 5 Apr 2016 09:48:58 +0800 Subject: [PATCH 021/273] remove include adlist.h from hircluster.h --- hircluster.c | 2 +- hircluster.h | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hircluster.c b/hircluster.c index 8d0678b9..0da49bc7 100644 --- a/hircluster.c +++ b/hircluster.c @@ -8,10 +8,10 @@ #include "hircluster.h" #include "hiutil.h" +#include "adlist.h" #include "hiarray.h" #include "command.h" #include "dict.c" -#include "async.h" #define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" #define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" diff --git a/hircluster.h b/hircluster.h index 6b76cc59..a99879d5 100644 --- a/hircluster.h +++ b/hircluster.h @@ -3,7 +3,6 @@ #define __HIRCLUSTER_H #include "hiredis.h" -#include "adlist.h" #include "async.h" #define HIREDIS_VIP_MAJOR 0 @@ -31,6 +30,7 @@ #define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 struct dict; +struct list; typedef struct cluster_node { @@ -42,8 +42,8 @@ typedef struct cluster_node uint8_t myself; /* myself ? */ redisContext *con; redisAsyncContext *acon; - list *slots; - list *slaves; + struct list *slots; + struct list *slaves; int failure_count; void *data; /* Not used by hiredis */ struct hiarray *migrating; /* copen_slot[] */ @@ -91,7 +91,7 @@ typedef struct redisClusterContext { int max_redirect_count; int retry_count; - list *requests; + struct list *requests; int need_update_route; int64_t update_route_time; @@ -130,7 +130,7 @@ struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int struct redisClusterAsyncContext; -typedef int (adapterAttachFn)(struct redisAsyncContext*, void*); +typedef int (adapterAttachFn)(redisAsyncContext*, void*); typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*); From 2a56e430fd49b0a3d642c7fb3557cf3e1c13980c Mon Sep 17 00:00:00 2001 From: deep011 Date: Tue, 19 Apr 2016 23:21:08 +0800 Subject: [PATCH 022/273] rename list to hilist --- adlist.c | 30 +++++++++++++++--------------- adlist.h | 30 +++++++++++++++--------------- command.h | 2 +- hircluster.c | 16 ++++++++-------- hircluster.h | 8 ++++---- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/adlist.c b/adlist.c index 1a3bff02..b490a6bd 100644 --- a/adlist.c +++ b/adlist.c @@ -38,9 +38,9 @@ * by the user before to call AlFreeList(). * * On error, NULL is returned. Otherwise the pointer to the new list. */ -list *listCreate(void) +hilist *listCreate(void) { - struct list *list; + struct hilist *list; if ((list = hi_alloc(sizeof(*list))) == NULL) return NULL; @@ -55,7 +55,7 @@ list *listCreate(void) /* Free the whole list. * * This function can't fail. */ -void listRelease(list *list) +void listRelease(hilist *list) { unsigned long len; listNode *current, *next; @@ -77,7 +77,7 @@ void listRelease(list *list) * 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. */ -list *listAddNodeHead(list *list, void *value) +hilist *listAddNodeHead(hilist *list, void *value) { listNode *node; @@ -103,7 +103,7 @@ list *listAddNodeHead(list *list, void *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. */ -list *listAddNodeTail(list *list, void *value) +hilist *listAddNodeTail(hilist *list, void *value) { listNode *node; @@ -123,7 +123,7 @@ list *listAddNodeTail(list *list, void *value) return list; } -list *listInsertNode(list *list, listNode *old_node, void *value, int after) { +hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) { listNode *node; if ((node = hi_alloc(sizeof(*node))) == NULL) @@ -156,7 +156,7 @@ list *listInsertNode(list *list, listNode *old_node, void *value, int after) { * It's up to the caller to free the private value of the node. * * This function can't fail. */ -void listDelNode(list *list, listNode *node) +void listDelNode(hilist *list, listNode *node) { if (node->prev) node->prev->next = node->next; @@ -175,7 +175,7 @@ void listDelNode(list *list, listNode *node) * call to listNext() will return the next element of the list. * * This function can't fail. */ -listIter *listGetIterator(list *list, int direction) +listIter *listGetIterator(hilist *list, int direction) { listIter *iter; @@ -194,12 +194,12 @@ void listReleaseIterator(listIter *iter) { } /* Create an iterator in the list private iterator structure */ -void listRewind(list *list, listIter *li) { +void listRewind(hilist *list, listIter *li) { li->next = list->head; li->direction = AL_START_HEAD; } -void listRewindTail(list *list, listIter *li) { +void listRewindTail(hilist *list, listIter *li) { li->next = list->tail; li->direction = AL_START_TAIL; } @@ -239,9 +239,9 @@ listNode *listNext(listIter *iter) * the original node is used as value of the copied node. * * The original list both on success or error is never modified. */ -list *listDup(list *orig) +hilist *listDup(hilist *orig) { - list *copy; + hilist *copy; listIter *iter; listNode *node; @@ -282,7 +282,7 @@ list *listDup(list *orig) * On success the first matching node pointer is returned * (search starts from head). If no matching node exists * NULL is returned. */ -listNode *listSearchKey(list *list, void *key) +listNode *listSearchKey(hilist *list, void *key) { listIter *iter; listNode *node; @@ -310,7 +310,7 @@ listNode *listSearchKey(list *list, void *key) * 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(list *list, long index) { +listNode *listIndex(hilist *list, long index) { listNode *n; if (index < 0) { @@ -325,7 +325,7 @@ listNode *listIndex(list *list, long index) { } /* Rotate the list removing the tail node and inserting it to the head. */ -void listRotate(list *list) { +void listRotate(hilist *list) { listNode *tail = list->tail; if (listLength(list) <= 1) return; diff --git a/adlist.h b/adlist.h index be322552..5b9a53ea 100644 --- a/adlist.h +++ b/adlist.h @@ -44,14 +44,14 @@ typedef struct listIter { int direction; } listIter; -typedef struct list { +typedef struct hilist { listNode *head; listNode *tail; void *(*dup)(void *ptr); void (*free)(void *ptr); int (*match)(void *ptr, void *key); unsigned long len; -} list; +} hilist; /* Functions implemented as macros */ #define listLength(l) ((l)->len) @@ -70,21 +70,21 @@ typedef struct list { #define listGetMatchMethod(l) ((l)->match) /* Prototypes */ -list *listCreate(void); -void listRelease(list *list); -list *listAddNodeHead(list *list, void *value); -list *listAddNodeTail(list *list, void *value); -list *listInsertNode(list *list, listNode *old_node, void *value, int after); -void listDelNode(list *list, listNode *node); -listIter *listGetIterator(list *list, int direction); +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); -list *listDup(list *orig); -listNode *listSearchKey(list *list, void *key); -listNode *listIndex(list *list, long index); -void listRewind(list *list, listIter *li); -void listRewindTail(list *list, listIter *li); -void listRotate(list *list); +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 diff --git a/command.h b/command.h index 9509630b..b7c388a6 100644 --- a/command.h +++ b/command.h @@ -168,7 +168,7 @@ struct cmd { redisReply *reply; - list *sub_commands; /* just for pipeline and multi-key commands */ + hilist *sub_commands; /* just for pipeline and multi-key commands */ }; void redis_parse_cmd(struct cmd *r); diff --git a/hircluster.c b/hircluster.c index 0da49bc7..bbe2f2be 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1940,7 +1940,7 @@ static void print_cluster_node_list(redisClusterContext *cc) listIter *it; listNode *ln; cluster_node *master, *slave; - list *slaves; + hilist *slaves; if(cc == NULL) { @@ -2926,7 +2926,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, } static int command_pre_fragment(redisClusterContext *cc, - struct cmd *command, list *commands) + struct cmd *command, hilist *commands) { struct keypos *kp, *sub_kp; @@ -3202,7 +3202,7 @@ static int command_pre_fragment(redisClusterContext *cc, } static void *command_post_fragment(redisClusterContext *cc, - struct cmd *command, list *commands) + struct cmd *command, hilist *commands) { struct cmd *sub_command; listNode *list_node; @@ -3331,7 +3331,7 @@ static void *command_post_fragment(redisClusterContext *cc, * Otherwise if the commands > 1 , slot_num is the last subcommand slot number. */ static int command_format_by_slot(redisClusterContext *cc, - struct cmd *command, list *commands) + struct cmd *command, hilist *commands) { struct keypos *kp; int key_count; @@ -3395,7 +3395,7 @@ void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) redisReply *reply = NULL; int slot_num; struct cmd *command = NULL, *sub_command; - list *commands = NULL; + hilist *commands = NULL; listNode *list_node; listIter *list_iter = NULL; @@ -3572,7 +3572,7 @@ int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len) { int slot_num; struct cmd *command = NULL, *sub_command; - list *commands = NULL; + hilist *commands = NULL; listNode *list_node; listIter *list_iter = NULL; @@ -3814,7 +3814,7 @@ static int redisCLusterSendAll(redisClusterContext *cc) int redisClusterGetReply(redisClusterContext *cc, void **reply) { struct cmd *command, *sub_command; - list *commands = NULL; + hilist *commands = NULL; listNode *list_command, *list_sub_command; listIter *list_iter; int slot_num; @@ -4443,7 +4443,7 @@ int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, cluster_node *node; redisAsyncContext *ac; struct cmd *command = NULL; - list *commands = NULL; + hilist *commands = NULL; cluster_async_data *cad; if(acc == NULL) diff --git a/hircluster.h b/hircluster.h index a99879d5..19aceaef 100644 --- a/hircluster.h +++ b/hircluster.h @@ -30,7 +30,7 @@ #define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 struct dict; -struct list; +struct hilist; typedef struct cluster_node { @@ -42,8 +42,8 @@ typedef struct cluster_node uint8_t myself; /* myself ? */ redisContext *con; redisAsyncContext *acon; - struct list *slots; - struct list *slaves; + struct hilist *slots; + struct hilist *slaves; int failure_count; void *data; /* Not used by hiredis */ struct hiarray *migrating; /* copen_slot[] */ @@ -91,7 +91,7 @@ typedef struct redisClusterContext { int max_redirect_count; int retry_count; - struct list *requests; + struct hilist *requests; int need_update_route; int64_t update_route_time; From 21217ddb64a4558dd460b3d6f2fe8e3a57e02c5e Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 16 Nov 2016 16:08:24 +0800 Subject: [PATCH 023/273] fix a coredump of all keys belong one slot for multi-key command --- hircluster.c | 1 - 1 file changed, 1 deletion(-) diff --git a/hircluster.c b/hircluster.c index bbe2f2be..8974c814 100644 --- a/hircluster.c +++ b/hircluster.c @@ -3187,7 +3187,6 @@ static int command_pre_fragment(redisClusterContext *cc, && listLength(commands) == 1) { listNode *list_node = listFirst(commands); - command_destroy(list_node->value); listDelNode(commands, list_node); if(command->frag_seq) { From 02dddd25cb9ff9e189770ff2c476364496601b69 Mon Sep 17 00:00:00 2001 From: deep011 Date: Fri, 25 Nov 2016 17:06:57 +0800 Subject: [PATCH 024/273] fix a bug about the pipeline hang when the cluster setted timeout option --- hircluster.c | 93 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 23 deletions(-) diff --git a/hircluster.c b/hircluster.c index 8974c814..0a5d1df5 100644 --- a/hircluster.c +++ b/hircluster.c @@ -2630,11 +2630,11 @@ static int __redisClusterAppendCommand(redisClusterContext *cc, /* Helper function for the redisClusterGetReply* family of functions. */ -int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) +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; @@ -2673,7 +2673,7 @@ int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void **reply) __redisClusterSetError(cc, c->err, c->errstr); return REDIS_ERR; } - + if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED) { cc->need_update_route = 1; @@ -3585,7 +3585,7 @@ int redisClusterAppendFormattedCommand(redisClusterContext *cc, } cc->requests->free = listCommandFree; - } + } command = command_get(); if(command == NULL) @@ -3791,7 +3791,7 @@ static int redisCLusterSendAll(redisClusterContext *cc) if(c == NULL) { continue; - } + } if (c->flags & REDIS_BLOCK) { /* Write until done */ @@ -3810,6 +3810,43 @@ static int redisCLusterSendAll(redisClusterContext *cc) return REDIS_OK; } +static int redisCLusterClearAll(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 = 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; @@ -3819,10 +3856,16 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { int slot_num; void *sub_reply; - if(cc == NULL || cc->requests == NULL || reply == NULL) - { + 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); @@ -3840,7 +3883,7 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { "command in the requests list is null"); goto error; } - + slot_num = command->slot_num; if(slot_num >= 0) { @@ -3911,22 +3954,26 @@ void redisCLusterReset(redisClusterContext *cc) return; } - redisCLusterSendAll(cc); - - do{ - status = redisClusterGetReply(cc, &reply); - if(status == REDIS_OK) - { - freeReplyObject(reply); - } - else - { - redisReaderFree(c->reader); - c->reader = redisReaderCreate(); - break; + if (cc->err) { + redisCLusterClearAll(cc); + } else { + redisCLusterSendAll(cc); + + do{ + status = redisClusterGetReply(cc, &reply); + if(status == REDIS_OK) + { + freeReplyObject(reply); + } + else + { + redisReaderFree(c->reader); + c->reader = redisReaderCreate(); + break; + } } + while(reply != NULL); } - while(reply != NULL); if(cc->requests) { From aeeb49852e68d0714e6923148ae68c02c1fcfdf1 Mon Sep 17 00:00:00 2001 From: deep011 Date: Tue, 6 Dec 2016 20:11:07 +0800 Subject: [PATCH 025/273] fix a coredump bug for the asynchronous api when the redis setted timeout option --- async.c | 5 +++ async.h | 1 + hircluster.c | 98 +++++++++++++++++++++++++++------------------------- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/async.c b/async.c index 28aa76f5..75a3575d 100644 --- a/async.c +++ b/async.c @@ -119,6 +119,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ac->err = 0; ac->errstr = NULL; ac->data = NULL; + ac->dataHandler = NULL; ac->ev.data = NULL; ac->ev.addRead = NULL; @@ -312,6 +313,10 @@ static void __redisAsyncFree(redisAsyncContext *ac) { } } + if (ac->dataHandler) { + ac->dataHandler(ac); + } + /* Cleanup self */ redisFree(c); } diff --git a/async.h b/async.h index 59cbf469..2ba7142b 100644 --- a/async.h +++ b/async.h @@ -68,6 +68,7 @@ typedef struct redisAsyncContext { /* Not used by hiredis */ void *data; + void (*dataHandler)(struct redisAsyncContext* ac); /* Event library data and hooks */ struct { diff --git a/hircluster.c b/hircluster.c index 0a5d1df5..d4a93d25 100644 --- a/hircluster.c +++ b/hircluster.c @@ -30,7 +30,6 @@ typedef struct cluster_async_data { redisClusterAsyncContext *acc; - struct cluster_node *node; struct cmd *command; redisClusterCallbackFn *callback; int retry_count; @@ -639,6 +638,10 @@ static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) 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; } } @@ -2332,7 +2335,7 @@ static cluster_node *node_get_by_slot(redisClusterContext *cc, uint32_t slot_num middle = start + (end - start)/2; } - ASSERT(middle >= 0 && middle < slot_count); + ASSERT(middle < slot_count); slot = hiarray_get(slots, middle); if((*slot)->start > slot_num) @@ -4061,7 +4064,6 @@ static cluster_async_data *cluster_async_data_get(void) } cad->acc = NULL; - cad->node = NULL; cad->command = NULL; cad->callback = NULL; cad->privdata = NULL; @@ -4086,6 +4088,16 @@ static void cluster_async_data_free(cluster_async_data *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) { @@ -4099,9 +4111,10 @@ redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, ac = node->acon; if(ac != NULL) { - if(ac->c.err == 0) - { + if (ac->c.err == 0) { return ac; + } else { + NOT_REACHED(); } } @@ -4132,9 +4145,11 @@ redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, { redisAsyncSetDisconnectCallback(ac, acc->onDisconnect); } - - node->acon = ac; + ac->data = node; + ac->dataHandler = unlinkAsyncContextAndNode; + node->acon = ac; + return ac; } @@ -4277,11 +4292,8 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv //If you have a better idea, please contact with me. Thank you. //My email: diguo58@gmail.com - node = cad->node; - if(node->acon != NULL) - { - node->acon = NULL; - } + node = (cluster_node *)(ac->data); + ASSERT(node != NULL); __redisClusterAsyncSetError(acc, ac->err, ac->errstr); @@ -4371,56 +4383,49 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv 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; } - - cad->node = node_get_by_table(cc, (uint32_t)command->slot_num); break; case CLUSTER_ERR_ASK: + node = node_get_by_ask_error_reply(cc, reply); + if(node == NULL) { - 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; - } + __redisClusterAsyncSetError(acc, + cc->err, cc->errstr); + goto done; + } - ret = redisAsyncCommand(ac_retry, - NULL,NULL,REDIS_COMMAND_ASKING); - if(ret != REDIS_OK) - { - goto error; - } + 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; + } - cad->node = node; - - break; + 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: @@ -4590,7 +4595,6 @@ int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, } cad->acc = acc; - cad->node = node; cad->command = command; cad->callback = fn; cad->privdata = privdata; From 9251664eeabf55e89800a167f3c6da81e2e30e52 Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 7 Dec 2016 11:31:52 +0800 Subject: [PATCH 026/273] fix a bug for flag HIRCLUSTER_FLAG_ROUTE_USE_SLOTS to connect to redis cluster --- hircluster.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hircluster.c b/hircluster.c index d4a93d25..90cd8625 100644 --- a/hircluster.c +++ b/hircluster.c @@ -853,7 +853,7 @@ parse_cluster_slots(redisClusterContext *cc, }else{ elem_nodes = elem_slots->element[idx]; if(elem_nodes->type != REDIS_REPLY_ARRAY || - elem_nodes->elements != 2){ + elem_nodes->elements != 3){ __redisClusterSetError(cc, REDIS_ERR_OTHER, "Command(cluster slots) reply error: " "nodes sub_reply is not an correct array."); From 59db97ca327ad2605fbac1de32b16395e71ccd5a Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 7 Dec 2016 14:47:27 +0800 Subject: [PATCH 027/273] fix a coredump bug for pipeline of the redisClusterReset function --- hircluster.c | 30 +++++++++++++++--------------- hircluster.h | 7 ++++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/hircluster.c b/hircluster.c index 90cd8625..edf9cb2f 100644 --- a/hircluster.c +++ b/hircluster.c @@ -3819,13 +3819,19 @@ static int redisCLusterClearAll(redisClusterContext *cc) dictEntry *de; struct cluster_node *node; redisContext *c = NULL; - int wdone = 0; - if(cc == NULL || cc->nodes == 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) { @@ -3946,9 +3952,8 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { return REDIS_ERR; } -void redisCLusterReset(redisClusterContext *cc) +void redisClusterReset(redisClusterContext *cc) { - redisContext *c = NULL; int status; void *reply; @@ -3962,20 +3967,15 @@ void redisCLusterReset(redisClusterContext *cc) } else { redisCLusterSendAll(cc); - do{ + do { status = redisClusterGetReply(cc, &reply); - if(status == REDIS_OK) - { + if (status == REDIS_OK) { freeReplyObject(reply); - } - else - { - redisReaderFree(c->reader); - c->reader = redisReaderCreate(); + } else { + redisCLusterClearAll(cc); break; } - } - while(reply != NULL); + } while(reply != NULL); } if(cc->requests) diff --git a/hircluster.h b/hircluster.h index 19aceaef..a0b9d814 100644 --- a/hircluster.h +++ b/hircluster.h @@ -25,8 +25,9 @@ /* The flag to decide whether add open slot * for master node. (10000000000000) */ #define HIRCLUSTER_FLAG_ADD_OPENSLOT 0x2000 -/* The flag to decide whether add open slot - * for master node. (100000000000000) */ +/* 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; @@ -118,7 +119,7 @@ int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_l 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); +void redisClusterReset(redisClusterContext *cc); int cluster_update_route(redisClusterContext *cc); int test_cluster_update_route(redisClusterContext *cc); From 83f437df070761d5d468949de3ca414d422739c3 Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 7 Dec 2016 14:52:24 +0800 Subject: [PATCH 028/273] update the readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bc69f55d..a0dfa3d6 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_l 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); +void redisClusterReset(redisClusterContext *cc); redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); @@ -145,9 +145,9 @@ subsequent replies. The return value for this function is either `REDIS_OK` or ` 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); +void redisClusterReset(redisClusterContext *cc); ``` -Warning: You must call `redisCLusterReset` function after one pipelining anyway. +Warning: You must call `redisClusterReset` function after one pipelining anyway. The following examples shows a simple cluster pipeline: ```c @@ -158,7 +158,7 @@ redisClusterGetReply(clusterContext,&reply); // reply for SET freeReplyObject(reply); redisClusterGetReply(clusterContext,&reply); // reply for GET freeReplyObject(reply); -redisCLusterReset(clusterContext); +redisClusterReset(clusterContext); ``` This API can also be used to implement a blocking subscriber: ```c @@ -168,7 +168,7 @@ while(redisClusterGetReply(clusterContext,&reply) == REDIS_OK) { // consume message freeReplyObject(reply); } -redisCLusterReset(clusterContext); +redisClusterReset(clusterContext); ``` ## Cluster asynchronous API From 1bb09b908596b93d6903ddce0e9d8c2558c9d054 Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 7 Dec 2016 15:40:51 +0800 Subject: [PATCH 029/273] update the readme --- README.md | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/README.md b/README.md index a0dfa3d6..89741939 100644 --- a/README.md +++ b/README.md @@ -160,16 +160,6 @@ redisClusterGetReply(clusterContext,&reply); // reply for GET freeReplyObject(reply); redisClusterReset(clusterContext); ``` -This API can also be used to implement a blocking subscriber: -```c -reply = redisClusterCommand(clusterContext,"SUBSCRIBE foo"); -freeReplyObject(reply); -while(redisClusterGetReply(clusterContext,&reply) == REDIS_OK) { - // consume message - freeReplyObject(reply); -} -redisClusterReset(clusterContext); -``` ## Cluster asynchronous API @@ -257,22 +247,6 @@ callbacks have been executed. After this, the disconnection callback is executed 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*. -## Package - -If you only want get library, you can "yum install hiredis-vip" or "apt-get install hiredis-vip" instead of building from source code. - -If you used hiredis-vip in your project, you can install "hiredis-vip-devel" package for development. - -Before install the package, execute the follow command first: - -**deb package** : `curl -s https://packagecloud.io/install/repositories/deep/packages/script.deb.sh | sudo bash` - -**rpm package** : `curl -s https://packagecloud.io/install/repositories/deep/packages/script.rpm.sh | sudo bash` - -You can also download the packages from "https://packagecloud.io/deep/packages" and install by yourself. - -If you want to support other OS packages, please contact with me. - ## AUTHORS Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). From 9cfa282bc6c8dca72f0d8cc87d3255631e8e674d Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 7 Dec 2016 15:42:28 +0800 Subject: [PATCH 030/273] change version to 0.3.0 --- hircluster.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hircluster.h b/hircluster.h index a0b9d814..5b9c5a35 100644 --- a/hircluster.h +++ b/hircluster.h @@ -6,8 +6,8 @@ #include "async.h" #define HIREDIS_VIP_MAJOR 0 -#define HIREDIS_VIP_MINOR 2 -#define HIREDIS_VIP_PATCH 2 +#define HIREDIS_VIP_MINOR 3 +#define HIREDIS_VIP_PATCH 0 #define REDIS_CLUSTER_SLOTS 16384 From bfff2abc0379ffff4d4c04c1cd5b358cb2b695fa Mon Sep 17 00:00:00 2001 From: deep011 Date: Wed, 7 Dec 2016 15:43:55 +0800 Subject: [PATCH 031/273] update the changelog --- CHANGELOG.md | 98 +++++++--------------------------------------------- 1 file changed, 12 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29d7cf33..db304b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,90 +1,16 @@ -### 0.13.1 - May 03, 2015 +### 0.3.0 - Dec 07, 2016 -This is a bug fix release. -The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. -Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. -Other non-C99 code can now use hiredis as usual again. -Sorry for the inconvenience. +* Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011) +* Add flags HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS. (deep011) +* Support redisClusterCommandArgv related api. (deep011) +* Fix some serious bugs. (deep011) -* Fix memory leak in async reply handling (Salvatore Sanfilippo) -* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) +### 0.2.1 - Nov 24, 2015 -### 0.13.0 - April 16, 2015 - -This release adds a minimal Windows compatibility layer. -The parser, standalone since v0.12.0, can now be compiled on Windows -(and thus used in other client libraries as well) - -* Windows compatibility layer for parser code (tzickel) -* Properly escape data printed to PKGCONF file (Dan Skorupski) -* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) -* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) - -### 0.12.1 - January 26, 2015 - -* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location -* Fix `make test` as 32 bit build on 64 bit platform - -### 0.12.0 - January 22, 2015 - -* Add optional KeepAlive support - -* Try again on EINTR errors - -* Add libuv adapter - -* Add IPv6 support - -* Remove possiblity of multiple close on same fd - -* Add ability to bind source address on connect - -* Add redisConnectFd() and redisFreeKeepFd() - -* Fix getaddrinfo() memory leak - -* Free string if it is unused (fixes memory leak) - -* Improve redisAppendCommandArgv performance 2.5x - -* Add support for SO_REUSEADDR - -* Fix redisvFormatCommand format parsing - -* Add GLib 2.0 adapter - -* Refactor reading code into read.c - -* Fix errno error buffers to not clobber errors - -* Generate pkgconf during build - -* Silence _BSD_SOURCE warnings - -* Improve digit counting for multibulk creation - - -### 0.11.0 - -* Increase the maximum multi-bulk reply depth to 7. - -* Increase the read buffer size from 2k to 16k. - -* Use poll(2) instead of select(2) to support large fds (>= 1024). - -### 0.10.1 - -* Makefile overhaul. Important to check out if you override one or more - variables using environment variables or via arguments to the "make" tool. - -* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements - being created by the default reply object functions. - -* Issue #43: Don't crash in an asynchronous context when Redis returns an error - reply after the connection has been made (this happens when the maximum - number of connections is reached). - -### 0.10.0 - -* See commit log. +This release support redis cluster api. +* Add hiredis 0.3.1. (deep011) +* Support cluster synchronous API. (deep011) +* Support multi-key command(mget/mset/del) for redis cluster. (deep011) +* Support cluster pipelining. (deep011) +* Support cluster asynchronous API. (deep011) From 7a30cd0b1a130067b88fcf0ecf158c01bf87b7be Mon Sep 17 00:00:00 2001 From: fortrue Date: Fri, 23 Dec 2016 18:55:49 +0800 Subject: [PATCH 032/273] Update libevent.h add return for redisLibeventAttach_link --- adapters/libevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 6bc911c7..1621cc15 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -114,7 +114,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) { - redisLibeventAttach(ac, (struct event_base *)base); + return redisLibeventAttach(ac, (struct event_base *)base); } static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { From 7c2a5060fefe84b531ca1e5dd3bf545dbdd157c5 Mon Sep 17 00:00:00 2001 From: deep011 Date: Thu, 4 May 2017 17:45:23 +0800 Subject: [PATCH 033/273] update hiredis_vip.pc file --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 58494bfc..a321bd1a 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o ad EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib TESTS=hiredis-test LIBNAME=libhiredis_vip -PKGCONFNAME=hiredis.pc +PKGCONFNAME=hiredis_vip.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}') @@ -161,10 +161,10 @@ $(PKGCONFNAME): hiredis.h @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ @echo >> $@ - @echo Name: hiredis >> $@ - @echo Description: Minimalistic C client library for Redis. >> $@ + @echo Name: hiredis-vip >> $@ + @echo Description: Minimalistic C client library for Redis and Redis Cluster. >> $@ @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ - @echo Libs: -L\$${libdir} -lhiredis >> $@ + @echo Libs: -L\$${libdir} -lhiredis_vip >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) From f12060498004494a3e1de11f653a8624f3d218c3 Mon Sep 17 00:00:00 2001 From: deep011 Date: Tue, 23 May 2017 16:41:38 +0800 Subject: [PATCH 034/273] change the cluster synchronous connect api --- README.md | 67 +++++++++- hircluster.c | 371 ++++++++++++++++++++++++++++++++++++++++----------- hircluster.h | 24 +++- hiredis.c | 16 +++ read.h | 4 + 5 files changed, 396 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 89741939..120a7f13 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,52 @@ Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hire ### CLUSTER API: +```c +redisClusterContext *redisClusterContextInit(void); +void redisClusterFree(redisClusterContext *cc); + +int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr); +int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); +int redisClusterSetOptionConnectBlock(redisClusterContext *cc); +int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc); +int redisClusterSetOptionParseSlaves(redisClusterContext *cc); +int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc); +int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); + +int redisClusterConnect2(redisClusterContext *cc); + +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); +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); + +redisContext *ctx_get_by_node(redisClusterContext *cc, struct cluster_node *node); + +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); +``` + +### CLUSTER API (old api, version <= 0.3.0): + ```c redisClusterContext *redisClusterConnect(const char *addrs, int flags); redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags); @@ -65,23 +111,31 @@ https://github.com/vipshop/hiredis-vip/wiki 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); +redisClusterContext *redisClusterContextInit(void); +int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterConnect2(redisClusterContext *cc); 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` +The function `redisClusterContextInit` is used to create a so-called `redisClusterContext`. +The function `redisClusterSetOptionAddNodes` is used to add the redis cluster address. +The function `redisClusterConnect2` is used to connect to the redis cluser. +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); +redisClusterContext *cc = redisClusterContextInit(); +redisClusterSetOptionAddNodes(cc, "127.0.0.1:6379,127.0.0.1:6380"); +redisClusterConnect2(cc); if (cc != NULL && cc->err) { printf("Error: %s\n", cc->errstr); // handle error @@ -250,6 +304,9 @@ 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/hircluster.c b/hircluster.c index edf9cb2f..b0519364 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1279,8 +1279,8 @@ cluster_update_route_by_addr(redisClusterContext *cc, goto error; } - if(cc->timeout){ - c = redisConnectWithTimeout(ip, port, *cc->timeout); + if(cc->connect_timeout){ + c = redisConnectWithTimeout(ip, port, *cc->connect_timeout); }else{ c = redisConnect(ip, port); } @@ -1294,11 +1294,20 @@ cluster_update_route_by_addr(redisClusterContext *cc, goto error; } + if (cc->timeout) { + redisSetTimeout(c, *cc->timeout); + } + 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)."); + if (c->err == REDIS_ERR_TIMEOUT) { + __redisClusterSetError(cc,c->err, + "Command(cluster slots) reply error(socket timeout)"); + } else { + __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){ @@ -1313,11 +1322,16 @@ cluster_update_route_by_addr(redisClusterContext *cc, } nodes = parse_cluster_slots(cc, reply, cc->flags); - }else{ + } else { reply = redisCommand(c, REDIS_COMMAND_CLUSTER_NODES); if(reply == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Command(cluster nodes) reply error(NULL)."); + if (c->err == REDIS_ERR_TIMEOUT) { + __redisClusterSetError(cc,c->err, + "Command(cluster nodes) reply error(socket timeout)"); + } else { + __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){ @@ -1512,9 +1526,9 @@ cluster_update_route_with_nodes_old(redisClusterContext *cc, goto error; } - if(cc->timeout) + if(cc->connect_timeout) { - c = redisConnectWithTimeout(ip, port, *cc->timeout); + c = redisConnectWithTimeout(ip, port, *cc->connect_timeout); } else { @@ -1992,7 +2006,7 @@ int test_cluster_update_route(redisClusterContext *cc) return ret; } -static redisClusterContext *redisClusterContextInit(void) { +redisClusterContext *redisClusterContextInit(void) { redisClusterContext *cc; cc = calloc(1,sizeof(redisClusterContext)); @@ -2004,6 +2018,7 @@ static redisClusterContext *redisClusterContextInit(void) { cc->ip = NULL; cc->port = 0; cc->flags = 0; + cc->connect_timeout = NULL; cc->timeout = NULL; cc->nodes = NULL; cc->slots = NULL; @@ -2016,6 +2031,8 @@ static redisClusterContext *redisClusterContextInit(void) { cc->route_version = 0LL; memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + + cc->flags |= REDIS_BLOCK; return cc; } @@ -2031,6 +2048,11 @@ void redisClusterFree(redisClusterContext *cc) { cc->ip = NULL; } + if (cc->connect_timeout) + { + free(cc->connect_timeout); + } + if (cc->timeout) { free(cc->timeout); @@ -2058,7 +2080,108 @@ void redisClusterFree(redisClusterContext *cc) { free(cc); } -static int redisClusterAddNode(redisClusterContext *cc, const char *addr) +/* 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 int _redisClusterConnect2(redisClusterContext *cc) +{ + + if (cc->nodes == NULL || dictSize(cc->nodes) == 0) + { + __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address does not set up"); + return REDIS_ERR; + } + + return cluster_update_route(cc); +} + +/* 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; + + ret = redisClusterSetOptionAddNodes(cc, addrs); + if (ret != REDIS_OK) + { + return cc; + } + + 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->connect_timeout == NULL) + { + cc->connect_timeout = malloc(sizeof(struct timeval)); + } + + memcpy(cc->connect_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); +} + +int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) { dictEntry *node_entry; cluster_node *node; @@ -2066,6 +2189,7 @@ static int redisClusterAddNode(redisClusterContext *cc, const char *addr) int ip_port_count = 0; sds ip; int port; + sds addr_sds = NULL; if(cc == NULL) { @@ -2081,7 +2205,9 @@ static int redisClusterAddNode(redisClusterContext *cc, const char *addr) } } - node_entry = dictFind(cc->nodes, addr); + addr_sds = sdsnew(addr); + node_entry = dictFind(cc->nodes, addr_sds); + sdsfree(addr_sds); if(node_entry == NULL) { ip_port = sdssplitlen(addr, strlen(addr), @@ -2139,12 +2265,8 @@ static int redisClusterAddNode(redisClusterContext *cc, const char *addr) 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 redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs) +{ int ret; sds *address = NULL; int address_count = 0; @@ -2152,105 +2274,199 @@ static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, const if(cc == NULL) { - return NULL; + return REDIS_ERR; } - 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; + return REDIS_ERR; } for(i = 0; i < address_count; i ++) { - ret = redisClusterAddNode(cc, address[i]); + ret = redisClusterSetOptionAddNode(cc, address[i]); if(ret != REDIS_OK) { sdsfreesplitres(address, address_count); - return cc; + return REDIS_ERR; } } sdsfreesplitres(address, address_count); - - cluster_update_route(cc); - return cc; + return REDIS_OK; } -redisClusterContext *redisClusterConnect(const char *addrs, int flags) +int redisClusterSetOptionConnectBlock(redisClusterContext *cc) { - redisClusterContext *cc; - - cc = redisClusterContextInit(); if(cc == NULL) { - return NULL; + return REDIS_ERR; } cc->flags |= REDIS_BLOCK; - if(flags) + + return REDIS_OK; +} + +int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc) +{ + + if(cc == NULL) { - cc->flags |= flags; + return REDIS_ERR; } - - return _redisClusterConnect(cc, addrs); + + cc->flags &= ~REDIS_BLOCK; + + return REDIS_OK; } -redisClusterContext *redisClusterConnectWithTimeout( - const char *addrs, const struct timeval tv, int flags) +int redisClusterSetOptionParseSlaves(redisClusterContext *cc) { - redisClusterContext *cc; - cc = redisClusterContextInit(); + if(cc == NULL) + { + return REDIS_ERR; + } + + cc->flags |= HIRCLUSTER_FLAG_ADD_SLAVE; + + return REDIS_OK; +} + +int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc) +{ if(cc == NULL) { - return NULL; + return REDIS_ERR; } - cc->flags |= REDIS_BLOCK; - if(flags) + cc->flags |= HIRCLUSTER_FLAG_ADD_OPENSLOT; + + return REDIS_OK; +} + +int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc) +{ + + if(cc == NULL) { - cc->flags |= flags; + return REDIS_ERR; + } + + cc->flags |= HIRCLUSTER_FLAG_ROUTE_USE_SLOTS; + + return REDIS_OK; +} + +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv) +{ + + if(cc == NULL) + { + return REDIS_ERR; + } + + if (cc->connect_timeout == NULL) + { + cc->connect_timeout = malloc(sizeof(struct timeval)); } + memcpy(cc->connect_timeout, &tv, sizeof(struct timeval)); + + return REDIS_OK; +} + +int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv) +{ + + if(cc == NULL) + { + return REDIS_ERR; + } + if (cc->timeout == NULL) { cc->timeout = malloc(sizeof(struct timeval)); + memcpy(cc->timeout, &tv, sizeof(struct timeval)); } - - memcpy(cc->timeout, &tv, sizeof(struct timeval)); - - return _redisClusterConnect(cc, addrs); -} + else if (cc->timeout->tv_sec != tv.tv_sec || cc->timeout->tv_usec != tv.tv_usec) + { + memcpy(cc->timeout, &tv, sizeof(struct timeval)); -redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { + if (cc->nodes && dictSize(cc->nodes) > 0) + { + dictEntry *de; + dictIterator *di; + cluster_node *node; - redisClusterContext *cc; + di = dictGetIterator(cc->nodes); - cc = redisClusterContextInit(); + while (de=dictNext(di)) + { + node = dictGetEntryVal(de); + if (node->con && node->con->flags&REDIS_CONNECTED && node->con->err == 0) + { + redisSetTimeout(node->con, tv); + } - if(cc == NULL) + if (node->slaves && listLength(node->slaves) > 0) + { + cluster_node *slave; + listIter *li; + listNode *ln; + + li = listGetIterator(node->slaves, AL_START_HEAD); + while (ln = listNext(li)) + { + slave = listNodeValue(ln); + if (slave->con && slave->con->flags&REDIS_CONNECTED && slave->con->err == 0) + { + redisSetTimeout(slave->con, tv); + } + } + + listReleaseIterator(li); + } + } + + dictReleaseIterator(di); + } + } + + return REDIS_OK; +} + +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count) +{ + if(cc == NULL || max_redirect_count <= 0) { - return NULL; + return REDIS_ERR; } - cc->flags &= ~REDIS_BLOCK; - if(flags) + cc->max_redirect_count = max_redirect_count; + + return REDIS_OK; +} + +int redisClusterConnect2(redisClusterContext *cc) +{ + + if(cc == NULL) { - cc->flags |= flags; + return REDIS_ERR; } - return _redisClusterConnect(cc, addrs); + return _redisClusterConnect2(cc); } -redisContext *ctx_get_by_node(cluster_node *node, - const struct timeval *timeout, int flags) +redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) { redisContext *c = NULL; if(node == NULL) @@ -2264,6 +2480,10 @@ redisContext *ctx_get_by_node(cluster_node *node, if(c->err) { redisReconnect(c); + + if (cc->timeout && c->err == 0) { + redisSetTimeout(c, *cc->timeout); + } } return c; @@ -2274,20 +2494,17 @@ redisContext *ctx_get_by_node(cluster_node *node, return NULL; } - if(flags & REDIS_BLOCK) + if(cc->connect_timeout) { - if(timeout) - { - c = redisConnectWithTimeout(node->host, node->port, *timeout); - } - else - { - c = redisConnect(node->host, node->port); - } + c = redisConnectWithTimeout(node->host, node->port, *cc->connect_timeout); } else { - c = redisConnectNonBlock(node->host, node->port); + c = redisConnect(node->host, node->port); + } + + if (cc->timeout && c != NULL && c->err == 0) { + redisSetTimeout(c, *cc->timeout); } node->con = c; @@ -2402,7 +2619,7 @@ static cluster_node *node_get_witch_connected(redisClusterContext *cc) continue; } - c = ctx_get_by_node(node, cc->timeout, REDIS_BLOCK); + c = ctx_get_by_node(cc, node); if(c == NULL || c->err) { continue; @@ -2518,7 +2735,7 @@ static char * cluster_config_get(redisClusterContext *cc, goto error; } - c = ctx_get_by_node(node, cc->timeout, cc->flags); + c = ctx_get_by_node(cc, node); reply = redisCommand(c, "config get %s", config_name); if(reply == NULL) @@ -2610,7 +2827,7 @@ static int __redisClusterAppendCommand(redisClusterContext *cc, return REDIS_ERR; } - c = ctx_get_by_node(node, cc->timeout, cc->flags); + c = ctx_get_by_node(cc, node); if(c == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); @@ -2650,7 +2867,7 @@ static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void ** return REDIS_ERR; } - c = ctx_get_by_node(node, cc->timeout, cc->flags); + c = ctx_get_by_node(cc, node); if(c == NULL) { __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); @@ -2796,7 +3013,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, return NULL; } - c = ctx_get_by_node(node, cc->timeout, cc->flags); + c = ctx_get_by_node(cc, node); if(c == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); @@ -2819,7 +3036,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, return NULL; } - c = ctx_get_by_node(node, cc->timeout, cc->flags); + c = ctx_get_by_node(cc, node); if(c == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); @@ -2886,7 +3103,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, freeReplyObject(reply); reply = NULL; - c = ctx_get_by_node(node, cc->timeout, cc->flags); + c = ctx_get_by_node(cc, node); if(c == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); @@ -3790,7 +4007,7 @@ static int redisCLusterSendAll(redisClusterContext *cc) continue; } - c = ctx_get_by_node(node, cc->timeout, cc->flags); + c = ctx_get_by_node(cc, node); if(c == NULL) { continue; diff --git a/hircluster.h b/hircluster.h index 5b9c5a35..95585c99 100644 --- a/hircluster.h +++ b/hircluster.h @@ -5,8 +5,8 @@ #include "hiredis.h" #include "async.h" -#define HIREDIS_VIP_MAJOR 0 -#define HIREDIS_VIP_MINOR 3 +#define HIREDIS_VIP_MAJOR 1 +#define HIREDIS_VIP_MINOR 0 #define HIREDIS_VIP_PATCH 0 #define REDIS_CLUSTER_SLOTS 16384 @@ -80,7 +80,9 @@ typedef struct redisClusterContext { int flags; enum redisConnectionType connection_type; - struct timeval *timeout; + struct timeval *connect_timeout; + + struct timeval *timeout; /* receive and send timeout. */ struct hiarray *slots; @@ -103,8 +105,22 @@ redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags); redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); +redisClusterContext *redisClusterContextInit(void); void redisClusterFree(redisClusterContext *cc); +int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr); +int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); +int redisClusterSetOptionConnectBlock(redisClusterContext *cc); +int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc); +int redisClusterSetOptionParseSlaves(redisClusterContext *cc); +int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc); +int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); + +int redisClusterConnect2(redisClusterContext *cc); + void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); @@ -112,7 +128,7 @@ void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list 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); +redisContext *ctx_get_by_node(redisClusterContext *cc, struct cluster_node *node); int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); diff --git a/hiredis.c b/hiredis.c index 73d0251b..1c38877e 100644 --- a/hiredis.c +++ b/hiredis.c @@ -804,7 +804,15 @@ int redisBufferRead(redisContext *c) { if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ } else { +#if 1 //shenzheng 2017-5-22 redis cluster + if (errno == EWOULDBLOCK && (c->flags & REDIS_BLOCK)) { + __redisSetError(c,REDIS_ERR_TIMEOUT,"Socket timeout"); + } else { +#endif //shenzheng 2017-5-22 redis cluster __redisSetError(c,REDIS_ERR_IO,NULL); +#if 1 //shenzheng 2017-5-22 redis cluster + } +#endif //shenzheng 2017-5-22 redis cluster return REDIS_ERR; } } else if (nread == 0) { @@ -841,7 +849,15 @@ int redisBufferWrite(redisContext *c, int *done) { if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { /* Try again later */ } else { +#if 1 //shenzheng 2017-5-22 redis cluster + if (errno == EWOULDBLOCK && (c->flags & REDIS_BLOCK)) { + __redisSetError(c,REDIS_ERR_TIMEOUT,"Socket timeout"); + } else { +#endif //shenzheng 2017-5-22 redis cluster __redisSetError(c,REDIS_ERR_IO,NULL); +#if 1 //shenzheng 2017-5-22 redis cluster + } +#endif //shenzheng 2017-5-22 redis cluster return REDIS_ERR; } } else if (nwritten > 0) { diff --git a/read.h b/read.h index 088c9790..d578c131 100644 --- a/read.h +++ b/read.h @@ -49,6 +49,10 @@ #if 1 //shenzheng 2015-8-10 redis cluster #define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6 #endif //shenzheng 2015-8-10 redis cluster +#if 1 //shenzheng 2017-5-22 redis cluster +#define REDIS_ERR_TIMEOUT 7 +#endif //shenzheng 2017-5-22 redis cluster + #define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 From 16fd0e3cd035457ad00c398b819330691da8c6da Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 19 Jan 2020 17:34:28 -0800 Subject: [PATCH 035/273] * Updating underlying hiredis version to 0.14.0 * Allow for compilation under windows * Made motions towards allowing for hiredis-vip to be a pure wrapper library around hiredis instead of a fork --- .gitignore | 1 + .travis.yml | 97 +++++- CHANGELOG.md | 207 ++++++++++++- CMakeLists.txt | 106 +++++++ Makefile | 190 ++++++++---- README.md | 524 ++++++++++++++++++++----------- adapters/glib.h | 20 +- adapters/ivykis.h | 81 +++++ adapters/libevent.h | 139 ++++++--- adapters/libuv.h | 11 +- adapters/macosx.h | 114 +++++++ adapters/qt.h | 135 ++++++++ appveyor.yml | 24 ++ async.c | 244 ++++++++++----- async.h | 15 +- async_private.h | 72 +++++ command.c | 2 +- dict.c | 6 +- examples/CMakeLists.txt | 46 +++ examples/example-ivykis.c | 58 ++++ examples/example-libevent-ssl.c | 73 +++++ examples/example-libevent.c | 15 +- examples/example-macosx.c | 66 ++++ examples/example-qt.cpp | 46 +++ examples/example-qt.h | 32 ++ examples/example-ssl.c | 97 ++++++ examples/example.c | 19 +- fmacros.h | 19 +- hircluster.c | 21 +- hiredis.c | 373 ++++++++++++---------- hiredis.h | 140 +++++++-- hiredis.pc.in | 11 + hiredis_ssl.h | 53 ++++ hiredis_ssl.pc.in | 12 + hiutil.c | 26 +- hiutil.h | 10 + net.c | 227 ++++++++++---- net.h | 7 +- read.c | 244 ++++++++++++--- read.h | 43 +-- sds.c | 532 ++++++++++++++++++++++---------- sds.h | 203 +++++++++++- sdsalloc.h | 42 +++ sockcompat.c | 248 +++++++++++++++ sockcompat.h | 91 ++++++ ssl.c | 448 +++++++++++++++++++++++++++ test.c | 285 ++++++++++++++--- test.sh | 70 +++++ win32.h | 22 +- 49 files changed, 4563 insertions(+), 1004 deletions(-) create mode 100644 CMakeLists.txt create mode 100644 adapters/ivykis.h create mode 100644 adapters/macosx.h create mode 100644 adapters/qt.h create mode 100644 appveyor.yml create mode 100644 async_private.h create mode 100644 examples/CMakeLists.txt create mode 100644 examples/example-ivykis.c create mode 100644 examples/example-libevent-ssl.c create mode 100644 examples/example-macosx.c create mode 100644 examples/example-qt.cpp create mode 100644 examples/example-qt.h create mode 100644 examples/example-ssl.c create mode 100644 hiredis.pc.in create mode 100644 hiredis_ssl.h create mode 100644 hiredis_ssl.pc.in create mode 100644 sdsalloc.h create mode 100644 sockcompat.c create mode 100644 sockcompat.h create mode 100644 ssl.c create mode 100644 test.sh diff --git a/.gitignore b/.gitignore index c44b5c53..8e50b543 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /*.dylib /*.a /*.pc +*.dSYM diff --git a/.travis.yml b/.travis.yml index 1df63b0b..dd8e0e73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,97 @@ language: c +sudo: false compiler: - gcc - clang +os: + - linux + - osx + +branches: + only: + - staging + - trying + - master + +before_script: + - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi + +addons: + apt: + packages: + - libc6-dbg + - libc6-dev + - libc6:i386 + - libc6-dev-i386 + - libc6-dbg:i386 + - gcc-multilib + - g++-multilib + - valgrind + env: - - CFLAGS="-Werror" - - PRE="valgrind --track-origins=yes --leak-check=full" - - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror" - - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" + - BITS="32" + - BITS="64" + +script: + - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON"; + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + if [ "$BITS" == "32" ]; then + CFLAGS="-m32 -Werror"; + CXXFLAGS="-m32 -Werror"; + LDFLAGS="-m32"; + EXTRA_CMAKE_OPTS=; + else + CFLAGS="-Werror"; + CXXFLAGS="-Werror"; + fi; + else + TEST_PREFIX="valgrind --track-origins=yes --leak-check=full"; + if [ "$BITS" == "32" ]; then + CFLAGS="-m32 -Werror"; + CXXFLAGS="-m32 -Werror"; + LDFLAGS="-m32"; + EXTRA_CMAKE_OPTS=; + else + CFLAGS="-Werror"; + CXXFLAGS="-Werror"; + fi; + fi; + export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS + - mkdir build/ && cd build/ + - cmake .. ${EXTRA_CMAKE_OPTS} + - make VERBOSE=1 + - ctest -V -install: - - sudo apt-get update -qq - - sudo apt-get install libc6-dbg libc6-dev libc6-i686:i386 libc6-dev-i386 libc6-dbg:i386 valgrind -y +matrix: + include: + # Windows MinGW cross compile on Linux + - os: linux + dist: xenial + compiler: mingw + addons: + apt: + packages: + - ninja-build + - gcc-mingw-w64-x86-64 + - g++-mingw-w64-x86-64 + script: + - mkdir build && cd build + - CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on + - ninja -v -script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example + # Windows MSVC 2017 + - os: windows + compiler: msvc + env: + - MATRIX_EVAL="CC=cl.exe && CXX=cl.exe" + before_install: + - eval "${MATRIX_EVAL}" + install: + - choco install ninja + script: + - mkdir build && cd build + - cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 && + cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release && + ninja -v' + - ctest -V diff --git a/CHANGELOG.md b/CHANGELOG.md index db304b6a..d1d37e51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,199 @@ -### 0.3.0 - Dec 07, 2016 +### 1.0.0 (unreleased) -* Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011) -* Add flags HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS. (deep011) -* Support redisClusterCommandArgv related api. (deep011) -* Fix some serious bugs. (deep011) +**BREAKING CHANGES**: -### 0.2.1 - Nov 24, 2015 +* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now + protocol errors. This is consistent with the RESP specification. On 32-bit + platforms, the upper bound is lowered to `SIZE_MAX`. -This release support redis cluster api. +* Change `redisReply.len` to `size_t`, as it denotes the the size of a string + + User code should compare this to `size_t` values as well. If it was used to + compare to other values, casting might be necessary or can be removed, if + casting was applied before. + +### 0.x.x (unreleased) +**BREAKING CHANGES**: + +* Change `redisReply.len` to `size_t`, as it denotes the the size of a string + +User code should compare this to `size_t` values as well. +If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. + +* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter. + +### 0.14.0 (2018-09-25) + +* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) +* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) +* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) +* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) +* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) +* Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) +* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) +* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) +* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) +* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) +* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) +* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) +* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) +* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) +* Fix libevent leak (zfz [515228]) +* Clean up GCC warning (Ichito Nagata [2ec774]) +* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) +* Solaris compilation fix (Donald Whyte [41b07d]) +* Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) +* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) +* libuv use after free fix (Paul Scott [cbb956]) +* Properly close socket fd on reconnect attempt (WSL [64d1ec]) +* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) +* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) +* Update libevent (Chris Xin [386802]) +* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) +* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) +* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) +* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) +* Compatibility fix for strerror_r (Tom Lee [bb1747]) +* Properly detect integer parse/overflow errors (Justin Brewer [93421f]) +* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) +* Catch a buffer overflow when formatting the error message +* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 +* Fix warnings, when compiled with -Wshadow +* Make hiredis compile in Cygwin on Windows, now CI-tested +* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now + protocol errors. This is consistent with the RESP specification. On 32-bit + platforms, the upper bound is lowered to `SIZE_MAX`. + +* Remove backwards compatibility macro's + +This removes the following old function aliases, use the new name now: + +| Old | New | +| --------------------------- | ---------------------- | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderCreate | redisReaderCreate | +| redisReplyReaderFree | redisReaderFree | +| redisReplyReaderFeed | redisReaderFeed | +| redisReplyReaderGetReply | redisReaderGetReply | +| redisReplyReaderSetPrivdata | redisReaderSetPrivdata | +| redisReplyReaderGetObject | redisReaderGetObject | +| redisReplyReaderGetError | redisReaderGetError | + +* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` + +Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, +due to debugging other software. +By renaming we avoid unintentional name clashes. + +Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. + +### 0.13.3 (2015-09-16) + +* Revert "Clear `REDIS_CONNECTED` flag when connection is closed". +* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) + + +If the `REDIS_CONNECTED` flag is cleared, +the async onDisconnect callback function will never be called. +This causes problems as the disconnect is never reported back to the user. + +### 0.13.2 (2015-08-25) + +* Prevent crash on pending replies in async code (Thanks, @switch-st) +* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) +* Add MacOS X addapter (Thanks, @dizzus) +* Add Qt adapter (Thanks, Pietro Cerutti) +* Add Ivykis adapter (Thanks, Gergely Nagy) + +All adapters are provided as is and are only tested where possible. + +### 0.13.1 (2015-05-03) + +This is a bug fix release. +The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. +Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. +Other non-C99 code can now use hiredis as usual again. +Sorry for the inconvenience. + +* Fix memory leak in async reply handling (Salvatore Sanfilippo) +* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) + +### 0.13.0 (2015-04-16) + +This release adds a minimal Windows compatibility layer. +The parser, standalone since v0.12.0, can now be compiled on Windows +(and thus used in other client libraries as well) + +* Windows compatibility layer for parser code (tzickel) +* Properly escape data printed to PKGCONF file (Dan Skorupski) +* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) +* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) + +### 0.12.1 (2015-01-26) + +* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location +* Fix `make test` as 32 bit build on 64 bit platform + +### 0.12.0 (2015-01-22) + +* Add optional KeepAlive support + +* Try again on EINTR errors + +* Add libuv adapter + +* Add IPv6 support + +* Remove possibility of multiple close on same fd + +* Add ability to bind source address on connect + +* Add redisConnectFd() and redisFreeKeepFd() + +* Fix getaddrinfo() memory leak + +* Free string if it is unused (fixes memory leak) + +* Improve redisAppendCommandArgv performance 2.5x + +* Add support for SO_REUSEADDR + +* Fix redisvFormatCommand format parsing + +* Add GLib 2.0 adapter + +* Refactor reading code into read.c + +* Fix errno error buffers to not clobber errors + +* Generate pkgconf during build + +* Silence _BSD_SOURCE warnings + +* Improve digit counting for multibulk creation + + +### 0.11.0 + +* Increase the maximum multi-bulk reply depth to 7. + +* Increase the read buffer size from 2k to 16k. + +* Use poll(2) instead of select(2) to support large fds (>= 1024). + +### 0.10.1 + +* Makefile overhaul. Important to check out if you override one or more + variables using environment variables or via arguments to the "make" tool. + +* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements + being created by the default reply object functions. + +* Issue #43: Don't crash in an asynchronous context when Redis returns an error + reply after the connection has been made (this happens when the maximum + number of connections is reached). + +### 0.10.0 + +* See commit log. -* Add hiredis 0.3.1. (deep011) -* Support cluster synchronous API. (deep011) -* Support multi-key command(mget/mset/del) for redis cluster. (deep011) -* Support cluster pipelining. (deep011) -* Support cluster asynchronous API. (deep011) diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..3e6edabc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,106 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) +INCLUDE(GNUInstallDirs) +PROJECT(hiredis) + +OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) +OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) + +IF (WIN32 OR MINGW) + SET(BUILD_SHARED_LIBS OFF) + ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS) +ENDIF() + +IF (MSVC) + ADD_COMPILE_OPTIONS("/wd 4267" "/wd 4244") +ENDIF() + +MACRO(getVersionBit name) + SET(VERSION_REGEX "^#define ${name} (.+)$") + FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h" + VERSION_BIT REGEX ${VERSION_REGEX}) + STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") +ENDMACRO(getVersionBit) + +getVersionBit(HIREDIS_MAJOR) +getVersionBit(HIREDIS_MINOR) +getVersionBit(HIREDIS_PATCH) +getVersionBit(HIREDIS_SONAME) +SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") +MESSAGE("Detected version: ${VERSION}") + +PROJECT(hiredis VERSION "${VERSION}") + +SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") + +ADD_LIBRARY(hiredis + async.c + dict.c + hiredis.c + net.c + read.c + sds.c + sockcompat.c + adlist.c + command.c + crc16.c + hiarray.c + hircluster.c + hiutil.c) + +SET_TARGET_PROPERTIES(hiredis + PROPERTIES + VERSION "${HIREDIS_SONAME}") +IF(WIN32 OR MINGW) + TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) +ENDIF() +TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .) + +CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) + +INSTALL(TARGETS hiredis + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + +INSTALL(FILES hiredis.h read.h sds.h async.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) + +INSTALL(DIRECTORY adapters + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) + +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +IF(ENABLE_SSL) + IF (NOT OPENSSL_ROOT_DIR) + IF (APPLE) + SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") + ENDIF() + ENDIF() + FIND_PACKAGE(OpenSSL REQUIRED) + ADD_LIBRARY(hiredis_ssl SHARED + ssl.c) + TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}") + TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES}) + CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) + + INSTALL(TARGETS hiredis_ssl + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + + INSTALL(FILES hiredis_ssl.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) + + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +ENDIF() + +IF(NOT (DISABLE_TESTS OR (WIN32 OR MINGW))) + ENABLE_TESTING() + ADD_EXECUTABLE(hiredis-test test.c) + TARGET_LINK_LIBRARIES(hiredis-test hiredis) + ADD_TEST(NAME hiredis-test + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) +ENDIF() + +# Add examples +IF(ENABLE_EXAMPLES) + ADD_SUBDIRECTORY(examples) +ENDIF(ENABLE_EXAMPLES) diff --git a/Makefile b/Makefile index a321bd1a..c885e0aa 100644 --- a/Makefile +++ b/Makefile @@ -3,19 +3,26 @@ # 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 +OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o adlist.o command.o crc16.o dict.o hiarray.o hircluster.o hiutil.o +SSL_OBJ=ssl.o EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib +ifeq ($(USE_SSL),1) +EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl +endif TESTS=hiredis-test -LIBNAME=libhiredis_vip -PKGCONFNAME=hiredis_vip.pc +LIBNAME=libhiredis +SSL_LIBNAME=libhiredis_ssl +PKGCONFNAME=hiredis.pc +SSL_PKGCONFNAME=hiredis_ssl.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}') +HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') +HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') +HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') +HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}') # Installation related variables and target PREFIX?=/usr/local -INCLUDE_PATH?=include/hiredis-vip +INCLUDE_PATH?=include/hiredis LIBRARY_PATH?=lib PKGCONF_PATH?=pkgconfig INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) @@ -35,72 +42,115 @@ 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') +CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') +CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') OPTIMIZATION?=-O3 -WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -DEBUG?= -g -ggdb -REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH) -REAL_LDFLAGS=$(LDFLAGS) $(ARCH) +WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers +DEBUG_FLAGS?= -g -ggdb +REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) +REAL_LDFLAGS=$(LDFLAGS) DYLIBSUFFIX=so STLIBSUFFIX=a -DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR) -DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) +DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) -DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) +SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) +DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) -STLIB_MAKE_CMD=ar rcs $(STLIBNAME) +SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX) +STLIB_MAKE_CMD=$(AR) rcs # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') + +USE_SSL?=0 + +# This is required for test.c only +ifeq ($(USE_SSL),1) + CFLAGS+=-DHIREDIS_TEST_SSL +endif + +ifeq ($(uname_S),Linux) + SSL_LDFLAGS=-lssl -lcrypto +else + OPENSSL_PREFIX?=/usr/local/opt/openssl + CFLAGS+=-I$(OPENSSL_PREFIX)/include + SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto +endif + 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) + DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) + DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) endif all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) +ifeq ($(USE_SSL),1) +all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) +endif # 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 +async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.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 +hiarray.o: hiarray.c hiutil.h hiarray.h +hircluster.o: hircluster.c fmacros.h win32.h hircluster.h hiredis.h read.h sds.h async.h hiutil.h adlist.h hiarray.h command.h dict.c dict.h +hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h async.h win32.h +hiutil.o: hiutil.c win32.h hiutil.h +net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h +read.o: read.c fmacros.h read.h sds.h win32.h +sds.o: sds.c sds.h sdsalloc.h +sockcompat.o: sockcompat.c sockcompat.h +ssl.o: ssl.c hiredis.h read.h sds.h async.h async_private.h test.o: test.c fmacros.h hiredis.h read.h sds.h net.h $(DYLIBNAME): $(OBJ) - $(DYLIB_MAKE_CMD) $(OBJ) + $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS) $(STLIBNAME): $(OBJ) - $(STLIB_MAKE_CMD) $(OBJ) + $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) + +$(SSL_DYLIBNAME): $(SSL_OBJ) + $(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS) + +$(SSL_STLIBNAME): $(SSL_OBJ) + $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ) dynamic: $(DYLIBNAME) static: $(STLIBNAME) +ifeq ($(USE_SSL),1) +dynamic: $(SSL_DYLIBNAME) +static: $(SSL_STLIBNAME) +endif # Binaries: hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) + +hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) 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) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) + +hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS) + +hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) + +hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) ifndef AE_DIR hiredis-example-ae: @@ -117,42 +167,52 @@ hiredis-example-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) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) +endif + +ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) +hiredis-example-qt: + @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR" + @false +else +hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) + $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ + $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore + $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ + $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore + $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore endif hiredis-example: examples/example.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) + $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) examples: $(EXAMPLES) -hiredis-test: test.o $(STLIBNAME) +TEST_LIBS = $(STLIBNAME) +ifeq ($(USE_SSL),1) + TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread +endif +hiredis-test: test.o $(TEST_LIBS) hiredis-%: %.o $(STLIBNAME) - $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) + $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS) 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` + TEST_SSL=$(USE_SSL) ./test.sh .c.o: $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< clean: - rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov + rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov dep: - $(CC) -MM *.c - -ifeq ($(uname_S),SunOS) - INSTALL?= cp -r -endif + $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c -INSTALL?= cp -a +INSTALL?= cp -pPR $(PKGCONFNAME): hiredis.h @echo "Generating $@ for pkgconfig..." @@ -161,18 +221,32 @@ $(PKGCONFNAME): hiredis.h @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@ @echo >> $@ - @echo Name: hiredis-vip >> $@ - @echo Description: Minimalistic C client library for Redis and Redis Cluster. >> $@ - @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ - @echo Libs: -L\$${libdir} -lhiredis_vip >> $@ + @echo Name: hiredis >> $@ + @echo Description: Minimalistic C client library for Redis. >> $@ + @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ + @echo Libs: -L\$${libdir} -lhiredis >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ +$(SSL_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_ssl >> $@ + @echo Description: SSL Support for hiredis. >> $@ + @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ + @echo Requires: hiredis >> $@ + @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@ + @echo Libs.private: -lssl -lcrypto >> $@ + 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) + mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH) + $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(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) + cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_PKGCONF_PATH) $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) @@ -202,4 +276,4 @@ coverage: gcov noopt: $(MAKE) OPTIMIZATION="" -.PHONY: all test check clean dep install 32bit gprof gcov noopt +.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt diff --git a/README.md b/README.md index 120a7f13..6095cec9 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,254 @@ +[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) -# HIREDIS-VIP +**This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.** -Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. +# HIREDIS -Hiredis-vip supported redis cluster. +Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. -Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . +It is minimalistic because it just adds minimal support for the protocol, but +at the same time it uses a high level printf-alike API in order to make it +much higher level than otherwise suggested by its minimal code base and the +lack of explicit bindings for every Redis command. -## CLUSTER SUPPORT +Apart from supporting sending commands and receiving replies, it comes with +a reply parser that is decoupled from the I/O layer. It +is a stream parser designed for easy reusability, which can for instance be used +in higher level language bindings for efficient reply parsing. -### FEATURES: +Hiredis only supports the binary-safe Redis protocol, so you can use it with any +Redis version >= 1.2.0. -* **`SUPPORT REDIS CLUSTER`**: - * Connect to redis cluster and run commands. +The library comes with multiple APIs. There is the +*synchronous API*, the *asynchronous API* and the *reply parsing API*. -* **`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. +## Upgrading to `1.0.0` -### CLUSTER API: +Version 1.0.0 marks a stable release of hiredis. +It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. +It also bundles the updated `sds` library, to sync up with upstream and Redis. +For most applications a recompile against the new hiredis should be enough. +For code changes see the [Changelog](CHANGELOG.md). -```c -redisClusterContext *redisClusterContextInit(void); -void redisClusterFree(redisClusterContext *cc); - -int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr); -int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); -int redisClusterSetOptionConnectBlock(redisClusterContext *cc); -int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc); -int redisClusterSetOptionParseSlaves(redisClusterContext *cc); -int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc); -int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); -int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); - -int redisClusterConnect2(redisClusterContext *cc); - -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); -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); - -redisContext *ctx_get_by_node(redisClusterContext *cc, struct cluster_node *node); - -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); -``` - -### CLUSTER API (old api, version <= 0.3.0): +## Upgrading from `<0.9.0` -```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); -``` +Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing +code using hiredis should not be a big pain. The key thing to keep in mind when +upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to +the stateless 0.0.1 that only has a file descriptor to work with. -## 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 +## Synchronous API To consume the synchronous API, there are only a few function calls that need to be introduced: ```c -redisClusterContext *redisClusterContextInit(void); -int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); -int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); -int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterConnect2(redisClusterContext *cc); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void redisClusterFree(redisClusterContext *cc); +redisContext *redisConnect(const char *ip, int port); +void *redisCommand(redisContext *c, const char *format, ...); +void freeReplyObject(void *reply); ``` -### Cluster connecting +### Connecting -The function `redisClusterContextInit` is used to create a so-called `redisClusterContext`. -The function `redisClusterSetOptionAddNodes` is used to add the redis cluster address. -The function `redisClusterConnect2` is used to connect to the redis cluser. -The context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext` +The function `redisConnect` is used to create a so-called `redisContext`. The +context is where Hiredis holds state for a connection. The `redisContext` 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 +the error. More information on errors can be found in the **Errors** section. +After trying to connect to Redis using `redisConnect` you should check the `err` field to see if establishing the connection was successful: ```c -redisClusterContext *cc = redisClusterContextInit(); -redisClusterSetOptionAddNodes(cc, "127.0.0.1:6379,127.0.0.1:6380"); -redisClusterConnect2(cc); -if (cc != NULL && cc->err) { - printf("Error: %s\n", cc->errstr); - // handle error +redisContext *c = redisConnect("127.0.0.1", 6379); +if (c == NULL || c->err) { + if (c) { + printf("Error: %s\n", c->errstr); + // handle error + } else { + printf("Can't allocate redis context\n"); + } } ``` -### Cluster sending commands +*Note: A `redisContext` is not thread-safe.* -The next that will be introduced is `redisClusterCommand`. -This function takes a format similar to printf. In the simplest form, +### Sending commands + +There are several ways to issue commands to Redis. The first that will be introduced is +`redisCommand`. This function takes a format similar to printf. In the simplest form, it is used like this: ```c -reply = redisClusterCommand(clustercontext, "SET foo bar"); +reply = redisCommand(context, "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); +reply = redisCommand(context, "SET foo %s", value); +``` +When you need to pass binary safe strings in a command, the `%b` specifier can be +used. Together with a pointer to the string, it requires a `size_t` length argument +of the string: +```c +reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); ``` -Internally, Hiredis-vip splits the command in different arguments and will +Internally, Hiredis 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); +reply = redisCommand(context, "SET key:%s %s", myid, value); ``` -### Cluster multi-key commands +### Using replies -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. +The return value of `redisCommand` holds a reply when the command was +successfully executed. When an error occurs, the return value is `NULL` and +the `err` field in the context will be set (see section on **Errors**). +Once an error is returned the context cannot be reused and you should set up +a new connection. -Example: -```c -reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); -``` +The standard replies that `redisCommand` are of the type `redisReply`. The +`type` field in the `redisReply` should be used to test what kind of reply +was received: + +* **`REDIS_REPLY_STATUS`**: + * The command replied with a status reply. The status string can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ERROR`**: + * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. + +* **`REDIS_REPLY_INTEGER`**: + * The command replied with an integer. The integer value can be accessed using the + `reply->integer` field of type `long long`. -### Cluster cleaning up +* **`REDIS_REPLY_NIL`**: + * The command replied with a **nil** object. There is no data to access. + +* **`REDIS_REPLY_STRING`**: + * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. + The length of this string can be accessed using `reply->len`. + +* **`REDIS_REPLY_ARRAY`**: + * A multi bulk reply. The number of elements in the multi bulk reply is stored in + `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well + and can be accessed via `reply->element[..index..]`. + Redis may reply with nested arrays but this is fully supported. + +Replies should be freed using the `freeReplyObject()` function. +Note that this function will take care of freeing sub-reply objects +contained in arrays and nested arrays, so there is no need for the user to +free the sub replies (it is actually harmful and will corrupt the memory). + +**Important:** the current version of hiredis (0.10.0) frees replies when the +asynchronous API is used. This means you should not call `freeReplyObject` when +you use this API. The reply is cleaned up by hiredis _after_ the callback +returns. This behavior will probably change in future releases, so make sure to +keep an eye on the changelog when upgrading (see issue #39). + +### Cleaning up To disconnect and free the context the following function can be used: ```c -void redisClusterFree(redisClusterContext *cc); +void redisFree(redisContext *c); ``` This function immediately closes the socket and then frees the allocations done in creating the context. -### Cluster pipelining +### Sending commands (cont'd) -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: +Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. +It has the following prototype: ```c -int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); -int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); +void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); ``` -After calling either function one or more times, `redisClusterGetReply` can be used to receive the +It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the +arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will +use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments +need to be binary safe, the entire array of lengths `argvlen` should be provided. + +The return value has the same semantic as `redisCommand`. + +### Pipelining + +To explain how Hiredis supports pipelining in a blocking connection, there needs to be +understanding of the internal execution flow. + +When any of the functions in the `redisCommand` family is called, Hiredis first formats the +command according to the Redis protocol. The formatted command is then put in the output buffer +of the context. This output buffer is dynamic, so it can hold any number of commands. +After the command is put in the output buffer, `redisGetReply` is called. This function has the +following two execution paths: + +1. The input buffer is non-empty: + * Try to parse a single reply from the input buffer and return it + * If no reply could be parsed, continue at *2* +2. The input buffer is empty: + * Write the **entire** output buffer to the socket + * Read from the socket until a single reply could be parsed + +The function `redisGetReply` 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 `redisCommand` family, apart from not returning a reply: +```c +void redisAppendCommand(redisContext *c, const char *format, ...); +void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +``` +After calling either function one or more times, `redisGetReply` 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: +The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and +a single call to `read(2)`): ```c redisReply *reply; -redisClusterAppendCommand(clusterContext,"SET foo bar"); -redisClusterAppendCommand(clusterContext,"GET foo"); -redisClusterGetReply(clusterContext,&reply); // reply for SET +redisAppendCommand(context,"SET foo bar"); +redisAppendCommand(context,"GET foo"); +redisGetReply(context,&reply); // reply for SET freeReplyObject(reply); -redisClusterGetReply(clusterContext,&reply); // reply for GET +redisGetReply(context,&reply); // reply for GET freeReplyObject(reply); -redisClusterReset(clusterContext); ``` +This API can also be used to implement a blocking subscriber: +```c +reply = redisCommand(context,"SUBSCRIBE foo"); +freeReplyObject(reply); +while(redisGetReply(context,&reply) == REDIS_OK) { + // consume message + freeReplyObject(reply); +} +``` +### Errors + +When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is +returned. The `err` field inside the context will be non-zero and set to one of the +following constants: + +* **`REDIS_ERR_IO`**: + There was an I/O error while creating the connection, trying to write + to the socket or read from the socket. If you included `errno.h` in your + application, you can use the global `errno` variable to find out what is + wrong. + +* **`REDIS_ERR_EOF`**: + The server closed the connection which resulted in an empty read. + +* **`REDIS_ERR_PROTOCOL`**: + There was an error while parsing the protocol. -## Cluster asynchronous API +* **`REDIS_ERR_OTHER`**: + Any other error. Currently, it is only used when a specified hostname to connect + to cannot be resolved. -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. +In every case, the `errstr` field in the context will be set to hold a string representation +of the error. + +## Asynchronous API + +Hiredis comes with an asynchronous API that works easily with any event library. +Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) +and [libevent](http://monkey.org/~provos/libevent/). ### Connecting @@ -228,15 +257,18 @@ Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The 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. + +*Note: A `redisAsyncContext` is not thread-safe.* + ```c -redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); -if (acc->err) { - printf("Error: %s\n", acc->errstr); +redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); +if (c->err) { + printf("Error: %s\n", c->errstr); // handle error } ``` -The cluster asynchronous context can hold a disconnect callback function that is called when the +The 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 @@ -246,37 +278,40 @@ On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection w 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. +The context object is always freed after the disconnect callback fired. When a reconnect is needed, +the disconnect callback is a good point to do so. 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); +int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); ``` +`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback. ### 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 +In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the synchronous API, there is only a single way to send commands. +Because commands are sent to Redis 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); +void(redisAsyncContext *c, 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, ...); +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); ``` -This function work like their blocking counterparts. The return value is `REDIS_OK` when the command +Both functions 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. +returned on calls to the `redisAsyncCommand` 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 @@ -286,9 +321,9 @@ All pending callbacks are called with a `NULL` reply when the context encountere ### Disconnecting -An cluster asynchronous connection can be terminated using: +An asynchronous connection can be terminated using: ```c -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); +void redisAsyncDisconnect(redisAsyncContext *ac); ``` 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 @@ -298,15 +333,138 @@ callbacks have been executed. After this, the disconnection callback is executed ### 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*. +There are a few hooks that need to be set on the context object after it is created. +See the `adapters/` directory for bindings to *libev* and *libevent*. -## AUTHORS +## Reply parsing API + +Hiredis comes with a reply parsing API that makes it easy for writing higher +level language bindings. + +The reply parsing API consists of the following functions: +```c +redisReader *redisReaderCreate(void); +void redisReaderFree(redisReader *reader); +int redisReaderFeed(redisReader *reader, const char *buf, size_t len); +int redisReaderGetReply(redisReader *reader, void **reply); +``` +The same set of functions are used internally by hiredis when creating a +normal Redis context, the above API just exposes it to the user for a direct +usage. + +### Usage + +The function `redisReaderCreate` creates a `redisReader` structure that holds a +buffer with unparsed data and state for the protocol parser. + +Incoming data -- most likely from a socket -- can be placed in the internal +buffer of the `redisReader` using `redisReaderFeed`. This function will make a +copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed +when `redisReaderGetReply` is called. This function returns an integer status +and a reply object (as described above) via `void **reply`. The returned status +can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went +wrong (either a protocol error, or an out of memory error). + +The parser limits the level of nesting for multi bulk payloads to 7. If the +multi bulk nesting level is higher than this, the parser returns an error. + +### Customizing replies + +The function `redisReaderGetReply` creates `redisReply` and makes the function +argument `reply` point to the created `redisReply` variable. For instance, if +the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` +will hold the status as a vanilla C string. However, the functions that are +responsible for creating instances of the `redisReply` can be customized by +setting the `fn` field on the `redisReader` struct. This should be done +immediately after creating the `redisReader`. + +For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) +uses customized reply object functions to create Ruby objects. + +### Reader max buffer + +Both when using the Reader API directly or when using it indirectly via a +normal Redis context, the redisReader structure uses a buffer in order to +accumulate data from the server. +Usually this buffer is destroyed when it is empty and is larger than 16 +KiB in order to avoid wasting memory in unused buffers + +However when working with very big payloads destroying the buffer may slow +down performances considerably, so it is possible to modify the max size of +an idle buffer changing the value of the `maxbuf` field of the reader structure +to the desired value. The special value of 0 means that there is no maximum +value for an idle buffer, so the buffer will never get freed. + +For instance if you have a normal Redis context you can set the maximum idle +buffer to zero (unlimited) just with: +```c +context->reader->maxbuf = 0; +``` +This should be done only in order to maximize performances when working with +large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again +as soon as possible in order to prevent allocation of useless memory. + +## SSL/TLS Support + +### Building + +SSL/TLS support is not built by default and requires an explicit flag: + + make USE_SSL=1 + +This requires OpenSSL development package (e.g. including header files to be +available. + +When enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and +`libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries +unaffected so no additional dependencies are introduced. -Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). +### Using it -The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). +First, you'll need to make sure you include the SSL header file: -The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). +```c +#include "hiredis.h" +#include "hiredis_ssl.h" +``` + +SSL can only be enabled on a `redisContext` connection after the connection has +been established and before any command has been processed. For example: + +```c +c = redisConnect('localhost', 6443); +if (c == NULL || c->err) { + /* Handle error and abort... */ +} + +if (redisSecureConnection(c, + "cacertbundle.crt", /* File name of trusted CA/ca bundle file */ + "client_cert.pem", /* File name of client certificate file */ + "client_key.pem", /* File name of client privat ekey */ + "redis.mydomain.com" /* Server name to request (SNI) */ + ) != REDIS_OK) { + printf("SSL error: %s\n", c->errstr); + /* Abort... */ +} +``` + +You will also need to link against `libhiredis_ssl`, **in addition** to +`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies. + +### OpenSSL Global State Initialization + +OpenSSL needs to have certain global state initialized before it can be used. +Using `redisSecureConnection()` will handle this automatically on the first +call. + +**If the calling application itself also initializes and uses OpenSSL directly, +`redisSecureConnection()` must not be used.** + +Instead, use `redisInitiateSSL()` which also provides greater control over the +configuration of the SSL connection, as the caller is responsible to create a +connection context using `SSL_new()` and configure it as required. + +## AUTHORS -Hiredis-vip is released under the BSD license. +Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and +Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. diff --git a/adapters/glib.h b/adapters/glib.h index e13eee73..e0a6411d 100644 --- a/adapters/glib.h +++ b/adapters/glib.h @@ -16,43 +16,43 @@ typedef struct static void redis_source_add_read (gpointer data) { - RedisSource *source = data; + RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events |= G_IO_IN; - g_main_context_wakeup(g_source_get_context(data)); + g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_del_read (gpointer data) { - RedisSource *source = data; + RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events &= ~G_IO_IN; - g_main_context_wakeup(g_source_get_context(data)); + g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_add_write (gpointer data) { - RedisSource *source = data; + RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events |= G_IO_OUT; - g_main_context_wakeup(g_source_get_context(data)); + g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_del_write (gpointer data) { - RedisSource *source = data; + RedisSource *source = (RedisSource *)data; g_return_if_fail(source); source->poll_fd.events &= ~G_IO_OUT; - g_main_context_wakeup(g_source_get_context(data)); + g_main_context_wakeup(g_source_get_context((GSource *)data)); } static void redis_source_cleanup (gpointer data) { - RedisSource *source = data; + RedisSource *source = (RedisSource *)data; g_return_if_fail(source); @@ -63,7 +63,7 @@ redis_source_cleanup (gpointer data) * current main loop. However, we will remove the GPollFD. */ if (source->poll_fd.fd >= 0) { - g_source_remove_poll(data, &source->poll_fd); + g_source_remove_poll((GSource *)data, &source->poll_fd); source->poll_fd.fd = -1; } } diff --git a/adapters/ivykis.h b/adapters/ivykis.h new file mode 100644 index 00000000..6a12a868 --- /dev/null +++ b/adapters/ivykis.h @@ -0,0 +1,81 @@ +#ifndef __HIREDIS_IVYKIS_H__ +#define __HIREDIS_IVYKIS_H__ +#include +#include "../hiredis.h" +#include "../async.h" + +typedef struct redisIvykisEvents { + redisAsyncContext *context; + struct iv_fd fd; +} redisIvykisEvents; + +static void redisIvykisReadEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleRead(context); +} + +static void redisIvykisWriteEvent(void *arg) { + redisAsyncContext *context = (redisAsyncContext *)arg; + redisAsyncHandleWrite(context); +} + +static void redisIvykisAddRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); +} + +static void redisIvykisDelRead(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_in(&e->fd, NULL); +} + +static void redisIvykisAddWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); +} + +static void redisIvykisDelWrite(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + iv_fd_set_handler_out(&e->fd, NULL); +} + +static void redisIvykisCleanup(void *privdata) { + redisIvykisEvents *e = (redisIvykisEvents*)privdata; + + iv_fd_unregister(&e->fd); + free(e); +} + +static int redisIvykisAttach(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisIvykisEvents *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 = (redisIvykisEvents*)malloc(sizeof(*e)); + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisIvykisAddRead; + ac->ev.delRead = redisIvykisDelRead; + ac->ev.addWrite = redisIvykisAddWrite; + ac->ev.delWrite = redisIvykisDelWrite; + ac->ev.cleanup = redisIvykisCleanup; + ac->ev.data = e; + + /* Initialize and install read/write events */ + IV_FD_INIT(&e->fd); + e->fd.fd = c->fd; + e->fd.handler_in = redisIvykisReadEvent; + e->fd.handler_out = redisIvykisWriteEvent; + e->fd.handler_err = NULL; + e->fd.cookie = e->context; + + iv_fd_register(&e->fd); + + return REDIS_OK; +} +#endif diff --git a/adapters/libevent.h b/adapters/libevent.h index 1621cc15..a4952776 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -30,56 +30,117 @@ #ifndef __HIREDIS_LIBEVENT_H__ #define __HIREDIS_LIBEVENT_H__ -#include +#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 +#define REDIS_LIBEVENT_DELETED 0x01 +#define REDIS_LIBEVENT_ENTERED 0x02 typedef struct redisLibeventEvents { redisAsyncContext *context; - struct event rev, wev; + struct event *ev; + struct event_base *base; + struct timeval tv; + short flags; + short state; } redisLibeventEvents; -static void redisLibeventReadEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleRead(e->context); +static void redisLibeventDestroy(redisLibeventEvents *e) { + free(e); } -static void redisLibeventWriteEvent(int fd, short event, void *arg) { - ((void)fd); ((void)event); +static void redisLibeventHandler(int fd, short event, void *arg) { + ((void)fd); redisLibeventEvents *e = (redisLibeventEvents*)arg; - redisAsyncHandleWrite(e->context); + e->state |= REDIS_LIBEVENT_ENTERED; + + #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ + redisLibeventDestroy(e);\ + return; \ + } + + if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { + redisAsyncHandleTimeout(e->context); + CHECK_DELETED(); + } + + if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { + redisAsyncHandleRead(e->context); + CHECK_DELETED(); + } + + if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { + redisAsyncHandleWrite(e->context); + CHECK_DELETED(); + } + + e->state &= ~REDIS_LIBEVENT_ENTERED; + #undef CHECK_DELETED +} + +static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL; + + if (isRemove) { + if ((e->flags & flag) == 0) { + return; + } else { + e->flags &= ~flag; + } + } else { + if (e->flags & flag) { + return; + } else { + e->flags |= flag; + } + } + + event_del(e->ev); + event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST, + redisLibeventHandler, privdata); + event_add(e->ev, tv); } static void redisLibeventAddRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(&e->rev,NULL); + redisLibeventUpdate(privdata, EV_READ, 0); } static void redisLibeventDelRead(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->rev); + redisLibeventUpdate(privdata, EV_READ, 1); } static void redisLibeventAddWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_add(&e->wev,NULL); + redisLibeventUpdate(privdata, EV_WRITE, 0); } static void redisLibeventDelWrite(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->wev); + redisLibeventUpdate(privdata, EV_WRITE, 1); } static void redisLibeventCleanup(void *privdata) { redisLibeventEvents *e = (redisLibeventEvents*)privdata; - event_del(&e->rev); - event_del(&e->wev); - free(e); + if (!e) { + return; + } + event_del(e->ev); + event_free(e->ev); + e->ev = NULL; + + if (e->state & REDIS_LIBEVENT_ENTERED) { + e->state |= REDIS_LIBEVENT_DELETED; + } else { + redisLibeventDestroy(e); + } +} + +static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { + redisLibeventEvents *e = (redisLibeventEvents *)privdata; + short flags = e->flags; + e->flags = 0; + e->tv = tv; + redisLibeventUpdate(e, flags, 0); } static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { @@ -91,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { return REDIS_ERR; /* Create container for context and r/w events */ - e = (redisLibeventEvents*)malloc(sizeof(*e)); + e = (redisLibeventEvents*)calloc(1, sizeof(*e)); e->context = ac; /* Register functions to start/stop listening for events */ @@ -100,36 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { ac->ev.addWrite = redisLibeventAddWrite; ac->ev.delWrite = redisLibeventDelWrite; ac->ev.cleanup = redisLibeventCleanup; + ac->ev.scheduleTimer = redisLibeventSetTimeout; 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); + e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); + e->base = base; return REDIS_OK; } - -#if 1 //shenzheng 2015-9-21 redis cluster - -static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) -{ - return 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/adapters/libuv.h b/adapters/libuv.h index 3c9a49f5..39ef7cf5 100644 --- a/adapters/libuv.h +++ b/adapters/libuv.h @@ -15,15 +15,12 @@ typedef struct redisLibuvEvents { static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { redisLibuvEvents* p = (redisLibuvEvents*)handle->data; + int ev = (status ? p->events : events); - if (status != 0) { - return; - } - - if (events & UV_READABLE) { + if (p->context != NULL && (ev & UV_READABLE)) { redisAsyncHandleRead(p->context); } - if (events & UV_WRITABLE) { + if (p->context != NULL && (ev & UV_WRITABLE)) { redisAsyncHandleWrite(p->context); } } @@ -83,6 +80,7 @@ static void on_close(uv_handle_t* handle) { static void redisLibuvCleanup(void *privdata) { redisLibuvEvents* p = (redisLibuvEvents*)privdata; + p->context = NULL; // indicate that context might no longer exist uv_close((uv_handle_t*)&p->handle, on_close); } @@ -118,5 +116,4 @@ static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) { return REDIS_OK; } - #endif diff --git a/adapters/macosx.h b/adapters/macosx.h new file mode 100644 index 00000000..72121f60 --- /dev/null +++ b/adapters/macosx.h @@ -0,0 +1,114 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#ifndef __HIREDIS_MACOSX_H__ +#define __HIREDIS_MACOSX_H__ + +#include + +#include "../hiredis.h" +#include "../async.h" + +typedef struct { + redisAsyncContext *context; + CFSocketRef socketRef; + CFRunLoopSourceRef sourceRef; +} RedisRunLoop; + +static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { + if( redisRunLoop != NULL ) { + if( redisRunLoop->sourceRef != NULL ) { + CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); + CFRelease(redisRunLoop->sourceRef); + } + if( redisRunLoop->socketRef != NULL ) { + CFSocketInvalidate(redisRunLoop->socketRef); + CFRelease(redisRunLoop->socketRef); + } + free(redisRunLoop); + } + return REDIS_ERR; +} + +static void redisMacOSAddRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSDelRead(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); +} + +static void redisMacOSAddWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSDelWrite(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); +} + +static void redisMacOSCleanup(void *privdata) { + RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; + freeRedisRunLoop(redisRunLoop); +} + +static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { + redisAsyncContext* context = (redisAsyncContext*) info; + + switch (callbackType) { + case kCFSocketReadCallBack: + redisAsyncHandleRead(context); + break; + + case kCFSocketWriteCallBack: + redisAsyncHandleWrite(context); + break; + + default: + break; + } +} + +static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { + redisContext *redisCtx = &(redisAsyncCtx->c); + + /* Nothing should be attached when something is already attached */ + if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; + + RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); + if( !redisRunLoop ) return REDIS_ERR; + + /* Setup redis stuff */ + redisRunLoop->context = redisAsyncCtx; + + redisAsyncCtx->ev.addRead = redisMacOSAddRead; + redisAsyncCtx->ev.delRead = redisMacOSDelRead; + redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; + redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; + redisAsyncCtx->ev.cleanup = redisMacOSCleanup; + redisAsyncCtx->ev.data = redisRunLoop; + + /* Initialize and install read/write events */ + CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; + + redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, + kCFSocketReadCallBack | kCFSocketWriteCallBack, + redisMacOSAsyncCallback, + &socketCtx); + if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); + + redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); + if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); + + CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); + + return REDIS_OK; +} + +#endif + diff --git a/adapters/qt.h b/adapters/qt.h new file mode 100644 index 00000000..5cc02e6c --- /dev/null +++ b/adapters/qt.h @@ -0,0 +1,135 @@ +/*- + * Copyright (C) 2014 Pietro Cerutti + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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_QT_H__ +#define __HIREDIS_QT_H__ +#include +#include "../async.h" + +static void RedisQtAddRead(void *); +static void RedisQtDelRead(void *); +static void RedisQtAddWrite(void *); +static void RedisQtDelWrite(void *); +static void RedisQtCleanup(void *); + +class RedisQtAdapter : public QObject { + + Q_OBJECT + + friend + void RedisQtAddRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addRead(); + } + + friend + void RedisQtDelRead(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delRead(); + } + + friend + void RedisQtAddWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->addWrite(); + } + + friend + void RedisQtDelWrite(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->delWrite(); + } + + friend + void RedisQtCleanup(void * adapter) { + RedisQtAdapter * a = static_cast(adapter); + a->cleanup(); + } + + public: + RedisQtAdapter(QObject * parent = 0) + : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } + + ~RedisQtAdapter() { + if (m_ctx != 0) { + m_ctx->ev.data = NULL; + } + } + + int setContext(redisAsyncContext * ac) { + if (ac->ev.data != NULL) { + return REDIS_ERR; + } + m_ctx = ac; + m_ctx->ev.data = this; + m_ctx->ev.addRead = RedisQtAddRead; + m_ctx->ev.delRead = RedisQtDelRead; + m_ctx->ev.addWrite = RedisQtAddWrite; + m_ctx->ev.delWrite = RedisQtDelWrite; + m_ctx->ev.cleanup = RedisQtCleanup; + return REDIS_OK; + } + + private: + void addRead() { + if (m_read) return; + m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); + connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); + } + + void delRead() { + if (!m_read) return; + delete m_read; + m_read = 0; + } + + void addWrite() { + if (m_write) return; + m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); + connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); + } + + void delWrite() { + if (!m_write) return; + delete m_write; + m_write = 0; + } + + void cleanup() { + delRead(); + delWrite(); + } + + private slots: + void read() { redisAsyncHandleRead(m_ctx); } + void write() { redisAsyncHandleWrite(m_ctx); } + + private: + redisAsyncContext * m_ctx; + QSocketNotifier * m_read; + QSocketNotifier * m_write; +}; + +#endif /* !__HIREDIS_QT_H__ */ diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..5b43fdbe --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,24 @@ +# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) +environment: + matrix: + - CYG_BASH: C:\cygwin64\bin\bash + CC: gcc + - CYG_BASH: C:\cygwin\bin\bash + CC: gcc + CFLAGS: -m32 + CXXFLAGS: -m32 + LDFLAGS: -m32 + +clone_depth: 1 + +# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail +init: + - git config --global core.autocrlf input + +# Install needed build dependencies +install: + - '%CYG_BASH% -lc "cygcheck -dc cygwin"' + +build_script: + - 'echo building...' + - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 #include +#ifndef _MSC_VER #include +#endif #include #include #include @@ -40,22 +42,9 @@ #include "net.h" #include "dict.c" #include "sds.h" +#include "win32.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); +#include "async_private.h" /* Forward declaration of function in hiredis.c */ int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); @@ -119,7 +108,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ac->err = 0; ac->errstr = NULL; ac->data = NULL; - ac->dataHandler = NULL; + ac->cleanup = NULL; ac->ev.data = NULL; ac->ev.addRead = NULL; @@ -127,6 +116,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ac->ev.addWrite = NULL; ac->ev.delWrite = NULL; ac->ev.cleanup = NULL; + ac->ev.scheduleTimer = NULL; ac->onConnect = NULL; ac->onDisconnect = NULL; @@ -151,56 +141,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) { ac->errstr = c->errstr; } -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { +redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) { + redisOptions myOptions = *options; redisContext *c; redisAsyncContext *ac; - c = redisConnectNonBlock(ip,port); - if (c == NULL) + myOptions.options |= REDIS_OPT_NONBLOCK; + c = redisConnectWithOptions(&myOptions); + if (c == NULL) { return NULL; - + } ac = redisAsyncInitialize(c); if (ac == NULL) { redisFree(c); return NULL; } - __redisAsyncCopyError(ac); return ac; } +redisAsyncContext *redisAsyncConnect(const char *ip, int port) { + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + return redisAsyncConnectWithOptions(&options); +} + 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + return redisAsyncConnectWithOptions(&options); } 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.options |= REDIS_OPT_REUSEADDR; + options.endpoint.tcp.source_addr = source_addr; + return redisAsyncConnectWithOptions(&options); } 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + return redisAsyncConnectWithOptions(&options); } int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { @@ -313,8 +299,8 @@ static void __redisAsyncFree(redisAsyncContext *ac) { } } - if (ac->dataHandler) { - ac->dataHandler(ac); + if (ac->cleanup) { + ac->cleanup(ac); } /* Cleanup self */ @@ -333,7 +319,7 @@ void redisAsyncFree(redisAsyncContext *ac) { } /* Helper function to make the disconnect happen and clean up. */ -static void __redisAsyncDisconnect(redisAsyncContext *ac) { +void __redisAsyncDisconnect(redisAsyncContext *ac) { redisContext *c = &(ac->c); /* Make sure error is accessible if there is any */ @@ -341,16 +327,23 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { if (ac->err == 0) { /* For clean disconnects, there should be no pending callbacks. */ - assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); + int ret = __redisShiftCallback(&ac->replies,NULL); + assert(ret == REDIS_ERR); } else { /* Disconnection is caused by an error, make sure that pending * callbacks cannot call new commands. */ c->flags |= REDIS_DISCONNECTING; } + /* cleanup event library on disconnect. + * this is safe to call multiple times */ + _EL_CLEANUP(ac); + /* For non-clean disconnects, __redisAsyncFree() will execute pending * callbacks with a NULL-reply. */ - __redisAsyncFree(ac); + if (!(c->flags & REDIS_NO_AUTO_FREE)) { + __redisAsyncFree(ac); + } } /* Tries to do a clean disconnect from Redis, meaning it stops new commands @@ -362,6 +355,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { void redisAsyncDisconnect(redisAsyncContext *ac) { redisContext *c = &(ac->c); c->flags |= REDIS_DISCONNECTING; + + /** unset the auto-free flag here, because disconnect undoes this */ + c->flags &= ~REDIS_NO_AUTO_FREE; if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) __redisAsyncDisconnect(ac); } @@ -369,6 +365,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) { static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { redisContext *c = &(ac->c); dict *callbacks; + redisCallback *cb; dictEntry *de; int pvariant; char *stype; @@ -392,16 +389,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); de = dictFind(callbacks,sname); if (de != NULL) { - memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); + cb = dictGetEntryVal(de); + + /* If this is an subscribe reply decrease pending counter. */ + if (strcasecmp(stype+pvariant,"subscribe") == 0) { + cb->pending_subs -= 1; + } + + memcpy(dstcb,cb,sizeof(*dstcb)); /* If this is an unsubscribe message, remove it. */ if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { - dictDelete(callbacks,sname); + if (cb->pending_subs == 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) + + /* Unset subscribed flag only when no pipelined pending subscribe. */ + if (reply->element[2]->integer == 0 + && dictSize(ac->sub.channels) == 0 + && dictSize(ac->sub.patterns) == 0) c->flags &= ~REDIS_SUBSCRIBED; } } @@ -415,7 +424,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, void redisProcessCallbacks(redisAsyncContext *ac) { redisContext *c = &(ac->c); - redisCallback cb = {NULL, NULL, NULL}; + redisCallback cb = {NULL, NULL, 0, NULL}; void *reply = NULL; int status; @@ -423,7 +432,8 @@ void redisProcessCallbacks(redisAsyncContext *ac) { 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) { + if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 + && ac->replies.head == NULL) { __redisAsyncDisconnect(ac); return; } @@ -493,25 +503,37 @@ void redisProcessCallbacks(redisAsyncContext *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 + * write event fires. When connecting was not successful, the connect callback * is called with a REDIS_ERR status and the context is free'd. */ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { + int completed = 0; 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); + if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { + /* Error! */ + redisCheckSocketError(c); + if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); __redisAsyncDisconnect(ac); return REDIS_ERR; + } else if (completed == 1) { + /* connected! */ + if (ac->onConnect) ac->onConnect(ac, REDIS_OK); + c->flags |= REDIS_CONNECTED; + return REDIS_OK; + } else { + return REDIS_OK; } +} - /* Mark context as connected. */ - c->flags |= REDIS_CONNECTED; - if (ac->onConnect) ac->onConnect(ac,REDIS_OK); - return REDIS_OK; +void redisAsyncRead(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + + if (redisBufferRead(c) == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + /* Always re-schedule reads */ + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } } /* This function should be called when the socket is readable. @@ -529,18 +551,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) { return; } - if (redisBufferRead(c) == REDIS_ERR) { + c->funcs->async_read(ac); +} + +void redisAsyncWrite(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + int done = 0; + + if (redisBufferWrite(c,&done) == REDIS_ERR) { __redisAsyncDisconnect(ac); } else { - /* Always re-schedule reads */ + /* 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); - redisProcessCallbacks(ac); } } void redisAsyncHandleWrite(redisAsyncContext *ac) { redisContext *c = &(ac->c); - int done = 0; if (!(c->flags & REDIS_CONNECTED)) { /* Abort connect was not successful. */ @@ -551,18 +584,37 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { 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); + c->funcs->async_write(ac); +} - /* Always schedule reads after writes */ - _EL_ADD_READ(ac); +void __redisSetError(redisContext *c, int type, const char *str); + +void redisAsyncHandleTimeout(redisAsyncContext *ac) { + redisContext *c = &(ac->c); + redisCallback cb; + + if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { + /* Nothing to do - just an idle timeout */ + return; } + + if (!c->err) { + __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); + } + + if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { + ac->onConnect(ac, REDIS_ERR); + } + + while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { + __redisRunCallback(ac, &cb, NULL); + } + + /** + * TODO: Don't automatically sever the connection, + * rather, allow to ignore responses before the queue is clear + */ + __redisAsyncDisconnect(ac); } /* Sets a pointer to the first argument and its length starting at p. Returns @@ -587,6 +639,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { redisContext *c = &(ac->c); redisCallback cb; + struct dict *cbdict; + dictEntry *de; + redisCallback *existcb; int pvariant, hasnext; const char *cstr, *astr; size_t clen, alen; @@ -600,6 +655,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void /* Setup callback */ cb.fn = fn; cb.privdata = privdata; + cb.pending_subs = 1; /* Find out which command will be appended. */ p = nextArgument(cmd,&cstr,&clen); @@ -616,9 +672,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void while ((p = nextArgument(p,&astr,&alen)) != NULL) { sname = sdsnewlen(astr,alen); if (pvariant) - ret = dictReplace(ac->sub.patterns,sname,&cb); + cbdict = ac->sub.patterns; else - ret = dictReplace(ac->sub.channels,sname,&cb); + cbdict = ac->sub.channels; + + de = dictFind(cbdict,sname); + + if (de != NULL) { + existcb = dictGetEntryVal(de); + cb.pending_subs = existcb->pending_subs + 1; + } + + ret = dictReplace(cbdict,sname,&cb); if (ret == 0) sdsfree(sname); } @@ -680,6 +745,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv int len; int status; len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); + if (len < 0) + return REDIS_ERR; status = __redisAsyncCommand(ac,fn,privdata,cmd,len); sdsfree(cmd); return status; @@ -689,3 +756,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); return status; } + +void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { + if (!ac->c.timeout) { + ac->c.timeout = calloc(1, sizeof(tv)); + } + + if (tv.tv_sec == ac->c.timeout->tv_sec && + tv.tv_usec == ac->c.timeout->tv_usec) { + return; + } + + *ac->c.timeout = tv; +} diff --git a/async.h b/async.h index 2ba7142b..e42985af 100644 --- a/async.h +++ b/async.h @@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); typedef struct redisCallback { struct redisCallback *next; /* simple singly linked list */ redisCallbackFn *fn; + int pending_subs; void *privdata; } redisCallback; @@ -56,6 +57,7 @@ typedef struct redisCallbackList { /* Connection callback prototypes */ typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); +typedef void(redisTimerCallback)(void *timer, void *privdata); /* Context for an async connection to Redis */ typedef struct redisAsyncContext { @@ -68,7 +70,7 @@ typedef struct redisAsyncContext { /* Not used by hiredis */ void *data; - void (*dataHandler)(struct redisAsyncContext* ac); + void (*cleanup)(struct redisAsyncContext* ac); /* Event library data and hooks */ struct { @@ -81,6 +83,7 @@ typedef struct redisAsyncContext { void (*addWrite)(void *privdata); void (*delWrite)(void *privdata); void (*cleanup)(void *privdata); + void (*scheduleTimer)(void *privdata, struct timeval tv); } ev; /* Called when either the connection is terminated due to an error or per @@ -93,6 +96,10 @@ typedef struct redisAsyncContext { /* Regular command callbacks */ redisCallbackList replies; + /* Address used for connect() */ + struct sockaddr *saddr; + size_t addrlen; + /* Subscription callbacks */ struct { redisCallbackList invalid; @@ -102,6 +109,7 @@ typedef struct redisAsyncContext { } redisAsyncContext; /* Functions that proxy to hiredis */ +redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options); 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, @@ -109,12 +117,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectUnix(const char *path); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); + +void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac); /* Handle read/write events */ void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleWrite(redisAsyncContext *ac); +void redisAsyncHandleTimeout(redisAsyncContext *ac); +void redisAsyncRead(redisAsyncContext *ac); +void redisAsyncWrite(redisAsyncContext *ac); /* Command functions for an async context. Write the command to the * output buffer and register the provided callback. */ diff --git a/async_private.h b/async_private.h new file mode 100644 index 00000000..d0133ae1 --- /dev/null +++ b/async_private.h @@ -0,0 +1,72 @@ +/* + * 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_PRIVATE_H +#define __HIREDIS_ASYNC_PRIVATE_H + +#define _EL_ADD_READ(ctx) \ + do { \ + refreshTimeout(ctx); \ + 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 { \ + refreshTimeout(ctx); \ + 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); \ + ctx->ev.cleanup = NULL; \ + } while(0); + +static inline void refreshTimeout(redisAsyncContext *ctx) { + if (ctx->c.timeout && ctx->ev.scheduleTimer && + (ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) { + ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout); + // } else { + // printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout); + // if (ctx->c.timeout){ + // printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec, + // ctx->c.timeout->tv_usec); + // } + } +} + +void __redisAsyncDisconnect(redisAsyncContext *ac); +void redisProcessCallbacks(redisAsyncContext *ac); + +#endif /* __HIREDIS_ASYNC_PRIVATE_H */ diff --git a/command.c b/command.c index e32091b4..24e2fa20 100644 --- a/command.c +++ b/command.c @@ -1665,7 +1665,7 @@ void command_destroy(struct cmd *command) if(command->cmd != NULL) { - free(command->cmd); + hi_free(command->cmd); } if(command->errstr != NULL){ diff --git a/dict.c b/dict.c index 79b1041c..5b349f07 100644 --- a/dict.c +++ b/dict.c @@ -161,7 +161,7 @@ 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. */ + * does not exists dictAdd will succeed. */ if (dictAdd(ht, key, val) == DICT_OK) return 1; /* It already exists, get the entry */ @@ -293,8 +293,8 @@ static void dictReleaseIterator(dictIterator *iter) { /* 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 the hash table is empty expand it to the initial size, + * if the table is "full" double its size. */ if (ht->size == 0) return dictExpand(ht, DICT_HT_INITIAL_SIZE); if (ht->used == ht->size) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..dd3a313a --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,46 @@ +INCLUDE(FindPkgConfig) +# Check for GLib + +PKG_CHECK_MODULES(GLIB2 glib-2.0) +if (GLIB2_FOUND) + INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS}) + LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS}) + ADD_EXECUTABLE(example-glib example-glib.c) + TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES}) +ENDIF(GLIB2_FOUND) + +FIND_PATH(LIBEV ev.h + HINTS /usr/local /usr/opt/local + ENV LIBEV_INCLUDE_DIR) + +if (LIBEV) + # Just compile and link with libev + ADD_EXECUTABLE(example-libev example-libev.c) + TARGET_LINK_LIBRARIES(example-libev hiredis ev) +ENDIF() + +FIND_PATH(LIBEVENT event.h) +if (LIBEVENT) + ADD_EXECUTABLE(example-libevent example-libevent) + TARGET_LINK_LIBRARIES(example-libevent hiredis event) +ENDIF() + +FIND_PATH(LIBUV uv.h) +IF (LIBUV) + ADD_EXECUTABLE(example-libuv example-libuv.c) + TARGET_LINK_LIBRARIES(example-libuv hiredis uv) +ENDIF() + +IF (APPLE) + FIND_LIBRARY(CF CoreFoundation) + ADD_EXECUTABLE(example-macosx example-macosx.c) + TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF}) +ENDIF() + +IF (ENABLE_SSL) + ADD_EXECUTABLE(example-ssl example-ssl.c) + TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl) +ENDIF() + +ADD_EXECUTABLE(example example.c) +TARGET_LINK_LIBRARIES(example hiredis) diff --git a/examples/example-ivykis.c b/examples/example-ivykis.c new file mode 100644 index 00000000..67affcef --- /dev/null +++ b/examples/example-ivykis.c @@ -0,0 +1,58 @@ +#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); + + iv_init(); + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisIvykisAttach(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"); + + iv_main(); + + iv_deinit(); + + return 0; +} diff --git a/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c new file mode 100644 index 00000000..1021113b --- /dev/null +++ b/examples/example-libevent-ssl.c @@ -0,0 +1,73 @@ +#include +#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(); + if (argc < 5) { + fprintf(stderr, + "Usage: %s [ca]\n", argv[0]); + exit(1); + } + + const char *value = argv[1]; + size_t nvalue = strlen(value); + + const char *hostname = argv[2]; + int port = atoi(argv[3]); + + const char *cert = argv[4]; + const char *certKey = argv[5]; + const char *caCert = argc > 5 ? argv[6] : NULL; + + redisAsyncContext *c = redisAsyncConnect(hostname, port); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) { + printf("SSL Error!\n"); + exit(1); + } + + redisLibeventAttach(c,base); + redisAsyncSetConnectCallback(c,connectCallback); + redisAsyncSetDisconnectCallback(c,disconnectCallback); + redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); + redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); + event_base_dispatch(base); + return 0; +} diff --git a/examples/example-libevent.c b/examples/example-libevent.c index d333c22b..1fe71ae4 100644 --- a/examples/example-libevent.c +++ b/examples/example-libevent.c @@ -9,7 +9,12 @@ void getCallback(redisAsyncContext *c, void *r, void *privdata) { redisReply *reply = r; - if (reply == NULL) return; + if (reply == NULL) { + if (c->errstr) { + printf("errstr: %s\n", c->errstr); + } + return; + } printf("argv[%s]: %s\n", (char*)privdata, reply->str); /* Disconnect after receiving the reply to GET */ @@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) { int main (int argc, char **argv) { signal(SIGPIPE, SIG_IGN); struct event_base *base = event_base_new(); + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); + struct timeval tv = {0}; + tv.tv_sec = 1; + options.timeout = &tv; + - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + redisAsyncContext *c = redisAsyncConnectWithOptions(&options); if (c->err) { /* Let *c leak for now... */ printf("Error: %s\n", c->errstr); diff --git a/examples/example-macosx.c b/examples/example-macosx.c new file mode 100644 index 00000000..bc84ed5b --- /dev/null +++ b/examples/example-macosx.c @@ -0,0 +1,66 @@ +// +// Created by Дмитрий Бахвалов on 13.07.15. +// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. +// + +#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; + } + CFRunLoopStop(CFRunLoopGetCurrent()); + printf("Disconnected...\n"); +} + +int main (int argc, char **argv) { + signal(SIGPIPE, SIG_IGN); + + CFRunLoopRef loop = CFRunLoopGetCurrent(); + if( !loop ) { + printf("Error: Cannot get current run loop\n"); + return 1; + } + + redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); + if (c->err) { + /* Let *c leak for now... */ + printf("Error: %s\n", c->errstr); + return 1; + } + + redisMacOSAttach(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"); + + CFRunLoopRun(); + + return 0; +} + diff --git a/examples/example-qt.cpp b/examples/example-qt.cpp new file mode 100644 index 00000000..f524c3f3 --- /dev/null +++ b/examples/example-qt.cpp @@ -0,0 +1,46 @@ +#include +using namespace std; + +#include +#include + +#include "example-qt.h" + +void getCallback(redisAsyncContext *, void * r, void * privdata) { + + redisReply * reply = static_cast(r); + ExampleQt * ex = static_cast(privdata); + if (reply == nullptr || ex == nullptr) return; + + cout << "key: " << reply->str << endl; + + ex->finish(); +} + +void ExampleQt::run() { + + m_ctx = redisAsyncConnect("localhost", 6379); + + if (m_ctx->err) { + cerr << "Error: " << m_ctx->errstr << endl; + redisAsyncFree(m_ctx); + emit finished(); + } + + m_adapter.setContext(m_ctx); + + redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); + redisAsyncCommand(m_ctx, getCallback, this, "GET key"); +} + +int main (int argc, char **argv) { + + QCoreApplication app(argc, argv); + + ExampleQt example(argv[argc-1]); + + QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); + QTimer::singleShot(0, &example, SLOT(run())); + + return app.exec(); +} diff --git a/examples/example-qt.h b/examples/example-qt.h new file mode 100644 index 00000000..374f4766 --- /dev/null +++ b/examples/example-qt.h @@ -0,0 +1,32 @@ +#ifndef __HIREDIS_EXAMPLE_QT_H +#define __HIREDIS_EXAMPLE_QT_H + +#include + +class ExampleQt : public QObject { + + Q_OBJECT + + public: + ExampleQt(const char * value, QObject * parent = 0) + : QObject(parent), m_value(value) {} + + signals: + void finished(); + + public slots: + void run(); + + private: + void finish() { emit finished(); } + + private: + const char * m_value; + redisAsyncContext * m_ctx; + RedisQtAdapter m_adapter; + + friend + void getCallback(redisAsyncContext *, void *, void *); +}; + +#endif /* !__HIREDIS_EXAMPLE_QT_H */ diff --git a/examples/example-ssl.c b/examples/example-ssl.c new file mode 100644 index 00000000..81f4648c --- /dev/null +++ b/examples/example-ssl.c @@ -0,0 +1,97 @@ +#include +#include +#include + +#include +#include + +int main(int argc, char **argv) { + unsigned int j; + redisContext *c; + redisReply *reply; + if (argc < 4) { + printf("Usage: %s [ca]\n", argv[0]); + exit(1); + } + const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + int port = atoi(argv[2]); + const char *cert = argv[3]; + const char *key = argv[4]; + const char *ca = argc > 4 ? argv[5] : NULL; + + struct timeval tv = { 1, 500000 }; // 1.5 seconds + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, hostname, port); + options.timeout = &tv; + c = redisConnectWithOptions(&options); + + 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); + } + + if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { + printf("Couldn't initialize SSL!\n"); + printf("Error: %s\n", c->errstr); + redisFree(c); + 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,"%u",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/examples/example.c b/examples/example.c index 25226a80..0e93fc8b 100644 --- a/examples/example.c +++ b/examples/example.c @@ -5,14 +5,27 @@ #include int main(int argc, char **argv) { - unsigned int j; + unsigned int j, isunix = 0; redisContext *c; redisReply *reply; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; + + if (argc > 2) { + if (*argv[2] == 'u' || *argv[2] == 'U') { + isunix = 1; + /* in this case, host is the path to the unix socket */ + printf("Will connect to unix socket @%s\n", hostname); + } + } + int port = (argc > 2) ? atoi(argv[2]) : 6379; struct timeval timeout = { 1, 500000 }; // 1.5 seconds - c = redisConnectWithTimeout(hostname, port, timeout); + if (isunix) { + c = redisConnectUnixWithTimeout(hostname, timeout); + } else { + c = redisConnectWithTimeout(hostname, port, timeout); + } if (c == NULL || c->err) { if (c) { printf("Connection error: %s\n", c->errstr); @@ -57,7 +70,7 @@ int main(int argc, char **argv) { for (j = 0; j < 10; j++) { char buf[64]; - snprintf(buf,64,"%d",j); + snprintf(buf,64,"%u",j); reply = redisCommand(c,"LPUSH mylist element-%s", buf); freeReplyObject(reply); } diff --git a/fmacros.h b/fmacros.h index a3b1df03..3227faaf 100644 --- a/fmacros.h +++ b/fmacros.h @@ -1,23 +1,12 @@ #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 +#define _POSIX_C_SOURCE 200112L -#if __APPLE__ && __MACH__ -#define _OSX +#if defined(__APPLE__) && defined(__MACH__) +/* Enable TCP_KEEPALIVE */ +#define _DARWIN_C_SOURCE #endif #endif diff --git a/hircluster.c b/hircluster.c index b0519364..07095db6 100644 --- a/hircluster.c +++ b/hircluster.c @@ -6,6 +6,7 @@ #include #include +#include "win32.h" #include "hircluster.h" #include "hiutil.h" #include "adlist.h" @@ -13,6 +14,18 @@ #include "command.h" #include "dict.c" +// Cluster errors are offset by 100 to be sufficiently out of range of +// standard Redis errors +#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 100 + +#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" + #define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" #define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" @@ -205,7 +218,7 @@ static void __redisClusterSetError(redisClusterContext *cc, int type, const char } else { /* Only REDIS_ERR_IO may lack a description! */ assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, cc->errstr, sizeof(cc->errstr)); + strerror_r(errno, cc->errstr, sizeof(cc->errstr)); } } @@ -652,7 +665,7 @@ static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) static int cluster_slot_start_cmp(const void *t1, const void *t2) { - const cluster_slot **s1 = t1, **s2 = t2; + const cluster_slot * const *s1 = t1, * const *s2 = t2; return (*s1)->start > (*s2)->start?1:-1; } @@ -4241,7 +4254,7 @@ static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, } else { /* Only REDIS_ERR_IO may lack a description! */ assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, acc->errstr, sizeof(acc->errstr)); + strerror_r(errno, acc->errstr, sizeof(acc->errstr)); } } @@ -4364,7 +4377,7 @@ redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, } ac->data = node; - ac->dataHandler = unlinkAsyncContextAndNode; + ac->cleanup = unlinkAsyncContextAndNode; node->acon = ac; return ac; diff --git a/hiredis.c b/hiredis.c index 1c38877e..8e438f2d 100644 --- a/hiredis.c +++ b/hiredis.c @@ -34,7 +34,6 @@ #include "fmacros.h" #include #include -#include #include #include #include @@ -42,12 +41,24 @@ #include "hiredis.h" #include "net.h" #include "sds.h" +#include "async.h" +#include "win32.h" + +static redisContextFuncs redisContextDefaultFuncs = { + .free_privdata = NULL, + .async_read = redisAsyncRead, + .async_write = redisAsyncWrite, + .read = redisNetRead, + .write = redisNetWrite +}; 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 *createArrayObject(const redisReadTask *task, size_t elements); static void *createIntegerObject(const redisReadTask *task, long long value); +static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); static void *createNilObject(const redisReadTask *task); +static void *createBoolObject(const redisReadTask *task, int bval); /* Default set of functions to build the reply. Keep in mind that such a * function returning NULL is interpreted as OOM. */ @@ -55,7 +66,9 @@ static redisReplyObjectFunctions defaultFunctions = { createStringObject, createArrayObject, createIntegerObject, + createDoubleObject, createNilObject, + createBoolObject, freeReplyObject }; @@ -82,18 +95,19 @@ void freeReplyObject(void *reply) { case REDIS_REPLY_INTEGER: break; /* Nothing to free */ case REDIS_REPLY_ARRAY: + case REDIS_REPLY_MAP: + case REDIS_REPLY_SET: if (r->element != NULL) { for (j = 0; j < r->elements; j++) - if (r->element[j] != NULL) - freeReplyObject(r->element[j]); + 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); + case REDIS_REPLY_DOUBLE: + free(r->str); break; } free(r); @@ -125,16 +139,18 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len if (task->parent) { parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); + assert(parent->type == REDIS_REPLY_ARRAY || + parent->type == REDIS_REPLY_MAP || + parent->type == REDIS_REPLY_SET); parent->element[task->idx] = r; } return r; } -static void *createArrayObject(const redisReadTask *task, int elements) { +static void *createArrayObject(const redisReadTask *task, size_t elements) { redisReply *r, *parent; - r = createReplyObject(REDIS_REPLY_ARRAY); + r = createReplyObject(task->type); if (r == NULL) return NULL; @@ -150,7 +166,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) { if (task->parent) { parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); + assert(parent->type == REDIS_REPLY_ARRAY || + parent->type == REDIS_REPLY_MAP || + parent->type == REDIS_REPLY_SET); parent->element[task->idx] = r; } return r; @@ -167,7 +185,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) { if (task->parent) { parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); + assert(parent->type == REDIS_REPLY_ARRAY || + parent->type == REDIS_REPLY_MAP || + parent->type == REDIS_REPLY_SET); + parent->element[task->idx] = r; + } + return r; +} + +static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_DOUBLE); + if (r == NULL) + return NULL; + + r->dval = value; + r->str = malloc(len+1); + if (r->str == NULL) { + freeReplyObject(r); + return NULL; + } + + /* The double reply also has the original protocol string representing a + * double as a null terminated string. This way the caller does not need + * to format back for string conversion, especially since Redis does efforts + * to make the string more human readable avoiding the calssical double + * decimal string conversion artifacts. */ + memcpy(r->str, str, len); + r->str[len] = '\0'; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY || + parent->type == REDIS_REPLY_MAP || + parent->type == REDIS_REPLY_SET); parent->element[task->idx] = r; } return r; @@ -182,7 +234,28 @@ static void *createNilObject(const redisReadTask *task) { if (task->parent) { parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY); + assert(parent->type == REDIS_REPLY_ARRAY || + parent->type == REDIS_REPLY_MAP || + parent->type == REDIS_REPLY_SET); + parent->element[task->idx] = r; + } + return r; +} + +static void *createBoolObject(const redisReadTask *task, int bval) { + redisReply *r, *parent; + + r = createReplyObject(REDIS_REPLY_BOOL); + if (r == NULL) + return NULL; + + r->integer = bval != 0; + + if (task->parent) { + parent = task->parent->obj; + assert(parent->type == REDIS_REPLY_ARRAY || + parent->type == REDIS_REPLY_MAP || + parent->type == REDIS_REPLY_SET); parent->element[task->idx] = r; } return r; @@ -432,11 +505,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) { } 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); + free(cmd); return error_type; } @@ -507,7 +576,7 @@ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, 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 = sdscatfmt(cmd, "$%u\r\n", len); cmd = sdscatlen(cmd, argv[j], len); cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1); } @@ -581,7 +650,7 @@ void __redisSetError(redisContext *c, int type, const char *str) { } else { /* Only REDIS_ERR_IO may lack a description! */ assert(type == REDIS_ERR_IO); - __redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); + strerror_r(errno, c->errstr, sizeof(c->errstr)); } } @@ -589,53 +658,48 @@ redisReader *redisReaderCreate(void) { return redisReaderCreateWithFunctions(&defaultFunctions); } -static redisContext *redisContextInit(void) { +static redisContext *redisContextInit(const redisOptions *options) { redisContext *c; - c = calloc(1,sizeof(redisContext)); + c = calloc(1, sizeof(*c)); if (c == NULL) return NULL; - c->err = 0; - c->errstr[0] = '\0'; + c->funcs = &redisContextDefaultFuncs; c->obuf = sdsempty(); c->reader = redisReaderCreate(); - c->tcp.host = NULL; - c->tcp.source_addr = NULL; - c->unix_sock.path = NULL; - c->timeout = NULL; + c->fd = REDIS_INVALID_FD; if (c->obuf == NULL || c->reader == NULL) { redisFree(c); return NULL; } - + (void)options; /* options are used in other functions */ 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); + redisNetClose(c); + + sdsfree(c->obuf); + redisReaderFree(c->reader); + free(c->tcp.host); + free(c->tcp.source_addr); + free(c->unix_sock.path); + free(c->timeout); + free(c->saddr); + if (c->funcs->free_privdata) { + c->funcs->free_privdata(c->privdata); + } + memset(c, 0xff, sizeof(*c)); free(c); } -int redisFreeKeepFd(redisContext *c) { - int fd = c->fd; - c->fd = -1; +redisFD redisFreeKeepFd(redisContext *c) { + redisFD fd = c->fd; + c->fd = REDIS_INVALID_FD; redisFree(c); return fd; } @@ -644,10 +708,13 @@ int redisReconnect(redisContext *c) { c->err = 0; memset(c->errstr, '\0', strlen(c->errstr)); - if (c->fd > 0) { - close(c->fd); + if (c->privdata && c->funcs->free_privdata) { + c->funcs->free_privdata(c->privdata); + c->privdata = NULL; } + redisNetClose(c); + sdsfree(c->obuf); redisReaderFree(c->reader); @@ -668,108 +735,107 @@ int redisReconnect(redisContext *c) { return REDIS_ERR; } +redisContext *redisConnectWithOptions(const redisOptions *options) { + redisContext *c = redisContextInit(options); + if (c == NULL) { + return NULL; + } + if (!(options->options & REDIS_OPT_NONBLOCK)) { + c->flags |= REDIS_BLOCK; + } + if (options->options & REDIS_OPT_REUSEADDR) { + c->flags |= REDIS_REUSEADDR; + } + if (options->options & REDIS_OPT_NOAUTOFREE) { + c->flags |= REDIS_NO_AUTO_FREE; + } + + if (options->type == REDIS_CONN_TCP) { + redisContextConnectBindTcp(c, options->endpoint.tcp.ip, + options->endpoint.tcp.port, options->timeout, + options->endpoint.tcp.source_addr); + } else if (options->type == REDIS_CONN_UNIX) { + redisContextConnectUnix(c, options->endpoint.unix_socket, + options->timeout); + } else if (options->type == REDIS_CONN_USERFD) { + c->fd = options->endpoint.fd; + c->flags |= REDIS_CONNECTED; + } else { + // Unknown type - FIXME - FREE + return NULL; + } + if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { + redisContextSetTimeout(c, *options->timeout); + } + return c; +} + /* 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + return redisConnectWithOptions(&options); } 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.timeout = &tv; + return redisConnectWithOptions(&options); } 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, ip, port); + options.endpoint.tcp.source_addr = source_addr; + options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; + return redisConnectWithOptions(&options); } redisContext *redisConnectUnix(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags |= REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + return redisConnectWithOptions(&options); } 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; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + options.timeout = &tv; + return redisConnectWithOptions(&options); } redisContext *redisConnectUnixNonBlock(const char *path) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->flags &= ~REDIS_BLOCK; - redisContextConnectUnix(c,path,NULL); - return c; + redisOptions options = {0}; + REDIS_OPTIONS_SET_UNIX(&options, path); + options.options |= REDIS_OPT_NONBLOCK; + return redisConnectWithOptions(&options); } -redisContext *redisConnectFd(int fd) { - redisContext *c; - - c = redisContextInit(); - if (c == NULL) - return NULL; - - c->fd = fd; - c->flags |= REDIS_BLOCK | REDIS_CONNECTED; - return c; +redisContext *redisConnectFd(redisFD fd) { + redisOptions options = {0}; + options.type = REDIS_CONN_USERFD; + options.endpoint.fd = fd; + return redisConnectWithOptions(&options); } /* Set read/write timeout on a blocking socket. */ @@ -789,7 +855,7 @@ int redisEnableKeepAlive(redisContext *c) { /* 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 + * After this function is called, you may use redisGetReplyFromReader to * see if there is a reply available. */ int redisBufferRead(redisContext *c) { char buf[1024*16]; @@ -799,30 +865,15 @@ int redisBufferRead(redisContext *c) { 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 { -#if 1 //shenzheng 2017-5-22 redis cluster - if (errno == EWOULDBLOCK && (c->flags & REDIS_BLOCK)) { - __redisSetError(c,REDIS_ERR_TIMEOUT,"Socket timeout"); - } else { -#endif //shenzheng 2017-5-22 redis cluster - __redisSetError(c,REDIS_ERR_IO,NULL); -#if 1 //shenzheng 2017-5-22 redis cluster - } -#endif //shenzheng 2017-5-22 redis cluster + nread = c->funcs->read(c, buf, sizeof(buf)); + if (nread > 0) { + if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { + __redisSetError(c, c->reader->err, c->reader->errstr); return REDIS_ERR; + } else { } - } else if (nread == 0) { - __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection"); + } else if (nread < 0) { 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; } @@ -830,36 +881,22 @@ int redisBufferRead(redisContext *c) { /* 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 + * successfully 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 + * Returns REDIS_ERR if an error occurred 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 { -#if 1 //shenzheng 2017-5-22 redis cluster - if (errno == EWOULDBLOCK && (c->flags & REDIS_BLOCK)) { - __redisSetError(c,REDIS_ERR_TIMEOUT,"Socket timeout"); - } else { -#endif //shenzheng 2017-5-22 redis cluster - __redisSetError(c,REDIS_ERR_IO,NULL); -#if 1 //shenzheng 2017-5-22 redis cluster - } -#endif //shenzheng 2017-5-22 redis cluster - return REDIS_ERR; - } + int nwritten = c->funcs->write(c); + if (nwritten < 0) { + return REDIS_ERR; } else if (nwritten > 0) { if (nwritten == (signed)sdslen(c->obuf)) { sdsfree(c->obuf); @@ -908,8 +945,13 @@ int redisGetReply(redisContext *c, void **reply) { } while (aux == NULL); } - /* Set reply object */ - if (reply != NULL) *reply = aux; + /* Set reply or free it if we were passed NULL */ + if (reply != NULL) { + *reply = aux; + } else { + freeReplyObject(aux); + } + return REDIS_OK; } @@ -1000,7 +1042,7 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s * 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 + * Returns the reply when a reply was successfully retrieved. Returns NULL * otherwise. When NULL is returned in a blocking context, the error field * in the context will be set. */ @@ -1023,9 +1065,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) { void *redisCommand(redisContext *c, const char *format, ...) { va_list ap; - void *reply = NULL; va_start(ap,format); - reply = redisvCommand(c,format,ap); + void *reply = redisvCommand(c,format,ap); va_end(ap); return reply; } diff --git a/hiredis.h b/hiredis.h index 87f7366f..bdc6670e 100644 --- a/hiredis.h +++ b/hiredis.h @@ -35,13 +35,18 @@ #define __HIREDIS_H #include "read.h" #include /* for va_list */ +#ifndef _MSC_VER #include /* for struct timeval */ +#else +struct timeval; /* forward declaration */ +#endif #include /* uintXX_t, etc */ #include "sds.h" /* for sds */ #define HIREDIS_MAJOR 0 -#define HIREDIS_MINOR 13 -#define HIREDIS_PATCH 1 +#define HIREDIS_MINOR 14 +#define HIREDIS_PATCH 0 +#define HIREDIS_SONAME 0.14 /* Connection type can be blocking or non-blocking and is set in the * least significant bit of the flags field in redisContext. */ @@ -73,36 +78,18 @@ /* Flag that is set when we should set SO_REUSEADDR before calling bind() */ #define REDIS_REUSEADDR 0x80 +/** + * Flag that indicates the user does not want the context to + * be automatically freed upon error + */ +#define REDIS_NO_AUTO_FREE 0x200 + #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 @@ -111,8 +98,10 @@ extern "C" { 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 */ + double dval; /* The double when type is REDIS_REPLY_DOUBLE */ + size_t len; /* Length of string */ + char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING + and REDIS_REPLY_DOUBLE (in additional to dval). */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ } redisReply; @@ -133,13 +122,92 @@ void redisFreeSdsCommand(sds cmd); enum redisConnectionType { REDIS_CONN_TCP, REDIS_CONN_UNIX, + REDIS_CONN_USERFD }; +struct redisSsl; + +#define REDIS_OPT_NONBLOCK 0x01 +#define REDIS_OPT_REUSEADDR 0x02 + +/** + * Don't automatically free the async object on a connection failure, + * or other implicit conditions. Only free on an explicit call to disconnect() or free() + */ +#define REDIS_OPT_NOAUTOFREE 0x04 + +/* In Unix systems a file descriptor is a regular signed int, with -1 + * representing an invalid descriptor. In Windows it is a SOCKET + * (32- or 64-bit unsigned integer depending on the architecture), where + * all bits set (~0) is INVALID_SOCKET. */ +#ifndef _WIN32 +typedef int redisFD; +#define REDIS_INVALID_FD -1 +#else +#ifdef _WIN64 +typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */ +#else +typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */ +#endif +#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */ +#endif + +typedef struct { + /* + * the type of connection to use. This also indicates which + * `endpoint` member field to use + */ + int type; + /* bit field of REDIS_OPT_xxx */ + int options; + /* timeout value. if NULL, no timeout is used */ + const struct timeval *timeout; + union { + /** use this field for tcp/ip connections */ + struct { + const char *source_addr; + const char *ip; + int port; + } tcp; + /** use this field for unix domain sockets */ + const char *unix_socket; + /** + * use this field to have hiredis operate an already-open + * file descriptor */ + redisFD fd; + } endpoint; +} redisOptions; + +/** + * Helper macros to initialize options to their specified fields. + */ +#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ + (opts)->type = REDIS_CONN_TCP; \ + (opts)->endpoint.tcp.ip = ip_; \ + (opts)->endpoint.tcp.port = port_; + +#define REDIS_OPTIONS_SET_UNIX(opts, path) \ + (opts)->type = REDIS_CONN_UNIX; \ + (opts)->endpoint.unix_socket = path; + +struct redisAsyncContext; +struct redisContext; + +typedef struct redisContextFuncs { + void (*free_privdata)(void *); + void (*async_read)(struct redisAsyncContext *); + void (*async_write)(struct redisAsyncContext *); + int (*read)(struct redisContext *, char *, size_t); + int (*write)(struct redisContext *); +} redisContextFuncs; + /* Context for a connection to Redis */ typedef struct redisContext { + const redisContextFuncs *funcs; /* Function table */ + int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ - int fd; + redisFD fd; int flags; char *obuf; /* Write buffer */ redisReader *reader; /* Protocol reader */ @@ -156,8 +224,16 @@ typedef struct redisContext { struct { char *path; } unix_sock; + + /* For non-blocking connect */ + struct sockadr *saddr; + size_t addrlen; + + /* Additional private data for hiredis addons such as SSL */ + void *privdata; } redisContext; +redisContext *redisConnectWithOptions(const redisOptions *options); 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); @@ -168,7 +244,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); -redisContext *redisConnectFd(int fd); +redisContext *redisConnectFd(redisFD fd); /** * Reconnect the given context using the saved information. @@ -177,14 +253,14 @@ redisContext *redisConnectFd(int fd); * 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. + * Returns REDIS_OK on successful 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); +redisFD redisFreeKeepFd(redisContext *c); int redisBufferRead(redisContext *c); int redisBufferWrite(redisContext *c, int *done); diff --git a/hiredis.pc.in b/hiredis.pc.in new file mode 100644 index 00000000..140b040f --- /dev/null +++ b/hiredis.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include +pkgincludedir=${includedir}/hiredis + +Name: hiredis +Description: Minimalistic C client library for Redis. +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -lhiredis +Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 diff --git a/hiredis_ssl.h b/hiredis_ssl.h new file mode 100644 index 00000000..f844f954 --- /dev/null +++ b/hiredis_ssl.h @@ -0,0 +1,53 @@ + +/* + * Copyright (c) 2019, Redis Labs + * + * 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_SSL_H +#define __HIREDIS_SSL_H + +/* This is the underlying struct for SSL in ssl.h, which is not included to + * keep build dependencies short here. + */ +struct ssl_st; + +/** + * Secure the connection using SSL. This should be done before any command is + * executed on the connection. + */ +int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, + const char *keypath, const char *servername); + +/** + * Initiate SSL/TLS negotiation on a provided context. + */ + +int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); + +#endif /* __HIREDIS_SSL_H */ diff --git a/hiredis_ssl.pc.in b/hiredis_ssl.pc.in new file mode 100644 index 00000000..588a978a --- /dev/null +++ b/hiredis_ssl.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include +pkgincludedir=${includedir}/hiredis + +Name: hiredis_ssl +Description: SSL Support for hiredis. +Version: @PROJECT_VERSION@ +Requires: hiredis +Libs: -L${libdir} -lhiredis_ssl +Libs.private: -lssl -lcrypto diff --git a/hiutil.c b/hiutil.c index d10cdace..087d04c7 100644 --- a/hiutil.c +++ b/hiutil.c @@ -2,23 +2,23 @@ #include #include #include -#include #include #include +#include "win32.h" +#ifndef WIN32 #include -#include - #include #include - - +#endif +#include #include "hiutil.h" #ifdef HI_HAVE_BACKTRACE # include #endif +#ifndef WIN32 int hi_set_blocking(int sd) { @@ -161,6 +161,7 @@ hi_get_rcvbuf(int sd) return size; } +#endif int _hi_atoi(uint8_t *line, size_t n) @@ -430,6 +431,7 @@ _scnprintf(char *buf, size_t size, const char *fmt, ...) return n; } +#ifndef WIN32 /* * Send n bytes on a blocking descriptor */ @@ -491,6 +493,7 @@ _hi_recvn(int sd, void *vptr, size_t n) return (ssize_t)(n - nleft); } +#endif /* * Return the current time in microseconds since Epoch @@ -498,8 +501,18 @@ _hi_recvn(int sd, void *vptr, size_t n) int64_t hi_usec_now(void) { - struct timeval now; int64_t usec; +#ifdef _MSC_VER + LARGE_INTEGER counter, frequency; + + if (!QueryPerformanceCounter(&counter) || !QueryPerformanceFrequency(&frequency)) + { + return -1; + } + + usec = counter.QuadPart * 1000000 / frequency.QuadPart; +#else + struct timeval now; int status; status = gettimeofday(&now, NULL); @@ -508,6 +521,7 @@ hi_usec_now(void) } usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; +#endif return usec; } diff --git a/hiutil.h b/hiutil.h index d9e1ddb0..128f1407 100644 --- a/hiutil.h +++ b/hiutil.h @@ -4,6 +4,10 @@ #include #include #include +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif #define HI_OK 0 #define HI_ERROR -1 @@ -134,6 +138,7 @@ typedef int rstatus_t; /* return type */ _uint_len((uint32_t)_n) +#ifndef WIN32 int hi_set_blocking(int sd); int hi_set_nonblocking(int sd); int hi_set_reuseaddr(int sd); @@ -144,6 +149,7 @@ 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); +#endif int _hi_atoi(uint8_t *line, size_t n); void _hi_itoa(uint8_t *s, int num); @@ -186,6 +192,7 @@ void _hi_free(void *ptr, const char *name, int line); #define hi_strndup(_s, _n) \ strndup((char *)(_s), (size_t)(_n)); +#ifndef WIN32 /* * Wrappers to send or receive n byte message on a blocking * socket descriptor. @@ -195,6 +202,7 @@ void _hi_free(void *ptr, const char *name, int line); #define hi_recvn(_s, _b, _n) \ _hi_recvn(_s, _b, (size_t)(_n)) +#endif /* * Wrappers to read or write data to/from (multiple) buffers @@ -212,8 +220,10 @@ void _hi_free(void *ptr, const char *name, int line); #define hi_writev(_d, _b, _n) \ writev(_d, _b, (int)(_n)) +#ifndef WIN32 ssize_t _hi_sendn(int sd, const void *vptr, size_t n); ssize_t _hi_recvn(int sd, void *vptr, size_t n); +#endif /* * Wrappers for defining custom assert based on whether macro diff --git a/net.c b/net.c index 60a2dc75..e5f40b0a 100644 --- a/net.c +++ b/net.c @@ -34,43 +34,72 @@ #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" +#include "sockcompat.h" +#include "win32.h" /* Defined in hiredis.c */ void __redisSetError(redisContext *c, int type, const char *str); -static void redisContextCloseFd(redisContext *c) { - if (c && c->fd >= 0) { +void redisNetClose(redisContext *c) { + if (c && c->fd != REDIS_INVALID_FD) { close(c->fd); - c->fd = -1; + c->fd = REDIS_INVALID_FD; } } +int redisNetRead(redisContext *c, char *buf, size_t bufcap) { + int nread = recv(c->fd, buf, bufcap, 0); + if (nread == -1) { + if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + return 0; + } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) { + /* especially in windows */ + __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); + return -1; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } else if (nread == 0) { + __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); + return -1; + } else { + return nread; + } +} + +int redisNetWrite(redisContext *c) { + int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); + if (nwritten < 0) { + if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { + /* Try again later */ + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } + return nwritten; +} + static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { + int errorno = errno; /* snprintf() may change errno */ 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); + strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); __redisSetError(c,type,buf); } @@ -78,15 +107,15 @@ 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); + redisNetClose(c); return REDIS_ERR; } return REDIS_OK; } static int redisCreateSocket(redisContext *c, int type) { - int s; - if ((s = socket(type, SOCK_STREAM, 0)) == -1) { + redisFD s; + if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); return REDIS_ERR; } @@ -100,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) { } static int redisSetBlocking(redisContext *c, int blocking) { +#ifndef _WIN32 int flags; /* Set the socket nonblocking. @@ -107,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) { * interrupted by a signal. */ if ((flags = fcntl(c->fd, F_GETFL)) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } @@ -118,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) { if (fcntl(c->fd, F_SETFL, flags) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); - redisContextCloseFd(c); + redisNetClose(c); + return REDIS_ERR; + } +#else + u_long mode = blocking ? 0 : 1; + if (ioctl(c->fd, FIONBIO, &mode) == -1) { + __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); + redisNetClose(c); return REDIS_ERR; } +#endif /* _WIN32 */ return REDIS_OK; } int redisKeepAlive(redisContext *c, int interval) { int val = 1; - int fd = c->fd; + redisFD fd = c->fd; if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); @@ -135,14 +173,13 @@ int redisKeepAlive(redisContext *c, int interval) { val = interval; -#ifdef _OSX +#if defined(__APPLE__) && defined(__MACH__) 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; @@ -170,7 +207,7 @@ 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); + redisNetClose(c); return REDIS_ERR; } return REDIS_OK; @@ -178,19 +215,15 @@ static int redisSetTcpNoDelay(redisContext *c) { #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; +static int redisContextTimeoutMsec(redisContext *c, long *result) +{ + const struct timeval *timeout = c->timeout; + long msec = -1; /* 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); + *result = msec; return REDIS_ERR; } @@ -201,33 +234,65 @@ static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) } } + *result = msec; + return REDIS_OK; +} + +static int redisContextWaitReady(redisContext *c, long msec) { + struct pollfd wfd[1]; + + wfd[0].fd = c->fd; + wfd[0].events = POLLOUT; + if (errno == EINPROGRESS) { int res; if ((res = poll(wfd, 1, msec)) == -1) { __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } else if (res == 0) { errno = ETIMEDOUT; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } - if (redisCheckSocketError(c) != REDIS_OK) + if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { + redisCheckSocketError(c); return REDIS_ERR; + } return REDIS_OK; } __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisContextCloseFd(c); + redisNetClose(c); return REDIS_ERR; } +int redisCheckConnectDone(redisContext *c, int *completed) { + int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); + if (rc == 0) { + *completed = 1; + return REDIS_OK; + } + switch (errno) { + case EISCONN: + *completed = 1; + return REDIS_OK; + case EALREADY: + case EINPROGRESS: + case EWOULDBLOCK: + *completed = 0; + return REDIS_OK; + default: + return REDIS_ERR; + } +} + int redisCheckSocketError(redisContext *c) { - int err = 0; + int err = 0, errno_saved = errno; socklen_t errlen = sizeof(err); if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { @@ -235,6 +300,10 @@ int redisCheckSocketError(redisContext *c) { return REDIS_ERR; } + if (err == 0) { + err = errno_saved; + } + if (err) { errno = err; __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); @@ -245,11 +314,18 @@ int redisCheckSocketError(redisContext *c) { } int redisContextSetTimeout(redisContext *c, const struct timeval tv) { - if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) { + const void *to_ptr = &tv; + size_t to_sz = sizeof(tv); +#ifdef _WIN32 + DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; + to_ptr = &timeout_msec; + to_sz = sizeof(timeout_msec); +#endif + if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); return REDIS_ERR; } - if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) { + if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) { __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); return REDIS_ERR; } @@ -259,13 +335,16 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout, const char *source_addr) { - int s, rv, n; + redisFD s; + int 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; + long timeout_msec = -1; + servinfo = NULL; c->connection_type = REDIS_CONN_TCP; c->tcp.port = port; @@ -277,8 +356,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, * 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); + free(c->tcp.host); c->tcp.host = strdup(addr); } @@ -291,11 +369,15 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, memcpy(c->timeout, timeout, sizeof(struct timeval)); } } else { - if (c->timeout) - free(c->timeout); + free(c->timeout); c->timeout = NULL; } + if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { + __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); + goto error; + } + if (source_addr == NULL) { free(c->tcp.source_addr); c->tcp.source_addr = NULL; @@ -323,7 +405,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, } for (p = servinfo; p != NULL; p = p->ai_next) { addrretry: - if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) + if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD) continue; c->fd = s; @@ -343,6 +425,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, sizeof(n)) < 0) { + freeaddrinfo(bservinfo); goto error; } } @@ -361,20 +444,35 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, goto error; } } + + /* For repeat connection */ + free(c->saddr); + c->saddr = malloc(p->ai_addrlen); + memcpy(c->saddr, p->ai_addr, p->ai_addrlen); + c->addrlen = p->ai_addrlen; + if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { - redisContextCloseFd(c); + redisNetClose(c); continue; - } else if (errno == EINPROGRESS && !blocking) { - /* This is ok. */ + } else if (errno == EINPROGRESS) { + if (blocking) { + goto wait_for_ready; + } + /* This is ok. + * Note that even when it's in blocking mode, we unset blocking + * for `connect()` + */ } else if (errno == EADDRNOTAVAIL && reuseaddr) { if (++reuses >= REDIS_CONNECT_RETRIES) { goto error; } else { + redisNetClose(c); goto addrretry; } } else { - if (redisContextWaitReady(c,c->timeout) != REDIS_OK) + wait_for_ready: + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) goto error; } } @@ -397,7 +495,10 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, error: rv = REDIS_ERR; end: - freeaddrinfo(servinfo); + if(servinfo) { + freeaddrinfo(servinfo); + } + return rv; // Need to return REDIS_OK if alright } @@ -413,10 +514,12 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, } int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { +#ifndef _WIN32 int blocking = (c->flags & REDIS_BLOCK); - struct sockaddr_un sa; + struct sockaddr_un *sa; + long timeout_msec = -1; - if (redisCreateSocket(c,AF_LOCAL) < 0) + if (redisCreateSocket(c,AF_UNIX) < 0) return REDIS_ERR; if (redisSetBlocking(c,0) != REDIS_OK) return REDIS_ERR; @@ -433,18 +536,22 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time memcpy(c->timeout, timeout, sizeof(struct timeval)); } } else { - if (c->timeout) - free(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 (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) + return REDIS_ERR; + + sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); + c->addrlen = sizeof(struct sockaddr_un); + sa->sun_family = AF_UNIX; + 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) + if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) return REDIS_ERR; } } @@ -455,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time c->flags |= REDIS_CONNECTED; return REDIS_OK; +#else + /* We currently do not support Unix sockets for Windows. */ + /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ + errno = EPROTONOSUPPORT; + return REDIS_ERR; +#endif /* _WIN32 */ } diff --git a/net.h b/net.h index 2f1a0bf8..a4393c06 100644 --- a/net.h +++ b/net.h @@ -37,9 +37,9 @@ #include "hiredis.h" -#if defined(__sun) -#define AF_LOCAL AF_UNIX -#endif +void redisNetClose(redisContext *c); +int redisNetRead(redisContext *c, char *buf, size_t bufcap); +int redisNetWrite(redisContext *c); int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); @@ -49,5 +49,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, const char *source_addr); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisKeepAlive(redisContext *c, int interval); +int redisCheckConnectDone(redisContext *c, int *completed); #endif diff --git a/read.c b/read.c index df1a467a..a264c376 100644 --- a/read.c +++ b/read.c @@ -29,19 +29,22 @@ * POSSIBILITY OF SUCH DAMAGE. */ - #include "fmacros.h" #include #include #ifndef _MSC_VER #include +#include #endif #include #include #include +#include +#include #include "read.h" #include "sds.h" +#include "win32.h" static void __redisReaderSetError(redisReader *r, int type, const char *str) { size_t len; @@ -52,11 +55,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) { } /* Clear input buffer on errors. */ - if (r->buf != NULL) { - sdsfree(r->buf); - r->buf = NULL; - r->pos = r->len = 0; - } + sdsfree(r->buf); + r->buf = NULL; + r->pos = r->len = 0; /* Reset task stack. */ r->ridx = -1; @@ -127,7 +128,7 @@ static char *seekNewline(char *s, size_t len) { * might not have a trailing NULL character. */ while (pos < _len) { while(pos < _len && s[pos] != '\r') pos++; - if (s[pos] != '\r') { + if (pos==_len) { /* Not found. */ return NULL; } else { @@ -143,33 +144,79 @@ static char *seekNewline(char *s, size_t len) { 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++; +/* Convert a string into a long long. Returns REDIS_OK if the string could be + * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value + * will be set to the parsed value when appropriate. + * + * Note that this function demands that the string strictly represents + * a long long: no spaces or other characters before or after the string + * representing the number are accepted, nor zeroes at the start if not + * for the string "0" representing the zero number. + * + * Because of its strictness, it is safe to use this function to check if + * you can convert a string into a long long, and obtain back the string + * from the number without any loss in the string representation. */ +static int string2ll(const char *s, size_t slen, long long *value) { + const char *p = s; + size_t plen = 0; + int negative = 0; + unsigned long long v; + + if (plen == slen) + return REDIS_ERR; + + /* Special case: first and only digit is 0. */ + if (slen == 1 && p[0] == '0') { + if (value != NULL) *value = 0; + return REDIS_OK; } - while ((c = *(s++)) != '\r') { - dec = c - '0'; - if (dec >= 0 && dec < 10) { - v *= 10; - v += dec; - } else { - /* Should not happen... */ - return -1; - } + if (p[0] == '-') { + negative = 1; + p++; plen++; + + /* Abort on only a negative sign. */ + if (plen == slen) + return REDIS_ERR; + } + + /* First digit should be 1-9, otherwise the string should just be 0. */ + if (p[0] >= '1' && p[0] <= '9') { + v = p[0]-'0'; + p++; plen++; + } else if (p[0] == '0' && slen == 1) { + *value = 0; + return REDIS_OK; + } else { + return REDIS_ERR; } - return mult*v; + while (plen < slen && p[0] >= '0' && p[0] <= '9') { + if (v > (ULLONG_MAX / 10)) /* Overflow. */ + return REDIS_ERR; + v *= 10; + + if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ + return REDIS_ERR; + v += p[0]-'0'; + + p++; plen++; + } + + /* Return if not all bytes were used. */ + if (plen < slen) + return REDIS_ERR; + + if (negative) { + if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ + return REDIS_ERR; + if (value != NULL) *value = -v; + } else { + if (v > LLONG_MAX) /* Overflow. */ + return REDIS_ERR; + if (value != NULL) *value = v; + } + return REDIS_OK; } static char *readLine(redisReader *r, int *_len) { @@ -198,7 +245,9 @@ static void moveToNextTask(redisReader *r) { cur = &(r->rstack[r->ridx]); prv = &(r->rstack[r->ridx-1]); - assert(prv->type == REDIS_REPLY_ARRAY); + assert(prv->type == REDIS_REPLY_ARRAY || + prv->type == REDIS_REPLY_MAP || + prv->type == REDIS_REPLY_SET); if (cur->idx == prv->elements-1) { r->ridx--; } else { @@ -220,10 +269,58 @@ static int processLineItem(redisReader *r) { 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 + if (r->fn && r->fn->createInteger) { + long long v; + if (string2ll(p, len, &v) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad integer value"); + return REDIS_ERR; + } + obj = r->fn->createInteger(cur,v); + } else { obj = (void*)REDIS_REPLY_INTEGER; + } + } else if (cur->type == REDIS_REPLY_DOUBLE) { + if (r->fn && r->fn->createDouble) { + char buf[326], *eptr; + double d; + + if ((size_t)len >= sizeof(buf)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Double value is too large"); + return REDIS_ERR; + } + + memcpy(buf,p,len); + buf[len] = '\0'; + + if (strcasecmp(buf,",inf") == 0) { + d = INFINITY; /* Positive infinite. */ + } else if (strcasecmp(buf,",-inf") == 0) { + d = -INFINITY; /* Negative infinite. */ + } else { + d = strtod((char*)buf,&eptr); + if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad double value"); + return REDIS_ERR; + } + } + obj = r->fn->createDouble(cur,d,buf,len); + } else { + obj = (void*)REDIS_REPLY_DOUBLE; + } + } else if (cur->type == REDIS_REPLY_NIL) { + if (r->fn && r->fn->createNil) + obj = r->fn->createNil(cur); + else + obj = (void*)REDIS_REPLY_NIL; + } else if (cur->type == REDIS_REPLY_BOOL) { + int bval = p[0] == 't' || p[0] == 'T'; + if (r->fn && r->fn->createBool) + obj = r->fn->createBool(cur,bval); + else + obj = (void*)REDIS_REPLY_BOOL; } else { /* Type will be error or status. */ if (r->fn && r->fn->createString) @@ -250,7 +347,7 @@ static int processBulkItem(redisReader *r) { redisReadTask *cur = &(r->rstack[r->ridx]); void *obj = NULL; char *p, *s; - long len; + long long len; unsigned long bytelen; int success = 0; @@ -259,9 +356,20 @@ static int processBulkItem(redisReader *r) { if (s != NULL) { p = r->buf+r->pos; bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ - len = readLongLong(p); - if (len < 0) { + if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad bulk string length"); + return REDIS_ERR; + } + + if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bulk string length out of range"); + return REDIS_ERR; + } + + if (len == -1) { /* The nil object can always be created. */ if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); @@ -299,12 +407,13 @@ static int processBulkItem(redisReader *r) { return REDIS_ERR; } -static int processMultiBulkItem(redisReader *r) { +/* Process the array, map and set types. */ +static int processAggregateItem(redisReader *r) { redisReadTask *cur = &(r->rstack[r->ridx]); void *obj; char *p; - long elements; - int root = 0; + long long elements; + int root = 0, len; /* Set error for nested multi bulks with depth > 7 */ if (r->ridx == 8) { @@ -313,10 +422,21 @@ static int processMultiBulkItem(redisReader *r) { return REDIS_ERR; } - if ((p = readLine(r,NULL)) != NULL) { - elements = readLongLong(p); + if ((p = readLine(r,&len)) != NULL) { + if (string2ll(p, len, &elements) == REDIS_ERR) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Bad multi-bulk length"); + return REDIS_ERR; + } + root = (r->ridx == 0); + if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) { + __redisReaderSetError(r,REDIS_ERR_PROTOCOL, + "Multi-bulk length out of range"); + return REDIS_ERR; + } + if (elements == -1) { if (r->fn && r->fn->createNil) obj = r->fn->createNil(cur); @@ -330,10 +450,12 @@ static int processMultiBulkItem(redisReader *r) { moveToNextTask(r); } else { + if (cur->type == REDIS_REPLY_MAP) elements *= 2; + if (r->fn && r->fn->createArray) obj = r->fn->createArray(cur,elements); else - obj = (void*)REDIS_REPLY_ARRAY; + obj = (void*)(long)cur->type; if (obj == NULL) { __redisReaderSetErrorOOM(r); @@ -381,12 +503,27 @@ static int processItem(redisReader *r) { case ':': cur->type = REDIS_REPLY_INTEGER; break; + case ',': + cur->type = REDIS_REPLY_DOUBLE; + break; + case '_': + cur->type = REDIS_REPLY_NIL; + break; case '$': cur->type = REDIS_REPLY_STRING; break; case '*': cur->type = REDIS_REPLY_ARRAY; break; + case '%': + cur->type = REDIS_REPLY_MAP; + break; + case '~': + cur->type = REDIS_REPLY_SET; + break; + case '#': + cur->type = REDIS_REPLY_BOOL; + break; default: __redisReaderSetErrorProtocolByte(r,*p); return REDIS_ERR; @@ -402,11 +539,16 @@ static int processItem(redisReader *r) { case REDIS_REPLY_ERROR: case REDIS_REPLY_STATUS: case REDIS_REPLY_INTEGER: + case REDIS_REPLY_DOUBLE: + case REDIS_REPLY_NIL: + case REDIS_REPLY_BOOL: return processLineItem(r); case REDIS_REPLY_STRING: return processBulkItem(r); case REDIS_REPLY_ARRAY: - return processMultiBulkItem(r); + case REDIS_REPLY_MAP: + case REDIS_REPLY_SET: + return processAggregateItem(r); default: assert(NULL); return REDIS_ERR; /* Avoid warning. */ @@ -416,12 +558,10 @@ static int processItem(redisReader *r) { redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { redisReader *r; - r = calloc(sizeof(redisReader),1); + r = calloc(1,sizeof(redisReader)); if (r == NULL) return NULL; - r->err = 0; - r->errstr[0] = '\0'; r->fn = fn; r->buf = sdsempty(); r->maxbuf = REDIS_READER_MAX_BUF; @@ -435,10 +575,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { } void redisReaderFree(redisReader *r) { + if (r == NULL) + return; if (r->reply != NULL && r->fn && r->fn->freeObject) r->fn->freeObject(r->reply); - if (r->buf != NULL) - sdsfree(r->buf); + sdsfree(r->buf); free(r); } @@ -517,8 +658,11 @@ int redisReaderGetReply(redisReader *r, void **reply) { /* Emit a reply when there is one. */ if (r->ridx == -1) { - if (reply != NULL) + if (reply != NULL) { *reply = r->reply; + } else if (r->reply != NULL && r->fn && r->fn->freeObject) { + r->fn->freeObject(r->reply); + } r->reply = NULL; } return REDIS_OK; diff --git a/read.h b/read.h index d578c131..af02aaf6 100644 --- a/read.h +++ b/read.h @@ -38,21 +38,15 @@ #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 + * error that occurred. 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_TIMEOUT 6 /* Timed out */ #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 -#if 1 //shenzheng 2017-5-22 redis cluster -#define REDIS_ERR_TIMEOUT 7 -#endif //shenzheng 2017-5-22 redis cluster - #define REDIS_REPLY_STRING 1 #define REDIS_REPLY_ARRAY 2 @@ -60,19 +54,17 @@ #define REDIS_REPLY_NIL 4 #define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_ERROR 6 +#define REDIS_REPLY_DOUBLE 7 +#define REDIS_REPLY_BOOL 8 +#define REDIS_REPLY_VERB 9 +#define REDIS_REPLY_MAP 9 +#define REDIS_REPLY_SET 10 +#define REDIS_REPLY_ATTR 11 +#define REDIS_REPLY_PUSH 12 +#define REDIS_REPLY_BIGNUM 13 #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 @@ -88,9 +80,11 @@ typedef struct redisReadTask { typedef struct redisReplyObjectFunctions { void *(*createString)(const redisReadTask*, char*, size_t); - void *(*createArray)(const redisReadTask*, int); + void *(*createArray)(const redisReadTask*, size_t); void *(*createInteger)(const redisReadTask*, long long); + void *(*createDouble)(const redisReadTask*, double, char*, size_t); void *(*createNil)(const redisReadTask*); + void *(*createBool)(const redisReadTask*, int); void (*freeObject)(void*); } redisReplyObjectFunctions; @@ -117,14 +111,9 @@ 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) +#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) +#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) +#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) #ifdef __cplusplus } diff --git a/sds.c b/sds.c index 5d55b779..6cf75841 100644 --- a/sds.c +++ b/sds.c @@ -1,6 +1,8 @@ -/* SDS (Simple Dynamic Strings), A C dynamic strings library. +/* SDSLib 2.0 -- A C dynamic strings library * - * Copyright (c) 2006-2014, Salvatore Sanfilippo + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,8 +35,36 @@ #include #include #include - #include "sds.h" +#include "sdsalloc.h" + +static inline int sdsHdrSize(char type) { + switch(type&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return sizeof(struct sdshdr5); + case SDS_TYPE_8: + return sizeof(struct sdshdr8); + case SDS_TYPE_16: + return sizeof(struct sdshdr16); + case SDS_TYPE_32: + return sizeof(struct sdshdr32); + case SDS_TYPE_64: + return sizeof(struct sdshdr64); + } + return 0; +} + +static inline char sdsReqType(size_t string_size) { + if (string_size < 32) + return SDS_TYPE_5; + if (string_size < 0xff) + return SDS_TYPE_8; + if (string_size < 0xffff) + return SDS_TYPE_16; + if (string_size < 0xffffffff) + return SDS_TYPE_32; + return SDS_TYPE_64; +} /* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. @@ -43,26 +73,65 @@ * 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"); + * 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); - } + void *sh; + sds s; + char type = sdsReqType(initlen); + /* Empty strings are usually created in order to append. Use type 8 + * since type 5 is not good at this. */ + if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; + int hdrlen = sdsHdrSize(type); + unsigned char *fp; /* flags pointer. */ + + sh = s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; - sh->len = initlen; - sh->free = 0; + if (!init) + memset(sh, 0, hdrlen+initlen+1); + s = (char*)sh+hdrlen; + fp = ((unsigned char*)s)-1; + switch(type) { + case SDS_TYPE_5: { + *fp = type | (initlen << SDS_TYPE_BITS); + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + sh->len = initlen; + sh->alloc = initlen; + *fp = type; + break; + } + } if (initlen && init) - memcpy(sh->buf, init, initlen); - sh->buf[initlen] = '\0'; - return (char*)sh->buf; + memcpy(s, init, initlen); + s[initlen] = '\0'; + return s; } /* Create an empty (zero length) sds string. Even in this case the string @@ -71,7 +140,7 @@ sds sdsempty(void) { return sdsnewlen("",0); } -/* Create a new sds string starting from a null termined C string. */ +/* Create a new sds string starting from a null terminated C string. */ sds sdsnew(const char *init) { size_t initlen = (init == NULL) ? 0 : strlen(init); return sdsnewlen(init, initlen); @@ -85,7 +154,7 @@ sds sdsdup(const sds 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)); + s_free((char*)s-sdsHdrSize(s[-1])); } /* Set the sds string length to the length as obtained with strlen(), so @@ -103,21 +172,17 @@ void sdsfree(sds s) { * 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; + sdssetlen(s, reallen); } -/* Modify an sds string on-place to make it empty (zero length). +/* Modify an sds string in-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'; + sdssetlen(s, 0); + s[0] = '\0'; } /* Enlarge the free space at the end of the sds string so that the caller @@ -127,23 +192,51 @@ void sdsclear(sds s) { * 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); + void *sh, *newsh; + size_t avail = sdsavail(s); size_t len, newlen; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + + /* Return ASAP if there is enough space left. */ + if (avail >= addlen) return s; - if (free >= addlen) return s; len = sdslen(s); - sh = (void*) (s-sizeof *sh); + sh = (char*)s-sdsHdrSize(oldtype); 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; + type = sdsReqType(newlen); + + /* Don't use type 5: the user is appending to the string and type 5 is + * not able to remember empty space, so sdsMakeRoomFor() must be called + * at every appending operation. */ + if (type == SDS_TYPE_5) type = SDS_TYPE_8; + + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+newlen+1); + if (newsh == NULL) { + s_free(sh); + return NULL; + } + s = (char*)newsh+hdrlen; + } else { + /* Since the header size changes, need to move the string forward, + * and can't use realloc */ + newsh = s_malloc(hdrlen+newlen+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, newlen); + return s; } /* Reallocate the sds string so that it has no free space at the end. The @@ -153,12 +246,29 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { * 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; + void *sh, *newsh; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + size_t len = sdslen(s); + sh = (char*)s-sdsHdrSize(oldtype); + + type = sdsReqType(len); + hdrlen = sdsHdrSize(type); + if (oldtype==type) { + newsh = s_realloc(sh, hdrlen+len+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + newsh = s_malloc(hdrlen+len+1); + if (newsh == NULL) return NULL; + memcpy((char*)newsh+hdrlen, s, len+1); + s_free(sh); + s = (char*)newsh+hdrlen; + s[-1] = type; + sdssetlen(s, len); + } + sdssetalloc(s, len); + return s; } /* Return the total size of the allocation of the specifed sds string, @@ -169,9 +279,14 @@ sds sdsRemoveFreeSpace(sds s) { * 4) The implicit null term. */ size_t sdsAllocSize(sds s) { - struct sdshdr *sh = (void*) (s-sizeof *sh); + size_t alloc = sdsalloc(s); + return sdsHdrSize(s[-1])+alloc+1; +} - return sizeof(*sh)+sh->len+sh->free+1; +/* Return the pointer of the actual SDS allocation (normally SDS strings + * are referenced by the start of the string buffer). */ +void *sdsAllocPtr(sds s) { + return (void*) (s-sdsHdrSize(s[-1])); } /* Increment the sds length and decrements the left free space at the @@ -198,13 +313,44 @@ size_t sdsAllocSize(sds s) { * 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'; + unsigned char flags = s[-1]; + size_t len; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char oldlen = SDS_TYPE_5_LEN(flags); + assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); + *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); + len = oldlen+incr; + break; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); + len = (sh->len += incr); + break; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); + len = (sh->len += incr); + break; + } + default: len = 0; /* Just to avoid compilation warnings. */ + } + s[len] = '\0'; } /* Grow the sds to have the specified length. Bytes that were not part of @@ -213,19 +359,15 @@ void sdsIncrLen(sds s, int incr) { * 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; + size_t curlen = sdslen(s); 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; + sdssetlen(s, len); return s; } @@ -235,15 +377,12 @@ sds sdsgrowzero(sds s, size_t len) { * 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; + sdssetlen(s, curlen+len); s[curlen+len] = '\0'; return s; } @@ -267,19 +406,13 @@ sds sdscatsds(sds s, const sds 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 (sdsalloc(s) < len) { + s = sdsMakeRoomFor(s,len-sdslen(s)); 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; + sdssetlen(s, len); return s; } @@ -293,7 +426,7 @@ sds sdscpy(sds s, const char *t) { * 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 + * The function returns the length of the null-terminated string * representation stored at 's'. */ #define SDS_LLSTR_SIZE 21 int sdsll2str(char *s, long long value) { @@ -356,27 +489,52 @@ int sdsull2str(char *s, unsigned long long v) { return l; } -/* Like sdscatpritf() but gets va_list instead of being variadic. */ +/* 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[SDS_LLSTR_SIZE]; + int len = sdsll2str(buf,value); + + return sdsnewlen(buf,len); +} + +/* Like sdscatprintf() 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; + char staticbuf[1024], *buf = staticbuf, *t; + size_t buflen = strlen(fmt)*2; - while(1) { - buf = malloc(buflen); + /* We try to start using a static buffer for speed. + * If not possible we revert to heap allocation. */ + if (buflen > sizeof(staticbuf)) { + buf = s_malloc(buflen); if (buf == NULL) return NULL; + } else { + buflen = sizeof(staticbuf); + } + + /* Try with buffers two times bigger every time we fail to + * fit the string in the current buffer size. */ + while(1) { buf[buflen-2] = '\0'; va_copy(cpy,ap); vsnprintf(buf, buflen, fmt, cpy); + va_end(cpy); if (buf[buflen-2] != '\0') { - free(buf); + if (buf != staticbuf) s_free(buf); buflen *= 2; + buf = s_malloc(buflen); + if (buf == NULL) return NULL; continue; } break; } + + /* Finally concat the obtained string to the SDS string and return it. */ t = sdscat(s, buf); - free(buf); + if (buf != staticbuf) s_free(buf); return t; } @@ -389,7 +547,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) { * Example: * * s = sdsnew("Sum is: "); - * s = sdscatprintf(s,"%d+%d = %d",a,b,a+b); + * 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: @@ -419,29 +577,25 @@ sds sdscatprintf(sds s, const char *fmt, ...) { * %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. */ + i = sdslen(s); /* Position of the next byte to write to dest str. */ while(*f) { char next, *str; - int l; + size_t l; long long num; unsigned long long unum; /* Make sure there is always space for at least 1 char. */ - if (sh->free == 0) { + if (sdsavail(s)==0) { s = sdsMakeRoomFor(s,1); - sh = (void*) (s-(sizeof(struct sdshdr))); + if (s == NULL) goto fmt_error; } switch(*f) { @@ -453,13 +607,12 @@ sds sdscatfmt(sds s, char const *fmt, ...) { case 'S': str = va_arg(ap,char*); l = (next == 's') ? strlen(str) : sdslen(str); - if (sh->free < l) { + if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); + if (s == NULL) goto fmt_error; } memcpy(s+i,str,l); - sh->len += l; - sh->free -= l; + sdsinclen(s,l); i += l; break; case 'i': @@ -471,49 +624,42 @@ sds sdscatfmt(sds s, char const *fmt, ...) { { char buf[SDS_LLSTR_SIZE]; l = sdsll2str(buf,num); - if (sh->free < l) { + if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); + if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); - sh->len += l; - sh->free -= l; + sdsinclen(s,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); + unum = va_arg(ap,unsigned long long); { char buf[SDS_LLSTR_SIZE]; l = sdsull2str(buf,unum); - if (sh->free < l) { + if (sdsavail(s) < l) { s = sdsMakeRoomFor(s,l); - sh = (void*) (s-(sizeof(struct sdshdr))); + if (s == NULL) goto fmt_error; } memcpy(s+i,buf,l); - sh->len += l; - sh->free -= l; + sdsinclen(s,l); i += l; } break; default: /* Handle %% and generally %. */ s[i++] = next; - sh->len += 1; - sh->free -= 1; + sdsinclen(s,1); break; } break; default: s[i++] = *f; - sh->len += 1; - sh->free -= 1; + sdsinclen(s,1); break; } f++; @@ -523,8 +669,11 @@ sds sdscatfmt(sds s, char const *fmt, ...) { /* Add null-term */ s[i] = '\0'; return s; -} +fmt_error: + va_end(ap); + return NULL; +} /* 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. @@ -535,25 +684,24 @@ sds sdscatfmt(sds s, char const *fmt, ...) { * Example: * * s = sdsnew("AA...AA.a.aa.aHelloWorld :::"); - * s = sdstrim(s,"A. :"); + * s = sdstrim(s,"Aa. :"); * printf("%s\n", s); * * Output will be just "Hello World". */ -void sdstrim(sds s, const char *cset) { - struct sdshdr *sh = (void*) (s-sizeof *sh); +sds sdstrim(sds s, const char *cset) { 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--; + while(ep > sp && 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; + if (s != sp) memmove(s, sp, len); + s[len] = '\0'; + sdssetlen(s,len); + return s; } /* Turn the string into a smaller (or equal) string containing only the @@ -573,7 +721,6 @@ void sdstrim(sds s, const char *cset) { * 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; @@ -596,10 +743,9 @@ void sdsrange(sds s, int start, int end) { } 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; + if (start && newlen) memmove(s, s+start, newlen); + s[newlen] = 0; + sdssetlen(s,newlen); } /* Apply tolower() to every character of the sds string 's'. */ @@ -620,8 +766,8 @@ void sdstoupper(sds s) { * * Return value: * - * 1 if s1 > s2. - * -1 if s1 < s2. + * positive if s1 > s2. + * negative 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 @@ -661,7 +807,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count if (seplen < 1 || len < 0) return NULL; - tokens = malloc(sizeof(sds)*slots); + tokens = s_malloc(sizeof(sds)*slots); if (tokens == NULL) return NULL; if (len == 0) { @@ -674,7 +820,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count sds *newtokens; slots *= 2; - newtokens = realloc(tokens,sizeof(sds)*slots); + newtokens = s_realloc(tokens,sizeof(sds)*slots); if (newtokens == NULL) goto cleanup; tokens = newtokens; } @@ -698,7 +844,7 @@ sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count { int i; for (i = 0; i < elements; i++) sdsfree(tokens[i]); - free(tokens); + s_free(tokens); *count = 0; return NULL; } @@ -709,26 +855,7 @@ 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)); + s_free(tokens); } /* Append to the sds string "s" an escaped string representation where @@ -902,13 +1029,21 @@ sds *sdssplitargs(const char *line, int *argc) { if (*p) p++; } /* add the token to the vector */ - vector = realloc(vector,((*argc)+1)*sizeof(char*)); - vector[*argc] = current; - (*argc)++; - current = NULL; + { + char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); + if (new_vector == NULL) { + s_free(vector); + return NULL; + } + + vector = new_vector; + vector[*argc] = current; + (*argc)++; + current = NULL; + } } else { /* Even on empty input string return something not NULL. */ - if (vector == NULL) vector = malloc(sizeof(void*)); + if (vector == NULL) vector = s_malloc(sizeof(void*)); return vector; } } @@ -916,7 +1051,7 @@ sds *sdssplitargs(const char *line, int *argc) { err: while((*argc)--) sdsfree(vector[*argc]); - free(vector); + s_free(vector); if (current) sdsfree(current); *argc = 0; return NULL; @@ -947,13 +1082,13 @@ sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen) { /* 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 sdsjoin(char **argv, int argc, char *sep) { 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); + if (j != argc-1) join = sdscat(join,sep); } return join; } @@ -970,13 +1105,23 @@ sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen) { return join; } -#ifdef SDS_TEST_MAIN +/* Wrappers to the allocators used by SDS. Note that SDS will actually + * just use the macros defined into sdsalloc.h in order to avoid to pay + * the overhead of function calls. Here we define these wrappers only for + * the programs SDS is linked to, if they want to touch the SDS internals + * even if they use a different allocator. */ +void *sds_malloc(size_t size) { return s_malloc(size); } +void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } +void sds_free(void *ptr) { s_free(ptr); } + +#if defined(SDS_TEST_MAIN) #include #include "testhelp.h" +#include "limits.h" -int main(void) { +#define UNUSED(x) (void)(x) +int sdsTest(void) { { - struct sdshdr *sh; sds x = sdsnew("foo"), y; test_cond("Create a string and obtain the length", @@ -1003,7 +1148,35 @@ int main(void) { 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) + sdslen(x) == 3 && memcmp(x,"123\0",4) == 0) + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); + test_cond("sdscatfmt() seems working in the base case", + sdslen(x) == 60 && + memcmp(x,"--Hello Hi! World -9223372036854775808," + "9223372036854775807--",60) == 0) + printf("[%s]\n",x); + + sdsfree(x); + x = sdsnew("--"); + x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); + test_cond("sdscatfmt() seems working with unsigned numbers", + sdslen(x) == 35 && + memcmp(x,"--4294967295,18446744073709551615--",35) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," x"); + test_cond("sdstrim() works when all chars match", + sdslen(x) == 0) + + sdsfree(x); + x = sdsnew(" x "); + sdstrim(x," "); + test_cond("sdstrim() works when a single char remains", + sdslen(x) == 1 && x[0] == 'x') sdsfree(x); x = sdsnew("xxciaoyyy"); @@ -1072,24 +1245,47 @@ int main(void) { memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0) { - int oldfree; + unsigned int oldfree; + char *p; + int step = 10, j, i; sdsfree(x); + sdsfree(y); 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_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); + + /* Run the test a few times in order to hit the first two + * SDS header types. */ + for (i = 0; i < 10; i++) { + int oldlen = sdslen(x); + x = sdsMakeRoomFor(x,step); + int type = x[-1]&SDS_TYPE_MASK; + + test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); + if (type != SDS_TYPE_5) { + test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); + oldfree = sdsavail(x); + } + p = x+oldlen; + for (j = 0; j < step; j++) { + p[j] = 'A'+j; + } + sdsIncrLen(x,step); + } + test_cond("sdsMakeRoomFor() content", + memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); + test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); + + sdsfree(x); } } test_report() return 0; } #endif + +#ifdef SDS_TEST_MAIN +int main(void) { + return sdsTest(); +} +#endif diff --git a/sds.h b/sds.h index 19a2abd3..3f9a9645 100644 --- a/sds.h +++ b/sds.h @@ -1,6 +1,8 @@ -/* SDS (Simple Dynamic Strings), A C dynamic strings library. +/* SDSLib 2.0 -- A C dynamic strings library * - * Copyright (c) 2006-2014, Salvatore Sanfilippo + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,38 +34,194 @@ #define __SDS_H #define SDS_MAX_PREALLOC (1024*1024) +#ifdef _MSC_VER +#define __attribute__(x) +#endif #include #include -#ifdef _MSC_VER -#include "win32.h" -#endif +#include typedef char *sds; -struct sdshdr { - int len; - int free; +/* Note: sdshdr5 is never used, we just access the flags byte directly. + * However is here to document the layout of type 5 SDS strings. */ +struct __attribute__ ((__packed__)) sdshdr5 { + unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr8 { + uint8_t len; /* used */ + uint8_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr16 { + uint16_t len; /* used */ + uint16_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr32 { + uint32_t len; /* used */ + uint32_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ + char buf[]; +}; +struct __attribute__ ((__packed__)) sdshdr64 { + uint64_t len; /* used */ + uint64_t alloc; /* excluding the header and null terminator */ + unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; +#define SDS_TYPE_5 0 +#define SDS_TYPE_8 1 +#define SDS_TYPE_16 2 +#define SDS_TYPE_32 3 +#define SDS_TYPE_64 4 +#define SDS_TYPE_MASK 7 +#define SDS_TYPE_BITS 3 +#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); +#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) +#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) + static inline size_t sdslen(const sds s) { - struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); - return sh->len; + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->len; + case SDS_TYPE_16: + return SDS_HDR(16,s)->len; + case SDS_TYPE_32: + return SDS_HDR(32,s)->len; + case SDS_TYPE_64: + return SDS_HDR(64,s)->len; + } + return 0; } static inline size_t sdsavail(const sds s) { - struct sdshdr *sh = (struct sdshdr *)(s-sizeof *sh); - return sh->free; + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: { + return 0; + } + case SDS_TYPE_8: { + SDS_HDR_VAR(8,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_16: { + SDS_HDR_VAR(16,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_32: { + SDS_HDR_VAR(32,s); + return sh->alloc - sh->len; + } + case SDS_TYPE_64: { + SDS_HDR_VAR(64,s); + return sh->alloc - sh->len; + } + } + return 0; +} + +static inline void sdssetlen(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len = (uint8_t)newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len = (uint16_t)newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len = (uint32_t)newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len = (uint64_t)newlen; + break; + } +} + +static inline void sdsinclen(sds s, size_t inc) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + { + unsigned char *fp = ((unsigned char*)s)-1; + unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; + *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); + } + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->len += (uint8_t)inc; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->len += (uint16_t)inc; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->len += (uint32_t)inc; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->len += (uint64_t)inc; + break; + } +} + +/* sdsalloc() = sdsavail() + sdslen() */ +static inline size_t sdsalloc(const sds s) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + return SDS_TYPE_5_LEN(flags); + case SDS_TYPE_8: + return SDS_HDR(8,s)->alloc; + case SDS_TYPE_16: + return SDS_HDR(16,s)->alloc; + case SDS_TYPE_32: + return SDS_HDR(32,s)->alloc; + case SDS_TYPE_64: + return SDS_HDR(64,s)->alloc; + } + return 0; +} + +static inline void sdssetalloc(sds s, size_t newlen) { + unsigned char flags = s[-1]; + switch(flags&SDS_TYPE_MASK) { + case SDS_TYPE_5: + /* Nothing to do, this type has no total allocation info. */ + break; + case SDS_TYPE_8: + SDS_HDR(8,s)->alloc = (uint8_t)newlen; + break; + case SDS_TYPE_16: + SDS_HDR(16,s)->alloc = (uint16_t)newlen; + break; + case SDS_TYPE_32: + SDS_HDR(32,s)->alloc = (uint32_t)newlen; + break; + case SDS_TYPE_64: + SDS_HDR(64,s)->alloc = (uint64_t)newlen; + break; + } } 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); @@ -80,7 +238,7 @@ sds sdscatprintf(sds s, const char *fmt, ...); #endif sds sdscatfmt(sds s, char const *fmt, ...); -void sdstrim(sds s, const char *cset); +sds sdstrim(sds s, const char *cset); void sdsrange(sds s, int start, int end); void sdsupdatelen(sds s); void sdsclear(sds s); @@ -93,7 +251,7 @@ 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 sdsjoin(char **argv, int argc, char *sep); sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); /* Low level functions exposed to the user API */ @@ -101,5 +259,18 @@ sds sdsMakeRoomFor(sds s, size_t addlen); void sdsIncrLen(sds s, int incr); sds sdsRemoveFreeSpace(sds s); size_t sdsAllocSize(sds s); +void *sdsAllocPtr(sds s); + +/* Export the allocator used by SDS to the program using SDS. + * Sometimes the program SDS is linked to, may use a different set of + * allocators, but may want to allocate or free things that SDS will + * respectively free or allocate. */ +void *sds_malloc(size_t size); +void *sds_realloc(void *ptr, size_t size); +void sds_free(void *ptr); + +#ifdef REDIS_TEST +int sdsTest(int argc, char *argv[]); +#endif #endif diff --git a/sdsalloc.h b/sdsalloc.h new file mode 100644 index 00000000..f43023c4 --- /dev/null +++ b/sdsalloc.h @@ -0,0 +1,42 @@ +/* SDSLib 2.0 -- A C dynamic strings library + * + * Copyright (c) 2006-2015, Salvatore Sanfilippo + * Copyright (c) 2015, Oran Agra + * Copyright (c) 2015, Redis Labs, Inc + * 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. + */ + +/* SDS allocator selection. + * + * This file is used in order to change the SDS allocator at compile time. + * Just define the following defines to what you want to use. Also add + * the include of your alternate allocator if needed (not needed in order + * to use the default libc allocator). */ + +#define s_malloc malloc +#define s_realloc realloc +#define s_free free diff --git a/sockcompat.c b/sockcompat.c new file mode 100644 index 00000000..f99d14b0 --- /dev/null +++ b/sockcompat.c @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2019, Marcus Geelnard + * + * 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. + */ + +#define REDIS_SOCKCOMPAT_IMPLEMENTATION +#include "sockcompat.h" + +#ifdef _WIN32 +static int _wsaErrorToErrno(int err) { + switch (err) { + case WSAEWOULDBLOCK: + return EWOULDBLOCK; + case WSAEINPROGRESS: + return EINPROGRESS; + case WSAEALREADY: + return EALREADY; + case WSAENOTSOCK: + return ENOTSOCK; + case WSAEDESTADDRREQ: + return EDESTADDRREQ; + case WSAEMSGSIZE: + return EMSGSIZE; + case WSAEPROTOTYPE: + return EPROTOTYPE; + case WSAENOPROTOOPT: + return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: + return EPROTONOSUPPORT; + case WSAEOPNOTSUPP: + return EOPNOTSUPP; + case WSAEAFNOSUPPORT: + return EAFNOSUPPORT; + case WSAEADDRINUSE: + return EADDRINUSE; + case WSAEADDRNOTAVAIL: + return EADDRNOTAVAIL; + case WSAENETDOWN: + return ENETDOWN; + case WSAENETUNREACH: + return ENETUNREACH; + case WSAENETRESET: + return ENETRESET; + case WSAECONNABORTED: + return ECONNABORTED; + case WSAECONNRESET: + return ECONNRESET; + case WSAENOBUFS: + return ENOBUFS; + case WSAEISCONN: + return EISCONN; + case WSAENOTCONN: + return ENOTCONN; + case WSAETIMEDOUT: + return ETIMEDOUT; + case WSAECONNREFUSED: + return ECONNREFUSED; + case WSAELOOP: + return ELOOP; + case WSAENAMETOOLONG: + return ENAMETOOLONG; + case WSAEHOSTUNREACH: + return EHOSTUNREACH; + case WSAENOTEMPTY: + return ENOTEMPTY; + default: + /* We just return a generic I/O error if we could not find a relevant error. */ + return EIO; + } +} + +static void _updateErrno(int success) { + errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError()); +} + +static int _initWinsock() { + static int s_initialized = 0; + if (!s_initialized) { + static WSADATA wsadata; + int err = WSAStartup(MAKEWORD(2,2), &wsadata); + if (err != 0) { + errno = _wsaErrorToErrno(err); + return 0; + } + s_initialized = 1; + } + return 1; +} + +int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { + /* Note: This function is likely to be called before other functions, so run init here. */ + if (!_initWinsock()) { + return EAI_FAIL; + } + + switch (getaddrinfo(node, service, hints, res)) { + case 0: return 0; + case WSATRY_AGAIN: return EAI_AGAIN; + case WSAEINVAL: return EAI_BADFLAGS; + case WSAEAFNOSUPPORT: return EAI_FAMILY; + case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY; + case WSAHOST_NOT_FOUND: return EAI_NONAME; + case WSATYPE_NOT_FOUND: return EAI_SERVICE; + case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE; + default: return EAI_FAIL; /* Including WSANO_RECOVERY */ + } +} + +const char *win32_gai_strerror(int errcode) { + switch (errcode) { + case 0: errcode = 0; break; + case EAI_AGAIN: errcode = WSATRY_AGAIN; break; + case EAI_BADFLAGS: errcode = WSAEINVAL; break; + case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break; + case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break; + case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break; + case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break; + case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break; + default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */ + } + return gai_strerror(errcode); +} + +void win32_freeaddrinfo(struct addrinfo *res) { + freeaddrinfo(res); +} + +SOCKET win32_socket(int domain, int type, int protocol) { + SOCKET s; + + /* Note: This function is likely to be called before other functions, so run init here. */ + if (!_initWinsock()) { + return INVALID_SOCKET; + } + + _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET); + return s; +} + +int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) { + int ret = ioctlsocket(fd, (long)request, argp); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { + int ret = bind(sockfd, addr, addrlen); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { + int ret = connect(sockfd, addr, addrlen); + _updateErrno(ret != SOCKET_ERROR); + + /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as + * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX + * logic consistent. */ + if (errno == EWOULDBLOCK) { + errno = EINPROGRESS; + } + + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { + int ret = 0; + if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { + if (*optlen >= sizeof (struct timeval)) { + struct timeval *tv = optval; + DWORD timeout = 0; + socklen_t dwlen = 0; + ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); + tv->tv_sec = timeout / 1000; + tv->tv_usec = (timeout * 1000) % 1000000; + } else { + ret = WSAEFAULT; + } + *optlen = sizeof (struct timeval); + } else { + ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); + } + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { + int ret = 0; + if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { + const struct timeval *tv = optval; + DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; + ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); + } else { + ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); + } + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_close(SOCKET fd) { + int ret = closesocket(fd); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) { + int ret = recv(sockfd, (char*)buf, (int)len, flags); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) { + int ret = send(sockfd, (const char*)buf, (int)len, flags); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} + +int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { + int ret = WSAPoll(fds, nfds, timeout); + _updateErrno(ret != SOCKET_ERROR); + return ret != SOCKET_ERROR ? ret : -1; +} +#endif /* _WIN32 */ diff --git a/sockcompat.h b/sockcompat.h new file mode 100644 index 00000000..56006c16 --- /dev/null +++ b/sockcompat.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019, Marcus Geelnard + * + * 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 __SOCKCOMPAT_H +#define __SOCKCOMPAT_H + +#ifndef _WIN32 +/* For POSIX systems we use the standard BSD socket API. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#else +/* For Windows we use winsock. */ +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */ +#include +#include +#include + +#ifdef _MSC_VER +typedef signed long ssize_t; +#endif + +/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ +int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +const char *win32_gai_strerror(int errcode); +void win32_freeaddrinfo(struct addrinfo *res); +SOCKET win32_socket(int domain, int type, int protocol); +int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp); +int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); +int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); +int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen); +int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen); +int win32_close(SOCKET fd); +ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags); +ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); +typedef ULONG nfds_t; +int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); + +#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION +#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) +#undef gai_strerror +#define gai_strerror(errcode) win32_gai_strerror(errcode) +#define freeaddrinfo(res) win32_freeaddrinfo(res) +#define socket(domain, type, protocol) win32_socket(domain, type, protocol) +#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp) +#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen) +#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen) +#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen) +#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen) +#define close(fd) win32_close(fd) +#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags) +#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags) +#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout) +#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */ +#endif /* _WIN32 */ + +#endif /* __SOCKCOMPAT_H */ diff --git a/ssl.c b/ssl.c new file mode 100644 index 00000000..b97fc1df --- /dev/null +++ b/ssl.c @@ -0,0 +1,448 @@ +/* + * Copyright (c) 2009-2011, Salvatore Sanfilippo + * Copyright (c) 2010-2011, Pieter Noordhuis + * Copyright (c) 2019, Redis Labs + * + * 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 "hiredis.h" +#include "async.h" + +#include +#include +#include +#include + +#include +#include + +#include "async_private.h" + +void __redisSetError(redisContext *c, int type, const char *str); + +/* The SSL context is attached to SSL/TLS connections as a privdata. */ +typedef struct redisSSLContext { + /** + * OpenSSL SSL_CTX; It is optional and will not be set when using + * user-supplied SSL. + */ + SSL_CTX *ssl_ctx; + + /** + * OpenSSL SSL object. + */ + SSL *ssl; + + /** + * SSL_write() requires to be called again with the same arguments it was + * previously called with in the event of an SSL_read/SSL_write situation + */ + size_t lastLen; + + /** Whether the SSL layer requires read (possibly before a write) */ + int wantRead; + + /** + * Whether a write was requested prior to a read. If set, the write() + * should resume whenever a read takes place, if possible + */ + int pendingWrite; +} redisSSLContext; + +/* Forward declaration */ +redisContextFuncs redisContextSSLFuncs; + +#ifdef HIREDIS_SSL_TRACE +/** + * Callback used for debugging + */ +static void sslLogCallback(const SSL *ssl, int where, int ret) { + const char *retstr; + int should_log = 0; + /* Ignore low-level SSL stuff */ + + if (where & SSL_CB_ALERT) { + should_log = 1; + } + if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) { + should_log = 1; + } + if ((where & SSL_CB_EXIT) && ret == 0) { + should_log = 1; + } + + if (!should_log) { + return; + } + + retstr = SSL_alert_type_string(ret); + printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr); + + if (where == SSL_CB_HANDSHAKE_DONE) { + printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl)); + } +} +#endif + +/** + * OpenSSL global initialization and locking handling callbacks. + * Note that this is only required for OpenSSL < 1.1.0. + */ + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define HIREDIS_USE_CRYPTO_LOCKS +#endif + +#ifdef HIREDIS_USE_CRYPTO_LOCKS +typedef pthread_mutex_t sslLockType; +static void sslLockInit(sslLockType *l) { + pthread_mutex_init(l, NULL); +} +static void sslLockAcquire(sslLockType *l) { + pthread_mutex_lock(l); +} +static void sslLockRelease(sslLockType *l) { + pthread_mutex_unlock(l); +} +static pthread_mutex_t *ossl_locks; + +static void opensslDoLock(int mode, int lkid, const char *f, int line) { + sslLockType *l = ossl_locks + lkid; + + if (mode & CRYPTO_LOCK) { + sslLockAcquire(l); + } else { + sslLockRelease(l); + } + + (void)f; + (void)line; +} + +static void initOpensslLocks(void) { + unsigned ii, nlocks; + if (CRYPTO_get_locking_callback() != NULL) { + /* Someone already set the callback before us. Don't destroy it! */ + return; + } + nlocks = CRYPTO_num_locks(); + ossl_locks = malloc(sizeof(*ossl_locks) * nlocks); + for (ii = 0; ii < nlocks; ii++) { + sslLockInit(ossl_locks + ii); + } + CRYPTO_set_locking_callback(opensslDoLock); +} +#endif /* HIREDIS_USE_CRYPTO_LOCKS */ + +/** + * SSL Connection initialization. + */ + +static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) { + if (c->privdata) { + __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated"); + return REDIS_ERR; + } + c->privdata = calloc(1, sizeof(redisSSLContext)); + + c->funcs = &redisContextSSLFuncs; + redisSSLContext *rssl = c->privdata; + + rssl->ssl_ctx = ssl_ctx; + rssl->ssl = ssl; + + SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_set_fd(rssl->ssl, c->fd); + SSL_set_connect_state(rssl->ssl); + + ERR_clear_error(); + int rv = SSL_connect(rssl->ssl); + if (rv == 1) { + return REDIS_OK; + } + + rv = SSL_get_error(rssl->ssl, rv); + if (((c->flags & REDIS_BLOCK) == 0) && + (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { + return REDIS_OK; + } + + if (c->err == 0) { + char err[512]; + if (rv == SSL_ERROR_SYSCALL) + snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno)); + else { + unsigned long e = ERR_peek_last_error(); + snprintf(err,sizeof(err)-1,"SSL_connect failed: %s", + ERR_reason_error_string(e)); + } + __redisSetError(c, REDIS_ERR_IO, err); + } + return REDIS_ERR; +} + +int redisInitiateSSL(redisContext *c, SSL *ssl) { + return redisSSLConnect(c, NULL, ssl); +} + +int redisSecureConnection(redisContext *c, const char *capath, + const char *certpath, const char *keypath, const char *servername) { + + SSL_CTX *ssl_ctx = NULL; + SSL *ssl = NULL; + + /* Initialize global OpenSSL stuff */ + static int isInit = 0; + if (!isInit) { + isInit = 1; + SSL_library_init(); +#ifdef HIREDIS_USE_CRYPTO_LOCKS + initOpensslLocks(); +#endif + } + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if (!ssl_ctx) { + __redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX"); + goto error; + } + +#ifdef HIREDIS_SSL_TRACE + SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback); +#endif + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); + if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) { + __redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together"); + goto error; + } + + if (capath) { + if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) { + __redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate"); + goto error; + } + } + if (certpath) { + if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) { + __redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate"); + goto error; + } + if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) { + __redisSetError(c, REDIS_ERR_OTHER, "Invalid client key"); + goto error; + } + } + + ssl = SSL_new(ssl_ctx); + if (!ssl) { + __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance"); + goto error; + } + if (servername) { + if (!SSL_set_tlsext_host_name(ssl, servername)) { + __redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication"); + goto error; + } + } + + return redisSSLConnect(c, ssl_ctx, ssl); + +error: + if (ssl) SSL_free(ssl); + if (ssl_ctx) SSL_CTX_free(ssl_ctx); + return REDIS_ERR; +} + +static int maybeCheckWant(redisSSLContext *rssl, int rv) { + /** + * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set + * and true is returned. False is returned otherwise + */ + if (rv == SSL_ERROR_WANT_READ) { + rssl->wantRead = 1; + return 1; + } else if (rv == SSL_ERROR_WANT_WRITE) { + rssl->pendingWrite = 1; + return 1; + } else { + return 0; + } +} + +/** + * Implementation of redisContextFuncs for SSL connections. + */ + +static void redisSSLFreeContext(void *privdata){ + redisSSLContext *rsc = privdata; + + if (!rsc) return; + if (rsc->ssl) { + SSL_free(rsc->ssl); + rsc->ssl = NULL; + } + if (rsc->ssl_ctx) { + SSL_CTX_free(rsc->ssl_ctx); + rsc->ssl_ctx = NULL; + } + free(rsc); +} + +static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) { + redisSSLContext *rssl = c->privdata; + + int nread = SSL_read(rssl->ssl, buf, bufcap); + if (nread > 0) { + return nread; + } else if (nread == 0) { + __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); + return -1; + } else { + int err = SSL_get_error(rssl->ssl, nread); + if (c->flags & REDIS_BLOCK) { + /** + * In blocking mode, we should never end up in a situation where + * we get an error without it being an actual error, except + * in the case of EINTR, which can be spuriously received from + * debuggers or whatever. + */ + if (errno == EINTR) { + return 0; + } else { + const char *msg = NULL; + if (errno == EAGAIN) { + msg = "Resource temporarily unavailable"; + } + __redisSetError(c, REDIS_ERR_IO, msg); + return -1; + } + } + + /** + * We can very well get an EWOULDBLOCK/EAGAIN, however + */ + if (maybeCheckWant(rssl, err)) { + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } +} + +static int redisSSLWrite(redisContext *c) { + redisSSLContext *rssl = c->privdata; + + size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); + int rv = SSL_write(rssl->ssl, c->obuf, len); + + if (rv > 0) { + rssl->lastLen = 0; + } else if (rv < 0) { + rssl->lastLen = len; + + int err = SSL_get_error(rssl->ssl, rv); + if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) { + return 0; + } else { + __redisSetError(c, REDIS_ERR_IO, NULL); + return -1; + } + } + return rv; +} + +static void redisSSLAsyncRead(redisAsyncContext *ac) { + int rv; + redisSSLContext *rssl = ac->c.privdata; + redisContext *c = &ac->c; + + rssl->wantRead = 0; + + if (rssl->pendingWrite) { + int done; + + /* This is probably just a write event */ + rssl->pendingWrite = 0; + rv = redisBufferWrite(c, &done); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + return; + } else if (!done) { + _EL_ADD_WRITE(ac); + } + } + + rv = redisBufferRead(c); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + } else { + _EL_ADD_READ(ac); + redisProcessCallbacks(ac); + } +} + +static void redisSSLAsyncWrite(redisAsyncContext *ac) { + int rv, done = 0; + redisSSLContext *rssl = ac->c.privdata; + redisContext *c = &ac->c; + + rssl->pendingWrite = 0; + rv = redisBufferWrite(c, &done); + if (rv == REDIS_ERR) { + __redisAsyncDisconnect(ac); + return; + } + + if (!done) { + if (rssl->wantRead) { + /* Need to read-before-write */ + rssl->pendingWrite = 1; + _EL_DEL_WRITE(ac); + } else { + /* No extra reads needed, just need to write more */ + _EL_ADD_WRITE(ac); + } + } else { + /* Already done! */ + _EL_DEL_WRITE(ac); + } + + /* Always reschedule a read */ + _EL_ADD_READ(ac); +} + +redisContextFuncs redisContextSSLFuncs = { + .free_privdata = redisSSLFreeContext, + .async_read = redisSSLAsyncRead, + .async_write = redisSSLAsyncWrite, + .read = redisSSLRead, + .write = redisSSLWrite +}; + diff --git a/test.c b/test.c index 8fde5544..9c0de6d4 100644 --- a/test.c +++ b/test.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -11,12 +13,16 @@ #include #include "hiredis.h" +#ifdef HIREDIS_TEST_SSL +#include "hiredis_ssl.h" +#endif #include "net.h" enum connection_type { CONN_TCP, CONN_UNIX, - CONN_FD + CONN_FD, + CONN_SSL }; struct config { @@ -30,7 +36,15 @@ struct config { struct { const char *path; - } unix; + } unix_sock; + + struct { + const char *host; + int port; + const char *ca_cert; + const char *cert; + const char *key; + } ssl; }; /* The following lines make up our testing "framework" :) */ @@ -91,16 +105,32 @@ static int disconnect(redisContext *c, int keep_fd) { return -1; } -static redisContext *connect(struct config config) { +static void do_ssl_handshake(redisContext *c, struct config config) { +#ifdef HIREDIS_TEST_SSL + redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL); + if (c->err) { + printf("SSL error: %s\n", c->errstr); + redisFree(c); + exit(1); + } +#else + (void) c; + (void) config; +#endif +} + +static redisContext *do_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_SSL) { + c = redisConnect(config.ssl.host, config.ssl.port); } else if (config.type == CONN_UNIX) { - c = redisConnectUnix(config.unix.path); + c = redisConnectUnix(config.unix_sock.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); + redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path); if (dummy_ctx) { int fd = disconnect(dummy_ctx, 1); printf("Connecting to inherited fd %d\n", fd); @@ -119,9 +149,21 @@ static redisContext *connect(struct config config) { exit(1); } + if (config.type == CONN_SSL) { + do_ssl_handshake(c, config); + } + return select_database(c); } +static void do_reconnect(redisContext *c, struct config config) { + redisReconnect(c); + + if (config.type == CONN_SSL) { + do_ssl_handshake(c, config); + } +} + static void test_format_commands(void) { char *cmd; int len; @@ -224,6 +266,22 @@ static void test_format_commands(void) { 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); + + sds sds_cmd; + + sds_cmd = NULL; + test("Format command into sds by passing argc/argv without lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); + test_cond(strncmp(sds_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)); + sdsfree(sds_cmd); + + sds_cmd = NULL; + test("Format command into sds by passing argc/argv with lengths: "); + len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); + test_cond(strncmp(sds_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)); + sdsfree(sds_cmd); } static void test_append_formatted_commands(struct config config) { @@ -232,7 +290,7 @@ static void test_append_formatted_commands(struct config config) { char *cmd; int len; - c = connect(config); + c = do_connect(config); test("Append format command: "); @@ -286,6 +344,82 @@ static void test_reply_reader(void) { strncasecmp(reader->errstr,"No support for",14) == 0); redisReaderFree(reader); + test("Correctly parses LLONG_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":9223372036854775807\r\n",22); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->integer == LLONG_MAX); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when > LLONG_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":9223372036854775808\r\n",22); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad integer value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Correctly parses LLONG_MIN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":-9223372036854775808\r\n",23); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_OK && + ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && + ((redisReply*)reply)->integer == LLONG_MIN); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when < LLONG_MIN: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, ":-9223372036854775809\r\n",23); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bad integer value") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when array < -1: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when bulk < -1: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "$-2\r\nasdf\r\n",11); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bulk string length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + +#if LLONG_MAX > SIZE_MAX + test("Set error when array > SIZE_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); + + test("Set error when bulk > SIZE_MAX: "); + reader = redisReaderCreate(); + redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); + ret = redisReaderGetReply(reader,&reply); + test_cond(ret == REDIS_ERR && + strcasecmp(reader->errstr,"Bulk string length out of range") == 0); + freeReplyObject(reply); + redisReaderFree(reader); +#endif + test("Works with NULL functions for reply: "); reader = redisReaderCreate(); reader->fn = NULL; @@ -328,31 +462,47 @@ static void test_reply_reader(void) { } static void test_free_null(void) { - void *redisContext = NULL; + void *redisCtx = NULL; void *reply = NULL; test("Don't fail when redisFree is passed a NULL value: "); - redisFree(redisContext); - test_cond(redisContext == NULL); + redisFree(redisCtx); + test_cond(redisCtx == NULL); test("Don't fail when freeReplyObject is passed a NULL value: "); freeReplyObject(reply); test_cond(reply == NULL); } +#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" 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); + struct addrinfo hints = {.ai_family = AF_INET}; + struct addrinfo *ai_tmp = NULL; + + int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp); + if (rv != 0) { + // Address does *not* exist + test("Returns error when host cannot be resolved: "); + // First see if this domain name *actually* resolves to NXDOMAIN + c = redisConnect(HIREDIS_BAD_DOMAIN, 6379); + test_cond( + c->err == REDIS_ERR_OTHER && + (strcmp(c->errstr, "Name or service not known") == 0 || + strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || + strcmp(c->errstr, "Name does not resolve") == 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, + "hostname nor servname provided, or not known") == 0 || + strcmp(c->errstr, "no address associated with name") == 0)); + redisFree(c); + } else { + printf("Skipping NXDOMAIN test. Found evil ISP!\n"); + freeaddrinfo(ai_tmp); + } test("Returns error when the port is not open: "); c = redisConnect((char*)"localhost", 1); @@ -360,7 +510,7 @@ static void test_blocking_connection_errors(void) { strcmp(c->errstr,"Connection refused") == 0); redisFree(c); - test("Returns error when the unix socket path doesn't accept connections: "); + test("Returns error when the unix_sock 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); @@ -370,7 +520,7 @@ static void test_blocking_connection(struct config config) { redisContext *c; redisReply *reply; - c = connect(config); + c = do_connect(config); test("Is able to deliver commands: "); reply = redisCommand(c,"PING"); @@ -441,6 +591,11 @@ static void test_blocking_connection(struct config config) { strcasecmp(reply->element[1]->str,"pong") == 0); freeReplyObject(reply); + /* Make sure passing NULL to redisGetReply is safe */ + test("Can pass NULL to redisGetReply: "); + assert(redisAppendCommand(c, "PING") == REDIS_OK); + test_cond(redisGetReply(c, NULL) == REDIS_OK); + disconnect(c, 0); } @@ -451,7 +606,7 @@ static void test_blocking_connection_timeouts(struct config config) { const char *cmd = "DEBUG SLEEP 3\r\n"; struct timeval tv; - c = connect(config); + c = do_connect(config); test("Successfully completes a command when the timeout is not exceeded: "); reply = redisCommand(c,"SET foo fast"); freeReplyObject(reply); @@ -463,9 +618,10 @@ static void test_blocking_connection_timeouts(struct config config) { freeReplyObject(reply); disconnect(c, 0); - c = connect(config); + c = do_connect(config); test("Does not return a reply when the command times out: "); - s = write(c->fd, cmd, strlen(cmd)); + redisAppendFormattedCommand(c, cmd, strlen(cmd)); + s = c->funcs->write(c); tv.tv_sec = 0; tv.tv_usec = 10000; redisSetTimeout(c, tv); @@ -474,15 +630,15 @@ static void test_blocking_connection_timeouts(struct config config) { freeReplyObject(reply); test("Reconnect properly reconnects after a timeout: "); - redisReconnect(c); + do_reconnect(c, config); 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); + config.unix_sock.path = "foo"; + do_reconnect(c, config); reply = redisCommand(c, "PING"); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0); freeReplyObject(reply); @@ -497,7 +653,7 @@ static void test_blocking_io_errors(struct config config) { int major, minor; /* Connect to target given by config. */ - c = connect(config); + c = do_connect(config); { /* Find out Redis version to determine the path for the next test */ const char *field = "redis_version:"; @@ -532,7 +688,7 @@ static void test_blocking_io_errors(struct config config) { strcmp(c->errstr,"Server closed the connection") == 0); redisFree(c); - c = connect(config); + c = do_connect(config); test("Returns I/O error on socket timeout: "); struct timeval tv = { 0, 1000 }; assert(redisSetTimeout(c,tv) == REDIS_OK); @@ -551,7 +707,7 @@ static void test_invalid_timeout_errors(struct config config) { c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - test_cond(c->err == REDIS_ERR_IO); + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); redisFree(c); test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); @@ -561,12 +717,12 @@ static void test_invalid_timeout_errors(struct config config) { c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); - test_cond(c->err == REDIS_ERR_IO); + test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); redisFree(c); } static void test_throughput(struct config config) { - redisContext *c = connect(config); + redisContext *c = do_connect(config); redisReply **replies; int i, num; long long t1, t2; @@ -599,6 +755,17 @@ static void test_throughput(struct config config) { free(replies); printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); + replies = malloc(sizeof(redisReply*)*num); + t1 = usec(); + for (i = 0; i < num; i++) { + replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000); + assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0); + num = 10000; replies = malloc(sizeof(redisReply*)*num); for (i = 0; i < num; i++) @@ -627,6 +794,19 @@ static void test_throughput(struct config config) { free(replies); printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + replies = malloc(sizeof(redisReply*)*num); + for (i = 0; i < num; i++) + redisAppendCommand(c,"INCRBY incrkey %d", 1000000); + 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_INTEGER); + } + t2 = usec(); + for (i = 0; i < num; i++) freeReplyObject(replies[i]); + free(replies); + printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); + disconnect(c, 0); } @@ -735,7 +915,7 @@ int main(int argc, char **argv) { .host = "127.0.0.1", .port = 6379 }, - .unix = { + .unix_sock = { .path = "/tmp/redis.sock" } }; @@ -756,11 +936,28 @@ int main(int argc, char **argv) { cfg.tcp.port = atoi(argv[0]); } else if (argc >= 2 && !strcmp(argv[0],"-s")) { argv++; argc--; - cfg.unix.path = argv[0]; + cfg.unix_sock.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; +#ifdef HIREDIS_TEST_SSL + } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) { + argv++; argc--; + cfg.ssl.port = atoi(argv[0]); + } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) { + argv++; argc--; + cfg.ssl.host = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) { + argv++; argc--; + cfg.ssl.ca_cert = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) { + argv++; argc--; + cfg.ssl.cert = argv[0]; + } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) { + argv++; argc--; + cfg.ssl.key = argv[0]; +#endif } else { fprintf(stderr, "Invalid argument: %s\n", argv[0]); exit(1); @@ -782,15 +979,29 @@ int main(int argc, char **argv) { test_append_formatted_commands(cfg); if (throughput) test_throughput(cfg); - printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path); + printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path); cfg.type = CONN_UNIX; test_blocking_connection(cfg); test_blocking_connection_timeouts(cfg); test_blocking_io_errors(cfg); if (throughput) test_throughput(cfg); +#ifdef HIREDIS_TEST_SSL + if (cfg.ssl.port && cfg.ssl.host) { + printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port); + cfg.type = CONN_SSL; + + 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); + } +#endif + if (test_inherit_fd) { - printf("\nTesting against inherited fd (%s):\n", cfg.unix.path); + printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); cfg.type = CONN_FD; test_blocking_connection(cfg); } diff --git a/test.sh b/test.sh new file mode 100644 index 00000000..2cab9e6f --- /dev/null +++ b/test.sh @@ -0,0 +1,70 @@ +#!/bin/sh -ue + +REDIS_SERVER=${REDIS_SERVER:-redis-server} +REDIS_PORT=${REDIS_PORT:-56379} +REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} +TEST_SSL=${TEST_SSL:-0} +SSL_TEST_ARGS= + +tmpdir=$(mktemp -d) +PID_FILE=${tmpdir}/hiredis-test-redis.pid +SOCK_FILE=${tmpdir}/hiredis-test-redis.sock + +if [ "$TEST_SSL" = "1" ]; then + SSL_CA_CERT=${tmpdir}/ca.crt + SSL_CA_KEY=${tmpdir}/ca.key + SSL_CERT=${tmpdir}/redis.crt + SSL_KEY=${tmpdir}/redis.key + + openssl genrsa -out ${tmpdir}/ca.key 4096 + openssl req \ + -x509 -new -nodes -sha256 \ + -key ${SSL_CA_KEY} \ + -days 3650 \ + -subj '/CN=Hiredis Test CA' \ + -out ${SSL_CA_CERT} + openssl genrsa -out ${SSL_KEY} 2048 + openssl req \ + -new -sha256 \ + -key ${SSL_KEY} \ + -subj '/CN=Hiredis Test Cert' | \ + openssl x509 \ + -req -sha256 \ + -CA ${SSL_CA_CERT} \ + -CAkey ${SSL_CA_KEY} \ + -CAserial ${tmpdir}/ca.txt \ + -CAcreateserial \ + -days 365 \ + -out ${SSL_CERT} + + SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}" +fi + +cleanup() { + set +e + kill $(cat ${PID_FILE}) + rm -rf ${tmpdir} +} +trap cleanup INT TERM EXIT + +cat > ${tmpdir}/redis.conf <> ${tmpdir}/redis.conf < /* for struct timeval */ + #ifndef inline #define inline __inline #endif +#ifndef strdup +#define strdup _strdup +#endif + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif + +#ifndef strncasecmp +#define strncasecmp _strnicmp +#endif + #ifndef va_copy #define va_copy(d,s) ((d) = (s)) #endif @@ -37,6 +51,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...) return count; } #endif +#endif /* _MSC_VER */ -#endif -#endif \ No newline at end of file +#ifdef _WIN32 +#define strerror_r(errno,buf,len) strerror_s(buf,len,errno) +#endif /* _WIN32 */ + +#endif /* _WIN32_HELPER_INCLUDE */ From f0ca4f5318486ecb9cabc2219dc1d09d9d136707 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 19 Jan 2020 22:41:15 -0800 Subject: [PATCH 036/273] Making sure all build files refer to the lib as hiredis-vip instead of hiredis --- CMakeLists.txt | 42 +++++++++++++++--------------- Makefile | 32 +++++++++++------------ hircluster.h | 1 + hiredis.pc.in => hiredis-vip.pc.in | 8 +++--- hiredis_ssl.pc.in | 6 ++--- 5 files changed, 45 insertions(+), 44 deletions(-) rename hiredis.pc.in => hiredis-vip.pc.in (52%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e6edabc..9f3cdd16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) INCLUDE(GNUInstallDirs) -PROJECT(hiredis) +PROJECT(hiredis-vip) OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) @@ -16,23 +16,23 @@ ENDIF() MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") - FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h" + FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hircluster.h" VERSION_BIT REGEX ${VERSION_REGEX}) STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") ENDMACRO(getVersionBit) -getVersionBit(HIREDIS_MAJOR) -getVersionBit(HIREDIS_MINOR) -getVersionBit(HIREDIS_PATCH) -getVersionBit(HIREDIS_SONAME) -SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}") +getVersionBit(HIREDIS_VIP_MAJOR) +getVersionBit(HIREDIS_VIP_MINOR) +getVersionBit(HIREDIS_VIP_PATCH) +getVersionBit(HIREDIS_VIP_SONAME) +SET(VERSION "${HIREDIS_VIP_MAJOR}.${HIREDIS_VIP_MINOR}.${HIREDIS_VIP_PATCH}") MESSAGE("Detected version: ${VERSION}") -PROJECT(hiredis VERSION "${VERSION}") +PROJECT(hiredis-vip VERSION "${VERSION}") SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") -ADD_LIBRARY(hiredis +ADD_LIBRARY(hiredis-vip async.c dict.c hiredis.c @@ -47,26 +47,26 @@ ADD_LIBRARY(hiredis hircluster.c hiutil.c) -SET_TARGET_PROPERTIES(hiredis +SET_TARGET_PROPERTIES(hiredis-vip PROPERTIES - VERSION "${HIREDIS_SONAME}") + VERSION "${HIREDIS_VIP_SONAME}") IF(WIN32 OR MINGW) - TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) + TARGET_LINK_LIBRARIES(hiredis-vip PRIVATE ws2_32) ENDIF() -TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .) +TARGET_INCLUDE_DIRECTORIES(hiredis-vip PUBLIC .) -CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) +CONFIGURE_FILE(hiredis-vip.pc.in hiredis-vip.pc @ONLY) -INSTALL(TARGETS hiredis +INSTALL(TARGETS hiredis-vip DESTINATION "${CMAKE_INSTALL_LIBDIR}") -INSTALL(FILES hiredis.h read.h sds.h async.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) +INSTALL(FILES hiredis.h read.h sds.h async.h hircluster.h dict.h adlist.h hiarray.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis-vip) INSTALL(DIRECTORY adapters - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis-vip) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-vip.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) IF(ENABLE_SSL) @@ -86,7 +86,7 @@ IF(ENABLE_SSL) DESTINATION "${CMAKE_INSTALL_LIBDIR}") INSTALL(FILES hiredis_ssl.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis-vip) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) @@ -95,7 +95,7 @@ ENDIF() IF(NOT (DISABLE_TESTS OR (WIN32 OR MINGW))) ENABLE_TESTING() ADD_EXECUTABLE(hiredis-test test.c) - TARGET_LINK_LIBRARIES(hiredis-test hiredis) + TARGET_LINK_LIBRARIES(hiredis-test hiredis-vip) ADD_TEST(NAME hiredis-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) ENDIF() diff --git a/Makefile b/Makefile index c885e0aa..cb03e25a 100644 --- a/Makefile +++ b/Makefile @@ -10,19 +10,19 @@ ifeq ($(USE_SSL),1) EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl endif TESTS=hiredis-test -LIBNAME=libhiredis +LIBNAME=libhiredis-vip SSL_LIBNAME=libhiredis_ssl -PKGCONFNAME=hiredis.pc +PKGCONFNAME=hiredis-vip.pc SSL_PKGCONFNAME=hiredis_ssl.pc -HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') -HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') -HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}') -HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}') +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}') +HIREDIS_VIP_SONAME=$(shell grep HIREDIS_VIP_SONAME hircluster.h | awk '{print $$3}') # Installation related variables and target PREFIX?=/usr/local -INCLUDE_PATH?=include/hiredis +INCLUDE_PATH?=include/hiredis-vip LIBRARY_PATH?=lib PKGCONF_PATH?=pkgconfig INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH) @@ -53,7 +53,7 @@ REAL_LDFLAGS=$(LDFLAGS) DYLIBSUFFIX=so STLIBSUFFIX=a DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) -DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) +DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) @@ -214,17 +214,17 @@ dep: INSTALL?= cp -pPR -$(PKGCONFNAME): hiredis.h +$(PKGCONFNAME): hircluster.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_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ - @echo Libs: -L\$${libdir} -lhiredis >> $@ + @echo Name: hiredis-vip >> $@ + @echo Description: Minimalistic C client library for Redis with cluster support. >> $@ + @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ + @echo Libs: -L\$${libdir} -lhiredis-vip >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ $(SSL_PKGCONFNAME): hiredis.h @@ -236,14 +236,14 @@ $(SSL_PKGCONFNAME): hiredis.h @echo >> $@ @echo Name: hiredis_ssl >> $@ @echo Description: SSL Support for hiredis. >> $@ - @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@ - @echo Requires: hiredis >> $@ + @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ + @echo Requires: hiredis-vip >> $@ @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@ @echo Libs.private: -lssl -lcrypto >> $@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH) + $(INSTALL) hiredis.h async.h read.h sds.h hircluster.h adlist.h dict.h hiarray.h $(INSTALL_INCLUDE_PATH) $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) diff --git a/hircluster.h b/hircluster.h index 95585c99..5cb23753 100644 --- a/hircluster.h +++ b/hircluster.h @@ -8,6 +8,7 @@ #define HIREDIS_VIP_MAJOR 1 #define HIREDIS_VIP_MINOR 0 #define HIREDIS_VIP_PATCH 0 +#define HIREDIS_VIP_SONAME 1.0 #define REDIS_CLUSTER_SLOTS 16384 diff --git a/hiredis.pc.in b/hiredis-vip.pc.in similarity index 52% rename from hiredis.pc.in rename to hiredis-vip.pc.in index 140b040f..6d90f175 100644 --- a/hiredis.pc.in +++ b/hiredis-vip.pc.in @@ -2,10 +2,10 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include -pkgincludedir=${includedir}/hiredis +pkgincludedir=${includedir}/hiredis-vip -Name: hiredis -Description: Minimalistic C client library for Redis. +Name: hiredis-vip +Description: Minimalistic C client library for Redis with cluster support. Version: @PROJECT_VERSION@ -Libs: -L${libdir} -lhiredis +Libs: -L${libdir} -lhiredis-vip Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 diff --git a/hiredis_ssl.pc.in b/hiredis_ssl.pc.in index 588a978a..8f700c08 100644 --- a/hiredis_ssl.pc.in +++ b/hiredis_ssl.pc.in @@ -2,11 +2,11 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include -pkgincludedir=${includedir}/hiredis +pkgincludedir=${includedir}/hiredis-vip Name: hiredis_ssl -Description: SSL Support for hiredis. +Description: SSL Support for hiredis-vip. Version: @PROJECT_VERSION@ -Requires: hiredis +Requires: hiredis-vip Libs: -L${libdir} -lhiredis_ssl Libs.private: -lssl -lcrypto From 5fb116ff8449144732d8c12fce9b5381905c25f6 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 19 Jan 2020 22:45:28 -0800 Subject: [PATCH 037/273] Correcting name of library from hiredis-vip to hiredis_vip --- CMakeLists.txt | 26 +++++++++++++------------- Makefile | 2 +- hiredis-vip.pc.in => hiredis_vip.pc.in | 0 3 files changed, 14 insertions(+), 14 deletions(-) rename hiredis-vip.pc.in => hiredis_vip.pc.in (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f3cdd16..c238761e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) INCLUDE(GNUInstallDirs) -PROJECT(hiredis-vip) +PROJECT(hiredis_vip) OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) @@ -28,11 +28,11 @@ getVersionBit(HIREDIS_VIP_SONAME) SET(VERSION "${HIREDIS_VIP_MAJOR}.${HIREDIS_VIP_MINOR}.${HIREDIS_VIP_PATCH}") MESSAGE("Detected version: ${VERSION}") -PROJECT(hiredis-vip VERSION "${VERSION}") +PROJECT(hiredis_vip VERSION "${VERSION}") SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") -ADD_LIBRARY(hiredis-vip +ADD_LIBRARY(hiredis_vip async.c dict.c hiredis.c @@ -47,26 +47,26 @@ ADD_LIBRARY(hiredis-vip hircluster.c hiutil.c) -SET_TARGET_PROPERTIES(hiredis-vip +SET_TARGET_PROPERTIES(hiredis_vip PROPERTIES VERSION "${HIREDIS_VIP_SONAME}") IF(WIN32 OR MINGW) - TARGET_LINK_LIBRARIES(hiredis-vip PRIVATE ws2_32) + TARGET_LINK_LIBRARIES(hiredis_vip PRIVATE ws2_32) ENDIF() -TARGET_INCLUDE_DIRECTORIES(hiredis-vip PUBLIC .) +TARGET_INCLUDE_DIRECTORIES(hiredis_vip PUBLIC .) -CONFIGURE_FILE(hiredis-vip.pc.in hiredis-vip.pc @ONLY) +CONFIGURE_FILE(hiredis_vip.pc.in hiredis_vip.pc @ONLY) -INSTALL(TARGETS hiredis-vip +INSTALL(TARGETS hiredis_vip DESTINATION "${CMAKE_INSTALL_LIBDIR}") INSTALL(FILES hiredis.h read.h sds.h async.h hircluster.h dict.h adlist.h hiarray.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis-vip) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) INSTALL(DIRECTORY adapters - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis-vip) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-vip.pc +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) IF(ENABLE_SSL) @@ -86,7 +86,7 @@ IF(ENABLE_SSL) DESTINATION "${CMAKE_INSTALL_LIBDIR}") INSTALL(FILES hiredis_ssl.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis-vip) + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) @@ -95,7 +95,7 @@ ENDIF() IF(NOT (DISABLE_TESTS OR (WIN32 OR MINGW))) ENABLE_TESTING() ADD_EXECUTABLE(hiredis-test test.c) - TARGET_LINK_LIBRARIES(hiredis-test hiredis-vip) + TARGET_LINK_LIBRARIES(hiredis-test hiredis_vip) ADD_TEST(NAME hiredis-test COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) ENDIF() diff --git a/Makefile b/Makefile index cb03e25a..0c7c744e 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ ifeq ($(USE_SSL),1) EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl endif TESTS=hiredis-test -LIBNAME=libhiredis-vip +LIBNAME=libhiredis_vip SSL_LIBNAME=libhiredis_ssl PKGCONFNAME=hiredis-vip.pc SSL_PKGCONFNAME=hiredis_ssl.pc diff --git a/hiredis-vip.pc.in b/hiredis_vip.pc.in similarity index 100% rename from hiredis-vip.pc.in rename to hiredis_vip.pc.in From efb649e406b0757efea5e7e38f435e97710b3891 Mon Sep 17 00:00:00 2001 From: nrivera Date: Tue, 21 Jan 2020 11:14:22 -0800 Subject: [PATCH 038/273] Make sure hiredis_ssl is the same library type as hiredis_vip --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c238761e..e76360fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ IF(ENABLE_SSL) ENDIF() ENDIF() FIND_PACKAGE(OpenSSL REQUIRED) - ADD_LIBRARY(hiredis_ssl SHARED + ADD_LIBRARY(hiredis_ssl ssl.c) TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}") TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES}) From 25cea82670e303cfd6a77ef6441d5dc4314ab1b8 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 22 Jan 2020 23:04:43 -0800 Subject: [PATCH 039/273] Restoring the hiredis-vip README and CHANGELOG files --- CHANGELOG.md | 207 +++------------------------------------------------ 1 file changed, 12 insertions(+), 195 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d37e51..db304b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,199 +1,16 @@ -### 1.0.0 (unreleased) +### 0.3.0 - Dec 07, 2016 -**BREAKING CHANGES**: +* Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011) +* Add flags HIRCLUSTER_FLAG_ADD_OPENSLOT and HIRCLUSTER_FLAG_ROUTE_USE_SLOTS. (deep011) +* Support redisClusterCommandArgv related api. (deep011) +* Fix some serious bugs. (deep011) -* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now - protocol errors. This is consistent with the RESP specification. On 32-bit - platforms, the upper bound is lowered to `SIZE_MAX`. +### 0.2.1 - Nov 24, 2015 -* Change `redisReply.len` to `size_t`, as it denotes the the size of a string - - User code should compare this to `size_t` values as well. If it was used to - compare to other values, casting might be necessary or can be removed, if - casting was applied before. - -### 0.x.x (unreleased) -**BREAKING CHANGES**: - -* Change `redisReply.len` to `size_t`, as it denotes the the size of a string - -User code should compare this to `size_t` values as well. -If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. - -* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter. - -### 0.14.0 (2018-09-25) - -* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) -* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) -* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) -* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8]) -* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8]) -* Fix bulk and multi-bulk length truncation (Justin Brewer [109197]) -* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94]) -* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6]) -* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1]) -* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b]) -* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96]) -* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234]) -* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129]) -* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c]) -* Fix libevent leak (zfz [515228]) -* Clean up GCC warning (Ichito Nagata [2ec774]) -* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88]) -* Solaris compilation fix (Donald Whyte [41b07d]) -* Reorder linker arguments when building examples (Tustfarm-heart [06eedd]) -* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999]) -* libuv use after free fix (Paul Scott [cbb956]) -* Properly close socket fd on reconnect attempt (WSL [64d1ec]) -* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78]) -* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5]) -* Update libevent (Chris Xin [386802]) -* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e]) -* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6]) -* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3]) -* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb]) -* Compatibility fix for strerror_r (Tom Lee [bb1747]) -* Properly detect integer parse/overflow errors (Justin Brewer [93421f]) -* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40]) -* Catch a buffer overflow when formatting the error message -* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13 -* Fix warnings, when compiled with -Wshadow -* Make hiredis compile in Cygwin on Windows, now CI-tested -* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now - protocol errors. This is consistent with the RESP specification. On 32-bit - platforms, the upper bound is lowered to `SIZE_MAX`. - -* Remove backwards compatibility macro's - -This removes the following old function aliases, use the new name now: - -| Old | New | -| --------------------------- | ---------------------- | -| redisReplyReaderCreate | redisReaderCreate | -| redisReplyReaderCreate | redisReaderCreate | -| redisReplyReaderFree | redisReaderFree | -| redisReplyReaderFeed | redisReaderFeed | -| redisReplyReaderGetReply | redisReaderGetReply | -| redisReplyReaderSetPrivdata | redisReaderSetPrivdata | -| redisReplyReaderGetObject | redisReaderGetObject | -| redisReplyReaderGetError | redisReaderGetError | - -* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS` - -Previously it broke some builds for people that had `DEBUG` set to some arbitrary value, -due to debugging other software. -By renaming we avoid unintentional name clashes. - -Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again. - -### 0.13.3 (2015-09-16) - -* Revert "Clear `REDIS_CONNECTED` flag when connection is closed". -* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni) - - -If the `REDIS_CONNECTED` flag is cleared, -the async onDisconnect callback function will never be called. -This causes problems as the disconnect is never reported back to the user. - -### 0.13.2 (2015-08-25) - -* Prevent crash on pending replies in async code (Thanks, @switch-st) -* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs) -* Add MacOS X addapter (Thanks, @dizzus) -* Add Qt adapter (Thanks, Pietro Cerutti) -* Add Ivykis adapter (Thanks, Gergely Nagy) - -All adapters are provided as is and are only tested where possible. - -### 0.13.1 (2015-05-03) - -This is a bug fix release. -The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code. -Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects. -Other non-C99 code can now use hiredis as usual again. -Sorry for the inconvenience. - -* Fix memory leak in async reply handling (Salvatore Sanfilippo) -* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa) - -### 0.13.0 (2015-04-16) - -This release adds a minimal Windows compatibility layer. -The parser, standalone since v0.12.0, can now be compiled on Windows -(and thus used in other client libraries as well) - -* Windows compatibility layer for parser code (tzickel) -* Properly escape data printed to PKGCONF file (Dan Skorupski) -* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff) -* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra) - -### 0.12.1 (2015-01-26) - -* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location -* Fix `make test` as 32 bit build on 64 bit platform - -### 0.12.0 (2015-01-22) - -* Add optional KeepAlive support - -* Try again on EINTR errors - -* Add libuv adapter - -* Add IPv6 support - -* Remove possibility of multiple close on same fd - -* Add ability to bind source address on connect - -* Add redisConnectFd() and redisFreeKeepFd() - -* Fix getaddrinfo() memory leak - -* Free string if it is unused (fixes memory leak) - -* Improve redisAppendCommandArgv performance 2.5x - -* Add support for SO_REUSEADDR - -* Fix redisvFormatCommand format parsing - -* Add GLib 2.0 adapter - -* Refactor reading code into read.c - -* Fix errno error buffers to not clobber errors - -* Generate pkgconf during build - -* Silence _BSD_SOURCE warnings - -* Improve digit counting for multibulk creation - - -### 0.11.0 - -* Increase the maximum multi-bulk reply depth to 7. - -* Increase the read buffer size from 2k to 16k. - -* Use poll(2) instead of select(2) to support large fds (>= 1024). - -### 0.10.1 - -* Makefile overhaul. Important to check out if you override one or more - variables using environment variables or via arguments to the "make" tool. - -* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements - being created by the default reply object functions. - -* Issue #43: Don't crash in an asynchronous context when Redis returns an error - reply after the connection has been made (this happens when the maximum - number of connections is reached). - -### 0.10.0 - -* See commit log. +This release support redis cluster api. +* Add hiredis 0.3.1. (deep011) +* Support cluster synchronous API. (deep011) +* Support multi-key command(mget/mset/del) for redis cluster. (deep011) +* Support cluster pipelining. (deep011) +* Support cluster asynchronous API. (deep011) From 959a31c0031d107c7c8d88116e177eba399d1152 Mon Sep 17 00:00:00 2001 From: nrivera Date: Thu, 23 Jan 2020 10:39:20 -0800 Subject: [PATCH 040/273] Chaning version number to 0.4 --- hircluster.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hircluster.h b/hircluster.h index 5cb23753..f954250e 100644 --- a/hircluster.h +++ b/hircluster.h @@ -5,10 +5,10 @@ #include "hiredis.h" #include "async.h" -#define HIREDIS_VIP_MAJOR 1 -#define HIREDIS_VIP_MINOR 0 +#define HIREDIS_VIP_MAJOR 0 +#define HIREDIS_VIP_MINOR 4 #define HIREDIS_VIP_PATCH 0 -#define HIREDIS_VIP_SONAME 1.0 +#define HIREDIS_VIP_SONAME 0.4 #define REDIS_CLUSTER_SLOTS 16384 From 8372d56cf439843c791821a0a533aa86f72d928b Mon Sep 17 00:00:00 2001 From: nrivera Date: Thu, 23 Jan 2020 10:40:56 -0800 Subject: [PATCH 041/273] Fixed the name of the compiled so file --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0c7c744e..7759fffc 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ REAL_LDFLAGS=$(LDFLAGS) DYLIBSUFFIX=so STLIBSUFFIX=a -DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) +DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_SONAME) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) @@ -85,7 +85,7 @@ ifeq ($(uname_S),SunOS) endif ifeq ($(uname_S),Darwin) DYLIBSUFFIX=dylib - DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) + DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_SONAME).$(DYLIBSUFFIX) DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) endif From b7a9e548105fd6a611c566d1e8eb212eaf7e3c0d Mon Sep 17 00:00:00 2001 From: nrivera Date: Thu, 23 Jan 2020 10:57:42 -0800 Subject: [PATCH 042/273] Actually updating the README --- README.md | 524 +++++++++++++++++++----------------------------------- 1 file changed, 183 insertions(+), 341 deletions(-) diff --git a/README.md b/README.md index 6095cec9..120a7f13 100644 --- a/README.md +++ b/README.md @@ -1,254 +1,225 @@ -[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) -**This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.** +# HIREDIS-VIP -# HIREDIS +Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. -Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database. +Hiredis-vip supported redis cluster. -It is minimalistic because it just adds minimal support for the protocol, but -at the same time it uses a high level printf-alike API in order to make it -much higher level than otherwise suggested by its minimal code base and the -lack of explicit bindings for every Redis command. +Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . -Apart from supporting sending commands and receiving replies, it comes with -a reply parser that is decoupled from the I/O layer. It -is a stream parser designed for easy reusability, which can for instance be used -in higher level language bindings for efficient reply parsing. +## CLUSTER SUPPORT -Hiredis only supports the binary-safe Redis protocol, so you can use it with any -Redis version >= 1.2.0. +### FEATURES: -The library comes with multiple APIs. There is the -*synchronous API*, the *asynchronous API* and the *reply parsing API*. +* **`SUPPORT REDIS CLUSTER`**: + * Connect to redis cluster and run commands. -## Upgrading to `1.0.0` +* **`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. -Version 1.0.0 marks a stable release of hiredis. -It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. -It also bundles the updated `sds` library, to sync up with upstream and Redis. -For most applications a recompile against the new hiredis should be enough. -For code changes see the [Changelog](CHANGELOG.md). +### CLUSTER API: -## Upgrading from `<0.9.0` +```c +redisClusterContext *redisClusterContextInit(void); +void redisClusterFree(redisClusterContext *cc); + +int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr); +int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); +int redisClusterSetOptionConnectBlock(redisClusterContext *cc); +int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc); +int redisClusterSetOptionParseSlaves(redisClusterContext *cc); +int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc); +int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); + +int redisClusterConnect2(redisClusterContext *cc); + +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); +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); + +redisContext *ctx_get_by_node(redisClusterContext *cc, struct cluster_node *node); + +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); +``` + +### CLUSTER API (old api, version <= 0.3.0): -Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing -code using hiredis should not be a big pain. The key thing to keep in mind when -upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to -the stateless 0.0.1 that only has a file descriptor to work with. +```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); +``` -## Synchronous API +## 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 -redisContext *redisConnect(const char *ip, int port); -void *redisCommand(redisContext *c, const char *format, ...); -void freeReplyObject(void *reply); +redisClusterContext *redisClusterContextInit(void); +int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); +int redisClusterConnect2(redisClusterContext *cc); +void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); +void redisClusterFree(redisClusterContext *cc); ``` -### Connecting +### Cluster connecting -The function `redisConnect` is used to create a so-called `redisContext`. The -context is where Hiredis holds state for a connection. The `redisContext` +The function `redisClusterContextInit` is used to create a so-called `redisClusterContext`. +The function `redisClusterSetOptionAddNodes` is used to add the redis cluster address. +The function `redisClusterConnect2` is used to connect to the redis cluser. +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. More information on errors can be found in the **Errors** section. -After trying to connect to Redis using `redisConnect` you should +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 -redisContext *c = redisConnect("127.0.0.1", 6379); -if (c == NULL || c->err) { - if (c) { - printf("Error: %s\n", c->errstr); - // handle error - } else { - printf("Can't allocate redis context\n"); - } +redisClusterContext *cc = redisClusterContextInit(); +redisClusterSetOptionAddNodes(cc, "127.0.0.1:6379,127.0.0.1:6380"); +redisClusterConnect2(cc); +if (cc != NULL && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error } ``` -*Note: A `redisContext` is not thread-safe.* +### Cluster sending commands -### Sending commands - -There are several ways to issue commands to Redis. The first that will be introduced is -`redisCommand`. This function takes a format similar to printf. In the simplest form, +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 = redisCommand(context, "SET foo bar"); +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 = redisCommand(context, "SET foo %s", value); -``` -When you need to pass binary safe strings in a command, the `%b` specifier can be -used. Together with a pointer to the string, it requires a `size_t` length argument -of the string: -```c -reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen); +reply = redisClusterCommand(clustercontext, "SET foo %s", value); ``` -Internally, Hiredis splits the command in different arguments and will +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 = redisCommand(context, "SET key:%s %s", myid, value); +reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); ``` -### Using replies - -The return value of `redisCommand` holds a reply when the command was -successfully executed. When an error occurs, the return value is `NULL` and -the `err` field in the context will be set (see section on **Errors**). -Once an error is returned the context cannot be reused and you should set up -a new connection. - -The standard replies that `redisCommand` are of the type `redisReply`. The -`type` field in the `redisReply` should be used to test what kind of reply -was received: - -* **`REDIS_REPLY_STATUS`**: - * The command replied with a status reply. The status string can be accessed using `reply->str`. - The length of this string can be accessed using `reply->len`. - -* **`REDIS_REPLY_ERROR`**: - * The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`. - -* **`REDIS_REPLY_INTEGER`**: - * The command replied with an integer. The integer value can be accessed using the - `reply->integer` field of type `long long`. +### Cluster multi-key commands -* **`REDIS_REPLY_NIL`**: - * The command replied with a **nil** object. There is no data to access. +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. -* **`REDIS_REPLY_STRING`**: - * A bulk (string) reply. The value of the reply can be accessed using `reply->str`. - The length of this string can be accessed using `reply->len`. - -* **`REDIS_REPLY_ARRAY`**: - * A multi bulk reply. The number of elements in the multi bulk reply is stored in - `reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well - and can be accessed via `reply->element[..index..]`. - Redis may reply with nested arrays but this is fully supported. - -Replies should be freed using the `freeReplyObject()` function. -Note that this function will take care of freeing sub-reply objects -contained in arrays and nested arrays, so there is no need for the user to -free the sub replies (it is actually harmful and will corrupt the memory). - -**Important:** the current version of hiredis (0.10.0) frees replies when the -asynchronous API is used. This means you should not call `freeReplyObject` when -you use this API. The reply is cleaned up by hiredis _after_ the callback -returns. This behavior will probably change in future releases, so make sure to -keep an eye on the changelog when upgrading (see issue #39). +Example: +```c +reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); +``` -### Cleaning up +### Cluster cleaning up To disconnect and free the context the following function can be used: ```c -void redisFree(redisContext *c); +void redisClusterFree(redisClusterContext *cc); ``` This function immediately closes the socket and then frees the allocations done in creating the context. -### Sending commands (cont'd) +### Cluster pipelining -Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands. -It has the following prototype: +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 -void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); +int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); ``` -It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the -arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will -use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments -need to be binary safe, the entire array of lengths `argvlen` should be provided. - -The return value has the same semantic as `redisCommand`. - -### Pipelining - -To explain how Hiredis supports pipelining in a blocking connection, there needs to be -understanding of the internal execution flow. - -When any of the functions in the `redisCommand` family is called, Hiredis first formats the -command according to the Redis protocol. The formatted command is then put in the output buffer -of the context. This output buffer is dynamic, so it can hold any number of commands. -After the command is put in the output buffer, `redisGetReply` is called. This function has the -following two execution paths: - -1. The input buffer is non-empty: - * Try to parse a single reply from the input buffer and return it - * If no reply could be parsed, continue at *2* -2. The input buffer is empty: - * Write the **entire** output buffer to the socket - * Read from the socket until a single reply could be parsed - -The function `redisGetReply` 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 `redisCommand` family, apart from not returning a reply: -```c -void redisAppendCommand(redisContext *c, const char *format, ...); -void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen); -``` -After calling either function one or more times, `redisGetReply` can be used to receive the +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 pipeline (resulting in only a single call to `write(2)` and -a single call to `read(2)`): +The following examples shows a simple cluster pipeline: ```c redisReply *reply; -redisAppendCommand(context,"SET foo bar"); -redisAppendCommand(context,"GET foo"); -redisGetReply(context,&reply); // reply for SET +redisClusterAppendCommand(clusterContext,"SET foo bar"); +redisClusterAppendCommand(clusterContext,"GET foo"); +redisClusterGetReply(clusterContext,&reply); // reply for SET freeReplyObject(reply); -redisGetReply(context,&reply); // reply for GET +redisClusterGetReply(clusterContext,&reply); // reply for GET freeReplyObject(reply); +redisClusterReset(clusterContext); ``` -This API can also be used to implement a blocking subscriber: -```c -reply = redisCommand(context,"SUBSCRIBE foo"); -freeReplyObject(reply); -while(redisGetReply(context,&reply) == REDIS_OK) { - // consume message - freeReplyObject(reply); -} -``` -### Errors - -When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is -returned. The `err` field inside the context will be non-zero and set to one of the -following constants: - -* **`REDIS_ERR_IO`**: - There was an I/O error while creating the connection, trying to write - to the socket or read from the socket. If you included `errno.h` in your - application, you can use the global `errno` variable to find out what is - wrong. - -* **`REDIS_ERR_EOF`**: - The server closed the connection which resulted in an empty read. - -* **`REDIS_ERR_PROTOCOL`**: - There was an error while parsing the protocol. -* **`REDIS_ERR_OTHER`**: - Any other error. Currently, it is only used when a specified hostname to connect - to cannot be resolved. +## Cluster asynchronous API -In every case, the `errstr` field in the context will be set to hold a string representation -of the error. - -## Asynchronous API - -Hiredis comes with an asynchronous API that works easily with any event library. -Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html) -and [libevent](http://monkey.org/~provos/libevent/). +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 @@ -257,18 +228,15 @@ Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The 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. - -*Note: A `redisAsyncContext` is not thread-safe.* - ```c -redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); -if (c->err) { - printf("Error: %s\n", c->errstr); +redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); +if (acc->err) { + printf("Error: %s\n", acc->errstr); // handle error } ``` -The asynchronous context can hold a disconnect callback function that is called when the +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 @@ -278,40 +246,37 @@ On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection w 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. -The context object is always freed after the disconnect callback fired. When a reconnect is needed, -the disconnect callback is a good point to do so. +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 redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); ``` -`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback. ### Sending commands and their callbacks -In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. -Therefore, unlike the synchronous API, there is only a single way to send commands. -Because commands are sent to Redis asynchronously, issuing a command requires a callback function +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(redisAsyncContext *c, void *reply, void *privdata); +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 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 redisClusterAsyncCommand( + redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, + void *privdata, const char *format, ...); ``` -Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command +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 `redisAsyncCommand` family. +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 @@ -321,9 +286,9 @@ All pending callbacks are called with a `NULL` reply when the context encountere ### Disconnecting -An asynchronous connection can be terminated using: +An cluster asynchronous connection can be terminated using: ```c -void redisAsyncDisconnect(redisAsyncContext *ac); +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 @@ -333,138 +298,15 @@ callbacks have been executed. After this, the disconnection callback is executed ### Hooking it up to event library *X* -There are a few hooks that need to be set on the context object after it is created. -See the `adapters/` directory for bindings to *libev* and *libevent*. - -## Reply parsing API - -Hiredis comes with a reply parsing API that makes it easy for writing higher -level language bindings. - -The reply parsing API consists of the following functions: -```c -redisReader *redisReaderCreate(void); -void redisReaderFree(redisReader *reader); -int redisReaderFeed(redisReader *reader, const char *buf, size_t len); -int redisReaderGetReply(redisReader *reader, void **reply); -``` -The same set of functions are used internally by hiredis when creating a -normal Redis context, the above API just exposes it to the user for a direct -usage. - -### Usage - -The function `redisReaderCreate` creates a `redisReader` structure that holds a -buffer with unparsed data and state for the protocol parser. - -Incoming data -- most likely from a socket -- can be placed in the internal -buffer of the `redisReader` using `redisReaderFeed`. This function will make a -copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed -when `redisReaderGetReply` is called. This function returns an integer status -and a reply object (as described above) via `void **reply`. The returned status -can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went -wrong (either a protocol error, or an out of memory error). - -The parser limits the level of nesting for multi bulk payloads to 7. If the -multi bulk nesting level is higher than this, the parser returns an error. - -### Customizing replies - -The function `redisReaderGetReply` creates `redisReply` and makes the function -argument `reply` point to the created `redisReply` variable. For instance, if -the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply` -will hold the status as a vanilla C string. However, the functions that are -responsible for creating instances of the `redisReply` can be customized by -setting the `fn` field on the `redisReader` struct. This should be done -immediately after creating the `redisReader`. - -For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c) -uses customized reply object functions to create Ruby objects. - -### Reader max buffer - -Both when using the Reader API directly or when using it indirectly via a -normal Redis context, the redisReader structure uses a buffer in order to -accumulate data from the server. -Usually this buffer is destroyed when it is empty and is larger than 16 -KiB in order to avoid wasting memory in unused buffers - -However when working with very big payloads destroying the buffer may slow -down performances considerably, so it is possible to modify the max size of -an idle buffer changing the value of the `maxbuf` field of the reader structure -to the desired value. The special value of 0 means that there is no maximum -value for an idle buffer, so the buffer will never get freed. - -For instance if you have a normal Redis context you can set the maximum idle -buffer to zero (unlimited) just with: -```c -context->reader->maxbuf = 0; -``` -This should be done only in order to maximize performances when working with -large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again -as soon as possible in order to prevent allocation of useless memory. - -## SSL/TLS Support - -### Building - -SSL/TLS support is not built by default and requires an explicit flag: - - make USE_SSL=1 - -This requires OpenSSL development package (e.g. including header files to be -available. - -When enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and -`libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries -unaffected so no additional dependencies are introduced. +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*. -### Using it - -First, you'll need to make sure you include the SSL header file: - -```c -#include "hiredis.h" -#include "hiredis_ssl.h" -``` - -SSL can only be enabled on a `redisContext` connection after the connection has -been established and before any command has been processed. For example: - -```c -c = redisConnect('localhost', 6443); -if (c == NULL || c->err) { - /* Handle error and abort... */ -} - -if (redisSecureConnection(c, - "cacertbundle.crt", /* File name of trusted CA/ca bundle file */ - "client_cert.pem", /* File name of client certificate file */ - "client_key.pem", /* File name of client privat ekey */ - "redis.mydomain.com" /* Server name to request (SNI) */ - ) != REDIS_OK) { - printf("SSL error: %s\n", c->errstr); - /* Abort... */ -} -``` - -You will also need to link against `libhiredis_ssl`, **in addition** to -`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies. - -### OpenSSL Global State Initialization - -OpenSSL needs to have certain global state initialized before it can be used. -Using `redisSecureConnection()` will handle this automatically on the first -call. +## AUTHORS -**If the calling application itself also initializes and uses OpenSSL directly, -`redisSecureConnection()` must not be used.** +Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). -Instead, use `redisInitiateSSL()` which also provides greater control over the -configuration of the SSL connection, as the caller is responsible to create a -connection context using `SSL_new()` and configure it as required. +The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). -## AUTHORS +The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). -Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and -Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. +Hiredis-vip is released under the BSD license. From 3d735ae0dfdb10376d130cbc7914955b47533582 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 23 Jan 2020 23:23:31 -0800 Subject: [PATCH 043/273] Fixed parsing of cluster nodes --- hircluster.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/hircluster.c b/hircluster.c index 07095db6..48fceff2 100644 --- a/hircluster.c +++ b/hircluster.c @@ -36,6 +36,8 @@ #define IP_PORT_SEPARATOR ":" +#define PORT_CPORT_SEPARATOR "@" + #define CLUSTER_ADDRESS_SEPARATOR "," #define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5 @@ -546,8 +548,8 @@ 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; + sds *ip_port = NULL, *port_cport = NULL; + int count_ip_port = 0, count_port_cport = 0; cluster_node *node; if(info_count < 8) @@ -591,9 +593,19 @@ static cluster_node *node_get_with_nodes( goto error; } node->host = ip_port[0]; - node->port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + + port_cport = sdssplitlen(ip_port[1], sdslen(ip_port[1]), + PORT_CPORT_SEPARATOR, strlen(PORT_CPORT_SEPARATOR), &count_port_cport); + if (port_cport == NULL || count_port_cport != 2) + { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "split port cport error"); + goto error; + } + node->port = hi_atoi(port_cport[0], sdslen(port_cport[0])); node->role = role; + sdsfreesplitres(port_cport, count_port_cport); sdsfree(ip_port[1]); free(ip_port); @@ -608,6 +620,11 @@ static cluster_node *node_get_with_nodes( sdsfreesplitres(ip_port, count_ip_port); } + if (port_cport != NULL) + { + sdsfreesplitres(port_cport, count_port_cport); + } + if(node != NULL) { hi_free(node); From cdd4ea94ec8188ee5a0ece2020c3355c84ad8e0b Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 23 Jan 2020 23:33:34 -0800 Subject: [PATCH 044/273] Use strtok to parse the node port instead of using sdssplit --- hircluster.c | 20 +++++++------------- win32.h | 4 ++++ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/hircluster.c b/hircluster.c index 48fceff2..33f8e735 100644 --- a/hircluster.c +++ b/hircluster.c @@ -548,8 +548,9 @@ static cluster_node *node_get_with_nodes( redisClusterContext *cc, sds *node_infos, int info_count, uint8_t role) { - sds *ip_port = NULL, *port_cport = NULL; - int count_ip_port = 0, count_port_cport = 0; + sds* ip_port = NULL; + int count_ip_port = 0; + char *port = NULL, *port_context = NULL; cluster_node *node; if(info_count < 8) @@ -594,18 +595,16 @@ static cluster_node *node_get_with_nodes( } node->host = ip_port[0]; - port_cport = sdssplitlen(ip_port[1], sdslen(ip_port[1]), - PORT_CPORT_SEPARATOR, strlen(PORT_CPORT_SEPARATOR), &count_port_cport); - if (port_cport == NULL || count_port_cport != 2) + port = strtok_r(ip_port[1], PORT_CPORT_SEPARATOR, &port_context); + if (port == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, - "split port cport error"); + "error parsing port"); goto error; } - node->port = hi_atoi(port_cport[0], sdslen(port_cport[0])); + node->port = hi_atoi(port, strlen(port)); node->role = role; - sdsfreesplitres(port_cport, count_port_cport); sdsfree(ip_port[1]); free(ip_port); @@ -620,11 +619,6 @@ static cluster_node *node_get_with_nodes( sdsfreesplitres(ip_port, count_ip_port); } - if (port_cport != NULL) - { - sdsfreesplitres(port_cport, count_port_cport); - } - if(node != NULL) { hi_free(node); diff --git a/win32.h b/win32.h index a84ab67d..a2ad24cc 100644 --- a/win32.h +++ b/win32.h @@ -20,6 +20,10 @@ #define strncasecmp _strnicmp #endif +#ifndef strtok_r +#define strtok_r strtok_s +#endif + #ifndef va_copy #define va_copy(d,s) ((d) = (s)) #endif From 34596e683e992ee376cca599c6c653cf573787f4 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 24 Jan 2020 00:10:14 -0800 Subject: [PATCH 045/273] Updated CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db304b6a..b791adac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### 0.4.0 - Jan 24, 2019 +* Updated underlying hiredis version to 0.14.0 +* Added CMake files to enable Windows and Mac builds +* Fixed bug due to CLUSTER NODES reply format change + + ### 0.3.0 - Dec 07, 2016 * Support redisClustervCommand, redisClustervAppendCommand and redisClustervAsyncCommand api. (deep011) From c4a2eeef7e707af1da54cb1652ebc26499cb55b3 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 24 Jan 2020 00:17:34 -0800 Subject: [PATCH 046/273] Restored missing adapter functions in libevent.h --- adapters/libevent.h | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index a4952776..20f7f2d1 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -31,7 +31,7 @@ #ifndef __HIREDIS_LIBEVENT_H__ #define __HIREDIS_LIBEVENT_H__ #include -#include "../hiredis.h" +#include "../hircluster.h" #include "../async.h" #define REDIS_LIBEVENT_DELETED 0x01 @@ -169,4 +169,26 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { e->base = base; 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 From d56190c8fbea42df2c1930d75af40637ca617db2 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 24 Jan 2020 22:56:08 -0800 Subject: [PATCH 047/273] Allow for building hiredis_ssl on windows --- CMakeLists.txt | 2 +- ssl.c | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e76360fc..3b4b7c26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) IF (WIN32 OR MINGW) SET(BUILD_SHARED_LIBS OFF) - ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS) + ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) ENDIF() IF (MSVC) diff --git a/ssl.c b/ssl.c index b97fc1df..0fff9a4c 100644 --- a/ssl.c +++ b/ssl.c @@ -34,13 +34,18 @@ #include "async.h" #include -#include #include #include +#ifdef WIN32 +#include +#else +#include +#endif #include #include +#include "win32.h" #include "async_private.h" void __redisSetError(redisContext *c, int type, const char *str); @@ -119,6 +124,18 @@ static void sslLogCallback(const SSL *ssl, int where, int ret) { #endif #ifdef HIREDIS_USE_CRYPTO_LOCKS +#ifdef WIN32 +typedef CRITICAL_SECTION sslLockType; +static void sslLockInit(sslLockType* l) { + InitializeCriticalSection(l); +} +static void sslLockAcquire(sslLockType* l) { + EnterCriticalSection(l); +} +static void sslLockRelease(sslLockType* l) { + LeaveCriticalSection(l); +} +#else typedef pthread_mutex_t sslLockType; static void sslLockInit(sslLockType *l) { pthread_mutex_init(l, NULL); @@ -129,7 +146,9 @@ static void sslLockAcquire(sslLockType *l) { static void sslLockRelease(sslLockType *l) { pthread_mutex_unlock(l); } -static pthread_mutex_t *ossl_locks; +#endif + +static sslLockType* ossl_locks; static void opensslDoLock(int mode, int lkid, const char *f, int line) { sslLockType *l = ossl_locks + lkid; From 16c68af85497571ee16734e19b7e10d3fd77522c Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 1 Mar 2020 00:22:31 -0800 Subject: [PATCH 048/273] hiredis_vip is no longer a fork of hiredis. It now uses the hiredis public API and links against it --- CMakeLists.txt | 78 +- adapters/ae.h | 105 +-- adapters/glib.h | 153 ---- adapters/ivykis.h | 81 -- adapters/libev.h | 147 ---- adapters/libevent.h | 147 +--- adapters/libuv.h | 119 --- adapters/macosx.h | 114 --- adapters/qt.h | 135 ---- async.c | 771 ------------------ async.h | 143 ---- async_private.h | 72 -- command.h | 2 +- dict.c | 2 +- examples/CMakeLists.txt | 46 -- examples/example-ae.c | 62 -- examples/example-glib.c | 73 -- examples/example-ivykis.c | 58 -- examples/example-libev.c | 52 -- examples/example-libevent-ssl.c | 73 -- examples/example-libevent.c | 64 -- examples/example-libuv.c | 53 -- examples/example-macosx.c | 66 -- examples/example-qt.cpp | 46 -- examples/example-qt.h | 32 - examples/example-ssl.c | 97 --- examples/example.c | 91 --- fmacros.h | 12 - hircluster.c | 13 +- hircluster.h | 4 +- hiredis.c | 1078 -------------------------- hiredis.h | 297 ------- hiredis_ssl.h | 53 -- hiredis_ssl.pc.in | 12 - hiredis_vip-config.cmake.in | 13 + net.c | 571 -------------- net.h | 54 -- read.c | 669 ---------------- read.h | 122 --- sds.c | 1291 ------------------------------- sds.h | 276 ------- sdsalloc.h | 42 - sockcompat.c | 248 ------ sockcompat.h | 91 --- ssl.c | 467 ----------- test.c | 1017 ------------------------ test.sh | 70 -- 47 files changed, 53 insertions(+), 9229 deletions(-) delete mode 100644 adapters/glib.h delete mode 100644 adapters/ivykis.h delete mode 100644 adapters/libev.h delete mode 100644 adapters/libuv.h delete mode 100644 adapters/macosx.h delete mode 100644 adapters/qt.h delete mode 100644 async.c delete mode 100644 async.h delete mode 100644 async_private.h delete mode 100644 examples/CMakeLists.txt delete mode 100644 examples/example-ae.c delete mode 100644 examples/example-glib.c delete mode 100644 examples/example-ivykis.c delete mode 100644 examples/example-libev.c delete mode 100644 examples/example-libevent-ssl.c delete mode 100644 examples/example-libevent.c delete mode 100644 examples/example-libuv.c delete mode 100644 examples/example-macosx.c delete mode 100644 examples/example-qt.cpp delete mode 100644 examples/example-qt.h delete mode 100644 examples/example-ssl.c delete mode 100644 examples/example.c delete mode 100644 fmacros.h delete mode 100644 hiredis.c delete mode 100644 hiredis.h delete mode 100644 hiredis_ssl.h delete mode 100644 hiredis_ssl.pc.in create mode 100644 hiredis_vip-config.cmake.in delete mode 100644 net.c delete mode 100644 net.h delete mode 100644 read.c delete mode 100644 read.h delete mode 100644 sds.c delete mode 100644 sds.h delete mode 100644 sdsalloc.h delete mode 100644 sockcompat.c delete mode 100644 sockcompat.h delete mode 100644 ssl.c delete mode 100644 test.c delete mode 100644 test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b4b7c26..a4b05e2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,7 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) INCLUDE(GNUInstallDirs) PROJECT(hiredis_vip) -OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) -OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF) - IF (WIN32 OR MINGW) - SET(BUILD_SHARED_LIBS OFF) ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) ENDIF() @@ -30,16 +26,7 @@ MESSAGE("Detected version: ${VERSION}") PROJECT(hiredis_vip VERSION "${VERSION}") -SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") - -ADD_LIBRARY(hiredis_vip - async.c - dict.c - hiredis.c - net.c - read.c - sds.c - sockcompat.c +ADD_LIBRARY(hiredis_vip SHARED adlist.c command.c crc16.c @@ -50,17 +37,21 @@ ADD_LIBRARY(hiredis_vip SET_TARGET_PROPERTIES(hiredis_vip PROPERTIES VERSION "${HIREDIS_VIP_SONAME}") + +FIND_PACKAGE(hiredis REQUIRED) + +TARGET_INCLUDE_DIRECTORIES(hiredis_vip PUBLIC ${hiredis_INCLUDE_DIRS}) + IF(WIN32 OR MINGW) - TARGET_LINK_LIBRARIES(hiredis_vip PRIVATE ws2_32) + TARGET_LINK_LIBRARIES(hiredis_vip PRIVATE ws2_32 hiredis::hiredis) ENDIF() -TARGET_INCLUDE_DIRECTORIES(hiredis_vip PUBLIC .) CONFIGURE_FILE(hiredis_vip.pc.in hiredis_vip.pc @ONLY) INSTALL(TARGETS hiredis_vip - DESTINATION "${CMAKE_INSTALL_LIBDIR}") + EXPORT hiredis_vip-targets) -INSTALL(FILES hiredis.h read.h sds.h async.h hircluster.h dict.h adlist.h hiarray.h +INSTALL(FILES hircluster.h adlist.h hiarray.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) INSTALL(DIRECTORY adapters @@ -69,38 +60,23 @@ INSTALL(DIRECTORY adapters INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -IF(ENABLE_SSL) - IF (NOT OPENSSL_ROOT_DIR) - IF (APPLE) - SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl") - ENDIF() - ENDIF() - FIND_PACKAGE(OpenSSL REQUIRED) - ADD_LIBRARY(hiredis_ssl - ssl.c) - TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}") - TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES}) - CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) - - INSTALL(TARGETS hiredis_ssl - DESTINATION "${CMAKE_INSTALL_LIBDIR}") - - INSTALL(FILES hiredis_ssl.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) - - INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -ENDIF() +export(EXPORT hiredis_vip-targets + FILE ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip-targets.cmake + NAMESPACE hiredis_vip::) + +SET(CMAKE_CONF_INSTALL_DIR share/hiredis_vip) +SET(INCLUDE_INSTALL_DIR include) +include(CMakePackageConfigHelpers) +configure_package_config_file(hiredis_vip-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip-config.cmake + INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} + PATH_VARS INCLUDE_INSTALL_DIR) + +INSTALL(EXPORT hiredis_vip-targets + FILE hiredis_vip-targets.cmake + NAMESPACE hiredis_vip:: + DESTINATION ${CMAKE_CONF_INSTALL_DIR}) + +INSTALL(FILES hiredis_vip-config.cmake + DESTINATION ${CMAKE_CONF_INSTALL_DIR}) -IF(NOT (DISABLE_TESTS OR (WIN32 OR MINGW))) - ENABLE_TESTING() - ADD_EXECUTABLE(hiredis-test test.c) - TARGET_LINK_LIBRARIES(hiredis-test hiredis_vip) - ADD_TEST(NAME hiredis-test - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) -ENDIF() -# Add examples -IF(ENABLE_EXAMPLES) - ADD_SUBDIRECTORY(examples) -ENDIF(ENABLE_EXAMPLES) diff --git a/adapters/ae.h b/adapters/ae.h index f861cf28..674e66e5 100644 --- a/adapters/ae.h +++ b/adapters/ae.h @@ -28,108 +28,11 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef __HIREDIS_AE_H__ -#define __HIREDIS_AE_H__ -#include -#include -#include "../hiredis.h" -#include "../async.h" +#ifndef __HIREDIS_VIP_AE_H__ +#define __HIREDIS_VIP_AE_H__ +#include -#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) { @@ -149,6 +52,4 @@ static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc return REDIS_OK; } -#endif //shenzheng 2015-11-5 redis cluster - #endif diff --git a/adapters/glib.h b/adapters/glib.h deleted file mode 100644 index e0a6411d..00000000 --- a/adapters/glib.h +++ /dev/null @@ -1,153 +0,0 @@ -#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 = (RedisSource *)data; - g_return_if_fail(source); - source->poll_fd.events |= G_IO_IN; - g_main_context_wakeup(g_source_get_context((GSource *)data)); -} - -static void -redis_source_del_read (gpointer data) -{ - RedisSource *source = (RedisSource *)data; - g_return_if_fail(source); - source->poll_fd.events &= ~G_IO_IN; - g_main_context_wakeup(g_source_get_context((GSource *)data)); -} - -static void -redis_source_add_write (gpointer data) -{ - RedisSource *source = (RedisSource *)data; - g_return_if_fail(source); - source->poll_fd.events |= G_IO_OUT; - g_main_context_wakeup(g_source_get_context((GSource *)data)); -} - -static void -redis_source_del_write (gpointer data) -{ - RedisSource *source = (RedisSource *)data; - g_return_if_fail(source); - source->poll_fd.events &= ~G_IO_OUT; - g_main_context_wakeup(g_source_get_context((GSource *)data)); -} - -static void -redis_source_cleanup (gpointer data) -{ - RedisSource *source = (RedisSource *)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((GSource *)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/adapters/ivykis.h b/adapters/ivykis.h deleted file mode 100644 index 6a12a868..00000000 --- a/adapters/ivykis.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef __HIREDIS_IVYKIS_H__ -#define __HIREDIS_IVYKIS_H__ -#include -#include "../hiredis.h" -#include "../async.h" - -typedef struct redisIvykisEvents { - redisAsyncContext *context; - struct iv_fd fd; -} redisIvykisEvents; - -static void redisIvykisReadEvent(void *arg) { - redisAsyncContext *context = (redisAsyncContext *)arg; - redisAsyncHandleRead(context); -} - -static void redisIvykisWriteEvent(void *arg) { - redisAsyncContext *context = (redisAsyncContext *)arg; - redisAsyncHandleWrite(context); -} - -static void redisIvykisAddRead(void *privdata) { - redisIvykisEvents *e = (redisIvykisEvents*)privdata; - iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent); -} - -static void redisIvykisDelRead(void *privdata) { - redisIvykisEvents *e = (redisIvykisEvents*)privdata; - iv_fd_set_handler_in(&e->fd, NULL); -} - -static void redisIvykisAddWrite(void *privdata) { - redisIvykisEvents *e = (redisIvykisEvents*)privdata; - iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent); -} - -static void redisIvykisDelWrite(void *privdata) { - redisIvykisEvents *e = (redisIvykisEvents*)privdata; - iv_fd_set_handler_out(&e->fd, NULL); -} - -static void redisIvykisCleanup(void *privdata) { - redisIvykisEvents *e = (redisIvykisEvents*)privdata; - - iv_fd_unregister(&e->fd); - free(e); -} - -static int redisIvykisAttach(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisIvykisEvents *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 = (redisIvykisEvents*)malloc(sizeof(*e)); - e->context = ac; - - /* Register functions to start/stop listening for events */ - ac->ev.addRead = redisIvykisAddRead; - ac->ev.delRead = redisIvykisDelRead; - ac->ev.addWrite = redisIvykisAddWrite; - ac->ev.delWrite = redisIvykisDelWrite; - ac->ev.cleanup = redisIvykisCleanup; - ac->ev.data = e; - - /* Initialize and install read/write events */ - IV_FD_INIT(&e->fd); - e->fd.fd = c->fd; - e->fd.handler_in = redisIvykisReadEvent; - e->fd.handler_out = redisIvykisWriteEvent; - e->fd.handler_err = NULL; - e->fd.cookie = e->context; - - iv_fd_register(&e->fd); - - return REDIS_OK; -} -#endif diff --git a/adapters/libev.h b/adapters/libev.h deleted file mode 100644 index 2bf8d521..00000000 --- a/adapters/libev.h +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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/adapters/libevent.h b/adapters/libevent.h index 20f7f2d1..942f9d5e 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -28,149 +28,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef __HIREDIS_LIBEVENT_H__ -#define __HIREDIS_LIBEVENT_H__ -#include +#ifndef __HIREDIS_VIP_LIBEVENT_H__ +#define __HIREDIS_VIP_LIBEVENT_H__ +#include #include "../hircluster.h" -#include "../async.h" - -#define REDIS_LIBEVENT_DELETED 0x01 -#define REDIS_LIBEVENT_ENTERED 0x02 - -typedef struct redisLibeventEvents { - redisAsyncContext *context; - struct event *ev; - struct event_base *base; - struct timeval tv; - short flags; - short state; -} redisLibeventEvents; - -static void redisLibeventDestroy(redisLibeventEvents *e) { - free(e); -} - -static void redisLibeventHandler(int fd, short event, void *arg) { - ((void)fd); - redisLibeventEvents *e = (redisLibeventEvents*)arg; - e->state |= REDIS_LIBEVENT_ENTERED; - - #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\ - redisLibeventDestroy(e);\ - return; \ - } - - if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) { - redisAsyncHandleTimeout(e->context); - CHECK_DELETED(); - } - - if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { - redisAsyncHandleRead(e->context); - CHECK_DELETED(); - } - - if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) { - redisAsyncHandleWrite(e->context); - CHECK_DELETED(); - } - - e->state &= ~REDIS_LIBEVENT_ENTERED; - #undef CHECK_DELETED -} - -static void redisLibeventUpdate(void *privdata, short flag, int isRemove) { - redisLibeventEvents *e = (redisLibeventEvents *)privdata; - const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL; - - if (isRemove) { - if ((e->flags & flag) == 0) { - return; - } else { - e->flags &= ~flag; - } - } else { - if (e->flags & flag) { - return; - } else { - e->flags |= flag; - } - } - - event_del(e->ev); - event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST, - redisLibeventHandler, privdata); - event_add(e->ev, tv); -} - -static void redisLibeventAddRead(void *privdata) { - redisLibeventUpdate(privdata, EV_READ, 0); -} - -static void redisLibeventDelRead(void *privdata) { - redisLibeventUpdate(privdata, EV_READ, 1); -} - -static void redisLibeventAddWrite(void *privdata) { - redisLibeventUpdate(privdata, EV_WRITE, 0); -} - -static void redisLibeventDelWrite(void *privdata) { - redisLibeventUpdate(privdata, EV_WRITE, 1); -} - -static void redisLibeventCleanup(void *privdata) { - redisLibeventEvents *e = (redisLibeventEvents*)privdata; - if (!e) { - return; - } - event_del(e->ev); - event_free(e->ev); - e->ev = NULL; - - if (e->state & REDIS_LIBEVENT_ENTERED) { - e->state |= REDIS_LIBEVENT_DELETED; - } else { - redisLibeventDestroy(e); - } -} - -static void redisLibeventSetTimeout(void *privdata, struct timeval tv) { - redisLibeventEvents *e = (redisLibeventEvents *)privdata; - short flags = e->flags; - e->flags = 0; - e->tv = tv; - redisLibeventUpdate(e, flags, 0); -} - -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*)calloc(1, 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.scheduleTimer = redisLibeventSetTimeout; - ac->ev.data = e; - - /* Initialize and install read/write events */ - e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e); - e->base = base; - 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); @@ -189,6 +50,4 @@ static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct even return REDIS_OK; } -#endif //shenzheng 2015-9-21 redis cluster - #endif diff --git a/adapters/libuv.h b/adapters/libuv.h deleted file mode 100644 index 39ef7cf5..00000000 --- a/adapters/libuv.h +++ /dev/null @@ -1,119 +0,0 @@ -#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; - int ev = (status ? p->events : events); - - if (p->context != NULL && (ev & UV_READABLE)) { - redisAsyncHandleRead(p->context); - } - if (p->context != NULL && (ev & 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; - - p->context = NULL; // indicate that context might no longer exist - 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/adapters/macosx.h b/adapters/macosx.h deleted file mode 100644 index 72121f60..00000000 --- a/adapters/macosx.h +++ /dev/null @@ -1,114 +0,0 @@ -// -// Created by Дмитрий Бахвалов on 13.07.15. -// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. -// - -#ifndef __HIREDIS_MACOSX_H__ -#define __HIREDIS_MACOSX_H__ - -#include - -#include "../hiredis.h" -#include "../async.h" - -typedef struct { - redisAsyncContext *context; - CFSocketRef socketRef; - CFRunLoopSourceRef sourceRef; -} RedisRunLoop; - -static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) { - if( redisRunLoop != NULL ) { - if( redisRunLoop->sourceRef != NULL ) { - CFRunLoopSourceInvalidate(redisRunLoop->sourceRef); - CFRelease(redisRunLoop->sourceRef); - } - if( redisRunLoop->socketRef != NULL ) { - CFSocketInvalidate(redisRunLoop->socketRef); - CFRelease(redisRunLoop->socketRef); - } - free(redisRunLoop); - } - return REDIS_ERR; -} - -static void redisMacOSAddRead(void *privdata) { - RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; - CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); -} - -static void redisMacOSDelRead(void *privdata) { - RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; - CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack); -} - -static void redisMacOSAddWrite(void *privdata) { - RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; - CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); -} - -static void redisMacOSDelWrite(void *privdata) { - RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; - CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack); -} - -static void redisMacOSCleanup(void *privdata) { - RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata; - freeRedisRunLoop(redisRunLoop); -} - -static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) { - redisAsyncContext* context = (redisAsyncContext*) info; - - switch (callbackType) { - case kCFSocketReadCallBack: - redisAsyncHandleRead(context); - break; - - case kCFSocketWriteCallBack: - redisAsyncHandleWrite(context); - break; - - default: - break; - } -} - -static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) { - redisContext *redisCtx = &(redisAsyncCtx->c); - - /* Nothing should be attached when something is already attached */ - if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; - - RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); - if( !redisRunLoop ) return REDIS_ERR; - - /* Setup redis stuff */ - redisRunLoop->context = redisAsyncCtx; - - redisAsyncCtx->ev.addRead = redisMacOSAddRead; - redisAsyncCtx->ev.delRead = redisMacOSDelRead; - redisAsyncCtx->ev.addWrite = redisMacOSAddWrite; - redisAsyncCtx->ev.delWrite = redisMacOSDelWrite; - redisAsyncCtx->ev.cleanup = redisMacOSCleanup; - redisAsyncCtx->ev.data = redisRunLoop; - - /* Initialize and install read/write events */ - CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL }; - - redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd, - kCFSocketReadCallBack | kCFSocketWriteCallBack, - redisMacOSAsyncCallback, - &socketCtx); - if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop); - - redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0); - if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop); - - CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode); - - return REDIS_OK; -} - -#endif - diff --git a/adapters/qt.h b/adapters/qt.h deleted file mode 100644 index 5cc02e6c..00000000 --- a/adapters/qt.h +++ /dev/null @@ -1,135 +0,0 @@ -/*- - * Copyright (C) 2014 Pietro Cerutti - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. 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. - * - * THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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_QT_H__ -#define __HIREDIS_QT_H__ -#include -#include "../async.h" - -static void RedisQtAddRead(void *); -static void RedisQtDelRead(void *); -static void RedisQtAddWrite(void *); -static void RedisQtDelWrite(void *); -static void RedisQtCleanup(void *); - -class RedisQtAdapter : public QObject { - - Q_OBJECT - - friend - void RedisQtAddRead(void * adapter) { - RedisQtAdapter * a = static_cast(adapter); - a->addRead(); - } - - friend - void RedisQtDelRead(void * adapter) { - RedisQtAdapter * a = static_cast(adapter); - a->delRead(); - } - - friend - void RedisQtAddWrite(void * adapter) { - RedisQtAdapter * a = static_cast(adapter); - a->addWrite(); - } - - friend - void RedisQtDelWrite(void * adapter) { - RedisQtAdapter * a = static_cast(adapter); - a->delWrite(); - } - - friend - void RedisQtCleanup(void * adapter) { - RedisQtAdapter * a = static_cast(adapter); - a->cleanup(); - } - - public: - RedisQtAdapter(QObject * parent = 0) - : QObject(parent), m_ctx(0), m_read(0), m_write(0) { } - - ~RedisQtAdapter() { - if (m_ctx != 0) { - m_ctx->ev.data = NULL; - } - } - - int setContext(redisAsyncContext * ac) { - if (ac->ev.data != NULL) { - return REDIS_ERR; - } - m_ctx = ac; - m_ctx->ev.data = this; - m_ctx->ev.addRead = RedisQtAddRead; - m_ctx->ev.delRead = RedisQtDelRead; - m_ctx->ev.addWrite = RedisQtAddWrite; - m_ctx->ev.delWrite = RedisQtDelWrite; - m_ctx->ev.cleanup = RedisQtCleanup; - return REDIS_OK; - } - - private: - void addRead() { - if (m_read) return; - m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0); - connect(m_read, SIGNAL(activated(int)), this, SLOT(read())); - } - - void delRead() { - if (!m_read) return; - delete m_read; - m_read = 0; - } - - void addWrite() { - if (m_write) return; - m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0); - connect(m_write, SIGNAL(activated(int)), this, SLOT(write())); - } - - void delWrite() { - if (!m_write) return; - delete m_write; - m_write = 0; - } - - void cleanup() { - delRead(); - delWrite(); - } - - private slots: - void read() { redisAsyncHandleRead(m_ctx); } - void write() { redisAsyncHandleWrite(m_ctx); } - - private: - redisAsyncContext * m_ctx; - QSocketNotifier * m_read; - QSocketNotifier * m_write; -}; - -#endif /* !__HIREDIS_QT_H__ */ diff --git a/async.c b/async.c deleted file mode 100644 index 05834f4d..00000000 --- a/async.c +++ /dev/null @@ -1,771 +0,0 @@ -/* - * 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 "async.h" -#include "net.h" -#include "dict.c" -#include "sds.h" -#include "win32.h" - -#include "async_private.h" - -/* 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->cleanup = 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->ev.scheduleTimer = 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 *redisAsyncConnectWithOptions(const redisOptions *options) { - redisOptions myOptions = *options; - redisContext *c; - redisAsyncContext *ac; - - myOptions.options |= REDIS_OPT_NONBLOCK; - c = redisConnectWithOptions(&myOptions); - if (c == NULL) { - return NULL; - } - ac = redisAsyncInitialize(c); - if (ac == NULL) { - redisFree(c); - return NULL; - } - __redisAsyncCopyError(ac); - return ac; -} - -redisAsyncContext *redisAsyncConnect(const char *ip, int port) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - return redisAsyncConnectWithOptions(&options); -} - -redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.endpoint.tcp.source_addr = source_addr; - return redisAsyncConnectWithOptions(&options); -} - -redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.options |= REDIS_OPT_REUSEADDR; - options.endpoint.tcp.source_addr = source_addr; - return redisAsyncConnectWithOptions(&options); -} - -redisAsyncContext *redisAsyncConnectUnix(const char *path) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - return redisAsyncConnectWithOptions(&options); -} - -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->cleanup) { - ac->cleanup(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. */ -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. */ - int ret = __redisShiftCallback(&ac->replies,NULL); - assert(ret == REDIS_ERR); - } else { - /* Disconnection is caused by an error, make sure that pending - * callbacks cannot call new commands. */ - c->flags |= REDIS_DISCONNECTING; - } - - /* cleanup event library on disconnect. - * this is safe to call multiple times */ - _EL_CLEANUP(ac); - - /* For non-clean disconnects, __redisAsyncFree() will execute pending - * callbacks with a NULL-reply. */ - if (!(c->flags & REDIS_NO_AUTO_FREE)) { - __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; - - /** unset the auto-free flag here, because disconnect undoes this */ - c->flags &= ~REDIS_NO_AUTO_FREE; - 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; - redisCallback *cb; - 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) { - cb = dictGetEntryVal(de); - - /* If this is an subscribe reply decrease pending counter. */ - if (strcasecmp(stype+pvariant,"subscribe") == 0) { - cb->pending_subs -= 1; - } - - memcpy(dstcb,cb,sizeof(*dstcb)); - - /* If this is an unsubscribe message, remove it. */ - if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { - if (cb->pending_subs == 0) - dictDelete(callbacks,sname); - - /* If this was the last unsubscribe message, revert to - * non-subscribe mode. */ - assert(reply->element[2]->type == REDIS_REPLY_INTEGER); - - /* Unset subscribed flag only when no pipelined pending subscribe. */ - if (reply->element[2]->integer == 0 - && dictSize(ac->sub.channels) == 0 - && dictSize(ac->sub.patterns) == 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, 0, 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 - && ac->replies.head == NULL) { - __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 successful, the connect callback - * is called with a REDIS_ERR status and the context is free'd. */ -static int __redisAsyncHandleConnect(redisAsyncContext *ac) { - int completed = 0; - redisContext *c = &(ac->c); - if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { - /* Error! */ - redisCheckSocketError(c); - if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); - __redisAsyncDisconnect(ac); - return REDIS_ERR; - } else if (completed == 1) { - /* connected! */ - if (ac->onConnect) ac->onConnect(ac, REDIS_OK); - c->flags |= REDIS_CONNECTED; - return REDIS_OK; - } else { - return REDIS_OK; - } -} - -void redisAsyncRead(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - - if (redisBufferRead(c) == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - /* Always re-schedule reads */ - _EL_ADD_READ(ac); - redisProcessCallbacks(ac); - } -} - -/* 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; - } - - c->funcs->async_read(ac); -} - -void redisAsyncWrite(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - int done = 0; - - 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); - } -} - -void redisAsyncHandleWrite(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; - } - - c->funcs->async_write(ac); -} - -void __redisSetError(redisContext *c, int type, const char *str); - -void redisAsyncHandleTimeout(redisAsyncContext *ac) { - redisContext *c = &(ac->c); - redisCallback cb; - - if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) { - /* Nothing to do - just an idle timeout */ - return; - } - - if (!c->err) { - __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout"); - } - - if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) { - ac->onConnect(ac, REDIS_ERR); - } - - while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) { - __redisRunCallback(ac, &cb, NULL); - } - - /** - * TODO: Don't automatically sever the connection, - * rather, allow to ignore responses before the queue is clear - */ - __redisAsyncDisconnect(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; - struct dict *cbdict; - dictEntry *de; - redisCallback *existcb; - 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; - cb.pending_subs = 1; - - /* 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) - cbdict = ac->sub.patterns; - else - cbdict = ac->sub.channels; - - de = dictFind(cbdict,sname); - - if (de != NULL) { - existcb = dictGetEntryVal(de); - cb.pending_subs = existcb->pending_subs + 1; - } - - ret = dictReplace(cbdict,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); - if (len < 0) - return REDIS_ERR; - 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; -} - -void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { - if (!ac->c.timeout) { - ac->c.timeout = calloc(1, sizeof(tv)); - } - - if (tv.tv_sec == ac->c.timeout->tv_sec && - tv.tv_usec == ac->c.timeout->tv_usec) { - return; - } - - *ac->c.timeout = tv; -} diff --git a/async.h b/async.h deleted file mode 100644 index e42985af..00000000 --- a/async.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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; - int pending_subs; - 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); -typedef void(redisTimerCallback)(void *timer, void *privdata); - -/* 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 (*cleanup)(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); - void (*scheduleTimer)(void *privdata, struct timeval tv); - } 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; - - /* Address used for connect() */ - struct sockaddr *saddr; - size_t addrlen; - - /* Subscription callbacks */ - struct { - redisCallbackList invalid; - struct dict *channels; - struct dict *patterns; - } sub; -} redisAsyncContext; - -/* Functions that proxy to hiredis */ -redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options); -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 redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); -void redisAsyncDisconnect(redisAsyncContext *ac); -void redisAsyncFree(redisAsyncContext *ac); - -/* Handle read/write events */ -void redisAsyncHandleRead(redisAsyncContext *ac); -void redisAsyncHandleWrite(redisAsyncContext *ac); -void redisAsyncHandleTimeout(redisAsyncContext *ac); -void redisAsyncRead(redisAsyncContext *ac); -void redisAsyncWrite(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/async_private.h b/async_private.h deleted file mode 100644 index d0133ae1..00000000 --- a/async_private.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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_PRIVATE_H -#define __HIREDIS_ASYNC_PRIVATE_H - -#define _EL_ADD_READ(ctx) \ - do { \ - refreshTimeout(ctx); \ - 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 { \ - refreshTimeout(ctx); \ - 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); \ - ctx->ev.cleanup = NULL; \ - } while(0); - -static inline void refreshTimeout(redisAsyncContext *ctx) { - if (ctx->c.timeout && ctx->ev.scheduleTimer && - (ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) { - ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout); - // } else { - // printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout); - // if (ctx->c.timeout){ - // printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec, - // ctx->c.timeout->tv_usec); - // } - } -} - -void __redisAsyncDisconnect(redisAsyncContext *ac); -void redisProcessCallbacks(redisAsyncContext *ac); - -#endif /* __HIREDIS_ASYNC_PRIVATE_H */ diff --git a/command.h b/command.h index b7c388a6..3d1948d3 100644 --- a/command.h +++ b/command.h @@ -3,7 +3,7 @@ #include -#include "hiredis.h" +#include #include "adlist.h" typedef enum cmd_parse_result { diff --git a/dict.c b/dict.c index 5b349f07..2bc0b34a 100644 --- a/dict.c +++ b/dict.c @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include "fmacros.h" +//#include "fmacros.h" #include #include #include diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index dd3a313a..00000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -INCLUDE(FindPkgConfig) -# Check for GLib - -PKG_CHECK_MODULES(GLIB2 glib-2.0) -if (GLIB2_FOUND) - INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS}) - LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS}) - ADD_EXECUTABLE(example-glib example-glib.c) - TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES}) -ENDIF(GLIB2_FOUND) - -FIND_PATH(LIBEV ev.h - HINTS /usr/local /usr/opt/local - ENV LIBEV_INCLUDE_DIR) - -if (LIBEV) - # Just compile and link with libev - ADD_EXECUTABLE(example-libev example-libev.c) - TARGET_LINK_LIBRARIES(example-libev hiredis ev) -ENDIF() - -FIND_PATH(LIBEVENT event.h) -if (LIBEVENT) - ADD_EXECUTABLE(example-libevent example-libevent) - TARGET_LINK_LIBRARIES(example-libevent hiredis event) -ENDIF() - -FIND_PATH(LIBUV uv.h) -IF (LIBUV) - ADD_EXECUTABLE(example-libuv example-libuv.c) - TARGET_LINK_LIBRARIES(example-libuv hiredis uv) -ENDIF() - -IF (APPLE) - FIND_LIBRARY(CF CoreFoundation) - ADD_EXECUTABLE(example-macosx example-macosx.c) - TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF}) -ENDIF() - -IF (ENABLE_SSL) - ADD_EXECUTABLE(example-ssl example-ssl.c) - TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl) -ENDIF() - -ADD_EXECUTABLE(example example.c) -TARGET_LINK_LIBRARIES(example hiredis) diff --git a/examples/example-ae.c b/examples/example-ae.c deleted file mode 100644 index 8efa7306..00000000 --- a/examples/example-ae.c +++ /dev/null @@ -1,62 +0,0 @@ -#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/examples/example-glib.c b/examples/example-glib.c deleted file mode 100644 index d6e10f8e..00000000 --- a/examples/example-glib.c +++ /dev/null @@ -1,73 +0,0 @@ -#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/examples/example-ivykis.c b/examples/example-ivykis.c deleted file mode 100644 index 67affcef..00000000 --- a/examples/example-ivykis.c +++ /dev/null @@ -1,58 +0,0 @@ -#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); - - iv_init(); - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisIvykisAttach(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"); - - iv_main(); - - iv_deinit(); - - return 0; -} diff --git a/examples/example-libev.c b/examples/example-libev.c deleted file mode 100644 index cc8b166e..00000000 --- a/examples/example-libev.c +++ /dev/null @@ -1,52 +0,0 @@ -#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/examples/example-libevent-ssl.c b/examples/example-libevent-ssl.c deleted file mode 100644 index 1021113b..00000000 --- a/examples/example-libevent-ssl.c +++ /dev/null @@ -1,73 +0,0 @@ -#include -#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(); - if (argc < 5) { - fprintf(stderr, - "Usage: %s [ca]\n", argv[0]); - exit(1); - } - - const char *value = argv[1]; - size_t nvalue = strlen(value); - - const char *hostname = argv[2]; - int port = atoi(argv[3]); - - const char *cert = argv[4]; - const char *certKey = argv[5]; - const char *caCert = argc > 5 ? argv[6] : NULL; - - redisAsyncContext *c = redisAsyncConnect(hostname, port); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) { - printf("SSL Error!\n"); - exit(1); - } - - redisLibeventAttach(c,base); - redisAsyncSetConnectCallback(c,connectCallback); - redisAsyncSetDisconnectCallback(c,disconnectCallback); - redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); - redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); - event_base_dispatch(base); - return 0; -} diff --git a/examples/example-libevent.c b/examples/example-libevent.c deleted file mode 100644 index 1fe71ae4..00000000 --- a/examples/example-libevent.c +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -void getCallback(redisAsyncContext *c, void *r, void *privdata) { - redisReply *reply = r; - if (reply == NULL) { - if (c->errstr) { - printf("errstr: %s\n", c->errstr); - } - 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(); - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); - struct timeval tv = {0}; - tv.tv_sec = 1; - options.timeout = &tv; - - - redisAsyncContext *c = redisAsyncConnectWithOptions(&options); - 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/examples/example-libuv.c b/examples/example-libuv.c deleted file mode 100644 index a5462d41..00000000 --- a/examples/example-libuv.c +++ /dev/null @@ -1,53 +0,0 @@ -#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/examples/example-macosx.c b/examples/example-macosx.c deleted file mode 100644 index bc84ed5b..00000000 --- a/examples/example-macosx.c +++ /dev/null @@ -1,66 +0,0 @@ -// -// Created by Дмитрий Бахвалов on 13.07.15. -// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved. -// - -#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; - } - CFRunLoopStop(CFRunLoopGetCurrent()); - printf("Disconnected...\n"); -} - -int main (int argc, char **argv) { - signal(SIGPIPE, SIG_IGN); - - CFRunLoopRef loop = CFRunLoopGetCurrent(); - if( !loop ) { - printf("Error: Cannot get current run loop\n"); - return 1; - } - - redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); - if (c->err) { - /* Let *c leak for now... */ - printf("Error: %s\n", c->errstr); - return 1; - } - - redisMacOSAttach(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"); - - CFRunLoopRun(); - - return 0; -} - diff --git a/examples/example-qt.cpp b/examples/example-qt.cpp deleted file mode 100644 index f524c3f3..00000000 --- a/examples/example-qt.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -using namespace std; - -#include -#include - -#include "example-qt.h" - -void getCallback(redisAsyncContext *, void * r, void * privdata) { - - redisReply * reply = static_cast(r); - ExampleQt * ex = static_cast(privdata); - if (reply == nullptr || ex == nullptr) return; - - cout << "key: " << reply->str << endl; - - ex->finish(); -} - -void ExampleQt::run() { - - m_ctx = redisAsyncConnect("localhost", 6379); - - if (m_ctx->err) { - cerr << "Error: " << m_ctx->errstr << endl; - redisAsyncFree(m_ctx); - emit finished(); - } - - m_adapter.setContext(m_ctx); - - redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value); - redisAsyncCommand(m_ctx, getCallback, this, "GET key"); -} - -int main (int argc, char **argv) { - - QCoreApplication app(argc, argv); - - ExampleQt example(argv[argc-1]); - - QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit())); - QTimer::singleShot(0, &example, SLOT(run())); - - return app.exec(); -} diff --git a/examples/example-qt.h b/examples/example-qt.h deleted file mode 100644 index 374f4766..00000000 --- a/examples/example-qt.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef __HIREDIS_EXAMPLE_QT_H -#define __HIREDIS_EXAMPLE_QT_H - -#include - -class ExampleQt : public QObject { - - Q_OBJECT - - public: - ExampleQt(const char * value, QObject * parent = 0) - : QObject(parent), m_value(value) {} - - signals: - void finished(); - - public slots: - void run(); - - private: - void finish() { emit finished(); } - - private: - const char * m_value; - redisAsyncContext * m_ctx; - RedisQtAdapter m_adapter; - - friend - void getCallback(redisAsyncContext *, void *, void *); -}; - -#endif /* !__HIREDIS_EXAMPLE_QT_H */ diff --git a/examples/example-ssl.c b/examples/example-ssl.c deleted file mode 100644 index 81f4648c..00000000 --- a/examples/example-ssl.c +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include - -#include -#include - -int main(int argc, char **argv) { - unsigned int j; - redisContext *c; - redisReply *reply; - if (argc < 4) { - printf("Usage: %s [ca]\n", argv[0]); - exit(1); - } - const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; - int port = atoi(argv[2]); - const char *cert = argv[3]; - const char *key = argv[4]; - const char *ca = argc > 4 ? argv[5] : NULL; - - struct timeval tv = { 1, 500000 }; // 1.5 seconds - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, hostname, port); - options.timeout = &tv; - c = redisConnectWithOptions(&options); - - 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); - } - - if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { - printf("Couldn't initialize SSL!\n"); - printf("Error: %s\n", c->errstr); - redisFree(c); - 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,"%u",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/examples/example.c b/examples/example.c deleted file mode 100644 index 0e93fc8b..00000000 --- a/examples/example.c +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#include - -#include - -int main(int argc, char **argv) { - unsigned int j, isunix = 0; - redisContext *c; - redisReply *reply; - const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; - - if (argc > 2) { - if (*argv[2] == 'u' || *argv[2] == 'U') { - isunix = 1; - /* in this case, host is the path to the unix socket */ - printf("Will connect to unix socket @%s\n", hostname); - } - } - - int port = (argc > 2) ? atoi(argv[2]) : 6379; - - struct timeval timeout = { 1, 500000 }; // 1.5 seconds - if (isunix) { - c = redisConnectUnixWithTimeout(hostname, timeout); - } else { - 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,"%u",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/fmacros.h b/fmacros.h deleted file mode 100644 index 3227faaf..00000000 --- a/fmacros.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef __HIREDIS_FMACRO_H -#define __HIREDIS_FMACRO_H - -#define _XOPEN_SOURCE 600 -#define _POSIX_C_SOURCE 200112L - -#if defined(__APPLE__) && defined(__MACH__) -/* Enable TCP_KEEPALIVE */ -#define _DARWIN_C_SOURCE -#endif - -#endif diff --git a/hircluster.c b/hircluster.c index 33f8e735..ab6b8e60 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1,5 +1,4 @@ -#include "fmacros.h" #include #include #include @@ -144,12 +143,6 @@ void listCommandFree(void *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 @@ -2863,7 +2856,7 @@ static int __redisClusterAppendCommand(redisClusterContext *cc, return REDIS_ERR; } - if (__redisAppendCommand(c, command->cmd, command->clen) != REDIS_OK) + if (redisAppendFormattedCommand(c, command->cmd, command->clen) != REDIS_OK) { __redisClusterSetError(cc, c->err, c->errstr); return REDIS_ERR; @@ -3075,7 +3068,7 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, ask_retry: - if (__redisAppendCommand(c,command->cmd, command->clen) != REDIS_OK) + if (redisAppendFormattedCommand(c,command->cmd, command->clen) != REDIS_OK) { __redisClusterSetError(cc, c->err, c->errstr); return NULL; @@ -4388,7 +4381,7 @@ redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, } ac->data = node; - ac->cleanup = unlinkAsyncContextAndNode; + ac->dataCleanup = unlinkAsyncContextAndNode; node->acon = ac; return ac; diff --git a/hircluster.h b/hircluster.h index f954250e..eb18a6d2 100644 --- a/hircluster.h +++ b/hircluster.h @@ -2,8 +2,8 @@ #ifndef __HIRCLUSTER_H #define __HIRCLUSTER_H -#include "hiredis.h" -#include "async.h" +#include +#include #define HIREDIS_VIP_MAJOR 0 #define HIREDIS_VIP_MINOR 4 diff --git a/hiredis.c b/hiredis.c deleted file mode 100644 index 8e438f2d..00000000 --- a/hiredis.c +++ /dev/null @@ -1,1078 +0,0 @@ -/* - * 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 "hiredis.h" -#include "net.h" -#include "sds.h" -#include "async.h" -#include "win32.h" - -static redisContextFuncs redisContextDefaultFuncs = { - .free_privdata = NULL, - .async_read = redisAsyncRead, - .async_write = redisAsyncWrite, - .read = redisNetRead, - .write = redisNetWrite -}; - -static redisReply *createReplyObject(int type); -static void *createStringObject(const redisReadTask *task, char *str, size_t len); -static void *createArrayObject(const redisReadTask *task, size_t elements); -static void *createIntegerObject(const redisReadTask *task, long long value); -static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len); -static void *createNilObject(const redisReadTask *task); -static void *createBoolObject(const redisReadTask *task, int bval); - -/* 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, - createDoubleObject, - createNilObject, - createBoolObject, - 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: - case REDIS_REPLY_MAP: - case REDIS_REPLY_SET: - if (r->element != NULL) { - for (j = 0; j < r->elements; j++) - freeReplyObject(r->element[j]); - free(r->element); - } - break; - case REDIS_REPLY_ERROR: - case REDIS_REPLY_STATUS: - case REDIS_REPLY_STRING: - case REDIS_REPLY_DOUBLE: - 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->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); - parent->element[task->idx] = r; - } - return r; -} - -static void *createArrayObject(const redisReadTask *task, size_t elements) { - redisReply *r, *parent; - - r = createReplyObject(task->type); - 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->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); - 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->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); - parent->element[task->idx] = r; - } - return r; -} - -static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_DOUBLE); - if (r == NULL) - return NULL; - - r->dval = value; - r->str = malloc(len+1); - if (r->str == NULL) { - freeReplyObject(r); - return NULL; - } - - /* The double reply also has the original protocol string representing a - * double as a null terminated string. This way the caller does not need - * to format back for string conversion, especially since Redis does efforts - * to make the string more human readable avoiding the calssical double - * decimal string conversion artifacts. */ - memcpy(r->str, str, len); - r->str[len] = '\0'; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); - 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->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); - parent->element[task->idx] = r; - } - return r; -} - -static void *createBoolObject(const redisReadTask *task, int bval) { - redisReply *r, *parent; - - r = createReplyObject(REDIS_REPLY_BOOL); - if (r == NULL) - return NULL; - - r->integer = bval != 0; - - if (task->parent) { - parent = task->parent->obj; - assert(parent->type == REDIS_REPLY_ARRAY || - parent->type == REDIS_REPLY_MAP || - parent->type == REDIS_REPLY_SET); - 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); - 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, "$%u\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); - strerror_r(errno, c->errstr, sizeof(c->errstr)); - } -} - -redisReader *redisReaderCreate(void) { - return redisReaderCreateWithFunctions(&defaultFunctions); -} - -static redisContext *redisContextInit(const redisOptions *options) { - redisContext *c; - - c = calloc(1, sizeof(*c)); - if (c == NULL) - return NULL; - - c->funcs = &redisContextDefaultFuncs; - c->obuf = sdsempty(); - c->reader = redisReaderCreate(); - c->fd = REDIS_INVALID_FD; - - if (c->obuf == NULL || c->reader == NULL) { - redisFree(c); - return NULL; - } - (void)options; /* options are used in other functions */ - return c; -} - -void redisFree(redisContext *c) { - if (c == NULL) - return; - redisNetClose(c); - - sdsfree(c->obuf); - redisReaderFree(c->reader); - free(c->tcp.host); - free(c->tcp.source_addr); - free(c->unix_sock.path); - free(c->timeout); - free(c->saddr); - if (c->funcs->free_privdata) { - c->funcs->free_privdata(c->privdata); - } - memset(c, 0xff, sizeof(*c)); - free(c); -} - -redisFD redisFreeKeepFd(redisContext *c) { - redisFD fd = c->fd; - c->fd = REDIS_INVALID_FD; - redisFree(c); - return fd; -} - -int redisReconnect(redisContext *c) { - c->err = 0; - memset(c->errstr, '\0', strlen(c->errstr)); - - if (c->privdata && c->funcs->free_privdata) { - c->funcs->free_privdata(c->privdata); - c->privdata = NULL; - } - - redisNetClose(c); - - 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; -} - -redisContext *redisConnectWithOptions(const redisOptions *options) { - redisContext *c = redisContextInit(options); - if (c == NULL) { - return NULL; - } - if (!(options->options & REDIS_OPT_NONBLOCK)) { - c->flags |= REDIS_BLOCK; - } - if (options->options & REDIS_OPT_REUSEADDR) { - c->flags |= REDIS_REUSEADDR; - } - if (options->options & REDIS_OPT_NOAUTOFREE) { - c->flags |= REDIS_NO_AUTO_FREE; - } - - if (options->type == REDIS_CONN_TCP) { - redisContextConnectBindTcp(c, options->endpoint.tcp.ip, - options->endpoint.tcp.port, options->timeout, - options->endpoint.tcp.source_addr); - } else if (options->type == REDIS_CONN_UNIX) { - redisContextConnectUnix(c, options->endpoint.unix_socket, - options->timeout); - } else if (options->type == REDIS_CONN_USERFD) { - c->fd = options->endpoint.fd; - c->flags |= REDIS_CONNECTED; - } else { - // Unknown type - FIXME - FREE - return NULL; - } - if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) { - redisContextSetTimeout(c, *options->timeout); - } - return c; -} - -/* 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) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.timeout = &tv; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectNonBlock(const char *ip, int port) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.options |= REDIS_OPT_NONBLOCK; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectBindNonBlock(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.endpoint.tcp.source_addr = source_addr; - options.options |= REDIS_OPT_NONBLOCK; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, - const char *source_addr) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, ip, port); - options.endpoint.tcp.source_addr = source_addr; - options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectUnix(const char *path) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - options.timeout = &tv; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectUnixNonBlock(const char *path) { - redisOptions options = {0}; - REDIS_OPTIONS_SET_UNIX(&options, path); - options.options |= REDIS_OPT_NONBLOCK; - return redisConnectWithOptions(&options); -} - -redisContext *redisConnectFd(redisFD fd) { - redisOptions options = {0}; - options.type = REDIS_CONN_USERFD; - options.endpoint.fd = fd; - return redisConnectWithOptions(&options); -} - -/* 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 redisGetReplyFromReader 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 = c->funcs->read(c, buf, sizeof(buf)); - if (nread > 0) { - if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) { - __redisSetError(c, c->reader->err, c->reader->errstr); - return REDIS_ERR; - } else { - } - } else if (nread < 0) { - 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 - * successfully 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 occurred trying to write and sets - * c->errstr to hold the appropriate error string. - */ -int redisBufferWrite(redisContext *c, int *done) { - - /* Return early when the context has seen an error. */ - if (c->err) - return REDIS_ERR; - - if (sdslen(c->obuf) > 0) { - int nwritten = c->funcs->write(c); - if (nwritten < 0) { - 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 or free it if we were passed NULL */ - if (reply != NULL) { - *reply = aux; - } else { - freeReplyObject(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 successfully 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; - va_start(ap,format); - void *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/hiredis.h b/hiredis.h deleted file mode 100644 index bdc6670e..00000000 --- a/hiredis.h +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 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 */ -#ifndef _MSC_VER -#include /* for struct timeval */ -#else -struct timeval; /* forward declaration */ -#endif -#include /* uintXX_t, etc */ -#include "sds.h" /* for sds */ - -#define HIREDIS_MAJOR 0 -#define HIREDIS_MINOR 14 -#define HIREDIS_PATCH 0 -#define HIREDIS_SONAME 0.14 - -/* 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 - -/** - * Flag that indicates the user does not want the context to - * be automatically freed upon error - */ -#define REDIS_NO_AUTO_FREE 0x200 - -#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 - -#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 */ - double dval; /* The double when type is REDIS_REPLY_DOUBLE */ - size_t len; /* Length of string */ - char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING - and REDIS_REPLY_DOUBLE (in additional to dval). */ - 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, - REDIS_CONN_USERFD -}; - -struct redisSsl; - -#define REDIS_OPT_NONBLOCK 0x01 -#define REDIS_OPT_REUSEADDR 0x02 - -/** - * Don't automatically free the async object on a connection failure, - * or other implicit conditions. Only free on an explicit call to disconnect() or free() - */ -#define REDIS_OPT_NOAUTOFREE 0x04 - -/* In Unix systems a file descriptor is a regular signed int, with -1 - * representing an invalid descriptor. In Windows it is a SOCKET - * (32- or 64-bit unsigned integer depending on the architecture), where - * all bits set (~0) is INVALID_SOCKET. */ -#ifndef _WIN32 -typedef int redisFD; -#define REDIS_INVALID_FD -1 -#else -#ifdef _WIN64 -typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */ -#else -typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */ -#endif -#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */ -#endif - -typedef struct { - /* - * the type of connection to use. This also indicates which - * `endpoint` member field to use - */ - int type; - /* bit field of REDIS_OPT_xxx */ - int options; - /* timeout value. if NULL, no timeout is used */ - const struct timeval *timeout; - union { - /** use this field for tcp/ip connections */ - struct { - const char *source_addr; - const char *ip; - int port; - } tcp; - /** use this field for unix domain sockets */ - const char *unix_socket; - /** - * use this field to have hiredis operate an already-open - * file descriptor */ - redisFD fd; - } endpoint; -} redisOptions; - -/** - * Helper macros to initialize options to their specified fields. - */ -#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \ - (opts)->type = REDIS_CONN_TCP; \ - (opts)->endpoint.tcp.ip = ip_; \ - (opts)->endpoint.tcp.port = port_; - -#define REDIS_OPTIONS_SET_UNIX(opts, path) \ - (opts)->type = REDIS_CONN_UNIX; \ - (opts)->endpoint.unix_socket = path; - -struct redisAsyncContext; -struct redisContext; - -typedef struct redisContextFuncs { - void (*free_privdata)(void *); - void (*async_read)(struct redisAsyncContext *); - void (*async_write)(struct redisAsyncContext *); - int (*read)(struct redisContext *, char *, size_t); - int (*write)(struct redisContext *); -} redisContextFuncs; - -/* Context for a connection to Redis */ -typedef struct redisContext { - const redisContextFuncs *funcs; /* Function table */ - - int err; /* Error flags, 0 when there is no error */ - char errstr[128]; /* String representation of error when applicable */ - redisFD 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; - - /* For non-blocking connect */ - struct sockadr *saddr; - size_t addrlen; - - /* Additional private data for hiredis addons such as SSL */ - void *privdata; -} redisContext; - -redisContext *redisConnectWithOptions(const redisOptions *options); -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(redisFD 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 successful 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); -redisFD 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/hiredis_ssl.h b/hiredis_ssl.h deleted file mode 100644 index f844f954..00000000 --- a/hiredis_ssl.h +++ /dev/null @@ -1,53 +0,0 @@ - -/* - * Copyright (c) 2019, Redis Labs - * - * 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_SSL_H -#define __HIREDIS_SSL_H - -/* This is the underlying struct for SSL in ssl.h, which is not included to - * keep build dependencies short here. - */ -struct ssl_st; - -/** - * Secure the connection using SSL. This should be done before any command is - * executed on the connection. - */ -int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, - const char *keypath, const char *servername); - -/** - * Initiate SSL/TLS negotiation on a provided context. - */ - -int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); - -#endif /* __HIREDIS_SSL_H */ diff --git a/hiredis_ssl.pc.in b/hiredis_ssl.pc.in deleted file mode 100644 index 8f700c08..00000000 --- a/hiredis_ssl.pc.in +++ /dev/null @@ -1,12 +0,0 @@ -prefix=@CMAKE_INSTALL_PREFIX@ -exec_prefix=${prefix} -libdir=${exec_prefix}/lib -includedir=${prefix}/include -pkgincludedir=${includedir}/hiredis-vip - -Name: hiredis_ssl -Description: SSL Support for hiredis-vip. -Version: @PROJECT_VERSION@ -Requires: hiredis-vip -Libs: -L${libdir} -lhiredis_ssl -Libs.private: -lssl -lcrypto diff --git a/hiredis_vip-config.cmake.in b/hiredis_vip-config.cmake.in new file mode 100644 index 00000000..fe986a18 --- /dev/null +++ b/hiredis_vip-config.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +set_and_check(hiredis_vip_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") + +IF (NOT TARGET hiredis_vip::hiredis_vip) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_vip-targets.cmake) +ENDIF() + +SET(hiredis_vip_LIBRARIES hiredis_vip::hiredis_vip) +SET(hiredis_vip_INCLUDE_DIRS ${hiredis_vip_INCLUDEDIR}) + +check_required_components(hiredis_vip) + diff --git a/net.c b/net.c deleted file mode 100644 index e5f40b0a..00000000 --- a/net.c +++ /dev/null @@ -1,571 +0,0 @@ -/* 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 "net.h" -#include "sds.h" -#include "sockcompat.h" -#include "win32.h" - -/* Defined in hiredis.c */ -void __redisSetError(redisContext *c, int type, const char *str); - -void redisNetClose(redisContext *c) { - if (c && c->fd != REDIS_INVALID_FD) { - close(c->fd); - c->fd = REDIS_INVALID_FD; - } -} - -int redisNetRead(redisContext *c, char *buf, size_t bufcap) { - int nread = recv(c->fd, buf, bufcap, 0); - if (nread == -1) { - if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - return 0; - } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) { - /* especially in windows */ - __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); - return -1; - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } else if (nread == 0) { - __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); - return -1; - } else { - return nread; - } -} - -int redisNetWrite(redisContext *c) { - int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); - if (nwritten < 0) { - if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { - /* Try again later */ - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } - return nwritten; -} - -static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { - int errorno = errno; /* snprintf() may change errno */ - char buf[128] = { 0 }; - size_t len = 0; - - if (prefix != NULL) - len = snprintf(buf,sizeof(buf),"%s: ",prefix); - strerror_r(errorno, (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); - redisNetClose(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -static int redisCreateSocket(redisContext *c, int type) { - redisFD s; - if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { - __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) { -#ifndef _WIN32 - 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)"); - redisNetClose(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)"); - redisNetClose(c); - return REDIS_ERR; - } -#else - u_long mode = blocking ? 0 : 1; - if (ioctl(c->fd, FIONBIO, &mode) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); - redisNetClose(c); - return REDIS_ERR; - } -#endif /* _WIN32 */ - return REDIS_OK; -} - -int redisKeepAlive(redisContext *c, int interval) { - int val = 1; - redisFD 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; - -#if defined(__APPLE__) && defined(__MACH__) - 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__) - 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)"); - redisNetClose(c); - return REDIS_ERR; - } - return REDIS_OK; -} - -#define __MAX_MSEC (((LONG_MAX) - 999) / 1000) - -static int redisContextTimeoutMsec(redisContext *c, long *result) -{ - const struct timeval *timeout = c->timeout; - long msec = -1; - - /* Only use timeout when not NULL. */ - if (timeout != NULL) { - if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { - *result = msec; - return REDIS_ERR; - } - - msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); - - if (msec < 0 || msec > INT_MAX) { - msec = INT_MAX; - } - } - - *result = msec; - return REDIS_OK; -} - -static int redisContextWaitReady(redisContext *c, long msec) { - struct pollfd wfd[1]; - - wfd[0].fd = c->fd; - wfd[0].events = POLLOUT; - - if (errno == EINPROGRESS) { - int res; - - if ((res = poll(wfd, 1, msec)) == -1) { - __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); - redisNetClose(c); - return REDIS_ERR; - } else if (res == 0) { - errno = ETIMEDOUT; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisNetClose(c); - return REDIS_ERR; - } - - if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { - redisCheckSocketError(c); - return REDIS_ERR; - } - - return REDIS_OK; - } - - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - redisNetClose(c); - return REDIS_ERR; -} - -int redisCheckConnectDone(redisContext *c, int *completed) { - int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); - if (rc == 0) { - *completed = 1; - return REDIS_OK; - } - switch (errno) { - case EISCONN: - *completed = 1; - return REDIS_OK; - case EALREADY: - case EINPROGRESS: - case EWOULDBLOCK: - *completed = 0; - return REDIS_OK; - default: - return REDIS_ERR; - } -} - -int redisCheckSocketError(redisContext *c) { - int err = 0, errno_saved = errno; - 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 == 0) { - err = errno_saved; - } - - if (err) { - errno = err; - __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); - return REDIS_ERR; - } - - return REDIS_OK; -} - -int redisContextSetTimeout(redisContext *c, const struct timeval tv) { - const void *to_ptr = &tv; - size_t to_sz = sizeof(tv); -#ifdef _WIN32 - DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000; - to_ptr = &timeout_msec; - to_sz = sizeof(timeout_msec); -#endif - if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { - __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); - return REDIS_ERR; - } - if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -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) { - redisFD s; - int 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; - long timeout_msec = -1; - - servinfo = NULL; - 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) { - 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 { - free(c->timeout); - c->timeout = NULL; - } - - if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { - __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); - goto error; - } - - 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)) == REDIS_INVALID_FD) - 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) { - freeaddrinfo(bservinfo); - 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; - } - } - - /* For repeat connection */ - free(c->saddr); - c->saddr = malloc(p->ai_addrlen); - memcpy(c->saddr, p->ai_addr, p->ai_addrlen); - c->addrlen = p->ai_addrlen; - - if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { - if (errno == EHOSTUNREACH) { - redisNetClose(c); - continue; - } else if (errno == EINPROGRESS) { - if (blocking) { - goto wait_for_ready; - } - /* This is ok. - * Note that even when it's in blocking mode, we unset blocking - * for `connect()` - */ - } else if (errno == EADDRNOTAVAIL && reuseaddr) { - if (++reuses >= REDIS_CONNECT_RETRIES) { - goto error; - } else { - redisNetClose(c); - goto addrretry; - } - } else { - wait_for_ready: - if (redisContextWaitReady(c,timeout_msec) != 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: - if(servinfo) { - 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) { -#ifndef _WIN32 - int blocking = (c->flags & REDIS_BLOCK); - struct sockaddr_un *sa; - long timeout_msec = -1; - - if (redisCreateSocket(c,AF_UNIX) < 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 { - free(c->timeout); - c->timeout = NULL; - } - - if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) - return REDIS_ERR; - - sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); - c->addrlen = sizeof(struct sockaddr_un); - sa->sun_family = AF_UNIX; - 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,timeout_msec) != 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; -#else - /* We currently do not support Unix sockets for Windows. */ - /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ - errno = EPROTONOSUPPORT; - return REDIS_ERR; -#endif /* _WIN32 */ -} diff --git a/net.h b/net.h deleted file mode 100644 index a4393c06..00000000 --- a/net.h +++ /dev/null @@ -1,54 +0,0 @@ -/* 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" - -void redisNetClose(redisContext *c); -int redisNetRead(redisContext *c, char *buf, size_t bufcap); -int redisNetWrite(redisContext *c); - -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); -int redisCheckConnectDone(redisContext *c, int *completed); - -#endif diff --git a/read.c b/read.c deleted file mode 100644 index a264c376..00000000 --- a/read.c +++ /dev/null @@ -1,669 +0,0 @@ -/* - * 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 -#include -#endif -#include -#include -#include -#include -#include - -#include "read.h" -#include "sds.h" -#include "win32.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. */ - 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 (pos==_len) { - /* Not found. */ - return NULL; - } else { - if (s[pos+1] == '\n') { - /* Found. */ - return s+pos; - } else { - /* Continue searching. */ - pos++; - } - } - } - return NULL; -} - -/* Convert a string into a long long. Returns REDIS_OK if the string could be - * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value - * will be set to the parsed value when appropriate. - * - * Note that this function demands that the string strictly represents - * a long long: no spaces or other characters before or after the string - * representing the number are accepted, nor zeroes at the start if not - * for the string "0" representing the zero number. - * - * Because of its strictness, it is safe to use this function to check if - * you can convert a string into a long long, and obtain back the string - * from the number without any loss in the string representation. */ -static int string2ll(const char *s, size_t slen, long long *value) { - const char *p = s; - size_t plen = 0; - int negative = 0; - unsigned long long v; - - if (plen == slen) - return REDIS_ERR; - - /* Special case: first and only digit is 0. */ - if (slen == 1 && p[0] == '0') { - if (value != NULL) *value = 0; - return REDIS_OK; - } - - if (p[0] == '-') { - negative = 1; - p++; plen++; - - /* Abort on only a negative sign. */ - if (plen == slen) - return REDIS_ERR; - } - - /* First digit should be 1-9, otherwise the string should just be 0. */ - if (p[0] >= '1' && p[0] <= '9') { - v = p[0]-'0'; - p++; plen++; - } else if (p[0] == '0' && slen == 1) { - *value = 0; - return REDIS_OK; - } else { - return REDIS_ERR; - } - - while (plen < slen && p[0] >= '0' && p[0] <= '9') { - if (v > (ULLONG_MAX / 10)) /* Overflow. */ - return REDIS_ERR; - v *= 10; - - if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */ - return REDIS_ERR; - v += p[0]-'0'; - - p++; plen++; - } - - /* Return if not all bytes were used. */ - if (plen < slen) - return REDIS_ERR; - - if (negative) { - if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */ - return REDIS_ERR; - if (value != NULL) *value = -v; - } else { - if (v > LLONG_MAX) /* Overflow. */ - return REDIS_ERR; - if (value != NULL) *value = v; - } - return REDIS_OK; -} - -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 || - prv->type == REDIS_REPLY_MAP || - prv->type == REDIS_REPLY_SET); - 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) { - long long v; - if (string2ll(p, len, &v) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad integer value"); - return REDIS_ERR; - } - obj = r->fn->createInteger(cur,v); - } else { - obj = (void*)REDIS_REPLY_INTEGER; - } - } else if (cur->type == REDIS_REPLY_DOUBLE) { - if (r->fn && r->fn->createDouble) { - char buf[326], *eptr; - double d; - - if ((size_t)len >= sizeof(buf)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Double value is too large"); - return REDIS_ERR; - } - - memcpy(buf,p,len); - buf[len] = '\0'; - - if (strcasecmp(buf,",inf") == 0) { - d = INFINITY; /* Positive infinite. */ - } else if (strcasecmp(buf,",-inf") == 0) { - d = -INFINITY; /* Negative infinite. */ - } else { - d = strtod((char*)buf,&eptr); - if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad double value"); - return REDIS_ERR; - } - } - obj = r->fn->createDouble(cur,d,buf,len); - } else { - obj = (void*)REDIS_REPLY_DOUBLE; - } - } else if (cur->type == REDIS_REPLY_NIL) { - if (r->fn && r->fn->createNil) - obj = r->fn->createNil(cur); - else - obj = (void*)REDIS_REPLY_NIL; - } else if (cur->type == REDIS_REPLY_BOOL) { - int bval = p[0] == 't' || p[0] == 'T'; - if (r->fn && r->fn->createBool) - obj = r->fn->createBool(cur,bval); - else - obj = (void*)REDIS_REPLY_BOOL; - } 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 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 */ - - if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad bulk string length"); - return REDIS_ERR; - } - - if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bulk string length out of range"); - return REDIS_ERR; - } - - if (len == -1) { - /* 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; -} - -/* Process the array, map and set types. */ -static int processAggregateItem(redisReader *r) { - redisReadTask *cur = &(r->rstack[r->ridx]); - void *obj; - char *p; - long long elements; - int root = 0, len; - - /* 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,&len)) != NULL) { - if (string2ll(p, len, &elements) == REDIS_ERR) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Bad multi-bulk length"); - return REDIS_ERR; - } - - root = (r->ridx == 0); - - if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) { - __redisReaderSetError(r,REDIS_ERR_PROTOCOL, - "Multi-bulk length out of range"); - return REDIS_ERR; - } - - 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 (cur->type == REDIS_REPLY_MAP) elements *= 2; - - if (r->fn && r->fn->createArray) - obj = r->fn->createArray(cur,elements); - else - obj = (void*)(long)cur->type; - - 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_DOUBLE; - break; - case '_': - cur->type = REDIS_REPLY_NIL; - break; - case '$': - cur->type = REDIS_REPLY_STRING; - break; - case '*': - cur->type = REDIS_REPLY_ARRAY; - break; - case '%': - cur->type = REDIS_REPLY_MAP; - break; - case '~': - cur->type = REDIS_REPLY_SET; - break; - case '#': - cur->type = REDIS_REPLY_BOOL; - 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: - case REDIS_REPLY_DOUBLE: - case REDIS_REPLY_NIL: - case REDIS_REPLY_BOOL: - return processLineItem(r); - case REDIS_REPLY_STRING: - return processBulkItem(r); - case REDIS_REPLY_ARRAY: - case REDIS_REPLY_MAP: - case REDIS_REPLY_SET: - return processAggregateItem(r); - default: - assert(NULL); - return REDIS_ERR; /* Avoid warning. */ - } -} - -redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { - redisReader *r; - - r = calloc(1,sizeof(redisReader)); - if (r == NULL) - return NULL; - - 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 == NULL) - return; - if (r->reply != NULL && r->fn && r->fn->freeObject) - r->fn->freeObject(r->reply); - 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; - } else if (r->reply != NULL && r->fn && r->fn->freeObject) { - r->fn->freeObject(r->reply); - } - r->reply = NULL; - } - return REDIS_OK; -} diff --git a/read.h b/read.h deleted file mode 100644 index af02aaf6..00000000 --- a/read.h +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 occurred. 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_TIMEOUT 6 /* Timed out */ -#define REDIS_ERR_OTHER 2 /* Everything else... */ - -#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_REPLY_DOUBLE 7 -#define REDIS_REPLY_BOOL 8 -#define REDIS_REPLY_VERB 9 -#define REDIS_REPLY_MAP 9 -#define REDIS_REPLY_SET 10 -#define REDIS_REPLY_ATTR 11 -#define REDIS_REPLY_PUSH 12 -#define REDIS_REPLY_BIGNUM 13 - -#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ - -#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*, size_t); - void *(*createInteger)(const redisReadTask*, long long); - void *(*createDouble)(const redisReadTask*, double, char*, size_t); - void *(*createNil)(const redisReadTask*); - void *(*createBool)(const redisReadTask*, int); - 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); - -#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) -#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply) -#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr) - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/sds.c b/sds.c deleted file mode 100644 index 6cf75841..00000000 --- a/sds.c +++ /dev/null @@ -1,1291 +0,0 @@ -/* SDSLib 2.0 -- A C dynamic strings library - * - * Copyright (c) 2006-2015, Salvatore Sanfilippo - * Copyright (c) 2015, Oran Agra - * Copyright (c) 2015, Redis Labs, Inc - * 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" -#include "sdsalloc.h" - -static inline int sdsHdrSize(char type) { - switch(type&SDS_TYPE_MASK) { - case SDS_TYPE_5: - return sizeof(struct sdshdr5); - case SDS_TYPE_8: - return sizeof(struct sdshdr8); - case SDS_TYPE_16: - return sizeof(struct sdshdr16); - case SDS_TYPE_32: - return sizeof(struct sdshdr32); - case SDS_TYPE_64: - return sizeof(struct sdshdr64); - } - return 0; -} - -static inline char sdsReqType(size_t string_size) { - if (string_size < 32) - return SDS_TYPE_5; - if (string_size < 0xff) - return SDS_TYPE_8; - if (string_size < 0xffff) - return SDS_TYPE_16; - if (string_size < 0xffffffff) - return SDS_TYPE_32; - return SDS_TYPE_64; -} - -/* 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) { - void *sh; - sds s; - char type = sdsReqType(initlen); - /* Empty strings are usually created in order to append. Use type 8 - * since type 5 is not good at this. */ - if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8; - int hdrlen = sdsHdrSize(type); - unsigned char *fp; /* flags pointer. */ - - sh = s_malloc(hdrlen+initlen+1); - if (sh == NULL) return NULL; - if (!init) - memset(sh, 0, hdrlen+initlen+1); - s = (char*)sh+hdrlen; - fp = ((unsigned char*)s)-1; - switch(type) { - case SDS_TYPE_5: { - *fp = type | (initlen << SDS_TYPE_BITS); - break; - } - case SDS_TYPE_8: { - SDS_HDR_VAR(8,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - case SDS_TYPE_16: { - SDS_HDR_VAR(16,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - case SDS_TYPE_32: { - SDS_HDR_VAR(32,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - case SDS_TYPE_64: { - SDS_HDR_VAR(64,s); - sh->len = initlen; - sh->alloc = initlen; - *fp = type; - break; - } - } - if (initlen && init) - memcpy(s, init, initlen); - s[initlen] = '\0'; - return s; -} - -/* 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 terminated 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; - s_free((char*)s-sdsHdrSize(s[-1])); -} - -/* 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) { - int reallen = strlen(s); - sdssetlen(s, reallen); -} - -/* Modify an sds string in-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) { - sdssetlen(s, 0); - s[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) { - void *sh, *newsh; - size_t avail = sdsavail(s); - size_t len, newlen; - char type, oldtype = s[-1] & SDS_TYPE_MASK; - int hdrlen; - - /* Return ASAP if there is enough space left. */ - if (avail >= addlen) return s; - - len = sdslen(s); - sh = (char*)s-sdsHdrSize(oldtype); - newlen = (len+addlen); - if (newlen < SDS_MAX_PREALLOC) - newlen *= 2; - else - newlen += SDS_MAX_PREALLOC; - - type = sdsReqType(newlen); - - /* Don't use type 5: the user is appending to the string and type 5 is - * not able to remember empty space, so sdsMakeRoomFor() must be called - * at every appending operation. */ - if (type == SDS_TYPE_5) type = SDS_TYPE_8; - - hdrlen = sdsHdrSize(type); - if (oldtype==type) { - newsh = s_realloc(sh, hdrlen+newlen+1); - if (newsh == NULL) { - s_free(sh); - return NULL; - } - s = (char*)newsh+hdrlen; - } else { - /* Since the header size changes, need to move the string forward, - * and can't use realloc */ - newsh = s_malloc(hdrlen+newlen+1); - if (newsh == NULL) return NULL; - memcpy((char*)newsh+hdrlen, s, len+1); - s_free(sh); - s = (char*)newsh+hdrlen; - s[-1] = type; - sdssetlen(s, len); - } - sdssetalloc(s, newlen); - return s; -} - -/* 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) { - void *sh, *newsh; - char type, oldtype = s[-1] & SDS_TYPE_MASK; - int hdrlen; - size_t len = sdslen(s); - sh = (char*)s-sdsHdrSize(oldtype); - - type = sdsReqType(len); - hdrlen = sdsHdrSize(type); - if (oldtype==type) { - newsh = s_realloc(sh, hdrlen+len+1); - if (newsh == NULL) return NULL; - s = (char*)newsh+hdrlen; - } else { - newsh = s_malloc(hdrlen+len+1); - if (newsh == NULL) return NULL; - memcpy((char*)newsh+hdrlen, s, len+1); - s_free(sh); - s = (char*)newsh+hdrlen; - s[-1] = type; - sdssetlen(s, len); - } - sdssetalloc(s, len); - return s; -} - -/* 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) { - size_t alloc = sdsalloc(s); - return sdsHdrSize(s[-1])+alloc+1; -} - -/* Return the pointer of the actual SDS allocation (normally SDS strings - * are referenced by the start of the string buffer). */ -void *sdsAllocPtr(sds s) { - return (void*) (s-sdsHdrSize(s[-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) { - unsigned char flags = s[-1]; - size_t len; - switch(flags&SDS_TYPE_MASK) { - case SDS_TYPE_5: { - unsigned char *fp = ((unsigned char*)s)-1; - unsigned char oldlen = SDS_TYPE_5_LEN(flags); - assert((incr > 0 && oldlen+incr < 32) || (incr < 0 && oldlen >= (unsigned int)(-incr))); - *fp = SDS_TYPE_5 | ((oldlen+incr) << SDS_TYPE_BITS); - len = oldlen+incr; - break; - } - case SDS_TYPE_8: { - SDS_HDR_VAR(8,s); - assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); - len = (sh->len += incr); - break; - } - case SDS_TYPE_16: { - SDS_HDR_VAR(16,s); - assert((incr >= 0 && sh->alloc-sh->len >= incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); - len = (sh->len += incr); - break; - } - case SDS_TYPE_32: { - SDS_HDR_VAR(32,s); - assert((incr >= 0 && sh->alloc-sh->len >= (unsigned int)incr) || (incr < 0 && sh->len >= (unsigned int)(-incr))); - len = (sh->len += incr); - break; - } - case SDS_TYPE_64: { - SDS_HDR_VAR(64,s); - assert((incr >= 0 && sh->alloc-sh->len >= (uint64_t)incr) || (incr < 0 && sh->len >= (uint64_t)(-incr))); - len = (sh->len += incr); - break; - } - default: len = 0; /* Just to avoid compilation warnings. */ - } - s[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) { - size_t curlen = sdslen(s); - - if (len <= curlen) return s; - s = sdsMakeRoomFor(s,len-curlen); - if (s == NULL) return NULL; - - /* Make sure added region doesn't contain garbage */ - memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */ - sdssetlen(s, 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) { - size_t curlen = sdslen(s); - - s = sdsMakeRoomFor(s,len); - if (s == NULL) return NULL; - memcpy(s+curlen, t, len); - sdssetlen(s, curlen+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) { - if (sdsalloc(s) < len) { - s = sdsMakeRoomFor(s,len-sdslen(s)); - if (s == NULL) return NULL; - } - memcpy(s, t, len); - s[len] = '\0'; - sdssetlen(s, 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 length 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; -} - -/* 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[SDS_LLSTR_SIZE]; - int len = sdsll2str(buf,value); - - return sdsnewlen(buf,len); -} - -/* Like sdscatprintf() but gets va_list instead of being variadic. */ -sds sdscatvprintf(sds s, const char *fmt, va_list ap) { - va_list cpy; - char staticbuf[1024], *buf = staticbuf, *t; - size_t buflen = strlen(fmt)*2; - - /* We try to start using a static buffer for speed. - * If not possible we revert to heap allocation. */ - if (buflen > sizeof(staticbuf)) { - buf = s_malloc(buflen); - if (buf == NULL) return NULL; - } else { - buflen = sizeof(staticbuf); - } - - /* Try with buffers two times bigger every time we fail to - * fit the string in the current buffer size. */ - while(1) { - buf[buflen-2] = '\0'; - va_copy(cpy,ap); - vsnprintf(buf, buflen, fmt, cpy); - va_end(cpy); - if (buf[buflen-2] != '\0') { - if (buf != staticbuf) s_free(buf); - buflen *= 2; - buf = s_malloc(buflen); - if (buf == NULL) return NULL; - continue; - } - break; - } - - /* Finally concat the obtained string to the SDS string and return it. */ - t = sdscat(s, buf); - if (buf != staticbuf) s_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) - * %% - Verbatim "%" character. - */ -sds sdscatfmt(sds s, char const *fmt, ...) { - const char *f = fmt; - int i; - va_list ap; - - va_start(ap,fmt); - i = sdslen(s); /* Position of the next byte to write to dest str. */ - while(*f) { - char next, *str; - size_t l; - long long num; - unsigned long long unum; - - /* Make sure there is always space for at least 1 char. */ - if (sdsavail(s)==0) { - s = sdsMakeRoomFor(s,1); - if (s == NULL) goto fmt_error; - } - - 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 (sdsavail(s) < l) { - s = sdsMakeRoomFor(s,l); - if (s == NULL) goto fmt_error; - } - memcpy(s+i,str,l); - sdsinclen(s,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 (sdsavail(s) < l) { - s = sdsMakeRoomFor(s,l); - if (s == NULL) goto fmt_error; - } - memcpy(s+i,buf,l); - sdsinclen(s,l); - i += l; - } - break; - case 'u': - case 'U': - if (next == 'u') - unum = va_arg(ap,unsigned int); - else - unum = va_arg(ap,unsigned long long); - { - char buf[SDS_LLSTR_SIZE]; - l = sdsull2str(buf,unum); - if (sdsavail(s) < l) { - s = sdsMakeRoomFor(s,l); - if (s == NULL) goto fmt_error; - } - memcpy(s+i,buf,l); - sdsinclen(s,l); - i += l; - } - break; - default: /* Handle %% and generally %. */ - s[i++] = next; - sdsinclen(s,1); - break; - } - break; - default: - s[i++] = *f; - sdsinclen(s,1); - break; - } - f++; - } - va_end(ap); - - /* Add null-term */ - s[i] = '\0'; - return s; - -fmt_error: - va_end(ap); - return NULL; -} - -/* 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,"Aa. :"); - * printf("%s\n", s); - * - * Output will be just "Hello World". - */ -sds sdstrim(sds s, const char *cset) { - 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 > sp && strchr(cset, *ep)) ep--; - len = (sp > ep) ? 0 : ((ep-sp)+1); - if (s != sp) memmove(s, sp, len); - s[len] = '\0'; - sdssetlen(s,len); - return s; -} - -/* 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) { - 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(s, s+start, newlen); - s[newlen] = 0; - sdssetlen(s,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: - * - * positive if s1 > s2. - * negative 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 = s_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 = s_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]); - s_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]); - s_free(tokens); -} - -/* 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 */ - { - char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); - if (new_vector == NULL) { - s_free(vector); - return NULL; - } - - vector = new_vector; - vector[*argc] = current; - (*argc)++; - current = NULL; - } - } else { - /* Even on empty input string return something not NULL. */ - if (vector == NULL) vector = s_malloc(sizeof(void*)); - return vector; - } - } - -err: - while((*argc)--) - sdsfree(vector[*argc]); - s_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) { - sds join = sdsempty(); - int j; - - for (j = 0; j < argc; j++) { - join = sdscat(join, argv[j]); - if (j != argc-1) join = sdscat(join,sep); - } - 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; -} - -/* Wrappers to the allocators used by SDS. Note that SDS will actually - * just use the macros defined into sdsalloc.h in order to avoid to pay - * the overhead of function calls. Here we define these wrappers only for - * the programs SDS is linked to, if they want to touch the SDS internals - * even if they use a different allocator. */ -void *sds_malloc(size_t size) { return s_malloc(size); } -void *sds_realloc(void *ptr, size_t size) { return s_realloc(ptr,size); } -void sds_free(void *ptr) { s_free(ptr); } - -#if defined(SDS_TEST_MAIN) -#include -#include "testhelp.h" -#include "limits.h" - -#define UNUSED(x) (void)(x) -int sdsTest(void) { - { - 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("--"); - x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX); - test_cond("sdscatfmt() seems working in the base case", - sdslen(x) == 60 && - memcmp(x,"--Hello Hi! World -9223372036854775808," - "9223372036854775807--",60) == 0) - printf("[%s]\n",x); - - sdsfree(x); - x = sdsnew("--"); - x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX); - test_cond("sdscatfmt() seems working with unsigned numbers", - sdslen(x) == 35 && - memcmp(x,"--4294967295,18446744073709551615--",35) == 0) - - sdsfree(x); - x = sdsnew(" x "); - sdstrim(x," x"); - test_cond("sdstrim() works when all chars match", - sdslen(x) == 0) - - sdsfree(x); - x = sdsnew(" x "); - sdstrim(x," "); - test_cond("sdstrim() works when a single char remains", - sdslen(x) == 1 && x[0] == 'x') - - 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) - - { - unsigned int oldfree; - char *p; - int step = 10, j, i; - - sdsfree(x); - sdsfree(y); - x = sdsnew("0"); - test_cond("sdsnew() free/len buffers", sdslen(x) == 1 && sdsavail(x) == 0); - - /* Run the test a few times in order to hit the first two - * SDS header types. */ - for (i = 0; i < 10; i++) { - int oldlen = sdslen(x); - x = sdsMakeRoomFor(x,step); - int type = x[-1]&SDS_TYPE_MASK; - - test_cond("sdsMakeRoomFor() len", sdslen(x) == oldlen); - if (type != SDS_TYPE_5) { - test_cond("sdsMakeRoomFor() free", sdsavail(x) >= step); - oldfree = sdsavail(x); - } - p = x+oldlen; - for (j = 0; j < step; j++) { - p[j] = 'A'+j; - } - sdsIncrLen(x,step); - } - test_cond("sdsMakeRoomFor() content", - memcmp("0ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJ",x,101) == 0); - test_cond("sdsMakeRoomFor() final length",sdslen(x)==101); - - sdsfree(x); - } - } - test_report() - return 0; -} -#endif - -#ifdef SDS_TEST_MAIN -int main(void) { - return sdsTest(); -} -#endif diff --git a/sds.h b/sds.h deleted file mode 100644 index 3f9a9645..00000000 --- a/sds.h +++ /dev/null @@ -1,276 +0,0 @@ -/* SDSLib 2.0 -- A C dynamic strings library - * - * Copyright (c) 2006-2015, Salvatore Sanfilippo - * Copyright (c) 2015, Oran Agra - * Copyright (c) 2015, Redis Labs, Inc - * 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) -#ifdef _MSC_VER -#define __attribute__(x) -#endif - -#include -#include -#include - -typedef char *sds; - -/* Note: sdshdr5 is never used, we just access the flags byte directly. - * However is here to document the layout of type 5 SDS strings. */ -struct __attribute__ ((__packed__)) sdshdr5 { - unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ - char buf[]; -}; -struct __attribute__ ((__packed__)) sdshdr8 { - uint8_t len; /* used */ - uint8_t alloc; /* excluding the header and null terminator */ - unsigned char flags; /* 3 lsb of type, 5 unused bits */ - char buf[]; -}; -struct __attribute__ ((__packed__)) sdshdr16 { - uint16_t len; /* used */ - uint16_t alloc; /* excluding the header and null terminator */ - unsigned char flags; /* 3 lsb of type, 5 unused bits */ - char buf[]; -}; -struct __attribute__ ((__packed__)) sdshdr32 { - uint32_t len; /* used */ - uint32_t alloc; /* excluding the header and null terminator */ - unsigned char flags; /* 3 lsb of type, 5 unused bits */ - char buf[]; -}; -struct __attribute__ ((__packed__)) sdshdr64 { - uint64_t len; /* used */ - uint64_t alloc; /* excluding the header and null terminator */ - unsigned char flags; /* 3 lsb of type, 5 unused bits */ - char buf[]; -}; - -#define SDS_TYPE_5 0 -#define SDS_TYPE_8 1 -#define SDS_TYPE_16 2 -#define SDS_TYPE_32 3 -#define SDS_TYPE_64 4 -#define SDS_TYPE_MASK 7 -#define SDS_TYPE_BITS 3 -#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); -#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) -#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) - -static inline size_t sdslen(const sds s) { - unsigned char flags = s[-1]; - switch(flags&SDS_TYPE_MASK) { - case SDS_TYPE_5: - return SDS_TYPE_5_LEN(flags); - case SDS_TYPE_8: - return SDS_HDR(8,s)->len; - case SDS_TYPE_16: - return SDS_HDR(16,s)->len; - case SDS_TYPE_32: - return SDS_HDR(32,s)->len; - case SDS_TYPE_64: - return SDS_HDR(64,s)->len; - } - return 0; -} - -static inline size_t sdsavail(const sds s) { - unsigned char flags = s[-1]; - switch(flags&SDS_TYPE_MASK) { - case SDS_TYPE_5: { - return 0; - } - case SDS_TYPE_8: { - SDS_HDR_VAR(8,s); - return sh->alloc - sh->len; - } - case SDS_TYPE_16: { - SDS_HDR_VAR(16,s); - return sh->alloc - sh->len; - } - case SDS_TYPE_32: { - SDS_HDR_VAR(32,s); - return sh->alloc - sh->len; - } - case SDS_TYPE_64: { - SDS_HDR_VAR(64,s); - return sh->alloc - sh->len; - } - } - return 0; -} - -static inline void sdssetlen(sds s, size_t newlen) { - unsigned char flags = s[-1]; - switch(flags&SDS_TYPE_MASK) { - case SDS_TYPE_5: - { - unsigned char *fp = ((unsigned char*)s)-1; - *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); - } - break; - case SDS_TYPE_8: - SDS_HDR(8,s)->len = (uint8_t)newlen; - break; - case SDS_TYPE_16: - SDS_HDR(16,s)->len = (uint16_t)newlen; - break; - case SDS_TYPE_32: - SDS_HDR(32,s)->len = (uint32_t)newlen; - break; - case SDS_TYPE_64: - SDS_HDR(64,s)->len = (uint64_t)newlen; - break; - } -} - -static inline void sdsinclen(sds s, size_t inc) { - unsigned char flags = s[-1]; - switch(flags&SDS_TYPE_MASK) { - case SDS_TYPE_5: - { - unsigned char *fp = ((unsigned char*)s)-1; - unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; - *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); - } - break; - case SDS_TYPE_8: - SDS_HDR(8,s)->len += (uint8_t)inc; - break; - case SDS_TYPE_16: - SDS_HDR(16,s)->len += (uint16_t)inc; - break; - case SDS_TYPE_32: - SDS_HDR(32,s)->len += (uint32_t)inc; - break; - case SDS_TYPE_64: - SDS_HDR(64,s)->len += (uint64_t)inc; - break; - } -} - -/* sdsalloc() = sdsavail() + sdslen() */ -static inline size_t sdsalloc(const sds s) { - unsigned char flags = s[-1]; - switch(flags&SDS_TYPE_MASK) { - case SDS_TYPE_5: - return SDS_TYPE_5_LEN(flags); - case SDS_TYPE_8: - return SDS_HDR(8,s)->alloc; - case SDS_TYPE_16: - return SDS_HDR(16,s)->alloc; - case SDS_TYPE_32: - return SDS_HDR(32,s)->alloc; - case SDS_TYPE_64: - return SDS_HDR(64,s)->alloc; - } - return 0; -} - -static inline void sdssetalloc(sds s, size_t newlen) { - unsigned char flags = s[-1]; - switch(flags&SDS_TYPE_MASK) { - case SDS_TYPE_5: - /* Nothing to do, this type has no total allocation info. */ - break; - case SDS_TYPE_8: - SDS_HDR(8,s)->alloc = (uint8_t)newlen; - break; - case SDS_TYPE_16: - SDS_HDR(16,s)->alloc = (uint16_t)newlen; - break; - case SDS_TYPE_32: - SDS_HDR(32,s)->alloc = (uint32_t)newlen; - break; - case SDS_TYPE_64: - SDS_HDR(64,s)->alloc = (uint64_t)newlen; - break; - } -} - -sds sdsnewlen(const void *init, size_t initlen); -sds sdsnew(const char *init); -sds sdsempty(void); -sds sdsdup(const sds s); -void sdsfree(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, ...); -sds 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); -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); -void *sdsAllocPtr(sds s); - -/* Export the allocator used by SDS to the program using SDS. - * Sometimes the program SDS is linked to, may use a different set of - * allocators, but may want to allocate or free things that SDS will - * respectively free or allocate. */ -void *sds_malloc(size_t size); -void *sds_realloc(void *ptr, size_t size); -void sds_free(void *ptr); - -#ifdef REDIS_TEST -int sdsTest(int argc, char *argv[]); -#endif - -#endif diff --git a/sdsalloc.h b/sdsalloc.h deleted file mode 100644 index f43023c4..00000000 --- a/sdsalloc.h +++ /dev/null @@ -1,42 +0,0 @@ -/* SDSLib 2.0 -- A C dynamic strings library - * - * Copyright (c) 2006-2015, Salvatore Sanfilippo - * Copyright (c) 2015, Oran Agra - * Copyright (c) 2015, Redis Labs, Inc - * 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. - */ - -/* SDS allocator selection. - * - * This file is used in order to change the SDS allocator at compile time. - * Just define the following defines to what you want to use. Also add - * the include of your alternate allocator if needed (not needed in order - * to use the default libc allocator). */ - -#define s_malloc malloc -#define s_realloc realloc -#define s_free free diff --git a/sockcompat.c b/sockcompat.c deleted file mode 100644 index f99d14b0..00000000 --- a/sockcompat.c +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2019, Marcus Geelnard - * - * 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. - */ - -#define REDIS_SOCKCOMPAT_IMPLEMENTATION -#include "sockcompat.h" - -#ifdef _WIN32 -static int _wsaErrorToErrno(int err) { - switch (err) { - case WSAEWOULDBLOCK: - return EWOULDBLOCK; - case WSAEINPROGRESS: - return EINPROGRESS; - case WSAEALREADY: - return EALREADY; - case WSAENOTSOCK: - return ENOTSOCK; - case WSAEDESTADDRREQ: - return EDESTADDRREQ; - case WSAEMSGSIZE: - return EMSGSIZE; - case WSAEPROTOTYPE: - return EPROTOTYPE; - case WSAENOPROTOOPT: - return ENOPROTOOPT; - case WSAEPROTONOSUPPORT: - return EPROTONOSUPPORT; - case WSAEOPNOTSUPP: - return EOPNOTSUPP; - case WSAEAFNOSUPPORT: - return EAFNOSUPPORT; - case WSAEADDRINUSE: - return EADDRINUSE; - case WSAEADDRNOTAVAIL: - return EADDRNOTAVAIL; - case WSAENETDOWN: - return ENETDOWN; - case WSAENETUNREACH: - return ENETUNREACH; - case WSAENETRESET: - return ENETRESET; - case WSAECONNABORTED: - return ECONNABORTED; - case WSAECONNRESET: - return ECONNRESET; - case WSAENOBUFS: - return ENOBUFS; - case WSAEISCONN: - return EISCONN; - case WSAENOTCONN: - return ENOTCONN; - case WSAETIMEDOUT: - return ETIMEDOUT; - case WSAECONNREFUSED: - return ECONNREFUSED; - case WSAELOOP: - return ELOOP; - case WSAENAMETOOLONG: - return ENAMETOOLONG; - case WSAEHOSTUNREACH: - return EHOSTUNREACH; - case WSAENOTEMPTY: - return ENOTEMPTY; - default: - /* We just return a generic I/O error if we could not find a relevant error. */ - return EIO; - } -} - -static void _updateErrno(int success) { - errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError()); -} - -static int _initWinsock() { - static int s_initialized = 0; - if (!s_initialized) { - static WSADATA wsadata; - int err = WSAStartup(MAKEWORD(2,2), &wsadata); - if (err != 0) { - errno = _wsaErrorToErrno(err); - return 0; - } - s_initialized = 1; - } - return 1; -} - -int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { - /* Note: This function is likely to be called before other functions, so run init here. */ - if (!_initWinsock()) { - return EAI_FAIL; - } - - switch (getaddrinfo(node, service, hints, res)) { - case 0: return 0; - case WSATRY_AGAIN: return EAI_AGAIN; - case WSAEINVAL: return EAI_BADFLAGS; - case WSAEAFNOSUPPORT: return EAI_FAMILY; - case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY; - case WSAHOST_NOT_FOUND: return EAI_NONAME; - case WSATYPE_NOT_FOUND: return EAI_SERVICE; - case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE; - default: return EAI_FAIL; /* Including WSANO_RECOVERY */ - } -} - -const char *win32_gai_strerror(int errcode) { - switch (errcode) { - case 0: errcode = 0; break; - case EAI_AGAIN: errcode = WSATRY_AGAIN; break; - case EAI_BADFLAGS: errcode = WSAEINVAL; break; - case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break; - case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break; - case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break; - case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break; - case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break; - default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */ - } - return gai_strerror(errcode); -} - -void win32_freeaddrinfo(struct addrinfo *res) { - freeaddrinfo(res); -} - -SOCKET win32_socket(int domain, int type, int protocol) { - SOCKET s; - - /* Note: This function is likely to be called before other functions, so run init here. */ - if (!_initWinsock()) { - return INVALID_SOCKET; - } - - _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET); - return s; -} - -int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) { - int ret = ioctlsocket(fd, (long)request, argp); - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} - -int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { - int ret = bind(sockfd, addr, addrlen); - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} - -int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) { - int ret = connect(sockfd, addr, addrlen); - _updateErrno(ret != SOCKET_ERROR); - - /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as - * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX - * logic consistent. */ - if (errno == EWOULDBLOCK) { - errno = EINPROGRESS; - } - - return ret != SOCKET_ERROR ? ret : -1; -} - -int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) { - int ret = 0; - if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { - if (*optlen >= sizeof (struct timeval)) { - struct timeval *tv = optval; - DWORD timeout = 0; - socklen_t dwlen = 0; - ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen); - tv->tv_sec = timeout / 1000; - tv->tv_usec = (timeout * 1000) % 1000000; - } else { - ret = WSAEFAULT; - } - *optlen = sizeof (struct timeval); - } else { - ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); - } - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} - -int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { - int ret = 0; - if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { - const struct timeval *tv = optval; - DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; - ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); - } else { - ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen); - } - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} - -int win32_close(SOCKET fd) { - int ret = closesocket(fd); - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} - -ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) { - int ret = recv(sockfd, (char*)buf, (int)len, flags); - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} - -ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) { - int ret = send(sockfd, (const char*)buf, (int)len, flags); - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} - -int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { - int ret = WSAPoll(fds, nfds, timeout); - _updateErrno(ret != SOCKET_ERROR); - return ret != SOCKET_ERROR ? ret : -1; -} -#endif /* _WIN32 */ diff --git a/sockcompat.h b/sockcompat.h deleted file mode 100644 index 56006c16..00000000 --- a/sockcompat.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2019, Marcus Geelnard - * - * 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 __SOCKCOMPAT_H -#define __SOCKCOMPAT_H - -#ifndef _WIN32 -/* For POSIX systems we use the standard BSD socket API. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#else -/* For Windows we use winsock. */ -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */ -#include -#include -#include - -#ifdef _MSC_VER -typedef signed long ssize_t; -#endif - -/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ -int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); -const char *win32_gai_strerror(int errcode); -void win32_freeaddrinfo(struct addrinfo *res); -SOCKET win32_socket(int domain, int type, int protocol); -int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp); -int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); -int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen); -int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen); -int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen); -int win32_close(SOCKET fd); -ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags); -ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); -typedef ULONG nfds_t; -int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); - -#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION -#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) -#undef gai_strerror -#define gai_strerror(errcode) win32_gai_strerror(errcode) -#define freeaddrinfo(res) win32_freeaddrinfo(res) -#define socket(domain, type, protocol) win32_socket(domain, type, protocol) -#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp) -#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen) -#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen) -#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen) -#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen) -#define close(fd) win32_close(fd) -#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags) -#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags) -#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout) -#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */ -#endif /* _WIN32 */ - -#endif /* __SOCKCOMPAT_H */ diff --git a/ssl.c b/ssl.c deleted file mode 100644 index 0fff9a4c..00000000 --- a/ssl.c +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Copyright (c) 2009-2011, Salvatore Sanfilippo - * Copyright (c) 2010-2011, Pieter Noordhuis - * Copyright (c) 2019, Redis Labs - * - * 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 "hiredis.h" -#include "async.h" - -#include -#include -#include -#ifdef WIN32 -#include -#else -#include -#endif - -#include -#include - -#include "win32.h" -#include "async_private.h" - -void __redisSetError(redisContext *c, int type, const char *str); - -/* The SSL context is attached to SSL/TLS connections as a privdata. */ -typedef struct redisSSLContext { - /** - * OpenSSL SSL_CTX; It is optional and will not be set when using - * user-supplied SSL. - */ - SSL_CTX *ssl_ctx; - - /** - * OpenSSL SSL object. - */ - SSL *ssl; - - /** - * SSL_write() requires to be called again with the same arguments it was - * previously called with in the event of an SSL_read/SSL_write situation - */ - size_t lastLen; - - /** Whether the SSL layer requires read (possibly before a write) */ - int wantRead; - - /** - * Whether a write was requested prior to a read. If set, the write() - * should resume whenever a read takes place, if possible - */ - int pendingWrite; -} redisSSLContext; - -/* Forward declaration */ -redisContextFuncs redisContextSSLFuncs; - -#ifdef HIREDIS_SSL_TRACE -/** - * Callback used for debugging - */ -static void sslLogCallback(const SSL *ssl, int where, int ret) { - const char *retstr; - int should_log = 0; - /* Ignore low-level SSL stuff */ - - if (where & SSL_CB_ALERT) { - should_log = 1; - } - if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) { - should_log = 1; - } - if ((where & SSL_CB_EXIT) && ret == 0) { - should_log = 1; - } - - if (!should_log) { - return; - } - - retstr = SSL_alert_type_string(ret); - printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr); - - if (where == SSL_CB_HANDSHAKE_DONE) { - printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl)); - } -} -#endif - -/** - * OpenSSL global initialization and locking handling callbacks. - * Note that this is only required for OpenSSL < 1.1.0. - */ - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#define HIREDIS_USE_CRYPTO_LOCKS -#endif - -#ifdef HIREDIS_USE_CRYPTO_LOCKS -#ifdef WIN32 -typedef CRITICAL_SECTION sslLockType; -static void sslLockInit(sslLockType* l) { - InitializeCriticalSection(l); -} -static void sslLockAcquire(sslLockType* l) { - EnterCriticalSection(l); -} -static void sslLockRelease(sslLockType* l) { - LeaveCriticalSection(l); -} -#else -typedef pthread_mutex_t sslLockType; -static void sslLockInit(sslLockType *l) { - pthread_mutex_init(l, NULL); -} -static void sslLockAcquire(sslLockType *l) { - pthread_mutex_lock(l); -} -static void sslLockRelease(sslLockType *l) { - pthread_mutex_unlock(l); -} -#endif - -static sslLockType* ossl_locks; - -static void opensslDoLock(int mode, int lkid, const char *f, int line) { - sslLockType *l = ossl_locks + lkid; - - if (mode & CRYPTO_LOCK) { - sslLockAcquire(l); - } else { - sslLockRelease(l); - } - - (void)f; - (void)line; -} - -static void initOpensslLocks(void) { - unsigned ii, nlocks; - if (CRYPTO_get_locking_callback() != NULL) { - /* Someone already set the callback before us. Don't destroy it! */ - return; - } - nlocks = CRYPTO_num_locks(); - ossl_locks = malloc(sizeof(*ossl_locks) * nlocks); - for (ii = 0; ii < nlocks; ii++) { - sslLockInit(ossl_locks + ii); - } - CRYPTO_set_locking_callback(opensslDoLock); -} -#endif /* HIREDIS_USE_CRYPTO_LOCKS */ - -/** - * SSL Connection initialization. - */ - -static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) { - if (c->privdata) { - __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated"); - return REDIS_ERR; - } - c->privdata = calloc(1, sizeof(redisSSLContext)); - - c->funcs = &redisContextSSLFuncs; - redisSSLContext *rssl = c->privdata; - - rssl->ssl_ctx = ssl_ctx; - rssl->ssl = ssl; - - SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_set_fd(rssl->ssl, c->fd); - SSL_set_connect_state(rssl->ssl); - - ERR_clear_error(); - int rv = SSL_connect(rssl->ssl); - if (rv == 1) { - return REDIS_OK; - } - - rv = SSL_get_error(rssl->ssl, rv); - if (((c->flags & REDIS_BLOCK) == 0) && - (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { - return REDIS_OK; - } - - if (c->err == 0) { - char err[512]; - if (rv == SSL_ERROR_SYSCALL) - snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno)); - else { - unsigned long e = ERR_peek_last_error(); - snprintf(err,sizeof(err)-1,"SSL_connect failed: %s", - ERR_reason_error_string(e)); - } - __redisSetError(c, REDIS_ERR_IO, err); - } - return REDIS_ERR; -} - -int redisInitiateSSL(redisContext *c, SSL *ssl) { - return redisSSLConnect(c, NULL, ssl); -} - -int redisSecureConnection(redisContext *c, const char *capath, - const char *certpath, const char *keypath, const char *servername) { - - SSL_CTX *ssl_ctx = NULL; - SSL *ssl = NULL; - - /* Initialize global OpenSSL stuff */ - static int isInit = 0; - if (!isInit) { - isInit = 1; - SSL_library_init(); -#ifdef HIREDIS_USE_CRYPTO_LOCKS - initOpensslLocks(); -#endif - } - - ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - if (!ssl_ctx) { - __redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX"); - goto error; - } - -#ifdef HIREDIS_SSL_TRACE - SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback); -#endif - SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL); - if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) { - __redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together"); - goto error; - } - - if (capath) { - if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) { - __redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate"); - goto error; - } - } - if (certpath) { - if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) { - __redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate"); - goto error; - } - if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) { - __redisSetError(c, REDIS_ERR_OTHER, "Invalid client key"); - goto error; - } - } - - ssl = SSL_new(ssl_ctx); - if (!ssl) { - __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance"); - goto error; - } - if (servername) { - if (!SSL_set_tlsext_host_name(ssl, servername)) { - __redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication"); - goto error; - } - } - - return redisSSLConnect(c, ssl_ctx, ssl); - -error: - if (ssl) SSL_free(ssl); - if (ssl_ctx) SSL_CTX_free(ssl_ctx); - return REDIS_ERR; -} - -static int maybeCheckWant(redisSSLContext *rssl, int rv) { - /** - * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set - * and true is returned. False is returned otherwise - */ - if (rv == SSL_ERROR_WANT_READ) { - rssl->wantRead = 1; - return 1; - } else if (rv == SSL_ERROR_WANT_WRITE) { - rssl->pendingWrite = 1; - return 1; - } else { - return 0; - } -} - -/** - * Implementation of redisContextFuncs for SSL connections. - */ - -static void redisSSLFreeContext(void *privdata){ - redisSSLContext *rsc = privdata; - - if (!rsc) return; - if (rsc->ssl) { - SSL_free(rsc->ssl); - rsc->ssl = NULL; - } - if (rsc->ssl_ctx) { - SSL_CTX_free(rsc->ssl_ctx); - rsc->ssl_ctx = NULL; - } - free(rsc); -} - -static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) { - redisSSLContext *rssl = c->privdata; - - int nread = SSL_read(rssl->ssl, buf, bufcap); - if (nread > 0) { - return nread; - } else if (nread == 0) { - __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); - return -1; - } else { - int err = SSL_get_error(rssl->ssl, nread); - if (c->flags & REDIS_BLOCK) { - /** - * In blocking mode, we should never end up in a situation where - * we get an error without it being an actual error, except - * in the case of EINTR, which can be spuriously received from - * debuggers or whatever. - */ - if (errno == EINTR) { - return 0; - } else { - const char *msg = NULL; - if (errno == EAGAIN) { - msg = "Resource temporarily unavailable"; - } - __redisSetError(c, REDIS_ERR_IO, msg); - return -1; - } - } - - /** - * We can very well get an EWOULDBLOCK/EAGAIN, however - */ - if (maybeCheckWant(rssl, err)) { - return 0; - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } -} - -static int redisSSLWrite(redisContext *c) { - redisSSLContext *rssl = c->privdata; - - size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); - int rv = SSL_write(rssl->ssl, c->obuf, len); - - if (rv > 0) { - rssl->lastLen = 0; - } else if (rv < 0) { - rssl->lastLen = len; - - int err = SSL_get_error(rssl->ssl, rv); - if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) { - return 0; - } else { - __redisSetError(c, REDIS_ERR_IO, NULL); - return -1; - } - } - return rv; -} - -static void redisSSLAsyncRead(redisAsyncContext *ac) { - int rv; - redisSSLContext *rssl = ac->c.privdata; - redisContext *c = &ac->c; - - rssl->wantRead = 0; - - if (rssl->pendingWrite) { - int done; - - /* This is probably just a write event */ - rssl->pendingWrite = 0; - rv = redisBufferWrite(c, &done); - if (rv == REDIS_ERR) { - __redisAsyncDisconnect(ac); - return; - } else if (!done) { - _EL_ADD_WRITE(ac); - } - } - - rv = redisBufferRead(c); - if (rv == REDIS_ERR) { - __redisAsyncDisconnect(ac); - } else { - _EL_ADD_READ(ac); - redisProcessCallbacks(ac); - } -} - -static void redisSSLAsyncWrite(redisAsyncContext *ac) { - int rv, done = 0; - redisSSLContext *rssl = ac->c.privdata; - redisContext *c = &ac->c; - - rssl->pendingWrite = 0; - rv = redisBufferWrite(c, &done); - if (rv == REDIS_ERR) { - __redisAsyncDisconnect(ac); - return; - } - - if (!done) { - if (rssl->wantRead) { - /* Need to read-before-write */ - rssl->pendingWrite = 1; - _EL_DEL_WRITE(ac); - } else { - /* No extra reads needed, just need to write more */ - _EL_ADD_WRITE(ac); - } - } else { - /* Already done! */ - _EL_DEL_WRITE(ac); - } - - /* Always reschedule a read */ - _EL_ADD_READ(ac); -} - -redisContextFuncs redisContextSSLFuncs = { - .free_privdata = redisSSLFreeContext, - .async_read = redisSSLAsyncRead, - .async_write = redisSSLAsyncWrite, - .read = redisSSLRead, - .write = redisSSLWrite -}; - diff --git a/test.c b/test.c deleted file mode 100644 index 9c0de6d4..00000000 --- a/test.c +++ /dev/null @@ -1,1017 +0,0 @@ -#include "fmacros.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" -#ifdef HIREDIS_TEST_SSL -#include "hiredis_ssl.h" -#endif -#include "net.h" - -enum connection_type { - CONN_TCP, - CONN_UNIX, - CONN_FD, - CONN_SSL -}; - -struct config { - enum connection_type type; - - struct { - const char *host; - int port; - struct timeval timeout; - } tcp; - - struct { - const char *path; - } unix_sock; - - struct { - const char *host; - int port; - const char *ca_cert; - const char *cert; - const char *key; - } ssl; -}; - -/* 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 void do_ssl_handshake(redisContext *c, struct config config) { -#ifdef HIREDIS_TEST_SSL - redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL); - if (c->err) { - printf("SSL error: %s\n", c->errstr); - redisFree(c); - exit(1); - } -#else - (void) c; - (void) config; -#endif -} - -static redisContext *do_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_SSL) { - c = redisConnect(config.ssl.host, config.ssl.port); - } else if (config.type == CONN_UNIX) { - c = redisConnectUnix(config.unix_sock.path); - } else if (config.type == CONN_FD) { - /* Create a dummy connection just to get an fd to inherit */ - redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.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); - } - - if (config.type == CONN_SSL) { - do_ssl_handshake(c, config); - } - - return select_database(c); -} - -static void do_reconnect(redisContext *c, struct config config) { - redisReconnect(c); - - if (config.type == CONN_SSL) { - do_ssl_handshake(c, config); - } -} - -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); - - sds sds_cmd; - - sds_cmd = NULL; - test("Format command into sds by passing argc/argv without lengths: "); - len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); - test_cond(strncmp(sds_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)); - sdsfree(sds_cmd); - - sds_cmd = NULL; - test("Format command into sds by passing argc/argv with lengths: "); - len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); - test_cond(strncmp(sds_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)); - sdsfree(sds_cmd); -} - -static void test_append_formatted_commands(struct config config) { - redisContext *c; - redisReply *reply; - char *cmd; - int len; - - c = do_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("Correctly parses LLONG_MAX: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, ":9223372036854775807\r\n",22); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && - ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && - ((redisReply*)reply)->integer == LLONG_MAX); - freeReplyObject(reply); - redisReaderFree(reader); - - test("Set error when > LLONG_MAX: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, ":9223372036854775808\r\n",22); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Bad integer value") == 0); - freeReplyObject(reply); - redisReaderFree(reader); - - test("Correctly parses LLONG_MIN: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, ":-9223372036854775808\r\n",23); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_OK && - ((redisReply*)reply)->type == REDIS_REPLY_INTEGER && - ((redisReply*)reply)->integer == LLONG_MIN); - freeReplyObject(reply); - redisReaderFree(reader); - - test("Set error when < LLONG_MIN: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, ":-9223372036854775809\r\n",23); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Bad integer value") == 0); - freeReplyObject(reply); - redisReaderFree(reader); - - test("Set error when array < -1: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); - freeReplyObject(reply); - redisReaderFree(reader); - - test("Set error when bulk < -1: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, "$-2\r\nasdf\r\n",11); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Bulk string length out of range") == 0); - freeReplyObject(reply); - redisReaderFree(reader); - -#if LLONG_MAX > SIZE_MAX - test("Set error when array > SIZE_MAX: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0); - freeReplyObject(reply); - redisReaderFree(reader); - - test("Set error when bulk > SIZE_MAX: "); - reader = redisReaderCreate(); - redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28); - ret = redisReaderGetReply(reader,&reply); - test_cond(ret == REDIS_ERR && - strcasecmp(reader->errstr,"Bulk string length out of range") == 0); - freeReplyObject(reply); - redisReaderFree(reader); -#endif - - 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 *redisCtx = NULL; - void *reply = NULL; - - test("Don't fail when redisFree is passed a NULL value: "); - redisFree(redisCtx); - test_cond(redisCtx == NULL); - - test("Don't fail when freeReplyObject is passed a NULL value: "); - freeReplyObject(reply); - test_cond(reply == NULL); -} - -#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" -static void test_blocking_connection_errors(void) { - redisContext *c; - struct addrinfo hints = {.ai_family = AF_INET}; - struct addrinfo *ai_tmp = NULL; - - int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp); - if (rv != 0) { - // Address does *not* exist - test("Returns error when host cannot be resolved: "); - // First see if this domain name *actually* resolves to NXDOMAIN - c = redisConnect(HIREDIS_BAD_DOMAIN, 6379); - test_cond( - c->err == REDIS_ERR_OTHER && - (strcmp(c->errstr, "Name or service not known") == 0 || - strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || - strcmp(c->errstr, "Name does not resolve") == 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, - "hostname nor servname provided, or not known") == 0 || - strcmp(c->errstr, "no address associated with name") == 0)); - redisFree(c); - } else { - printf("Skipping NXDOMAIN test. Found evil ISP!\n"); - freeaddrinfo(ai_tmp); - } - - 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_sock 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 = do_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); - - /* Make sure passing NULL to redisGetReply is safe */ - test("Can pass NULL to redisGetReply: "); - assert(redisAppendCommand(c, "PING") == REDIS_OK); - test_cond(redisGetReply(c, NULL) == REDIS_OK); - - 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 = do_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 = do_connect(config); - test("Does not return a reply when the command times out: "); - redisAppendFormattedCommand(c, cmd, strlen(cmd)); - s = c->funcs->write(c); - 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: "); - do_reconnect(c, config); - 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_sock.path = "foo"; - do_reconnect(c, config); - 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 = do_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 = do_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 && strcmp(c->errstr, "Invalid timeout specified") == 0); - 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 && strcmp(c->errstr, "Invalid timeout specified") == 0); - redisFree(c); -} - -static void test_throughput(struct config config) { - redisContext *c = do_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); - - replies = malloc(sizeof(redisReply*)*num); - t1 = usec(); - for (i = 0; i < num; i++) { - replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000); - assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx INCRBY: %.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); - - replies = malloc(sizeof(redisReply*)*num); - for (i = 0; i < num; i++) - redisAppendCommand(c,"INCRBY incrkey %d", 1000000); - 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_INTEGER); - } - t2 = usec(); - for (i = 0; i < num; i++) freeReplyObject(replies[i]); - free(replies); - printf("\t(%dx INCRBY (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_sock = { - .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_sock.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; -#ifdef HIREDIS_TEST_SSL - } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) { - argv++; argc--; - cfg.ssl.port = atoi(argv[0]); - } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) { - argv++; argc--; - cfg.ssl.host = argv[0]; - } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) { - argv++; argc--; - cfg.ssl.ca_cert = argv[0]; - } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) { - argv++; argc--; - cfg.ssl.cert = argv[0]; - } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) { - argv++; argc--; - cfg.ssl.key = argv[0]; -#endif - } 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_sock.path); - cfg.type = CONN_UNIX; - test_blocking_connection(cfg); - test_blocking_connection_timeouts(cfg); - test_blocking_io_errors(cfg); - if (throughput) test_throughput(cfg); - -#ifdef HIREDIS_TEST_SSL - if (cfg.ssl.port && cfg.ssl.host) { - printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port); - cfg.type = CONN_SSL; - - 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); - } -#endif - - if (test_inherit_fd) { - printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.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/test.sh b/test.sh deleted file mode 100644 index 2cab9e6f..00000000 --- a/test.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh -ue - -REDIS_SERVER=${REDIS_SERVER:-redis-server} -REDIS_PORT=${REDIS_PORT:-56379} -REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} -TEST_SSL=${TEST_SSL:-0} -SSL_TEST_ARGS= - -tmpdir=$(mktemp -d) -PID_FILE=${tmpdir}/hiredis-test-redis.pid -SOCK_FILE=${tmpdir}/hiredis-test-redis.sock - -if [ "$TEST_SSL" = "1" ]; then - SSL_CA_CERT=${tmpdir}/ca.crt - SSL_CA_KEY=${tmpdir}/ca.key - SSL_CERT=${tmpdir}/redis.crt - SSL_KEY=${tmpdir}/redis.key - - openssl genrsa -out ${tmpdir}/ca.key 4096 - openssl req \ - -x509 -new -nodes -sha256 \ - -key ${SSL_CA_KEY} \ - -days 3650 \ - -subj '/CN=Hiredis Test CA' \ - -out ${SSL_CA_CERT} - openssl genrsa -out ${SSL_KEY} 2048 - openssl req \ - -new -sha256 \ - -key ${SSL_KEY} \ - -subj '/CN=Hiredis Test Cert' | \ - openssl x509 \ - -req -sha256 \ - -CA ${SSL_CA_CERT} \ - -CAkey ${SSL_CA_KEY} \ - -CAserial ${tmpdir}/ca.txt \ - -CAcreateserial \ - -days 365 \ - -out ${SSL_CERT} - - SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}" -fi - -cleanup() { - set +e - kill $(cat ${PID_FILE}) - rm -rf ${tmpdir} -} -trap cleanup INT TERM EXIT - -cat > ${tmpdir}/redis.conf <> ${tmpdir}/redis.conf < Date: Sun, 1 Mar 2020 00:38:42 -0800 Subject: [PATCH 049/273] Properly export functions for dll usage --- CMakeLists.txt | 10 +++++++++- dict.c | 1 - hiredis_vip.def | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 hiredis_vip.def diff --git a/CMakeLists.txt b/CMakeLists.txt index a4b05e2f..a44ead5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,13 +26,21 @@ MESSAGE("Detected version: ${VERSION}") PROJECT(hiredis_vip VERSION "${VERSION}") -ADD_LIBRARY(hiredis_vip SHARED +SET(hiredis_vip_SOURCES adlist.c command.c crc16.c hiarray.c hircluster.c hiutil.c) +IF(WIN32 OR MINGW) + SET(hiredis_vip_SOURCES + ${hiredis_vip_SOURCES} + hiredis_vip.def) +ENDIF() + +ADD_LIBRARY(hiredis_vip SHARED + ${hiredis_vip_SOURCES}) SET_TARGET_PROPERTIES(hiredis_vip PROPERTIES diff --git a/dict.c b/dict.c index 2bc0b34a..61995c1c 100644 --- a/dict.c +++ b/dict.c @@ -33,7 +33,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -//#include "fmacros.h" #include #include #include diff --git a/hiredis_vip.def b/hiredis_vip.def new file mode 100644 index 00000000..00c84e2d --- /dev/null +++ b/hiredis_vip.def @@ -0,0 +1,42 @@ + EXPORTS + actx_get_by_node + cluster_update_route + ctx_get_by_node + parse_cluster_nodes + parse_cluster_slots + redisClusterAppendCommand + redisClusterAppendCommandArgv + redisClusterAppendFormattedCommand + redisClusterAsyncCommand + redisClusterAsyncCommandArgv + redisClusterAsyncConnect + redisClusterAsyncDisconnect + redisClusterAsyncFormattedCommand + redisClusterAsyncFree + redisClusterAsyncSetConnectCallback + redisClusterAsyncSetDisconnectCallback + redisClusterCommand + redisClusterCommandArgv + redisClusterConnect + redisClusterConnect2 + redisClusterConnectNonBlock + redisClusterConnectWithTimeout + redisClusterContextInit + redisClusterFormattedCommand + redisClusterFree + redisClusterGetReply + redisClusterReset + redisClusterSetMaxRedirect + redisClusterSetOptionAddNode + redisClusterSetOptionConnectBlock + redisClusterSetOptionConnectNonBlock + redisClusterSetOptionConnectTimeout + redisClusterSetOptionMaxRedirect + redisClusterSetOptionParseOpenSlots + redisClusterSetOptionParseSlaves + redisClusterSetOptionRouteUseSlots + redisClusterSetOptionTimeout + redisClustervAppendCommand + redisClustervAsyncCommand + redisClustervCommand + test_cluster_update_route From 09cf3e8d71d2f6f3bbf1a4c73a9296006fa4cb82 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 1 Mar 2020 00:50:17 -0800 Subject: [PATCH 050/273] First pass at fixing Makefile --- Makefile | 160 ++----------------------------------------------------- 1 file changed, 3 insertions(+), 157 deletions(-) diff --git a/Makefile b/Makefile index 7759fffc..38e885a6 100644 --- a/Makefile +++ b/Makefile @@ -3,17 +3,9 @@ # 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 sockcompat.o adlist.o command.o crc16.o dict.o hiarray.o hircluster.o hiutil.o -SSL_OBJ=ssl.o -EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib -ifeq ($(USE_SSL),1) -EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl -endif -TESTS=hiredis-test +OBJ=adlist.o command.o crc16.o hiarray.o hircluster.o hiutil.o LIBNAME=libhiredis_vip -SSL_LIBNAME=libhiredis_ssl PKGCONFNAME=hiredis-vip.pc -SSL_PKGCONFNAME=hiredis_ssl.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}') @@ -29,18 +21,6 @@ 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') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') @@ -64,52 +44,15 @@ STLIB_MAKE_CMD=$(AR) rcs # Platform-specific overrides uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -USE_SSL?=0 - -# This is required for test.c only -ifeq ($(USE_SSL),1) - CFLAGS+=-DHIREDIS_TEST_SSL -endif - -ifeq ($(uname_S),Linux) - SSL_LDFLAGS=-lssl -lcrypto -else - OPENSSL_PREFIX?=/usr/local/opt/openssl - CFLAGS+=-I$(OPENSSL_PREFIX)/include - SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto -endif - -ifeq ($(uname_S),SunOS) - REAL_LDFLAGS+= -ldl -lnsl -lsocket - DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) -endif -ifeq ($(uname_S),Darwin) - DYLIBSUFFIX=dylib - DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_SONAME).$(DYLIBSUFFIX) - DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) -endif - -all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) -ifeq ($(USE_SSL),1) -all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME) -endif +all: $(DYLIBNAME) $(STLIBNAME) $(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 win32.h async_private.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 hiutil.h hiarray.h hircluster.o: hircluster.c fmacros.h win32.h hircluster.h hiredis.h read.h sds.h async.h hiutil.h adlist.h hiarray.h command.h dict.c dict.h -hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h async.h win32.h hiutil.o: hiutil.c win32.h hiutil.h -net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h -read.o: read.c fmacros.h read.h sds.h win32.h -sds.o: sds.c sds.h sdsalloc.h -sockcompat.o: sockcompat.c sockcompat.h -ssl.o: ssl.c hiredis.h read.h sds.h async.h async_private.h -test.o: test.c fmacros.h hiredis.h read.h sds.h net.h $(DYLIBNAME): $(OBJ) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS) @@ -117,97 +60,14 @@ $(DYLIBNAME): $(OBJ) $(STLIBNAME): $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) -$(SSL_DYLIBNAME): $(SSL_OBJ) - $(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS) - -$(SSL_STLIBNAME): $(SSL_OBJ) - $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ) - dynamic: $(DYLIBNAME) static: $(STLIBNAME) -ifeq ($(USE_SSL),1) -dynamic: $(SSL_DYLIBNAME) -static: $(SSL_STLIBNAME) -endif - -# Binaries: -hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS) - -hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) - -hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS) - -hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS) - -hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS) - -hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS) - -hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) - -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) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS) -endif - -ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) -hiredis-example-qt: - @echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR" - @false -else -hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) - $(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ - $(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore - $(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \ - $(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore - $(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore -endif - -hiredis-example: examples/example.c $(STLIBNAME) - $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) - -examples: $(EXAMPLES) - -TEST_LIBS = $(STLIBNAME) -ifeq ($(USE_SSL),1) - TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread -endif -hiredis-test: test.o $(TEST_LIBS) - -hiredis-%: %.o $(STLIBNAME) - $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS) - -test: hiredis-test - ./hiredis-test - -check: hiredis-test - TEST_SSL=$(USE_SSL) ./test.sh .c.o: $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< clean: - rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov + rm -rf $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) *.o *.gcda *.gcno *.gcov dep: $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c @@ -227,20 +87,6 @@ $(PKGCONFNAME): hircluster.h @echo Libs: -L\$${libdir} -lhiredis-vip >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ -$(SSL_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_ssl >> $@ - @echo Description: SSL Support for hiredis. >> $@ - @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ - @echo Requires: hiredis-vip >> $@ - @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@ - @echo Libs.private: -lssl -lcrypto >> $@ - install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) $(INSTALL) hiredis.h async.h read.h sds.h hircluster.h adlist.h dict.h hiarray.h $(INSTALL_INCLUDE_PATH) From 0902215950f0f9405bd347440060e6ce27e09599 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Sun, 25 Oct 2020 12:16:55 +0100 Subject: [PATCH 051/273] Memoryleak in async disconnect --- hircluster.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hircluster.c b/hircluster.c index ab6b8e60..5aa11b6d 100644 --- a/hircluster.c +++ b/hircluster.c @@ -4961,6 +4961,8 @@ void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { node->acon = NULL; } + + dictReleaseIterator(di); } void redisClusterAsyncFree(redisClusterAsyncContext *acc) From 7795efc926adb998e1f8b7960157749608b38158 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 26 Oct 2020 10:19:56 +0100 Subject: [PATCH 052/273] Correcting include path in adapters --- adapters/libevent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/libevent.h b/adapters/libevent.h index 942f9d5e..95a3b329 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -30,7 +30,7 @@ #ifndef __HIREDIS_VIP_LIBEVENT_H__ #define __HIREDIS_VIP_LIBEVENT_H__ -#include +#include #include "../hircluster.h" static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) { From 31cb2de849996b45682ba2bb97faef835664c00f Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 26 Oct 2020 10:32:55 +0100 Subject: [PATCH 053/273] Correcting argument type in dataCleanup callback Avoids a segfault when running async --- hircluster.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hircluster.c b/hircluster.c index 5aa11b6d..bdbb593a 100644 --- a/hircluster.c +++ b/hircluster.c @@ -4322,12 +4322,12 @@ static void cluster_async_data_free(cluster_async_data *cad) cad = NULL; } -static void unlinkAsyncContextAndNode(redisAsyncContext* ac) +static void unlinkAsyncContextAndNode(void *data) { cluster_node *node; - if (ac->data) { - node = (cluster_node *)(ac->data); + if (data) { + node = (cluster_node *)(data); node->acon = NULL; } } From af70864e184095e977be882403fb3151a33b0c3b Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Thu, 29 Oct 2020 16:01:39 +0100 Subject: [PATCH 054/273] Add examples and its build instructions Will build hiredis, hiredis-vip and examples as external projects, this enables the usage of cmakes find_package() --- CMakeLists.txt | 33 +++++------ README.md | 22 ++++++- examples/CMakeLists.txt | 29 +++++++++ examples/main.cpp | 30 ++++++++++ examples/main_async.cpp | 94 ++++++++++++++++++++++++++++++ examples/main_ipv6.cpp | 30 ++++++++++ examples/main_tls.cpp | 76 ++++++++++++++++++++++++ examples_cmakebuild/CMakeLists.txt | 37 ++++++++++++ 8 files changed, 332 insertions(+), 19 deletions(-) create mode 100644 examples/CMakeLists.txt create mode 100644 examples/main.cpp create mode 100644 examples/main_async.cpp create mode 100644 examples/main_ipv6.cpp create mode 100644 examples/main_tls.cpp create mode 100644 examples_cmakebuild/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index a44ead5e..116bb29c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,6 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0) -INCLUDE(GNUInstallDirs) -PROJECT(hiredis_vip) +cmake_minimum_required(VERSION 3.14) -IF (WIN32 OR MINGW) - ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) -ENDIF() - -IF (MSVC) - ADD_COMPILE_OPTIONS("/wd 4267" "/wd 4244") -ENDIF() +include(GNUInstallDirs) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") @@ -17,6 +9,7 @@ MACRO(getVersionBit name) STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") ENDMACRO(getVersionBit) +# Get version information from src-code getVersionBit(HIREDIS_VIP_MAJOR) getVersionBit(HIREDIS_VIP_MINOR) getVersionBit(HIREDIS_VIP_PATCH) @@ -24,7 +17,9 @@ getVersionBit(HIREDIS_VIP_SONAME) SET(VERSION "${HIREDIS_VIP_MAJOR}.${HIREDIS_VIP_MINOR}.${HIREDIS_VIP_PATCH}") MESSAGE("Detected version: ${VERSION}") -PROJECT(hiredis_vip VERSION "${VERSION}") +project(hiredis_vip + VERSION "${VERSION}" + LANGUAGES C) SET(hiredis_vip_SOURCES adlist.c @@ -33,12 +28,18 @@ SET(hiredis_vip_SOURCES hiarray.c hircluster.c hiutil.c) + IF(WIN32 OR MINGW) + ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) SET(hiredis_vip_SOURCES ${hiredis_vip_SOURCES} hiredis_vip.def) ENDIF() +IF (MSVC) + ADD_COMPILE_OPTIONS("/wd 4267" "/wd 4244") +ENDIF() + ADD_LIBRARY(hiredis_vip SHARED ${hiredis_vip_SOURCES}) @@ -48,7 +49,7 @@ SET_TARGET_PROPERTIES(hiredis_vip FIND_PACKAGE(hiredis REQUIRED) -TARGET_INCLUDE_DIRECTORIES(hiredis_vip PUBLIC ${hiredis_INCLUDE_DIRS}) +TARGET_INCLUDE_DIRECTORIES(hiredis_vip PRIVATE ${hiredis_INCLUDE_DIRS}) IF(WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis_vip PRIVATE ws2_32 hiredis::hiredis) @@ -61,10 +62,10 @@ INSTALL(TARGETS hiredis_vip INSTALL(FILES hircluster.h adlist.h hiarray.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) - + INSTALL(DIRECTORY adapters DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) - + INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) @@ -84,7 +85,5 @@ INSTALL(EXPORT hiredis_vip-targets NAMESPACE hiredis_vip:: DESTINATION ${CMAKE_CONF_INSTALL_DIR}) -INSTALL(FILES hiredis_vip-config.cmake +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip-config.cmake DESTINATION ${CMAKE_CONF_INSTALL_DIR}) - - diff --git a/README.md b/README.md index 120a7f13..80fff5aa 100644 --- a/README.md +++ b/README.md @@ -301,11 +301,29 @@ callbacks have been executed. After this, the disconnection callback is executed 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*. +## Build instructions + +Build the examples + +``` +mkdir build_examples +cd build_examples +cmake ../examples_cmakebuild +make + +# Run +./examples/src/examples-build/examples +./examples/src/examples-build/examples_async +./examples/src/examples-build/examples_ipv6 +./examples/src/examples-build/examples_tls +``` + ## AUTHORS -Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). +Hiredis-vip was created by vipshop (https://github.com/vipshop/hiredis-vip). -The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). +This fork is based on the heronr fork (https://github.com/heronr/hiredis-vip) +and uses hiredis (https://github.com/redis/hiredis). The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..39f77595 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.14) + +project(examples CXX) + +include(ExternalProject) + +# Handle libevent and hiredis +find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) +find_package(hiredis REQUIRED) +find_package(hiredis_vip REQUIRED) + +include_directories("${hiredis_INCLUDE_DIRS}") +include_directories("${hiredis_vip_INCLUDE_DIRS}") + +# Executable: IPv4 +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES}) + +# Executable: IPv6 +add_executable(${PROJECT_NAME}_ipv6 main_ipv6.cpp) +target_link_libraries(${PROJECT_NAME}_ipv6 ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES}) + +# Executable: async +add_executable(${PROJECT_NAME}_async main_async.cpp) +target_link_libraries(${PROJECT_NAME}_async ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES} ${EVENT_LIBRARY}) + +# Executable: tls +add_executable(${PROJECT_NAME}_tls main_async.cpp) +target_link_libraries(${PROJECT_NAME}_tls ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES} ${EVENT_LIBRARY}) diff --git a/examples/main.cpp b/examples/main.cpp new file mode 100644 index 00000000..bea17306 --- /dev/null +++ b/examples/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "hiredis_vip/hircluster.h" + +int main(int argc, char **argv) +{ + struct timeval timeout = { 1, 500000 }; // 1.5s + + redisClusterContext *cc = redisClusterContextInit(); + redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); + redisClusterSetOptionConnectTimeout(cc, timeout); + redisClusterSetOptionRouteUseSlots(cc); + redisClusterConnect2(cc); + if (cc && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error + exit(-1); + } + + redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + printf("GET: %s\n", reply2->str); + freeReplyObject(reply2); + + redisClusterFree(cc); + return 0; +} diff --git a/examples/main_async.cpp b/examples/main_async.cpp new file mode 100644 index 00000000..a132158f --- /dev/null +++ b/examples/main_async.cpp @@ -0,0 +1,94 @@ +#include +#include +#include "hiredis_vip/hircluster.h" +#include "hiredis_vip/adapters/libevent.h" + +void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + redisReply *reply = (redisReply*)r; + if (reply == NULL) { + if (cc->errstr) { + printf("errstr: %s\n", cc->errstr); + } + return; + } + printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisClusterAsyncDisconnect(cc); +} + +void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + redisReply *reply = (redisReply*)r; + if (reply == NULL) { + if (cc->errstr) { + printf("errstr: %s\n", cc->errstr); + } + return; + } + printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); +} + +void connectCallback(const redisAsyncContext *ac, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", ac->errstr); + return; + } + printf("Connected to %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); +} + +void disconnectCallback(const redisAsyncContext *ac, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", ac->errstr); + return; + } + printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); +} + +int main(int argc, char **argv) +{ + printf("Connecting...\n"); + redisClusterAsyncContext *cc = redisClusterAsyncConnect("127.0.0.1:30001", + HIRCLUSTER_FLAG_NULL); + if (cc && cc->err) { + printf("Error: %s\n", cc->errstr); + return 1; + } + + struct event_base *base = event_base_new(); + redisClusterLibeventAttach(cc, base); + redisClusterAsyncSetConnectCallback(cc, connectCallback); + redisClusterAsyncSetDisconnectCallback(cc, disconnectCallback); + + int status; + status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key", "value"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key2", "value2"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key2"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + printf("Dispatch..\n"); + event_base_dispatch(base); + + printf("Done..\n"); + redisClusterAsyncFree(cc); + event_base_free(base); + return 0; +} diff --git a/examples/main_ipv6.cpp b/examples/main_ipv6.cpp new file mode 100644 index 00000000..ed7e6add --- /dev/null +++ b/examples/main_ipv6.cpp @@ -0,0 +1,30 @@ +#include +#include +#include "hiredis_vip/hircluster.h" + +int main(int argc, char **argv) +{ + struct timeval timeout = { 1, 500000 }; // 1.5s + + redisClusterContext *cc = redisClusterContextInit(); + redisClusterSetOptionAddNodes(cc, "::1:30001"); + redisClusterSetOptionConnectTimeout(cc, timeout); + redisClusterSetOptionRouteUseSlots(cc); + redisClusterConnect2(cc); + if (cc && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error + exit(-1); + } + + redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + printf("GET: %s\n", reply2->str); + freeReplyObject(reply2); + + redisClusterFree(cc); + return 0; +} diff --git a/examples/main_tls.cpp b/examples/main_tls.cpp new file mode 100644 index 00000000..32d54b16 --- /dev/null +++ b/examples/main_tls.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include "hiredis_vip/hircluster.h" + +int main(int argc, char **argv) +{ + redisContext *c; + redisSSLContext *ssl; + redisSSLContextError ssl_error; + struct timeval timeout = { 1, 500000 }; // 1.5s + + // Get current work directory + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + printf("getcwd() error"); + exit(1); + } + printf("Current path: %s\n", cwd); + + char cert[PATH_MAX]; + strcpy(cert, cwd); + strcat(cert, "/../../configs/tls/redis.crt"); + printf("Cert: %s\n", cert); + char key[PATH_MAX]; + strcpy(key, cwd); + strcat(key, "/../../configs/tls/redis.key"); + printf("Key: %s\n", key); + char ca[PATH_MAX]; + strcpy(ca, cwd); + strcat(ca, "/../../configs/tls/ca.crt"); + printf("Ca: %s\n", ca); + + redisInitOpenSSL(); + ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error); + if (!ssl) { + printf("SSL Context error: %s\n", + redisSSLContextGetError(ssl_error)); + exit(1); + } + + redisOptions options = {0}; + REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 33001); + options.connect_timeout = &timeout; + c = redisConnectWithOptions(&options); + 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); + } + + if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) { + printf("Couldn't initialize SSL!\n"); + printf("Error: %s\n", c->errstr); + redisFree(c); + exit(1); + } + + redisReply *reply = (redisReply*)redisCommand(c,"SET %s %s", "key", "value"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + redisReply *reply2 = (redisReply*)redisCommand(c, "GET %s", "key"); + printf("GET: %s\n", reply2->str); + freeReplyObject(reply2); + + redisFree(c); + redisFreeSSLContext(ssl); + return 0; +} diff --git a/examples_cmakebuild/CMakeLists.txt b/examples_cmakebuild/CMakeLists.txt new file mode 100644 index 00000000..262a09eb --- /dev/null +++ b/examples_cmakebuild/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.14) + +project(hiredis_vip_externalproject C) + +include(ExternalProject) + +ExternalProject_Add(hiredis + PREFIX hiredis + GIT_REPOSITORY https://github.com/redis/hiredis + GIT_TAG v1.0.0 + CMAKE_CACHE_ARGS + "-DENABLE_SSL:BOOL=ON" + "-DCMAKE_BUILD_TYPE:STRING=Debug" + "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis" +) + +ExternalProject_Add(hiredis_vip + PREFIX hiredis_vip + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." + CMAKE_CACHE_ARGS + "-DCMAKE_BUILD_TYPE:STRING=Debug" + "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip" + "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" + DEPENDS hiredis +) + +ExternalProject_Add(examples + PREFIX examples + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../examples" + INSTALL_COMMAND "" + CMAKE_CACHE_ARGS + "-DCMAKE_BUILD_TYPE:STRING=Debug" + "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/examples" + "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" + "-Dhiredis_vip_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip/share/hiredis_vip" + DEPENDS hiredis_vip +) From b02636d0494e05784ae142f47d1e3d21a0badcb2 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Thu, 29 Oct 2020 19:10:13 +0100 Subject: [PATCH 055/273] Download dependency hiredis via makefiles This can be turned off via: cmake -DDOWNLOAD_HIREDIS=OFF .. --- CMakeLists.txt | 32 ++++++++++++++++++++++++++++++-- README.md | 9 +++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 116bb29c..60ba0bfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -include(GNUInstallDirs) +option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") @@ -47,7 +47,31 @@ SET_TARGET_PROPERTIES(hiredis_vip PROPERTIES VERSION "${HIREDIS_VIP_SONAME}") -FIND_PACKAGE(hiredis REQUIRED) +if(DOWNLOAD_HIREDIS) + message("Downloading dependency 'hiredis'..") + + include(FetchContent) + FetchContent_Declare(hiredis + GIT_REPOSITORY https://github.com/redis/hiredis + GIT_TAG v1.0.0 + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/hiredis" + ) + FetchContent_MakeAvailable(hiredis) + + # Create an empty *Config.cmake for find_package + # See: https://github.com/abandonware-pjz37/cmake-find-package-include/blob/master/hooks/fetch.cmake + set(stub_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/pkg") + file(WRITE "${stub_dir}/hiredisConfig.cmake" "") + set(hiredis_DIR ${stub_dir}) + + # Set variables normally got from hiredisConfig.cmake + SET(hiredis_LIBRARIES hiredis::hiredis) + SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") +else() + message("Expecting to find dependency 'hiredis' in path..") +endif() + +find_package(hiredis REQUIRED) TARGET_INCLUDE_DIRECTORIES(hiredis_vip PRIVATE ${hiredis_INCLUDE_DIRS}) @@ -55,6 +79,10 @@ IF(WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis_vip PRIVATE ws2_32 hiredis::hiredis) ENDIF() + +# Get CMAKE_INSTALL_ variables +include(GNUInstallDirs) + CONFIGURE_FILE(hiredis_vip.pc.in hiredis_vip.pc @ONLY) INSTALL(TARGETS hiredis_vip diff --git a/README.md b/README.md index 80fff5aa..1b84fc63 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,15 @@ See the `adapters/` directory for bindings to *ae* and *libevent*. ## Build instructions +Build the library + +``` +mkdir build +cd build +cmake .. +make +``` + Build the examples ``` From 5be5ac36533ff2b6f346979e9952bd6b64e403c4 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Thu, 29 Oct 2020 23:28:47 +0100 Subject: [PATCH 056/273] Add test structure Tests can be run using `make test` --- CMakeLists.txt | 17 +++++++- README.md | 10 +++++ tests/CMakeLists.txt | 20 ++++++++++ tests/main.c | 30 ++++++++++++++ tests/main_async.c | 94 ++++++++++++++++++++++++++++++++++++++++++++ tests/main_ipv6.c | 30 ++++++++++++++ tests/main_tls.c | 30 ++++++++++++++ 7 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/main.c create mode 100644 tests/main_async.c create mode 100644 tests/main_ipv6.c create mode 100644 tests/main_tls.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 60ba0bfa..a93e242b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.14) option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) +option(DISABLE_TESTS "Disable compilation of test" OFF) MACRO(getVersionBit name) SET(VERSION_REGEX "^#define ${name} (.+)$") @@ -56,7 +57,12 @@ if(DOWNLOAD_HIREDIS) GIT_TAG v1.0.0 SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/hiredis" ) + + # Disable tests in hiredis + set(DISABLE_TESTS_OLD ${DISABLE_TESTS}) + set(DISABLE_TESTS ON CACHE INTERNAL "") FetchContent_MakeAvailable(hiredis) + set(DISABLE_TESTS ${DISABLE_TESTS_OLD} CACHE INTERNAL "") # Create an empty *Config.cmake for find_package # See: https://github.com/abandonware-pjz37/cmake-find-package-include/blob/master/hooks/fetch.cmake @@ -73,12 +79,21 @@ endif() find_package(hiredis REQUIRED) -TARGET_INCLUDE_DIRECTORIES(hiredis_vip PRIVATE ${hiredis_INCLUDE_DIRS}) +target_include_directories(hiredis_vip PUBLIC + $ + $) IF(WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis_vip PRIVATE ws2_32 hiredis::hiredis) ENDIF() +if (NOT DISABLE_TESTS) + include(CTest) + + if(BUILD_TESTING) + add_subdirectory(tests) + endif() +endif() # Get CMAKE_INSTALL_ variables include(GNUInstallDirs) diff --git a/README.md b/README.md index 1b84fc63..2206cccd 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,16 @@ cmake .. make ``` +Options: + +``` +# Dont download hiredis from git +cmake -DDOWNLOAD_HIREDIS=OFF .. + +# Build without tests: +cmake -DDISABLE_TESTS=ON .. +``` + Build the examples ``` diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..7cf8a866 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,20 @@ +# Executable: IPv4 +add_executable(example_ipv4 main.c) +target_link_libraries(example_ipv4 hiredis_vip hiredis) +add_test(NAME example_ipv4 COMMAND "$") + +# Executable: IPv6 +add_executable(example_ipv6 main_ipv6.c) +target_link_libraries(example_ipv6 hiredis_vip hiredis) +add_test(NAME example_ipv6 COMMAND "$") + +# Executable: async +add_executable(example_async main_async.c) +find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) +target_link_libraries(example_async hiredis_vip hiredis ${EVENT_LIBRARY}) +add_test(NAME example_async COMMAND "$") + +# Executable: tls +add_executable(example_tls main_tls.c) +target_link_libraries(example_tls hiredis_vip hiredis) +add_test(NAME example_tls COMMAND "$") diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 00000000..5b983fd7 --- /dev/null +++ b/tests/main.c @@ -0,0 +1,30 @@ +#include +#include +#include "hircluster.h" + +int main(int argc, char **argv) +{ + struct timeval timeout = { 1, 500000 }; // 1.5s + + redisClusterContext *cc = redisClusterContextInit(); + redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); + redisClusterSetOptionConnectTimeout(cc, timeout); + redisClusterSetOptionRouteUseSlots(cc); + redisClusterConnect2(cc); + if (cc && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error + exit(-1); + } + + redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + printf("GET: %s\n", reply2->str); + freeReplyObject(reply2); + + redisClusterFree(cc); + return 0; +} diff --git a/tests/main_async.c b/tests/main_async.c new file mode 100644 index 00000000..d4b30326 --- /dev/null +++ b/tests/main_async.c @@ -0,0 +1,94 @@ +#include +#include +#include "hircluster.h" +#include "adapters/libevent.h" + +void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + redisReply *reply = (redisReply*)r; + if (reply == NULL) { + if (cc->errstr) { + printf("errstr: %s\n", cc->errstr); + } + return; + } + printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisClusterAsyncDisconnect(cc); +} + +void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + redisReply *reply = (redisReply*)r; + if (reply == NULL) { + if (cc->errstr) { + printf("errstr: %s\n", cc->errstr); + } + return; + } + printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); +} + +void connectCallback(const redisAsyncContext *ac, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", ac->errstr); + return; + } + printf("Connected to %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); +} + +void disconnectCallback(const redisAsyncContext *ac, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", ac->errstr); + return; + } + printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); +} + +int main(int argc, char **argv) +{ + printf("Connecting...\n"); + redisClusterAsyncContext *cc = redisClusterAsyncConnect("127.0.0.1:30001", + HIRCLUSTER_FLAG_NULL); + if (cc && cc->err) { + printf("Error: %s\n", cc->errstr); + return 1; + } + + struct event_base *base = event_base_new(); + redisClusterLibeventAttach(cc, base); + redisClusterAsyncSetConnectCallback(cc, connectCallback); + redisClusterAsyncSetDisconnectCallback(cc, disconnectCallback); + + int status; + status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key", "value"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key2", "value2"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key2"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); + } + + printf("Dispatch..\n"); + event_base_dispatch(base); + + printf("Done..\n"); + redisClusterAsyncFree(cc); + event_base_free(base); + return 0; +} diff --git a/tests/main_ipv6.c b/tests/main_ipv6.c new file mode 100644 index 00000000..4775d38d --- /dev/null +++ b/tests/main_ipv6.c @@ -0,0 +1,30 @@ +#include +#include +#include "hircluster.h" + +int main(int argc, char **argv) +{ + struct timeval timeout = { 1, 500000 }; // 1.5s + + redisClusterContext *cc = redisClusterContextInit(); + redisClusterSetOptionAddNodes(cc, "::1:30001"); + redisClusterSetOptionConnectTimeout(cc, timeout); + redisClusterSetOptionRouteUseSlots(cc); + redisClusterConnect2(cc); + if (cc && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error + exit(-1); + } + + redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + printf("GET: %s\n", reply2->str); + freeReplyObject(reply2); + + redisClusterFree(cc); + return 0; +} diff --git a/tests/main_tls.c b/tests/main_tls.c new file mode 100644 index 00000000..5b983fd7 --- /dev/null +++ b/tests/main_tls.c @@ -0,0 +1,30 @@ +#include +#include +#include "hircluster.h" + +int main(int argc, char **argv) +{ + struct timeval timeout = { 1, 500000 }; // 1.5s + + redisClusterContext *cc = redisClusterContextInit(); + redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); + redisClusterSetOptionConnectTimeout(cc, timeout); + redisClusterSetOptionRouteUseSlots(cc); + redisClusterConnect2(cc); + if (cc && cc->err) { + printf("Error: %s\n", cc->errstr); + // handle error + exit(-1); + } + + redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + printf("SET: %s\n", reply->str); + freeReplyObject(reply); + + redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + printf("GET: %s\n", reply2->str); + freeReplyObject(reply2); + + redisClusterFree(cc); + return 0; +} From cc7f8601e1c63b141ab8939ed623712e1ce38f41 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 30 Oct 2020 09:37:36 +0100 Subject: [PATCH 057/273] Cleanup of examples --- README.md | 4 +- examples/CMakeLists.txt | 56 ++++++++++++---------- examples/main_ipv6.cpp | 30 ------------ examples/main_tls.cpp | 76 ------------------------------ examples/src/CMakeLists.txt | 21 +++++++++ examples/{ => src}/main.cpp | 0 examples/{ => src}/main_async.cpp | 0 examples_cmakebuild/CMakeLists.txt | 37 --------------- 8 files changed, 54 insertions(+), 170 deletions(-) delete mode 100644 examples/main_ipv6.cpp delete mode 100644 examples/main_tls.cpp create mode 100644 examples/src/CMakeLists.txt rename examples/{ => src}/main.cpp (100%) rename examples/{ => src}/main_async.cpp (100%) delete mode 100644 examples_cmakebuild/CMakeLists.txt diff --git a/README.md b/README.md index 2206cccd..9aea7cb7 100644 --- a/README.md +++ b/README.md @@ -327,14 +327,12 @@ Build the examples ``` mkdir build_examples cd build_examples -cmake ../examples_cmakebuild +cmake ../examples make # Run ./examples/src/examples-build/examples ./examples/src/examples-build/examples_async -./examples/src/examples-build/examples_ipv6 -./examples/src/examples-build/examples_tls ``` ## AUTHORS diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 39f77595..8b0482f9 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,29 +1,37 @@ cmake_minimum_required(VERSION 3.14) -project(examples CXX) +project(hiredis_vip_externalproject C) include(ExternalProject) -# Handle libevent and hiredis -find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) -find_package(hiredis REQUIRED) -find_package(hiredis_vip REQUIRED) - -include_directories("${hiredis_INCLUDE_DIRS}") -include_directories("${hiredis_vip_INCLUDE_DIRS}") - -# Executable: IPv4 -add_executable(${PROJECT_NAME} main.cpp) -target_link_libraries(${PROJECT_NAME} ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES}) - -# Executable: IPv6 -add_executable(${PROJECT_NAME}_ipv6 main_ipv6.cpp) -target_link_libraries(${PROJECT_NAME}_ipv6 ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES}) - -# Executable: async -add_executable(${PROJECT_NAME}_async main_async.cpp) -target_link_libraries(${PROJECT_NAME}_async ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES} ${EVENT_LIBRARY}) - -# Executable: tls -add_executable(${PROJECT_NAME}_tls main_async.cpp) -target_link_libraries(${PROJECT_NAME}_tls ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES} ${EVENT_LIBRARY}) +ExternalProject_Add(hiredis + PREFIX hiredis + GIT_REPOSITORY https://github.com/redis/hiredis + GIT_TAG v1.0.0 + CMAKE_CACHE_ARGS + "-DENABLE_SSL:BOOL=ON" + "-DCMAKE_BUILD_TYPE:STRING=Debug" + "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis" +) + +ExternalProject_Add(hiredis_vip + PREFIX hiredis_vip + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." + CMAKE_CACHE_ARGS + "-DCMAKE_BUILD_TYPE:STRING=Debug" + "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip" + "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" + DEPENDS hiredis +) + +ExternalProject_Add(examples + PREFIX examples + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src" + INSTALL_COMMAND "" + CMAKE_CACHE_ARGS + "-DCMAKE_BUILD_TYPE:STRING=Debug" + "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/examples" + "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" + "-Dhiredis_vip_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip/share/hiredis_vip" + DEPENDS hiredis_vip +) diff --git a/examples/main_ipv6.cpp b/examples/main_ipv6.cpp deleted file mode 100644 index ed7e6add..00000000 --- a/examples/main_ipv6.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include "hiredis_vip/hircluster.h" - -int main(int argc, char **argv) -{ - struct timeval timeout = { 1, 500000 }; // 1.5s - - redisClusterContext *cc = redisClusterContextInit(); - redisClusterSetOptionAddNodes(cc, "::1:30001"); - redisClusterSetOptionConnectTimeout(cc, timeout); - redisClusterSetOptionRouteUseSlots(cc); - redisClusterConnect2(cc); - if (cc && cc->err) { - printf("Error: %s\n", cc->errstr); - // handle error - exit(-1); - } - - redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); - printf("SET: %s\n", reply->str); - freeReplyObject(reply); - - redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); - printf("GET: %s\n", reply2->str); - freeReplyObject(reply2); - - redisClusterFree(cc); - return 0; -} diff --git a/examples/main_tls.cpp b/examples/main_tls.cpp deleted file mode 100644 index 32d54b16..00000000 --- a/examples/main_tls.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include -#include -#include -#include -#include "hiredis_vip/hircluster.h" - -int main(int argc, char **argv) -{ - redisContext *c; - redisSSLContext *ssl; - redisSSLContextError ssl_error; - struct timeval timeout = { 1, 500000 }; // 1.5s - - // Get current work directory - char cwd[PATH_MAX]; - if (getcwd(cwd, sizeof(cwd)) == NULL) { - printf("getcwd() error"); - exit(1); - } - printf("Current path: %s\n", cwd); - - char cert[PATH_MAX]; - strcpy(cert, cwd); - strcat(cert, "/../../configs/tls/redis.crt"); - printf("Cert: %s\n", cert); - char key[PATH_MAX]; - strcpy(key, cwd); - strcat(key, "/../../configs/tls/redis.key"); - printf("Key: %s\n", key); - char ca[PATH_MAX]; - strcpy(ca, cwd); - strcat(ca, "/../../configs/tls/ca.crt"); - printf("Ca: %s\n", ca); - - redisInitOpenSSL(); - ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error); - if (!ssl) { - printf("SSL Context error: %s\n", - redisSSLContextGetError(ssl_error)); - exit(1); - } - - redisOptions options = {0}; - REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 33001); - options.connect_timeout = &timeout; - c = redisConnectWithOptions(&options); - 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); - } - - if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) { - printf("Couldn't initialize SSL!\n"); - printf("Error: %s\n", c->errstr); - redisFree(c); - exit(1); - } - - redisReply *reply = (redisReply*)redisCommand(c,"SET %s %s", "key", "value"); - printf("SET: %s\n", reply->str); - freeReplyObject(reply); - - redisReply *reply2 = (redisReply*)redisCommand(c, "GET %s", "key"); - printf("GET: %s\n", reply2->str); - freeReplyObject(reply2); - - redisFree(c); - redisFreeSSLContext(ssl); - return 0; -} diff --git a/examples/src/CMakeLists.txt b/examples/src/CMakeLists.txt new file mode 100644 index 00000000..86ad259d --- /dev/null +++ b/examples/src/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.14) + +project(examples CXX) + +include(ExternalProject) + +# Handle libevent and hiredis +find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) +find_package(hiredis REQUIRED) +find_package(hiredis_vip REQUIRED) + +include_directories("${hiredis_INCLUDE_DIRS}") +include_directories("${hiredis_vip_INCLUDE_DIRS}") + +# Executable: IPv4 +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES}) + +# Executable: async +add_executable(${PROJECT_NAME}_async main_async.cpp) +target_link_libraries(${PROJECT_NAME}_async ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES} ${EVENT_LIBRARY}) diff --git a/examples/main.cpp b/examples/src/main.cpp similarity index 100% rename from examples/main.cpp rename to examples/src/main.cpp diff --git a/examples/main_async.cpp b/examples/src/main_async.cpp similarity index 100% rename from examples/main_async.cpp rename to examples/src/main_async.cpp diff --git a/examples_cmakebuild/CMakeLists.txt b/examples_cmakebuild/CMakeLists.txt deleted file mode 100644 index 262a09eb..00000000 --- a/examples_cmakebuild/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 3.14) - -project(hiredis_vip_externalproject C) - -include(ExternalProject) - -ExternalProject_Add(hiredis - PREFIX hiredis - GIT_REPOSITORY https://github.com/redis/hiredis - GIT_TAG v1.0.0 - CMAKE_CACHE_ARGS - "-DENABLE_SSL:BOOL=ON" - "-DCMAKE_BUILD_TYPE:STRING=Debug" - "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis" -) - -ExternalProject_Add(hiredis_vip - PREFIX hiredis_vip - SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." - CMAKE_CACHE_ARGS - "-DCMAKE_BUILD_TYPE:STRING=Debug" - "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip" - "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" - DEPENDS hiredis -) - -ExternalProject_Add(examples - PREFIX examples - SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../examples" - INSTALL_COMMAND "" - CMAKE_CACHE_ARGS - "-DCMAKE_BUILD_TYPE:STRING=Debug" - "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/examples" - "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" - "-Dhiredis_vip_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip/share/hiredis_vip" - DEPENDS hiredis_vip -) From 164c496a4018576c529ebe89c0c86635d59b53f9 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 30 Oct 2020 09:48:02 +0100 Subject: [PATCH 058/273] Cleanup of adapters Return values, indentation and include order --- adapters/ae.h | 18 +++++++++--------- adapters/libevent.h | 7 ++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/adapters/ae.h b/adapters/ae.h index 674e66e5..9bc4f25e 100644 --- a/adapters/ae.h +++ b/adapters/ae.h @@ -30,25 +30,25 @@ #ifndef __HIREDIS_VIP_AE_H__ #define __HIREDIS_VIP_AE_H__ -#include #include "../hircluster.h" +#include static int redisAeAttach_link(redisAsyncContext *ac, void *base) { - redisAeAttach((aeEventLoop *)base, ac); + return redisAeAttach((aeEventLoop *)base, ac); } static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) { - if(acc == NULL || loop == NULL) - { - return REDIS_ERR; - } + if(acc == NULL || loop == NULL) + { + return REDIS_ERR; + } + + acc->adapter = loop; + acc->attach_fn = redisAeAttach_link; - acc->adapter = loop; - acc->attach_fn = redisAeAttach_link; - return REDIS_OK; } diff --git a/adapters/libevent.h b/adapters/libevent.h index 95a3b329..c6f581df 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -30,11 +30,12 @@ #ifndef __HIREDIS_VIP_LIBEVENT_H__ #define __HIREDIS_VIP_LIBEVENT_H__ -#include + #include "../hircluster.h" +#include static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) { - redisLibeventAttach(ac, (struct event_base *)base); + return redisLibeventAttach(ac, (struct event_base *)base); } static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { @@ -46,7 +47,7 @@ static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct even acc->adapter = base; acc->attach_fn = redisLibeventAttach_link; - + return REDIS_OK; } From 3cbcc2b57a90016d14ec6b3513fcfc1e81f98ff6 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 30 Oct 2020 10:57:54 +0100 Subject: [PATCH 059/273] Enable warnings --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 10 +++++++--- appveyor.yml | 24 ------------------------ hircluster.h | 2 ++ tests/main.c | 3 +++ tests/main_async.c | 3 +++ tests/main_ipv6.c | 3 +++ tests/main_tls.c | 3 +++ 8 files changed, 25 insertions(+), 27 deletions(-) delete mode 100644 appveyor.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index b791adac..4cd18d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ ### 0.4.0 - Jan 24, 2019 + * Updated underlying hiredis version to 0.14.0 * Added CMake files to enable Windows and Mac builds * Fixed bug due to CLUSTER NODES reply format change +https://github.com/heronr/hiredis-vip ### 0.3.0 - Dec 07, 2016 @@ -11,6 +13,8 @@ * Support redisClusterCommandArgv related api. (deep011) * Fix some serious bugs. (deep011) +https://github.com/vipshop/hiredis-vip + ### 0.2.1 - Nov 24, 2015 This release support redis cluster api. diff --git a/CMakeLists.txt b/CMakeLists.txt index a93e242b..3d722efa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,9 +37,13 @@ IF(WIN32 OR MINGW) hiredis_vip.def) ENDIF() -IF (MSVC) - ADD_COMPILE_OPTIONS("/wd 4267" "/wd 4244") -ENDIF() +if(MSVC) + # MS Visual: Suppress warnings + add_compile_options("/wd 4267" "/wd 4244") +else() + add_compile_options(-Wall -Wextra -pedantic) + # add_compile_options(-Wall -Wextra -pedantic -Werror) +endif() ADD_LIBRARY(hiredis_vip SHARED ${hiredis_vip_SOURCES}) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 5b43fdbe..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,24 +0,0 @@ -# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) -environment: - matrix: - - CYG_BASH: C:\cygwin64\bin\bash - CC: gcc - - CYG_BASH: C:\cygwin\bin\bash - CC: gcc - CFLAGS: -m32 - CXXFLAGS: -m32 - LDFLAGS: -m32 - -clone_depth: 1 - -# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail -init: - - git config --global core.autocrlf input - -# Install needed build dependencies -install: - - '%CYG_BASH% -lc "cygcheck -dc cygwin"' - -build_script: - - 'echo building...' - - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 #include +#define UNUSED(x) (void)(x) + #define HIREDIS_VIP_MAJOR 0 #define HIREDIS_VIP_MINOR 4 #define HIREDIS_VIP_PATCH 0 diff --git a/tests/main.c b/tests/main.c index 5b983fd7..3456e369 100644 --- a/tests/main.c +++ b/tests/main.c @@ -4,6 +4,9 @@ int main(int argc, char **argv) { + UNUSED(argc); + UNUSED(argv); + struct timeval timeout = { 1, 500000 }; // 1.5s redisClusterContext *cc = redisClusterContextInit(); diff --git a/tests/main_async.c b/tests/main_async.c index d4b30326..cf28d601 100644 --- a/tests/main_async.c +++ b/tests/main_async.c @@ -46,6 +46,9 @@ void disconnectCallback(const redisAsyncContext *ac, int status) { int main(int argc, char **argv) { + UNUSED(argc); + UNUSED(argv); + printf("Connecting...\n"); redisClusterAsyncContext *cc = redisClusterAsyncConnect("127.0.0.1:30001", HIRCLUSTER_FLAG_NULL); diff --git a/tests/main_ipv6.c b/tests/main_ipv6.c index 4775d38d..6abdac63 100644 --- a/tests/main_ipv6.c +++ b/tests/main_ipv6.c @@ -4,6 +4,9 @@ int main(int argc, char **argv) { + UNUSED(argc); + UNUSED(argv); + struct timeval timeout = { 1, 500000 }; // 1.5s redisClusterContext *cc = redisClusterContextInit(); diff --git a/tests/main_tls.c b/tests/main_tls.c index 5b983fd7..3456e369 100644 --- a/tests/main_tls.c +++ b/tests/main_tls.c @@ -4,6 +4,9 @@ int main(int argc, char **argv) { + UNUSED(argc); + UNUSED(argv); + struct timeval timeout = { 1, 500000 }; // 1.5s redisClusterContext *cc = redisClusterContextInit(); From c44df457984e4ec3d5caa3ec4f4655b8bb7e8da1 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 30 Oct 2020 11:29:55 +0100 Subject: [PATCH 060/273] Enable warnings and warnings gives errors Correcting some errors aswell - Unused functions - Missing break - explicit in if-statement --- CMakeLists.txt | 12 +- command.c | 3 +- dict.c | 56 ----- dict.h | 2 - hircluster.c | 547 ++----------------------------------------------- 5 files changed, 21 insertions(+), 599 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d722efa..c7cfb4fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,17 +37,17 @@ IF(WIN32 OR MINGW) hiredis_vip.def) ENDIF() +add_library(hiredis_vip + SHARED + ${hiredis_vip_SOURCES}) + if(MSVC) # MS Visual: Suppress warnings - add_compile_options("/wd 4267" "/wd 4244") + target_compile_options(hiredis_vip PRIVATE "/wd 4267" "/wd 4244") else() - add_compile_options(-Wall -Wextra -pedantic) - # add_compile_options(-Wall -Wextra -pedantic -Werror) + target_compile_options(hiredis_vip PRIVATE -Wall -Wextra -pedantic -Werror) endif() -ADD_LIBRARY(hiredis_vip SHARED - ${hiredis_vip_SOURCES}) - SET_TARGET_PROPERTIES(hiredis_vip PROPERTIES VERSION "${HIREDIS_VIP_SONAME}") diff --git a/command.c b/command.c index 24e2fa20..cd7b8b54 100644 --- a/command.c +++ b/command.c @@ -940,6 +940,8 @@ redis_parse_cmd(struct cmd *r) break; } + break; + case 11: if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { r->type = CMD_REQ_REDIS_INCRBYFLOAT; @@ -984,7 +986,6 @@ redis_parse_cmd(struct cmd *r) break; } - break; case 13: diff --git a/dict.c b/dict.c index 61995c1c..3a60b1eb 100644 --- a/dict.c +++ b/dict.c @@ -152,62 +152,6 @@ static int dictAdd(dict *ht, void *key, void *val) { 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 succeed. */ - 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; diff --git a/dict.h b/dict.h index 95fcd280..2f6949fd 100644 --- a/dict.h +++ b/dict.h @@ -115,8 +115,6 @@ 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); diff --git a/hircluster.c b/hircluster.c index bdbb593a..ff0c10f0 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1504,399 +1504,6 @@ cluster_update_route_by_addr(redisClusterContext *cc, 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->connect_timeout) - { - c = redisConnectWithTimeout(ip, port, *cc->connect_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) { @@ -1967,6 +1574,7 @@ cluster_update_route(redisClusterContext *cc) return REDIS_ERR; } +#ifdef DEBUG static void print_cluster_node_list(redisClusterContext *cc) { dictIterator *di = NULL; @@ -1984,11 +1592,11 @@ static void print_cluster_node_list(redisClusterContext *cc) 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, + printf("%s\t%s\t%d\t%s\n",master->name, master->addr, master->role, master->slaves?"hava":"null"); slaves = master->slaves; @@ -1996,12 +1604,12 @@ static void print_cluster_node_list(redisClusterContext *cc) { 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, + printf("%s\t%s\t%d\t%s\n",slave->name, slave->addr, slave->role, slave->slaves?"hava":"null"); } @@ -2010,16 +1618,18 @@ static void print_cluster_node_list(redisClusterContext *cc) printf("\n"); } } - +#endif /* DEBUG */ int test_cluster_update_route(redisClusterContext *cc) { int ret; - + ret = cluster_update_route(cc); - //print_cluster_node_list(cc); - +#ifdef DEBUG + print_cluster_node_list(cc); +#endif /* DEBUG */ + return ret; } @@ -2425,7 +2035,7 @@ int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval t di = dictGetIterator(cc->nodes); - while (de=dictNext(di)) + while ((de = dictNext(di)) != NULL) { node = dictGetEntryVal(de); if (node->con && node->con->flags&REDIS_CONNECTED && node->con->err == 0) @@ -2440,7 +2050,7 @@ int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval t listNode *ln; li = listGetIterator(node->slaves, AL_START_HEAD); - while (ln = listNext(li)) + while ((ln = listNext(li)) != NULL) { slave = listNodeValue(ln); if (slave->con && slave->con->flags&REDIS_CONNECTED && slave->con->err == 0) @@ -2529,75 +2139,6 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) 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) @@ -2665,68 +2206,6 @@ static cluster_node *node_get_witch_connected(redisClusterContext *cc) 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. */ From 0a59de1d615c36acd64b63b4b9c7b836792e87b0 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 30 Oct 2020 11:45:41 +0100 Subject: [PATCH 061/273] Remove makefile, only cmake supported and maintained --- Makefile | 125 ------------------------------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 38e885a6..00000000 --- a/Makefile +++ /dev/null @@ -1,125 +0,0 @@ -# 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=adlist.o command.o crc16.o hiarray.o hircluster.o hiutil.o -LIBNAME=libhiredis_vip -PKGCONFNAME=hiredis-vip.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}') -HIREDIS_VIP_SONAME=$(shell grep HIREDIS_VIP_SONAME 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) - -# Fallback to gcc when $CC is not in $PATH. -CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc') -CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++') -OPTIMIZATION?=-O3 -WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -DEBUG_FLAGS?= -g -ggdb -REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) -REAL_LDFLAGS=$(LDFLAGS) - -DYLIBSUFFIX=so -STLIBSUFFIX=a -DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_SONAME) -DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR) -DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) -SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX) -DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) -SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX) -STLIB_MAKE_CMD=$(AR) rcs - -# Platform-specific overrides -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') - -all: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) - -# Deps (use make dep to generate this) -adlist.o: adlist.c adlist.h hiutil.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 -hiarray.o: hiarray.c hiutil.h hiarray.h -hircluster.o: hircluster.c fmacros.h win32.h hircluster.h hiredis.h read.h sds.h async.h hiutil.h adlist.h hiarray.h command.h dict.c dict.h -hiutil.o: hiutil.c win32.h hiutil.h - -$(DYLIBNAME): $(OBJ) - $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS) - -$(STLIBNAME): $(OBJ) - $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) - -dynamic: $(DYLIBNAME) -static: $(STLIBNAME) - -.c.o: - $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< - -clean: - rm -rf $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) *.o *.gcda *.gcno *.gcov - -dep: - $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c - -INSTALL?= cp -pPR - -$(PKGCONFNAME): hircluster.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-vip >> $@ - @echo Description: Minimalistic C client library for Redis with cluster support. >> $@ - @echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@ - @echo Libs: -L\$${libdir} -lhiredis-vip >> $@ - @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ - -install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) - mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) - $(INSTALL) hiredis.h async.h read.h sds.h hircluster.h adlist.h dict.h hiarray.h $(INSTALL_INCLUDE_PATH) - $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters - $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) - cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_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 32bit-vars gprof gcov noopt From 29de946cf2412e0a988f20c2852ada706c5f5481 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 30 Oct 2020 14:50:01 +0100 Subject: [PATCH 062/273] Remove unused ip/port from cluster context --- hircluster.c | 19 ------------------- hircluster.h | 2 -- 2 files changed, 21 deletions(-) diff --git a/hircluster.c b/hircluster.c index ff0c10f0..f9c6512c 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1518,17 +1518,6 @@ cluster_update_route(redisClusterContext *cc) 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) @@ -1642,8 +1631,6 @@ redisClusterContext *redisClusterContextInit(void) { cc->err = 0; cc->errstr[0] = '\0'; - cc->ip = NULL; - cc->port = 0; cc->flags = 0; cc->connect_timeout = NULL; cc->timeout = NULL; @@ -1669,12 +1656,6 @@ void redisClusterFree(redisClusterContext *cc) { if (cc == NULL) return; - if(cc->ip) - { - sdsfree(cc->ip); - cc->ip = NULL; - } - if (cc->connect_timeout) { free(cc->connect_timeout); diff --git a/hircluster.h b/hircluster.h index a120f612..e91f2044 100644 --- a/hircluster.h +++ b/hircluster.h @@ -77,8 +77,6 @@ extern "C" { 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; From 73640584b74f1e8143fa264eced1ae69ea2bd245 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 30 Oct 2020 16:07:58 +0100 Subject: [PATCH 063/273] Remove unused redisClusterContext member connection_type --- hircluster.h | 1 - 1 file changed, 1 deletion(-) diff --git a/hircluster.h b/hircluster.h index e91f2044..6a6275d5 100644 --- a/hircluster.h +++ b/hircluster.h @@ -80,7 +80,6 @@ typedef struct redisClusterContext { int flags; - enum redisConnectionType connection_type; struct timeval *connect_timeout; struct timeval *timeout; /* receive and send timeout. */ From 0d951c4bcf562b4a67fd0160ac2724b6c258c5d7 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Sun, 1 Nov 2020 12:30:42 +0100 Subject: [PATCH 064/273] Generate SSL/TLS certs and keys for tests via cmake --- CMakeLists.txt | 16 ++++++++++++---- tests/CMakeLists.txt | 14 +++++++++++++- tests/main_tls.c | 12 ++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7cfb4fa..98c3fa32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,23 +65,31 @@ if(DOWNLOAD_HIREDIS) # Disable tests in hiredis set(DISABLE_TESTS_OLD ${DISABLE_TESTS}) set(DISABLE_TESTS ON CACHE INTERNAL "") + set(ENABLE_SSL ON CACHE INTERNAL "") FetchContent_MakeAvailable(hiredis) set(DISABLE_TESTS ${DISABLE_TESTS_OLD} CACHE INTERNAL "") - # Create an empty *Config.cmake for find_package + # Create an empty *-config.cmake for find_package # See: https://github.com/abandonware-pjz37/cmake-find-package-include/blob/master/hooks/fetch.cmake set(stub_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/pkg") - file(WRITE "${stub_dir}/hiredisConfig.cmake" "") + file(WRITE "${stub_dir}/hiredis-config.cmake" "") + file(WRITE "${stub_dir}/hiredis_ssl-config.cmake" "") set(hiredis_DIR ${stub_dir}) + set(hiredis_ssl_DIR ${stub_dir}) - # Set variables normally got from hiredisConfig.cmake + # Set variables normally got from hiredis-config.cmake SET(hiredis_LIBRARIES hiredis::hiredis) SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") + + # Set variables normally got from hiredis_ssl-config.cmake + SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl) + SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") else() - message("Expecting to find dependency 'hiredis' in path..") + message("Expecting to find dependency 'hiredis' and 'hiredis_ssl' in path..") endif() find_package(hiredis REQUIRED) +find_package(hiredis_ssl REQUIRED) target_include_directories(hiredis_vip PUBLIC $ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7cf8a866..f3b23084 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,14 @@ +# Generate SSL certs and keys when needed +set(SSL_CONFIGS ca.crt ca.key ca.txt redis.crt redis.key) +add_custom_command( + OUTPUT ${SSL_CONFIGS} + COMMAND openssl genrsa -out ca.key 4096 + COMMAND openssl req -x509 -new -nodes -sha256 -key ca.key -days 3650 -subj '/CN=Redis Test CA' -out ca.crt + COMMAND openssl genrsa -out redis.key 2048 + COMMAND openssl req -new -sha256 -key redis.key -subj '/CN=Redis Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out redis.crt +) +add_custom_target(generate_tls_configs DEPENDS ${SSL_CONFIGS}) + # Executable: IPv4 add_executable(example_ipv4 main.c) target_link_libraries(example_ipv4 hiredis_vip hiredis) @@ -16,5 +27,6 @@ add_test(NAME example_async COMMAND "$") # Executable: tls add_executable(example_tls main_tls.c) -target_link_libraries(example_tls hiredis_vip hiredis) +target_link_libraries(example_tls hiredis_vip hiredis hiredis_ssl) +add_dependencies(example_tls generate_tls_configs) add_test(NAME example_tls COMMAND "$") diff --git a/tests/main_tls.c b/tests/main_tls.c index 3456e369..10b0c2e4 100644 --- a/tests/main_tls.c +++ b/tests/main_tls.c @@ -1,12 +1,24 @@ #include #include #include "hircluster.h" +#include "hiredis_ssl.h" // REMOVE ?? int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); + redisSSLContext *ssl; + redisSSLContextError ssl_error; + + redisInitOpenSSL(); + ssl = redisCreateSSLContext("ca.crt", NULL, "redis.crt", "redis.key", NULL, &ssl_error); + if (!ssl) { + printf("SSL Context error: %s\n", + redisSSLContextGetError(ssl_error)); + exit(1); + } + struct timeval timeout = { 1, 500000 }; // 1.5s redisClusterContext *cc = redisClusterContextInit(); From 1d35ff89edfe96a1b756a462f4a5a31b7d8a48a0 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Sun, 1 Nov 2020 22:43:27 +0100 Subject: [PATCH 065/273] Add TLS/SSL support This is a variant of enabling TLS similar to how its done in hiredis. The user of this library initiates OpenSSL, setups the global SSL context and let hiredis-vip use it when connecting to Redis instances. This will probably not handle handle async connections, to be tested. --- CMakeLists.txt | 25 ++++++++++++++++--------- hircluster.c | 35 +++++++++++++++++++++++++++++++++++ hircluster.h | 11 +++++++++++ tests/CMakeLists.txt | 17 ++++++++++------- tests/main_tls.c | 20 ++++++++++++++++---- 5 files changed, 88 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98c3fa32..151062f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.14) option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) +option(ENABLE_SSL "Enable SSL/TLS support" ON) option(DISABLE_TESTS "Disable compilation of test" OFF) MACRO(getVersionBit name) @@ -65,31 +66,37 @@ if(DOWNLOAD_HIREDIS) # Disable tests in hiredis set(DISABLE_TESTS_OLD ${DISABLE_TESTS}) set(DISABLE_TESTS ON CACHE INTERNAL "") - set(ENABLE_SSL ON CACHE INTERNAL "") FetchContent_MakeAvailable(hiredis) set(DISABLE_TESTS ${DISABLE_TESTS_OLD} CACHE INTERNAL "") # Create an empty *-config.cmake for find_package # See: https://github.com/abandonware-pjz37/cmake-find-package-include/blob/master/hooks/fetch.cmake set(stub_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/pkg") + file(WRITE "${stub_dir}/hiredis-config.cmake" "") - file(WRITE "${stub_dir}/hiredis_ssl-config.cmake" "") set(hiredis_DIR ${stub_dir}) - set(hiredis_ssl_DIR ${stub_dir}) - # Set variables normally got from hiredis-config.cmake SET(hiredis_LIBRARIES hiredis::hiredis) SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") - # Set variables normally got from hiredis_ssl-config.cmake - SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl) - SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") + if(ENABLE_SSL) + file(WRITE "${stub_dir}/hiredis_ssl-config.cmake" "") + set(hiredis_ssl_DIR ${stub_dir}) + # Set variables normally got from hiredis_ssl-config.cmake + SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl) + SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") + endif() + else() - message("Expecting to find dependency 'hiredis' and 'hiredis_ssl' in path..") + message("Expecting to find dependencies in path..") endif() find_package(hiredis REQUIRED) -find_package(hiredis_ssl REQUIRED) + +if(ENABLE_SSL) + find_package(hiredis_ssl REQUIRED) + add_definitions(-DSSL_SUPPORT) +endif() target_include_directories(hiredis_vip PUBLIC $ diff --git a/hircluster.c b/hircluster.c index f9c6512c..f8a59692 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1315,6 +1315,12 @@ cluster_update_route_by_addr(redisClusterContext *cc, redisSetTimeout(c, *cc->timeout); } +#ifdef SSL_SUPPORT + if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { + __redisClusterSetError(cc, c->err, c->errstr); + goto error; + } +#endif if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){ reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); if(reply == NULL){ @@ -1648,6 +1654,9 @@ redisClusterContext *redisClusterContextInit(void) { cc->flags |= REDIS_BLOCK; +#ifdef SSL_SUPPORT + cc->ssl = NULL; +#endif return cc; } @@ -2063,6 +2072,18 @@ int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_c return REDIS_OK; } +#ifdef SSL_SUPPORT +int redisClusterSetOptionEnableSSL(redisClusterContext *cc, redisSSLContext *ssl) { + if(cc == NULL || ssl == NULL) { + return REDIS_ERR; + } + + cc->ssl = ssl; + + return REDIS_OK; +} +#endif + int redisClusterConnect2(redisClusterContext *cc) { @@ -2089,6 +2110,12 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) { redisReconnect(c); +#ifdef SSL_SUPPORT + if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { + __redisClusterSetError(cc, c->err, c->errstr); + } +#endif + if (cc->timeout && c->err == 0) { redisSetTimeout(c, *cc->timeout); } @@ -2115,6 +2142,14 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) redisSetTimeout(c, *cc->timeout); } +#ifdef SSL_SUPPORT + if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { + __redisClusterSetError(cc, c->err, c->errstr); + redisFree(c); + return NULL; + } +#endif + node->con = c; return c; diff --git a/hircluster.h b/hircluster.h index 6a6275d5..532e5721 100644 --- a/hircluster.h +++ b/hircluster.h @@ -6,6 +6,9 @@ #include #define UNUSED(x) (void)(x) +#ifdef SSL_SUPPORT +#include +#endif #define HIREDIS_VIP_MAJOR 0 #define HIREDIS_VIP_MINOR 4 @@ -98,6 +101,11 @@ typedef struct redisClusterContext { int need_update_route; int64_t update_route_time; + +#ifdef SSL_SUPPORT + redisSSLContext *ssl; +#endif + } redisClusterContext; redisClusterContext *redisClusterConnect(const char *addrs, int flags); @@ -118,6 +126,9 @@ int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); +#ifdef SSL_SUPPORT +int redisClusterSetOptionEnableSSL(redisClusterContext *cc, redisSSLContext *ssl); +#endif int redisClusterConnect2(redisClusterContext *cc); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f3b23084..bf2a8972 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,32 +1,35 @@ # Generate SSL certs and keys when needed -set(SSL_CONFIGS ca.crt ca.key ca.txt redis.crt redis.key) +set(SSL_CONFIGS ca.crt ca.key ca.txt redis.crt redis.key client.crt client.key) add_custom_command( OUTPUT ${SSL_CONFIGS} COMMAND openssl genrsa -out ca.key 4096 COMMAND openssl req -x509 -new -nodes -sha256 -key ca.key -days 3650 -subj '/CN=Redis Test CA' -out ca.crt COMMAND openssl genrsa -out redis.key 2048 - COMMAND openssl req -new -sha256 -key redis.key -subj '/CN=Redis Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out redis.crt + COMMAND openssl req -new -sha256 -key redis.key -subj '/CN=Redis Server Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out redis.crt + COMMAND openssl genrsa -out client.key 2048 + COMMAND openssl req -new -sha256 -key client.key -subj '/CN=Redis Client Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out client.crt ) add_custom_target(generate_tls_configs DEPENDS ${SSL_CONFIGS}) +find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) + # Executable: IPv4 add_executable(example_ipv4 main.c) -target_link_libraries(example_ipv4 hiredis_vip hiredis) +target_link_libraries(example_ipv4 hiredis_vip hiredis hiredis_ssl) add_test(NAME example_ipv4 COMMAND "$") # Executable: IPv6 add_executable(example_ipv6 main_ipv6.c) -target_link_libraries(example_ipv6 hiredis_vip hiredis) +target_link_libraries(example_ipv6 hiredis_vip hiredis hiredis_ssl) add_test(NAME example_ipv6 COMMAND "$") # Executable: async add_executable(example_async main_async.c) -find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) -target_link_libraries(example_async hiredis_vip hiredis ${EVENT_LIBRARY}) +target_link_libraries(example_async hiredis_vip hiredis hiredis_ssl ${EVENT_LIBRARY}) add_test(NAME example_async COMMAND "$") # Executable: tls add_executable(example_tls main_tls.c) -target_link_libraries(example_tls hiredis_vip hiredis hiredis_ssl) +target_link_libraries(example_tls hiredis_vip hiredis hiredis_ssl hiredis_ssl) add_dependencies(example_tls generate_tls_configs) add_test(NAME example_tls COMMAND "$") diff --git a/tests/main_tls.c b/tests/main_tls.c index 10b0c2e4..48c7a37a 100644 --- a/tests/main_tls.c +++ b/tests/main_tls.c @@ -12,19 +12,20 @@ int main(int argc, char **argv) redisSSLContextError ssl_error; redisInitOpenSSL(); - ssl = redisCreateSSLContext("ca.crt", NULL, "redis.crt", "redis.key", NULL, &ssl_error); + ssl = redisCreateSSLContext("ca.crt", NULL, "client.crt", "client.key", NULL, &ssl_error); if (!ssl) { - printf("SSL Context error: %s\n", - redisSSLContextGetError(ssl_error)); + printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error)); exit(1); } struct timeval timeout = { 1, 500000 }; // 1.5s redisClusterContext *cc = redisClusterContextInit(); - redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); + redisClusterSetOptionAddNodes(cc, "127.0.0.1:31001"); redisClusterSetOptionConnectTimeout(cc, timeout); redisClusterSetOptionRouteUseSlots(cc); + redisClusterSetOptionParseSlaves(cc); + redisClusterSetOptionEnableSSL(cc, ssl); redisClusterConnect2(cc); if (cc && cc->err) { printf("Error: %s\n", cc->errstr); @@ -33,13 +34,24 @@ int main(int argc, char **argv) } redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + if (!reply) + { + printf("Reply missing: %s\n", cc->errstr); + exit(-1); + } printf("SET: %s\n", reply->str); freeReplyObject(reply); redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + if (!reply2) + { + printf("Reply missing: %s\n", cc->errstr); + exit(-1); + } printf("GET: %s\n", reply2->str); freeReplyObject(reply2); redisClusterFree(cc); + redisFreeSSLContext(ssl); return 0; } From 06c37d89f2c4e93a9e825a9a36c21127c24b0267 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 2 Nov 2020 09:12:42 +0100 Subject: [PATCH 066/273] Only perform SSL connect when configured --- hircluster.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/hircluster.c b/hircluster.c index f8a59692..9fce3dfb 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1316,9 +1316,11 @@ cluster_update_route_by_addr(redisClusterContext *cc, } #ifdef SSL_SUPPORT - if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { - __redisClusterSetError(cc, c->err, c->errstr); - goto error; + if (cc->ssl) { + if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { + __redisClusterSetError(cc, c->err, c->errstr); + goto error; + } } #endif if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){ @@ -2111,8 +2113,10 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) redisReconnect(c); #ifdef SSL_SUPPORT - if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { - __redisClusterSetError(cc, c->err, c->errstr); + if (cc->ssl) { + if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { + __redisClusterSetError(cc, c->err, c->errstr); + } } #endif @@ -2143,10 +2147,12 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) } #ifdef SSL_SUPPORT - if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { - __redisClusterSetError(cc, c->err, c->errstr); - redisFree(c); - return NULL; + if (cc->ssl) { + if (redisInitiateSSLWithContext(c, cc->ssl) != REDIS_OK) { + __redisClusterSetError(cc, c->err, c->errstr); + redisFree(c); + return NULL; + } } #endif From ba3a98754cdce075e2ade0c52eb18f9836886dfa Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 2 Nov 2020 10:43:54 +0100 Subject: [PATCH 067/273] Simplify code by removing __redisClusterAsyncCopyError Only used once and not public anyway, functionality moved to user function. --- hircluster.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/hircluster.c b/hircluster.c index 9fce3dfb..2e44e9bf 100644 --- a/hircluster.c +++ b/hircluster.c @@ -3734,17 +3734,6 @@ void redisClusterReset(redisClusterContext *cc) /*############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) { @@ -3777,7 +3766,12 @@ static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext acc->cc = cc; - acc->err = 0; + /* We want the error field to be accessible directly instead of requiring + * an indirection to the redisContext struct. */ + // TODO: really needed? + acc->err = cc->err; + memcpy(acc->errstr, cc->errstr, 128); + acc->data = NULL; acc->adapter = NULL; acc->attach_fn = NULL; @@ -3955,9 +3949,7 @@ redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) redisClusterFree(cc); return NULL; } - - __redisClusterAsyncCopyError(acc); - + return acc; } From 06c5818f4969fae3af6acc6e127b3a539654759f Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Tue, 3 Nov 2020 10:37:29 +0100 Subject: [PATCH 068/273] Support SSL/TLS in async interface aswell --- hircluster.c | 29 ++++++++++++ hircluster.h | 1 + tests/CMakeLists.txt | 6 +++ tests/main_async_tls.c | 104 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 tests/main_async_tls.c diff --git a/hircluster.c b/hircluster.c index 2e44e9bf..341838b6 100644 --- a/hircluster.c +++ b/hircluster.c @@ -3860,6 +3860,15 @@ redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, return NULL; } +#ifdef SSL_SUPPORT + if(acc->cc->ssl) { + if(redisInitiateSSLWithContext(&ac->c, acc->cc->ssl) != REDIS_OK) { + __redisClusterAsyncSetError(acc, ac->c.err, ac->c.errstr); + return NULL; + } + } +#endif + if(acc->adapter) { acc->attach_fn(ac, acc->adapter); @@ -3933,6 +3942,26 @@ static redisAsyncContext *actx_get_after_update_route_by_slot( return ac; } +redisClusterAsyncContext *redisClusterAsyncContextInit() { + redisClusterContext *cc; + redisClusterAsyncContext *acc; + + cc = redisClusterContextInit(); + if(cc == NULL) { + return NULL; + } + + cc->flags &= ~REDIS_BLOCK; + + acc = redisClusterAsyncInitialize(cc); + if(acc == NULL) { + redisClusterFree(cc); + return NULL; + } + + return acc; +} + redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) { redisClusterContext *cc; diff --git a/hircluster.h b/hircluster.h index 532e5721..f971cc0b 100644 --- a/hircluster.h +++ b/hircluster.h @@ -186,6 +186,7 @@ typedef struct redisClusterAsyncContext { } redisClusterAsyncContext; +redisClusterAsyncContext *redisClusterAsyncContextInit(void); redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bf2a8972..0424a7c7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,3 +33,9 @@ add_executable(example_tls main_tls.c) target_link_libraries(example_tls hiredis_vip hiredis hiredis_ssl hiredis_ssl) add_dependencies(example_tls generate_tls_configs) add_test(NAME example_tls COMMAND "$") + +# Executable: async tls +add_executable(example_async_tls main_async_tls.c) +target_link_libraries(example_async_tls hiredis_vip hiredis hiredis_ssl ${EVENT_LIBRARY}) +add_dependencies(example_async_tls generate_tls_configs) +add_test(NAME example_async_tls COMMAND "$") diff --git a/tests/main_async_tls.c b/tests/main_async_tls.c new file mode 100644 index 00000000..118f677d --- /dev/null +++ b/tests/main_async_tls.c @@ -0,0 +1,104 @@ +#include +#include +#include + +#include "hircluster.h" +#include "adapters/libevent.h" +#include "hiredis_ssl.h" + +void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + redisReply *reply = (redisReply*)r; + if (reply == NULL) { + if (cc->errstr) { + printf("errstr: %s\n", cc->errstr); + } + return; + } + printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + + /* Disconnect after receiving the reply to GET */ + redisClusterAsyncDisconnect(cc); +} + +void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + redisReply *reply = (redisReply*)r; + if (reply == NULL) { + if (cc->errstr) { + printf("errstr: %s\n", cc->errstr); + } + return; + } + printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); +} + +void connectCallback(const redisAsyncContext *ac, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", ac->errstr); + return; + } + + printf("Connected to %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); +} + +void disconnectCallback(const redisAsyncContext *ac, int status) { + if (status != REDIS_OK) { + printf("Error: %s\n", ac->errstr); + return; + } + printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); +} + +int main(int argc, char **argv) +{ + UNUSED(argc); + UNUSED(argv); + + redisSSLContext *ssl; + redisSSLContextError ssl_error; + + redisInitOpenSSL(); + ssl = redisCreateSSLContext("ca.crt", NULL, "client.crt", "client.key", NULL, &ssl_error); + if (!ssl) { + printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error)); + exit(1); + } + + redisClusterAsyncContext *acc = redisClusterAsyncContextInit(); + assert(acc); + redisClusterAsyncSetConnectCallback(acc, connectCallback); + redisClusterAsyncSetDisconnectCallback(acc, disconnectCallback); + redisClusterSetOptionAddNodes(acc->cc, "127.0.0.1:31001"); + redisClusterSetOptionRouteUseSlots(acc->cc); + redisClusterSetOptionParseSlaves(acc->cc); + redisClusterSetOptionEnableSSL(acc->cc, ssl); + redisClusterConnect2(acc->cc); + if (acc->err) { + printf("Error: %s\n", acc->errstr); + exit(-1); + } + + struct event_base *base = event_base_new(); + redisClusterLibeventAttach(acc, base); + + int status; + status = redisClusterAsyncCommand(acc, setCallback, (char*)"THE_ID", "SET %s %s", "key", "value"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", acc->err, acc->errstr); + } + + status = redisClusterAsyncCommand(acc, getCallback, (char*)"THE_ID", "GET %s", "key"); + if(status != REDIS_OK) + { + printf("error: err=%d errstr=%s\n", acc->err, acc->errstr); + } + + printf("Dispatch..\n"); + event_base_dispatch(base); + + printf("Done..\n"); + redisClusterAsyncFree(acc); + redisFreeSSLContext(ssl); + event_base_free(base); + return 0; +} From 6c99c75bfa8d72d5d3ebf85edddeccd02a9ecd01 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Tue, 3 Nov 2020 10:37:29 +0100 Subject: [PATCH 069/273] Add IPv6 support Also allow to use Redis versions that dont give cport in 'cluster nodes' --- hircluster.c | 159 ++++++++++++++++++++-------------------------- tests/main_ipv6.c | 12 ++-- 2 files changed, 76 insertions(+), 95 deletions(-) diff --git a/hircluster.c b/hircluster.c index 341838b6..c6875c46 100644 --- a/hircluster.c +++ b/hircluster.c @@ -33,9 +33,9 @@ #define REDIS_PROTOCOL_ASKING "*1\r\n$6\r\nASKING\r\n" -#define IP_PORT_SEPARATOR ":" +#define IP_PORT_SEPARATOR ':' -#define PORT_CPORT_SEPARATOR "@" +#define PORT_CPORT_SEPARATOR '@' #define CLUSTER_ADDRESS_SEPARATOR "," @@ -537,86 +537,71 @@ static cluster_node *node_get_with_slots( /** * 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) +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; - char *port = NULL, *port_context = NULL; + char *p = NULL; + char *port = NULL; cluster_node *node; - if(info_count < 8) - { + 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; + if(node == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); + return NULL; } - cluster_node_init(node); - if(role == REDIS_ROLE_MASTER) - { + 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"); + if(node->slots == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "slots for node listCreate error"); goto error; } node->slots->free = listClusterSlotDestructor; } - - node->name = node_infos[0]; + + 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->role = role; - port = strtok_r(ip_port[1], PORT_CPORT_SEPARATOR, &port_context); - if (port == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "error parsing port"); + // Get host part + if ((p = strrchr(node_infos[1], IP_PORT_SEPARATOR)) == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, port separator missing."); goto error; } - node->port = hi_atoi(port, strlen(port)); - node->role = role; - sdsfree(ip_port[1]); - free(ip_port); + node->host = sdsnewlen(node_infos[1], p - node_infos[1]); + p++; // remove found separator character + // Strip away cport if given by redis + size_t port_len; + if ((port = strchr(p, PORT_CPORT_SEPARATOR)) == NULL) { + port_len = strlen(p); + } else { + port_len = port-p; + } + node->port = hi_atoi(p, port_len); + + // Ownership moved to node 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); + if (node->slots) { + listRelease(node->slots); } - + if (node->host) { + sdsfree(node->host); + } + hi_free(node); return NULL; } @@ -1804,22 +1789,17 @@ int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) { dictEntry *node_entry; cluster_node *node; - sds *ip_port = NULL; - int ip_port_count = 0; sds ip; int port; sds addr_sds = NULL; - - if(cc == NULL) - { + + if(cc == NULL) { return REDIS_ERR; } - if(cc->nodes == NULL) - { + if(cc->nodes == NULL) { cc->nodes = dictCreate(&clusterNodesDictType, NULL); - if(cc->nodes == NULL) - { + if(cc->nodes == NULL) { return REDIS_ERR; } } @@ -1827,51 +1807,48 @@ int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) addr_sds = sdsnew(addr); node_entry = dictFind(cc->nodes, addr_sds); sdsfree(addr_sds); - 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)"); + if(node_entry == NULL) { + + char *p; + if ((p = strrchr(addr, IP_PORT_SEPARATOR)) == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, port separator missing."); + return REDIS_ERR; + } + // p includes separator + + if (p-addr <= 0) { /* length until separator */ + __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, address part missing."); return REDIS_ERR; } + ip = sdsnewlen(addr, p-addr); - ip = ip_port[0]; - port = hi_atoi(ip_port[1], sdslen(ip_port[1])); + p++; // remove separator character - if(port <= 0) - { - sdsfreesplitres(ip_port, ip_port_count); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"server port is error"); + if (strlen(p) <= 0) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, port part missing."); + return REDIS_ERR; + } + + port = hi_atoi(p, strlen(p)); + if(port <= 0) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "server port is incorrect"); return REDIS_ERR; } - sdsfree(ip_port[1]); - free(ip_port); - ip_port = NULL; - node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { + if(node == NULL) { sdsfree(ip); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"alloc cluster node error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "alloc cluster node error"); return REDIS_ERR; } cluster_node_init(node); node->addr = sdsnew(addr); - if(node->addr == NULL) - { + if(node->addr == NULL) { sdsfree(ip); hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"new node address error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "new node address error"); return REDIS_ERR; } @@ -1880,7 +1857,7 @@ int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); } - + return REDIS_OK; } diff --git a/tests/main_ipv6.c b/tests/main_ipv6.c index 6abdac63..8ca46b99 100644 --- a/tests/main_ipv6.c +++ b/tests/main_ipv6.c @@ -1,5 +1,6 @@ #include #include +#include #include "hircluster.h" int main(int argc, char **argv) @@ -10,13 +11,16 @@ int main(int argc, char **argv) struct timeval timeout = { 1, 500000 }; // 1.5s redisClusterContext *cc = redisClusterContextInit(); - redisClusterSetOptionAddNodes(cc, "::1:30001"); + assert(cc); redisClusterSetOptionConnectTimeout(cc, timeout); redisClusterSetOptionRouteUseSlots(cc); - redisClusterConnect2(cc); - if (cc && cc->err) { + + if (redisClusterSetOptionAddNodes(cc, "::1:30001") != REDIS_OK) { + printf("Error: %s\n", cc->errstr); + exit(-1); + } + if (redisClusterConnect2(cc) != REDIS_OK) { printf("Error: %s\n", cc->errstr); - // handle error exit(-1); } From e80a68d7ead2069a2614b1b147a13bc76bd82559 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 9 Nov 2020 00:01:31 +0100 Subject: [PATCH 070/273] Change the name of the project in the Nordix fork Updating makefiles, includepaths and file names --- .gitignore | 8 -- CMakeLists.txt | 113 ++++++++++----------- adapters/ae.h | 4 +- adapters/libevent.h | 4 +- examples/CMakeLists.txt | 18 ++-- examples/src/CMakeLists.txt | 22 ++-- examples/src/main.cpp | 2 +- examples/src/main_async.cpp | 4 +- hircluster.h | 8 +- hiredis_cluster-config.cmake.in | 13 +++ hiredis_vip.def => hiredis_cluster.def | 0 hiredis_vip.pc.in => hiredis_cluster.pc.in | 6 +- hiredis_vip-config.cmake.in | 13 --- tests/CMakeLists.txt | 59 ++++++----- tests/main_async_tls.c | 1 - tests/main_tls.c | 1 - 16 files changed, 137 insertions(+), 139 deletions(-) create mode 100644 hiredis_cluster-config.cmake.in rename hiredis_vip.def => hiredis_cluster.def (100%) rename hiredis_vip.pc.in => hiredis_cluster.pc.in (71%) delete mode 100644 hiredis_vip-config.cmake.in diff --git a/.gitignore b/.gitignore index 8e50b543..e69de29b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +0,0 @@ -/hiredis-test -/examples/hiredis-example* -/*.o -/*.so -/*.dylib -/*.a -/*.pc -*.dSYM diff --git a/CMakeLists.txt b/CMakeLists.txt index 151062f1..8b666802 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,29 +1,31 @@ cmake_minimum_required(VERSION 3.14) +include(GNUInstallDirs) +project(hiredis-cluster) option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) option(ENABLE_SSL "Enable SSL/TLS support" ON) option(DISABLE_TESTS "Disable compilation of test" OFF) -MACRO(getVersionBit name) - SET(VERSION_REGEX "^#define ${name} (.+)$") - FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hircluster.h" +macro(getVersionBit name) + set(VERSION_REGEX "^#define ${name} (.+)$") + file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hircluster.h" VERSION_BIT REGEX ${VERSION_REGEX}) - STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") -ENDMACRO(getVersionBit) - -# Get version information from src-code -getVersionBit(HIREDIS_VIP_MAJOR) -getVersionBit(HIREDIS_VIP_MINOR) -getVersionBit(HIREDIS_VIP_PATCH) -getVersionBit(HIREDIS_VIP_SONAME) -SET(VERSION "${HIREDIS_VIP_MAJOR}.${HIREDIS_VIP_MINOR}.${HIREDIS_VIP_PATCH}") -MESSAGE("Detected version: ${VERSION}") - -project(hiredis_vip + string(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}") +endmacro(getVersionBit) + +# Get version information from src +getVersionBit(HIREDIS_CLUSTER_MAJOR) +getVersionBit(HIREDIS_CLUSTER_MINOR) +getVersionBit(HIREDIS_CLUSTER_PATCH) +getVersionBit(HIREDIS_CLUSTER_SONAME) +set(VERSION "${HIREDIS_CLUSTER_MAJOR}.${HIREDIS_CLUSTER_MINOR}.${HIREDIS_CLUSTER_PATCH}") +message("Detected version: ${VERSION}") + +project(hiredis-cluster VERSION "${VERSION}" LANGUAGES C) -SET(hiredis_vip_SOURCES +SET(hiredis_cluster_sources adlist.c command.c crc16.c @@ -31,27 +33,27 @@ SET(hiredis_vip_SOURCES hircluster.c hiutil.c) -IF(WIN32 OR MINGW) - ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) - SET(hiredis_vip_SOURCES - ${hiredis_vip_SOURCES} - hiredis_vip.def) -ENDIF() +if(WIN32 OR MINGW) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN) + set(hiredis_cluster_sources + ${hiredis_cluster_sources} + hiredis_cluster.def) +endif() -add_library(hiredis_vip +add_library(hiredis_cluster SHARED - ${hiredis_vip_SOURCES}) + ${hiredis_cluster_sources}) if(MSVC) # MS Visual: Suppress warnings - target_compile_options(hiredis_vip PRIVATE "/wd 4267" "/wd 4244") + target_compile_options(hiredis_cluster PRIVATE "/wd 4267" "/wd 4244") else() - target_compile_options(hiredis_vip PRIVATE -Wall -Wextra -pedantic -Werror) + target_compile_options(hiredis_cluster PRIVATE -Wall -Wextra -pedantic -Werror) endif() -SET_TARGET_PROPERTIES(hiredis_vip +set_target_properties(hiredis_cluster PROPERTIES - VERSION "${HIREDIS_VIP_SONAME}") + VERSION "${HIREDIS_CLUSTER_SONAME}") if(DOWNLOAD_HIREDIS) message("Downloading dependency 'hiredis'..") @@ -76,15 +78,15 @@ if(DOWNLOAD_HIREDIS) file(WRITE "${stub_dir}/hiredis-config.cmake" "") set(hiredis_DIR ${stub_dir}) # Set variables normally got from hiredis-config.cmake - SET(hiredis_LIBRARIES hiredis::hiredis) - SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") + set(hiredis_LIBRARIES hiredis::hiredis) + set(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") if(ENABLE_SSL) file(WRITE "${stub_dir}/hiredis_ssl-config.cmake" "") set(hiredis_ssl_DIR ${stub_dir}) # Set variables normally got from hiredis_ssl-config.cmake - SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl) - SET(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") + set(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl) + set(hiredis_INCLUDE_DIRS "${CMAKE_CURRENT_BINARY_DIR}/_deps") endif() else() @@ -98,13 +100,13 @@ if(ENABLE_SSL) add_definitions(-DSSL_SUPPORT) endif() -target_include_directories(hiredis_vip PUBLIC +target_include_directories(hiredis_cluster PUBLIC $ $) -IF(WIN32 OR MINGW) - TARGET_LINK_LIBRARIES(hiredis_vip PRIVATE ws2_32 hiredis::hiredis) -ENDIF() +if(WIN32 OR MINGW) + TARGET_LINK_LIBRARIES(hiredis_cluster PRIVATE ws2_32 hiredis::hiredis) +endif() if (NOT DISABLE_TESTS) include(CTest) @@ -114,38 +116,35 @@ if (NOT DISABLE_TESTS) endif() endif() -# Get CMAKE_INSTALL_ variables -include(GNUInstallDirs) - -CONFIGURE_FILE(hiredis_vip.pc.in hiredis_vip.pc @ONLY) +configure_file(hiredis_cluster.pc.in hiredis_cluster.pc @ONLY) -INSTALL(TARGETS hiredis_vip - EXPORT hiredis_vip-targets) +install(TARGETS hiredis_cluster + EXPORT hiredis_cluster-targets) -INSTALL(FILES hircluster.h adlist.h hiarray.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) +install(FILES hircluster.h adlist.h hiarray.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_cluster) -INSTALL(DIRECTORY adapters - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_vip) +install(DIRECTORY adapters + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis_cluster) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip.pc +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_cluster.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -export(EXPORT hiredis_vip-targets - FILE ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip-targets.cmake - NAMESPACE hiredis_vip::) +export(EXPORT hiredis_cluster-targets + FILE ${CMAKE_CURRENT_BINARY_DIR}/hiredis_cluster-targets.cmake + NAMESPACE hiredis_cluster::) -SET(CMAKE_CONF_INSTALL_DIR share/hiredis_vip) -SET(INCLUDE_INSTALL_DIR include) +set(CMAKE_CONF_INSTALL_DIR share/hiredis_cluster) +set(INCLUDE_INSTALL_DIR include) include(CMakePackageConfigHelpers) -configure_package_config_file(hiredis_vip-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip-config.cmake +configure_package_config_file(hiredis_cluster-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_cluster-config.cmake INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR} PATH_VARS INCLUDE_INSTALL_DIR) -INSTALL(EXPORT hiredis_vip-targets - FILE hiredis_vip-targets.cmake - NAMESPACE hiredis_vip:: +install(EXPORT hiredis_cluster-targets + FILE hiredis_cluster-targets.cmake + NAMESPACE hiredis_cluster:: DESTINATION ${CMAKE_CONF_INSTALL_DIR}) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip-config.cmake +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_cluster-config.cmake DESTINATION ${CMAKE_CONF_INSTALL_DIR}) diff --git a/adapters/ae.h b/adapters/ae.h index 9bc4f25e..2cb371e8 100644 --- a/adapters/ae.h +++ b/adapters/ae.h @@ -28,8 +28,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef __HIREDIS_VIP_AE_H__ -#define __HIREDIS_VIP_AE_H__ +#ifndef __HIREDIS_CLUSTER_AE_H__ +#define __HIREDIS_CLUSTER_AE_H__ #include "../hircluster.h" #include diff --git a/adapters/libevent.h b/adapters/libevent.h index c6f581df..d90fa75f 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -28,8 +28,8 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef __HIREDIS_VIP_LIBEVENT_H__ -#define __HIREDIS_VIP_LIBEVENT_H__ +#ifndef __HIREDIS_CLUSTER_LIBEVENT_H__ +#define __HIREDIS_CLUSTER_LIBEVENT_H__ #include "../hircluster.h" #include diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8b0482f9..e9426e26 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.14) - -project(hiredis_vip_externalproject C) - +project(hiredis_cluster_externalproject) include(ExternalProject) ExternalProject_Add(hiredis @@ -9,17 +7,19 @@ ExternalProject_Add(hiredis GIT_REPOSITORY https://github.com/redis/hiredis GIT_TAG v1.0.0 CMAKE_CACHE_ARGS - "-DENABLE_SSL:BOOL=ON" + "-DENABLE_SSL:BOOL=OFF" "-DCMAKE_BUILD_TYPE:STRING=Debug" "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis" ) -ExternalProject_Add(hiredis_vip - PREFIX hiredis_vip +ExternalProject_Add(hiredis_cluster + PREFIX hiredis_cluster SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." CMAKE_CACHE_ARGS + "-DENABLE_SSL:BOOL=OFF" + "-DDISABLE_TESTS:BOOL=ON" "-DCMAKE_BUILD_TYPE:STRING=Debug" - "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip" + "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_cluster" "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" DEPENDS hiredis ) @@ -32,6 +32,6 @@ ExternalProject_Add(examples "-DCMAKE_BUILD_TYPE:STRING=Debug" "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/examples" "-Dhiredis_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis/share/hiredis" - "-Dhiredis_vip_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_vip/share/hiredis_vip" - DEPENDS hiredis_vip + "-Dhiredis_cluster_DIR:PATH=${CMAKE_CURRENT_BINARY_DIR}/hiredis_cluster/share/hiredis_cluster" + DEPENDS hiredis_cluster ) diff --git a/examples/src/CMakeLists.txt b/examples/src/CMakeLists.txt index 86ad259d..eff25457 100644 --- a/examples/src/CMakeLists.txt +++ b/examples/src/CMakeLists.txt @@ -1,21 +1,23 @@ cmake_minimum_required(VERSION 3.14) - -project(examples CXX) - -include(ExternalProject) +project(examples LANGUAGES CXX) # Handle libevent and hiredis find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) find_package(hiredis REQUIRED) -find_package(hiredis_vip REQUIRED) +find_package(hiredis_cluster REQUIRED) include_directories("${hiredis_INCLUDE_DIRS}") -include_directories("${hiredis_vip_INCLUDE_DIRS}") +include_directories("${hiredis_cluster_INCLUDE_DIRS}") # Executable: IPv4 -add_executable(${PROJECT_NAME} main.cpp) -target_link_libraries(${PROJECT_NAME} ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES}) +add_executable(example_ipv4 main.cpp) +target_link_libraries(example_ipv4 + ${hiredis_LIBRARIES} + ${hiredis_cluster_LIBRARIES}) # Executable: async -add_executable(${PROJECT_NAME}_async main_async.cpp) -target_link_libraries(${PROJECT_NAME}_async ${hiredis_LIBRARIES} ${hiredis_vip_LIBRARIES} ${EVENT_LIBRARY}) +add_executable(example_async main_async.cpp) +target_link_libraries(example_async + ${hiredis_LIBRARIES} + ${hiredis_cluster_LIBRARIES} + ${EVENT_LIBRARY}) diff --git a/examples/src/main.cpp b/examples/src/main.cpp index bea17306..76b8db93 100644 --- a/examples/src/main.cpp +++ b/examples/src/main.cpp @@ -1,6 +1,6 @@ #include #include -#include "hiredis_vip/hircluster.h" +#include "hiredis_cluster/hircluster.h" int main(int argc, char **argv) { diff --git a/examples/src/main_async.cpp b/examples/src/main_async.cpp index a132158f..e42f8c5e 100644 --- a/examples/src/main_async.cpp +++ b/examples/src/main_async.cpp @@ -1,7 +1,7 @@ #include #include -#include "hiredis_vip/hircluster.h" -#include "hiredis_vip/adapters/libevent.h" +#include "hiredis_cluster/hircluster.h" +#include "hiredis_cluster/adapters/libevent.h" void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply*)r; diff --git a/hircluster.h b/hircluster.h index f971cc0b..228ebece 100644 --- a/hircluster.h +++ b/hircluster.h @@ -10,10 +10,10 @@ #include #endif -#define HIREDIS_VIP_MAJOR 0 -#define HIREDIS_VIP_MINOR 4 -#define HIREDIS_VIP_PATCH 0 -#define HIREDIS_VIP_SONAME 0.4 +#define HIREDIS_CLUSTER_MAJOR 0 +#define HIREDIS_CLUSTER_MINOR 5 +#define HIREDIS_CLUSTER_PATCH 0 +#define HIREDIS_CLUSTER_SONAME 0.5 #define REDIS_CLUSTER_SLOTS 16384 diff --git a/hiredis_cluster-config.cmake.in b/hiredis_cluster-config.cmake.in new file mode 100644 index 00000000..be9a588f --- /dev/null +++ b/hiredis_cluster-config.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +set_and_check(hiredis_cluster_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") + +if(NOT TARGET hiredis_cluster::hiredis_cluster) + include(${CMAKE_CURRENT_LIST_DIR}/hiredis_cluster-targets.cmake) +endif() + +set(hiredis_cluster_LIBRARIES hiredis_cluster::hiredis_cluster) +set(hiredis_cluster_INCLUDE_DIRS ${hiredis_cluster_INCLUDEDIR}) + +check_required_components(hiredis_cluster) + diff --git a/hiredis_vip.def b/hiredis_cluster.def similarity index 100% rename from hiredis_vip.def rename to hiredis_cluster.def diff --git a/hiredis_vip.pc.in b/hiredis_cluster.pc.in similarity index 71% rename from hiredis_vip.pc.in rename to hiredis_cluster.pc.in index 6d90f175..0857071b 100644 --- a/hiredis_vip.pc.in +++ b/hiredis_cluster.pc.in @@ -2,10 +2,10 @@ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include -pkgincludedir=${includedir}/hiredis-vip +pkgincludedir=${includedir}/hiredis_cluster -Name: hiredis-vip +Name: hiredis_cluster Description: Minimalistic C client library for Redis with cluster support. Version: @PROJECT_VERSION@ -Libs: -L${libdir} -lhiredis-vip +Libs: -L${libdir} -lhiredis_cluster Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64 diff --git a/hiredis_vip-config.cmake.in b/hiredis_vip-config.cmake.in deleted file mode 100644 index fe986a18..00000000 --- a/hiredis_vip-config.cmake.in +++ /dev/null @@ -1,13 +0,0 @@ -@PACKAGE_INIT@ - -set_and_check(hiredis_vip_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@") - -IF (NOT TARGET hiredis_vip::hiredis_vip) - INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_vip-targets.cmake) -ENDIF() - -SET(hiredis_vip_LIBRARIES hiredis_vip::hiredis_vip) -SET(hiredis_vip_INCLUDE_DIRS ${hiredis_vip_INCLUDEDIR}) - -check_required_components(hiredis_vip) - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0424a7c7..f73bb3ac 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,41 +1,48 @@ -# Generate SSL certs and keys when needed -set(SSL_CONFIGS ca.crt ca.key ca.txt redis.crt redis.key client.crt client.key) -add_custom_command( - OUTPUT ${SSL_CONFIGS} - COMMAND openssl genrsa -out ca.key 4096 - COMMAND openssl req -x509 -new -nodes -sha256 -key ca.key -days 3650 -subj '/CN=Redis Test CA' -out ca.crt - COMMAND openssl genrsa -out redis.key 2048 - COMMAND openssl req -new -sha256 -key redis.key -subj '/CN=Redis Server Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out redis.crt - COMMAND openssl genrsa -out client.key 2048 - COMMAND openssl req -new -sha256 -key client.key -subj '/CN=Redis Client Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out client.crt -) -add_custom_target(generate_tls_configs DEPENDS ${SSL_CONFIGS}) +if(ENABLE_SSL) + # Generate SSL certs and keys when needed + set(SSL_CONFIGS ca.crt ca.key ca.txt redis.crt redis.key client.crt client.key) + add_custom_command( + OUTPUT ${SSL_CONFIGS} + COMMAND openssl genrsa -out ca.key 4096 + COMMAND openssl req -x509 -new -nodes -sha256 -key ca.key -days 3650 -subj '/CN=Redis Test CA' -out ca.crt + COMMAND openssl genrsa -out redis.key 2048 + COMMAND openssl req -new -sha256 -key redis.key -subj '/CN=Redis Server Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out redis.crt + COMMAND openssl genrsa -out client.key 2048 + COMMAND openssl req -new -sha256 -key client.key -subj '/CN=Redis Client Test Cert' | openssl x509 -req -sha256 -CA ca.crt -CAkey ca.key -CAserial ca.txt -CAcreateserial -days 365 -out client.crt + ) + add_custom_target(generate_tls_configs DEPENDS ${SSL_CONFIGS}) + + set(SSL_LIBRARY hiredis_ssl) +endif() + find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) # Executable: IPv4 add_executable(example_ipv4 main.c) -target_link_libraries(example_ipv4 hiredis_vip hiredis hiredis_ssl) +target_link_libraries(example_ipv4 hiredis_cluster hiredis ${SSL_LIBRARY}) add_test(NAME example_ipv4 COMMAND "$") # Executable: IPv6 add_executable(example_ipv6 main_ipv6.c) -target_link_libraries(example_ipv6 hiredis_vip hiredis hiredis_ssl) +target_link_libraries(example_ipv6 hiredis_cluster hiredis ${SSL_LIBRARY}) add_test(NAME example_ipv6 COMMAND "$") # Executable: async add_executable(example_async main_async.c) -target_link_libraries(example_async hiredis_vip hiredis hiredis_ssl ${EVENT_LIBRARY}) +target_link_libraries(example_async hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) add_test(NAME example_async COMMAND "$") -# Executable: tls -add_executable(example_tls main_tls.c) -target_link_libraries(example_tls hiredis_vip hiredis hiredis_ssl hiredis_ssl) -add_dependencies(example_tls generate_tls_configs) -add_test(NAME example_tls COMMAND "$") - -# Executable: async tls -add_executable(example_async_tls main_async_tls.c) -target_link_libraries(example_async_tls hiredis_vip hiredis hiredis_ssl ${EVENT_LIBRARY}) -add_dependencies(example_async_tls generate_tls_configs) -add_test(NAME example_async_tls COMMAND "$") +if(ENABLE_SSL) + # Executable: tls + add_executable(example_tls main_tls.c) + target_link_libraries(example_tls hiredis_cluster hiredis ${SSL_LIBRARY}) + add_dependencies(example_tls generate_tls_configs) + add_test(NAME example_tls COMMAND "$") + + # Executable: async tls + add_executable(example_async_tls main_async_tls.c) + target_link_libraries(example_async_tls hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) + add_dependencies(example_async_tls generate_tls_configs) + add_test(NAME example_async_tls COMMAND "$") +endif() diff --git a/tests/main_async_tls.c b/tests/main_async_tls.c index 118f677d..644f00e4 100644 --- a/tests/main_async_tls.c +++ b/tests/main_async_tls.c @@ -4,7 +4,6 @@ #include "hircluster.h" #include "adapters/libevent.h" -#include "hiredis_ssl.h" void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply*)r; diff --git a/tests/main_tls.c b/tests/main_tls.c index 48c7a37a..cf0b79c4 100644 --- a/tests/main_tls.c +++ b/tests/main_tls.c @@ -1,7 +1,6 @@ #include #include #include "hircluster.h" -#include "hiredis_ssl.h" // REMOVE ?? int main(int argc, char **argv) { From 024a33fafb95a5d035b4a7d8e406fe32f9b66d0f Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 9 Nov 2020 00:08:06 +0100 Subject: [PATCH 071/273] Add github CI action --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..48523a10 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: [push] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + working-directory: ${{runner.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE + + - name: Test + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C $BUILD_TYPE From bcfdfc79efa5635cccc52e303d5b225072bb52db Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 9 Nov 2020 00:28:44 +0100 Subject: [PATCH 072/273] Update new CI job to also install libevent --- .github/workflows/ci.yml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48523a10..ecb1179f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,32 +15,27 @@ jobs: runs-on: ubuntu-latest steps: + - name: Prepare + run: sudo apt install libevent-dev + - uses: actions/checkout@v2 - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands + - name: Create out-of-source build folder run: cmake -E make_directory ${{runner.workspace}}/build - - name: Configure CMake + - name: Generate makefiles # Use a bash shell so we can use the same syntax for environment variable # access regardless of the host operating system shell: bash working-directory: ${{runner.workspace}}/build - # Note the current convention is to use the -S and -B options here to specify source - # and build directories, but this is only available with CMake 3.13 and higher. - # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - name: Build working-directory: ${{runner.workspace}}/build shell: bash - # Execute the build. You can specify a specific target with "--target " run: cmake --build . --config $BUILD_TYPE - - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C $BUILD_TYPE + # - name: Test + # working-directory: ${{runner.workspace}}/build + # shell: bash + # run: ctest -C $BUILD_TYPE From a30df00977b42b108d5428b2b86c0773b892f937 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 9 Nov 2020 11:25:41 +0100 Subject: [PATCH 073/273] Start using clang-format for code formatting (#1) --- .clang-format | 2 + .github/workflows/ci.yml | 5 +- adapters/ae.h | 9 +- adapters/libevent.h | 6 +- adlist.c | 63 +- adlist.h | 9 +- command.c | 198 ++- command.h | 288 ++-- crc16.c | 66 +- dict.c | 43 +- dict.h | 60 +- examples/src/main.cpp | 12 +- examples/src/main_async.cpp | 43 +- hiarray.c | 50 +- hiarray.h | 24 +- hircluster.c | 2901 ++++++++++++++++------------------- hircluster.h | 154 +- hiutil.c | 226 +-- hiutil.h | 196 ++- tests/main.c | 12 +- tests/main_async.c | 43 +- tests/main_async_tls.c | 30 +- tests/main_ipv6.c | 14 +- tests/main_tls.c | 21 +- win32.h | 11 +- 25 files changed, 2011 insertions(+), 2475 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..80d3293a --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +IndentWidth: 4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecb1179f..bd8a3671 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [push] +on: [push, pull_request] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) @@ -20,6 +20,9 @@ jobs: - uses: actions/checkout@v2 + - name: Run clang-format style check (.c and .h) + uses: jidicula/clang-format-action@master + - name: Create out-of-source build folder run: cmake -E make_directory ${{runner.workspace}}/build diff --git a/adapters/ae.h b/adapters/ae.h index 2cb371e8..9314c2b7 100644 --- a/adapters/ae.h +++ b/adapters/ae.h @@ -34,15 +34,14 @@ #include "../hircluster.h" #include -static int redisAeAttach_link(redisAsyncContext *ac, void *base) -{ +static int redisAeAttach_link(redisAsyncContext *ac, void *base) { return redisAeAttach((aeEventLoop *)base, ac); } -static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) { +static int redisClusterAeAttach(aeEventLoop *loop, + redisClusterAsyncContext *acc) { - if(acc == NULL || loop == NULL) - { + if (acc == NULL || loop == NULL) { return REDIS_ERR; } diff --git a/adapters/libevent.h b/adapters/libevent.h index d90fa75f..87d312f0 100644 --- a/adapters/libevent.h +++ b/adapters/libevent.h @@ -38,10 +38,10 @@ static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) { return redisLibeventAttach(ac, (struct event_base *)base); } -static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { +static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, + struct event_base *base) { - if(acc == NULL || base == NULL) - { + if (acc == NULL || base == NULL) { return REDIS_ERR; } diff --git a/adlist.c b/adlist.c index b490a6bd..607d7fa8 100644 --- a/adlist.c +++ b/adlist.c @@ -28,18 +28,16 @@ * POSSIBILITY OF SUCH DAMAGE. */ - -#include #include "adlist.h" #include "hiutil.h" +#include /* 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) -{ +hilist *listCreate(void) { struct hilist *list; if ((list = hi_alloc(sizeof(*list))) == NULL) @@ -55,16 +53,16 @@ hilist *listCreate(void) /* Free the whole list. * * This function can't fail. */ -void listRelease(hilist *list) -{ +void listRelease(hilist *list) { unsigned long len; listNode *current, *next; current = list->head; len = list->len; - while(len--) { + while (len--) { next = current->next; - if (list->free) list->free(current->value); + if (list->free) + list->free(current->value); hi_free(current); current = next; } @@ -77,8 +75,7 @@ void listRelease(hilist *list) * 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) -{ +hilist *listAddNodeHead(hilist *list, void *value) { listNode *node; if ((node = hi_alloc(sizeof(*node))) == NULL) @@ -103,8 +100,7 @@ hilist *listAddNodeHead(hilist *list, void *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) -{ +hilist *listAddNodeTail(hilist *list, void *value) { listNode *node; if ((node = hi_alloc(sizeof(*node))) == NULL) @@ -123,7 +119,8 @@ hilist *listAddNodeTail(hilist *list, void *value) return list; } -hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) { +hilist *listInsertNode(hilist *list, listNode *old_node, void *value, + int after) { listNode *node; if ((node = hi_alloc(sizeof(*node))) == NULL) @@ -156,8 +153,7 @@ hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) * 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) -{ +void listDelNode(hilist *list, listNode *node) { if (node->prev) node->prev->next = node->next; else @@ -166,7 +162,8 @@ void listDelNode(hilist *list, listNode *node) node->next->prev = node->prev; else list->tail = node->prev; - if (list->free) list->free(node->value); + if (list->free) + list->free(node->value); hi_free(node); list->len--; } @@ -175,11 +172,11 @@ void listDelNode(hilist *list, listNode *node) * call to listNext() will return the next element of the list. * * This function can't fail. */ -listIter *listGetIterator(hilist *list, int direction) -{ +listIter *listGetIterator(hilist *list, int direction) { listIter *iter; - if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL; + if ((iter = hi_alloc(sizeof(*iter))) == NULL) + return NULL; if (direction == AL_START_HEAD) iter->next = list->head; else @@ -189,9 +186,7 @@ listIter *listGetIterator(hilist *list, int direction) } /* Release the iterator memory */ -void listReleaseIterator(listIter *iter) { - hi_free(iter); -} +void listReleaseIterator(listIter *iter) { hi_free(iter); } /* Create an iterator in the list private iterator structure */ void listRewind(hilist *list, listIter *li) { @@ -218,8 +213,7 @@ void listRewindTail(hilist *list, listIter *li) { * } * * */ -listNode *listNext(listIter *iter) -{ +listNode *listNext(listIter *iter) { listNode *current = iter->next; if (current != NULL) { @@ -239,8 +233,7 @@ listNode *listNext(listIter *iter) * 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 *listDup(hilist *orig) { hilist *copy; listIter *iter; listNode *node; @@ -251,7 +244,7 @@ hilist *listDup(hilist *orig) copy->free = orig->free; copy->match = orig->match; iter = listGetIterator(orig, AL_START_HEAD); - while((node = listNext(iter)) != NULL) { + while ((node = listNext(iter)) != NULL) { void *value; if (copy->dup) { @@ -282,13 +275,12 @@ hilist *listDup(hilist *orig) * 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) -{ +listNode *listSearchKey(hilist *list, void *key) { listIter *iter; listNode *node; iter = listGetIterator(list, AL_START_HEAD); - while((node = listNext(iter)) != NULL) { + while ((node = listNext(iter)) != NULL) { if (list->match) { if (list->match(node->value, key)) { listReleaseIterator(iter); @@ -314,12 +306,14 @@ listNode *listIndex(hilist *list, long index) { listNode *n; if (index < 0) { - index = (-index)-1; + index = (-index) - 1; n = list->tail; - while(index-- && n) n = n->prev; + while (index-- && n) + n = n->prev; } else { n = list->head; - while(index-- && n) n = n->next; + while (index-- && n) + n = n->next; } return n; } @@ -328,7 +322,8 @@ listNode *listIndex(hilist *list, long index) { void listRotate(hilist *list) { listNode *tail = list->tail; - if (listLength(list) <= 1) return; + if (listLength(list) <= 1) + return; /* Detach current tail */ list->tail = tail->prev; diff --git a/adlist.h b/adlist.h index 5b9a53ea..982894e4 100644 --- a/adlist.h +++ b/adlist.h @@ -61,9 +61,9 @@ typedef struct hilist { #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 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) @@ -74,7 +74,8 @@ 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); +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); diff --git a/command.c b/command.c index cd7b8b54..4936ccd5 100644 --- a/command.c +++ b/command.c @@ -2,20 +2,16 @@ #include #include "command.h" -#include "hiutil.h" #include "hiarray.h" +#include "hiutil.h" - -static uint64_t cmd_id = 0; /* command id counter */ - +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) -{ +static int redis_argz(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_PING: case CMD_REQ_REDIS_QUIT: @@ -32,9 +28,7 @@ redis_argz(struct cmd *r) * Return true, if the redis command accepts no arguments, otherwise * return false */ -static int -redis_arg0(struct cmd *r) -{ +static int redis_arg0(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_EXISTS: case CMD_REQ_REDIS_PERSIST: @@ -78,9 +72,7 @@ redis_arg0(struct cmd *r) * Return true, if the redis command accepts exactly 1 argument, otherwise * return false */ -static int -redis_arg1(struct cmd *r) -{ +static int redis_arg1(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_EXPIRE: case CMD_REQ_REDIS_EXPIREAT: @@ -121,9 +113,7 @@ redis_arg1(struct cmd *r) * Return true, if the redis command accepts exactly 2 arguments, otherwise * return false */ -static int -redis_arg2(struct cmd *r) -{ +static int redis_arg2(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_GETRANGE: case CMD_REQ_REDIS_PSETEX: @@ -164,9 +154,7 @@ redis_arg2(struct cmd *r) * Return true, if the redis command accepts exactly 3 arguments, otherwise * return false */ -static int -redis_arg3(struct cmd *r) -{ +static int redis_arg3(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_LINSERT: return 1; @@ -182,9 +170,7 @@ redis_arg3(struct cmd *r) * Return true, if the redis command accepts 0 or more arguments, otherwise * return false */ -static int -redis_argn(struct cmd *r) -{ +static int redis_argn(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_BITCOUNT: @@ -234,9 +220,7 @@ redis_argn(struct cmd *r) * 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) -{ +static int redis_argx(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_MGET: case CMD_REQ_REDIS_DEL: @@ -253,9 +237,7 @@ redis_argx(struct cmd *r) * 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) -{ +static int redis_argkvx(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_MSET: return 1; @@ -273,9 +255,7 @@ redis_argkvx(struct cmd *r) * 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) -{ +static int redis_argeval(struct cmd *r) { switch (r->type) { case CMD_REQ_REDIS_EVAL: case CMD_REQ_REDIS_EVALSHA: @@ -312,9 +292,7 @@ redis_argeval(struct cmd *r) * * only supports the Redis unified protocol for requests. */ -void -redis_parse_cmd(struct cmd *r) -{ +void redis_parse_cmd(struct cmd *r) { int len; char *p, *m, *token = NULL; char *cmd_end; @@ -443,9 +421,9 @@ redis_parse_cmd(struct cmd *r) m = token + rlen; if (m >= cmd_end) { - //m = cmd_end - 1; - //p = m; - //break; + // m = cmd_end - 1; + // p = m; + // break; goto error; } @@ -935,7 +913,8 @@ redis_parse_cmd(struct cmd *r) break; case 10: - if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) { + if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', + 'e')) { r->type = CMD_REQ_REDIS_SDIFFSTORE; break; } @@ -943,37 +922,44 @@ redis_parse_cmd(struct cmd *r) break; case 11: - if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { + 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')) { + 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')) { + 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')) { + 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')) { + 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')) { + 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')) { + if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', + 'e', 'x')) { r->type = CMD_REQ_REDIS_ZRANGEBYLEX; break; } @@ -981,7 +967,8 @@ redis_parse_cmd(struct cmd *r) break; case 12: - if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { + if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', + 'o', 'a', 't')) { r->type = CMD_REQ_REDIS_HINCRBYFLOAT; break; } @@ -989,7 +976,8 @@ redis_parse_cmd(struct cmd *r) break; case 13: - if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', + 'c', 'o', 'r', 'e')) { r->type = CMD_REQ_REDIS_ZRANGEBYSCORE; break; } @@ -997,7 +985,8 @@ redis_parse_cmd(struct cmd *r) break; case 14: - if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { + if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', + 'b', 'y', 'l', 'e', 'x')) { r->type = CMD_REQ_REDIS_ZREMRANGEBYLEX; break; } @@ -1005,7 +994,8 @@ redis_parse_cmd(struct cmd *r) break; case 15: - if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) { + 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; } @@ -1013,12 +1003,14 @@ redis_parse_cmd(struct cmd *r) break; case 16: - if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { + 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')) { + 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; } @@ -1064,7 +1056,7 @@ redis_parse_cmd(struct cmd *r) } else if (isdigit(ch)) { rlen = rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { - + if (rnarg == 0) { goto error; } @@ -1096,18 +1088,18 @@ redis_parse_cmd(struct cmd *r) m = token + rlen; if (m >= cmd_end) { - //m = b->last - 1; - //p = m; - //break; + // m = b->last - 1; + // p = m; + // break; goto error; } if (*m != CR) { goto error; - } else { /* got a key */ + } else { /* got a key */ struct keypos *kpos; - p = m; /* move forward by rlen bytes */ + p = m; /* move forward by rlen bytes */ rlen = 0; m = token; token = NULL; @@ -1118,7 +1110,7 @@ redis_parse_cmd(struct cmd *r) } kpos->start = m; kpos->end = p; - //kpos->v_len = 0; + // kpos->v_len = 0; state = SW_KEY_LF; } @@ -1209,7 +1201,7 @@ redis_parse_cmd(struct cmd *r) { goto error; } - + kpos = array_n(r->keys, array_len-1); if (kpos == NULL || kpos->v_len != 0) { goto error; @@ -1240,10 +1232,10 @@ redis_parse_cmd(struct cmd *r) case SW_ARG1: m = p + rlen; if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; + // rlen -= (uint32_t)(b->last - p); + // m = b->last - 1; + // p = m; + // break; goto error; } @@ -1340,18 +1332,18 @@ redis_parse_cmd(struct cmd *r) 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. + * 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; + // rlen -= (uint32_t)(b->last - p); + // m = b->last - 1; + // p = m; + // break; goto error; } @@ -1467,10 +1459,10 @@ redis_parse_cmd(struct cmd *r) case SW_ARG3: m = p + rlen; if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; + // rlen -= (uint32_t)(b->last - p); + // m = b->last - 1; + // p = m; + // break; goto error; } @@ -1546,10 +1538,10 @@ redis_parse_cmd(struct cmd *r) case SW_ARGN: m = p + rlen; if (m >= cmd_end) { - //rlen -= (uint32_t)(b->last - p); - //m = b->last - 1; - //p = m; - //break; + // rlen -= (uint32_t)(b->last - p); + // m = b->last - 1; + // p = m; + // break; goto error; } @@ -1597,39 +1589,39 @@ redis_parse_cmd(struct cmd *r) 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)); + 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.", + 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_get() { struct cmd *command; command = hi_alloc(sizeof(struct cmd)); - if(command == NULL) - { + if (command == NULL) { return NULL; } - + command->id = ++cmd_id; command->result = CMD_PARSE_OK; command->errstr = NULL; @@ -1648,8 +1640,7 @@ struct cmd *command_get() command->sub_commands = NULL; command->keys = hiarray_create(1, sizeof(struct keypos)); - if (command->keys == NULL) - { + if (command->keys == NULL) { hi_free(command); return NULL; } @@ -1657,45 +1648,36 @@ struct cmd *command_get() return command; } -void command_destroy(struct cmd *command) -{ - if(command == NULL) - { +void command_destroy(struct cmd *command) { + if (command == NULL) { return; } - if(command->cmd != NULL) - { + if (command->cmd != NULL) { hi_free(command->cmd); } - if(command->errstr != NULL){ + if (command->errstr != NULL) { hi_free(command->errstr); } - if(command->keys != NULL) - { + if (command->keys != NULL) { command->keys->nelem = 0; hiarray_destroy(command->keys); } - if(command->frag_seq != NULL) - { + if (command->frag_seq != NULL) { hi_free(command->frag_seq); command->frag_seq = NULL; } - if(command->reply != NULL) - { + if (command->reply != NULL) { freeReplyObject(command->reply); } - if(command->sub_commands != NULL) - { + if (command->sub_commands != NULL) { listRelease(command->sub_commands); } - + hi_free(command); } - - diff --git a/command.h b/command.h index 3d1948d3..02dd4cbd 100644 --- a/command.h +++ b/command.h @@ -3,172 +3,170 @@ #include -#include #include "adlist.h" +#include 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_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 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; +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 */ + 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 */ + 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 */ - cmd_type_t type; /* command type */ + char *cmd; + uint32_t clen; /* command length */ - char *cmd; - uint32_t clen; /* command length */ - - struct hiarray *keys; /* array of keypos, for req */ + 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) */ + 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) */ + 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*/ + 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; + redisReply *reply; - hilist *sub_commands; /* just for pipeline and multi-key commands */ + hilist *sub_commands; /* just for pipeline and multi-key commands */ }; void redis_parse_cmd(struct cmd *r); diff --git a/crc16.c b/crc16.c index 0f304f6e..a5fb0747 100644 --- a/crc16.c +++ b/crc16.c @@ -43,45 +43,41 @@ */ #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 -}; +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]; + crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *buf++) & 0x00FF]; return crc; } diff --git a/dict.c b/dict.c index 3a60b1eb..92c4b49f 100644 --- a/dict.c +++ b/dict.c @@ -33,10 +33,10 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include +#include "dict.h" #include #include -#include "dict.h" +#include /* -------------------------- private prototypes ---------------------------- */ @@ -71,7 +71,7 @@ static void _dictReset(dict *ht) { /* Create a new hash table */ static dict *dictCreate(dictType *type, void *privDataPtr) { dict *ht = malloc(sizeof(*ht)); - _dictInit(ht,type,privDataPtr); + _dictInit(ht, type, privDataPtr); return ht; } @@ -95,8 +95,8 @@ static int dictExpand(dict *ht, unsigned long size) { _dictInit(&n, ht->type, ht->privdata); n.size = realsize; - n.sizemask = realsize-1; - n.table = calloc(realsize,sizeof(dictEntry*)); + 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, @@ -105,11 +105,12 @@ static int dictExpand(dict *ht, unsigned long size) { for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; - if (ht->table[i] == NULL) continue; + if (ht->table[i] == NULL) + continue; /* For each hash entry on this slot... */ he = ht->table[i]; - while(he) { + while (he) { unsigned int h; nextHe = he->next; @@ -160,8 +161,9 @@ static int _dictClear(dict *ht) { for (i = 0; i < ht->size && ht->used > 0; i++) { dictEntry *he, *nextHe; - if ((he = ht->table[i]) == NULL) continue; - while(he) { + if ((he = ht->table[i]) == NULL) + continue; + while (he) { nextHe = he->next; dictFreeEntryKey(ht, he); dictFreeEntryVal(ht, he); @@ -187,10 +189,11 @@ static dictEntry *dictFind(dict *ht, const void *key) { dictEntry *he; unsigned int h; - if (ht->size == 0) return NULL; + if (ht->size == 0) + return NULL; h = dictHashKey(ht, key) & ht->sizemask; he = ht->table[h]; - while(he) { + while (he) { if (dictCompareHashKeys(ht, key, he->key)) return he; he = he->next; @@ -212,8 +215,8 @@ static dictEntry *dictNext(dictIterator *iter) { while (1) { if (iter->entry == NULL) { iter->index++; - if (iter->index >= - (signed)iter->ht->size) break; + if (iter->index >= (signed)iter->ht->size) + break; iter->entry = iter->ht->table[iter->index]; } else { iter->entry = iter->nextEntry; @@ -228,9 +231,7 @@ static dictEntry *dictNext(dictIterator *iter) { return NULL; } -static void dictReleaseIterator(dictIterator *iter) { - free(iter); -} +static void dictReleaseIterator(dictIterator *iter) { free(iter); } /* ------------------------- private functions ------------------------------ */ @@ -241,7 +242,7 @@ static int _dictExpandIfNeeded(dict *ht) { if (ht->size == 0) return dictExpand(ht, DICT_HT_INITIAL_SIZE); if (ht->used == ht->size) - return dictExpand(ht, ht->size*2); + return dictExpand(ht, ht->size * 2); return DICT_OK; } @@ -249,8 +250,9 @@ static int _dictExpandIfNeeded(dict *ht) { static unsigned long _dictNextPower(unsigned long size) { unsigned long i = DICT_HT_INITIAL_SIZE; - if (size >= LONG_MAX) return LONG_MAX; - while(1) { + if (size >= LONG_MAX) + return LONG_MAX; + while (1) { if (i >= size) return i; i *= 2; @@ -271,11 +273,10 @@ static int _dictKeyIndex(dict *ht, const void *key) { h = dictHashKey(ht, key) & ht->sizemask; /* Search if this slot does not already contain the given key */ he = ht->table[h]; - while(he) { + while (he) { if (dictCompareHashKeys(ht, key, he->key)) return -1; he = he->next; } return h; } - diff --git a/dict.h b/dict.h index 2f6949fd..1773dbd9 100644 --- a/dict.h +++ b/dict.h @@ -40,7 +40,7 @@ #define DICT_ERR 1 /* Unused arguments generate annoying warnings... */ -#define DICT_NOTUSED(V) ((void) V) +#define DICT_NOTUSED(V) ((void)V) typedef struct dictEntry { void *key; @@ -73,35 +73,37 @@ typedef struct dictIterator { } dictIterator; /* This is the initial size of every hash table */ -#define DICT_HT_INITIAL_SIZE 4 +#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 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) @@ -116,7 +118,7 @@ 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 void dictRelease(dict *ht); -static dictEntry * dictFind(dict *ht, const void *key); +static dictEntry *dictFind(dict *ht, const void *key); static dictIterator *dictGetIterator(dict *ht); static dictEntry *dictNext(dictIterator *iter); static void dictReleaseIterator(dictIterator *iter); diff --git a/examples/src/main.cpp b/examples/src/main.cpp index 76b8db93..64ac44ad 100644 --- a/examples/src/main.cpp +++ b/examples/src/main.cpp @@ -1,10 +1,9 @@ +#include "hiredis_cluster/hircluster.h" #include #include -#include "hiredis_cluster/hircluster.h" -int main(int argc, char **argv) -{ - struct timeval timeout = { 1, 500000 }; // 1.5s +int main(int argc, char **argv) { + struct timeval timeout = {1, 500000}; // 1.5s redisClusterContext *cc = redisClusterContextInit(); redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); @@ -17,11 +16,12 @@ int main(int argc, char **argv) exit(-1); } - redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + redisReply *reply = + (redisReply *)redisClusterCommand(cc, "SET %s %s", "key", "value"); printf("SET: %s\n", reply->str); freeReplyObject(reply); - redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + redisReply *reply2 = (redisReply *)redisClusterCommand(cc, "GET %s", "key"); printf("GET: %s\n", reply2->str); freeReplyObject(reply2); diff --git a/examples/src/main_async.cpp b/examples/src/main_async.cpp index e42f8c5e..cfc189b5 100644 --- a/examples/src/main_async.cpp +++ b/examples/src/main_async.cpp @@ -1,31 +1,31 @@ +#include "hiredis_cluster/adapters/libevent.h" +#include "hiredis_cluster/hircluster.h" #include #include -#include "hiredis_cluster/hircluster.h" -#include "hiredis_cluster/adapters/libevent.h" void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { - redisReply *reply = (redisReply*)r; + redisReply *reply = (redisReply *)r; if (reply == NULL) { if (cc->errstr) { printf("errstr: %s\n", cc->errstr); } return; } - printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisClusterAsyncDisconnect(cc); } void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { - redisReply *reply = (redisReply*)r; + redisReply *reply = (redisReply *)r; if (reply == NULL) { if (cc->errstr) { printf("errstr: %s\n", cc->errstr); } return; } - printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); } void connectCallback(const redisAsyncContext *ac, int status) { @@ -44,11 +44,10 @@ void disconnectCallback(const redisAsyncContext *ac, int status) { printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); } -int main(int argc, char **argv) -{ +int main(int argc, char **argv) { printf("Connecting...\n"); - redisClusterAsyncContext *cc = redisClusterAsyncConnect("127.0.0.1:30001", - HIRCLUSTER_FLAG_NULL); + redisClusterAsyncContext *cc = + redisClusterAsyncConnect("127.0.0.1:30001", HIRCLUSTER_FLAG_NULL); if (cc && cc->err) { printf("Error: %s\n", cc->errstr); return 1; @@ -60,27 +59,27 @@ int main(int argc, char **argv) redisClusterAsyncSetDisconnectCallback(cc, disconnectCallback); int status; - status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key", "value"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, setCallback, (char *)"THE_ID", + "SET %s %s", "key", "value"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } - status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, getCallback, (char *)"THE_ID", + "GET %s", "key"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } - status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key2", "value2"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, setCallback, (char *)"THE_ID", + "SET %s %s", "key2", "value2"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } - status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key2"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, getCallback, (char *)"THE_ID", + "GET %s", "key2"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } diff --git a/hiarray.c b/hiarray.c index cf742ecf..c4dc6593 100644 --- a/hiarray.c +++ b/hiarray.c @@ -1,11 +1,9 @@ #include -#include "hiutil.h" #include "hiarray.h" +#include "hiutil.h" -struct hiarray * -hiarray_create(uint32_t n, size_t size) -{ +struct hiarray *hiarray_create(uint32_t n, size_t size) { struct hiarray *a; ASSERT(n != 0 && size != 0); @@ -28,16 +26,12 @@ hiarray_create(uint32_t n, size_t size) return a; } -void -hiarray_destroy(struct hiarray *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) -{ +int hiarray_init(struct hiarray *a, uint32_t n, size_t size) { ASSERT(n != 0 && size != 0); a->elem = hi_alloc(n * size); @@ -52,9 +46,7 @@ hiarray_init(struct hiarray *a, uint32_t n, size_t size) return HI_OK; } -void -hiarray_deinit(struct hiarray *a) -{ +void hiarray_deinit(struct hiarray *a) { ASSERT(a->nelem == 0); if (a->elem != NULL) { @@ -62,9 +54,7 @@ hiarray_deinit(struct hiarray *a) } } -uint32_t -hiarray_idx(struct hiarray *a, void *elem) -{ +uint32_t hiarray_idx(struct hiarray *a, void *elem) { uint8_t *p, *q; uint32_t off, idx; @@ -81,9 +71,7 @@ hiarray_idx(struct hiarray *a, void *elem) return idx; } -void * -hiarray_push(struct hiarray *a) -{ +void *hiarray_push(struct hiarray *a) { void *elem, *new; size_t size; @@ -106,9 +94,7 @@ hiarray_push(struct hiarray *a) return elem; } -void * -hiarray_pop(struct hiarray *a) -{ +void *hiarray_pop(struct hiarray *a) { void *elem; ASSERT(a->nelem != 0); @@ -119,9 +105,7 @@ hiarray_pop(struct hiarray *a) return elem; } -void * -hiarray_get(struct hiarray *a, uint32_t idx) -{ +void *hiarray_get(struct hiarray *a, uint32_t idx) { void *elem; ASSERT(a->nelem != 0); @@ -132,17 +116,13 @@ hiarray_get(struct hiarray *a, uint32_t idx) return elem; } -void * -hiarray_top(struct hiarray *a) -{ +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) -{ +void hiarray_swap(struct hiarray *a, struct hiarray *b) { struct hiarray tmp; tmp = *a; @@ -154,9 +134,7 @@ hiarray_swap(struct hiarray *a, struct hiarray *b) * Sort nelem elements of the array in ascending order based on the * compare comparator. */ -void -hiarray_sort(struct hiarray *a, hiarray_compare_t compare) -{ +void hiarray_sort(struct hiarray *a, hiarray_compare_t compare) { ASSERT(a->nelem != 0); qsort(a->elem, a->nelem, a->size, compare); @@ -166,9 +144,7 @@ hiarray_sort(struct hiarray *a, hiarray_compare_t 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) -{ +int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) { uint32_t i, nelem; ASSERT(array_n(a) != 0); diff --git a/hiarray.h b/hiarray.h index fda3a4b8..81f2695f 100644 --- a/hiarray.h +++ b/hiarray.h @@ -1,43 +1,37 @@ #ifndef __HIARRAY_H_ #define __HIARRAY_H_ -#include +#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 */ + void *elem; /* element */ + size_t size; /* element size */ uint32_t nalloc; /* # allocated element */ }; -#define null_hiarray { 0, NULL, 0, 0 } +#define null_hiarray \ + { 0, NULL, 0, 0 } -static inline void -hiarray_null(struct hiarray *a) -{ +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) -{ +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; -} +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); diff --git a/hircluster.c b/hircluster.c index c6875c46..e2b2b31d 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1,29 +1,29 @@ +#include +#include #include -#include #include -#include -#include +#include -#include "win32.h" -#include "hircluster.h" -#include "hiutil.h" #include "adlist.h" -#include "hiarray.h" #include "command.h" #include "dict.c" +#include "hiarray.h" +#include "hircluster.h" +#include "hiutil.h" +#include "win32.h" // Cluster errors are offset by 100 to be sufficiently out of range of // standard Redis errors #define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 100 -#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_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" +#define REDIS_STATUS_OK "OK" #define REDIS_COMMAND_CLUSTER_NODES "CLUSTER NODES" #define REDIS_COMMAND_CLUSTER_SLOTS "CLUSTER SLOTS" @@ -41,16 +41,15 @@ #define CLUSTER_DEFAULT_MAX_REDIRECT_COUNT 5 -typedef struct cluster_async_data -{ +typedef struct cluster_async_data { redisClusterAsyncContext *acc; struct cmd *command; redisClusterCallbackFn *callback; int retry_count; void *privdata; -}cluster_async_data; +} cluster_async_data; -typedef enum CLUSTER_ERR_TYPE{ +typedef enum CLUSTER_ERR_TYPE { CLUSTER_NOT_ERR = 0, CLUSTER_ERR_MOVED, CLUSTER_ERR_ASK, @@ -58,49 +57,42 @@ typedef enum CLUSTER_ERR_TYPE{ CLUSTER_ERR_CROSSSLOT, CLUSTER_ERR_CLUSTERDOWN, CLUSTER_ERR_SENTINEL -}CLUSTER_ERR_TYPE; +} 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) -{ +void listClusterNodeDestructor(void *val) { cluster_node_deinit(val); hi_free(val); } -void listClusterSlotDestructor(void *val) -{ - cluster_slot_destroy(val); -} +void listClusterSlotDestructor(void *val) { cluster_slot_destroy(val); } unsigned int dictSdsHash(const void *key) { - return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); + return dictGenHashFunction((unsigned char *)key, sdslen((char *)key)); } -int dictSdsKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - int l1,l2; +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; + if (l1 != l2) + return 0; return memcmp(key1, key2, l1) == 0; } -void dictSdsDestructor(void *privdata, void *val) -{ +void dictSdsDestructor(void *privdata, void *val) { DICT_NOTUSED(privdata); sdsfree(val); } -void dictClusterNodeDestructor(void *privdata, void *val) -{ +void dictClusterNodeDestructor(void *privdata, void *val) { DICT_NOTUSED(privdata); cluster_node_deinit(val); @@ -108,37 +100,35 @@ void dictClusterNodeDestructor(void *privdata, void *val) hi_free(val); } -/* Cluster nodes hash table, mapping nodes - * name(437c719f50dc9d0745032f3b280ce7ecc40792ac) +/* 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 */ + 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) +/* 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 */ + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL /* val destructor */ }; - -void listCommandFree(void *command) -{ +void listCommandFree(void *command) { struct cmd *cmd = command; command_destroy(cmd); } @@ -158,14 +148,13 @@ static void *__redisBlockForReply(redisContext *c) { void *reply; if (c->flags & REDIS_BLOCK) { - if (redisGetReply(c,&reply) != REDIS_OK) + if (redisGetReply(c, &reply) != REDIS_OK) return NULL; return reply; } return NULL; } - /* ----------------------------------------------------------------------------- * Key space handling * -------------------------------------------------------------------------- */ @@ -180,35 +169,40 @@ 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; + if (key[s] == '{') + break; /* No '{' ? Hash the whole key. This is the base case. */ - if (s == keylen) return crc16(key,keylen) & 0x3FFF; + 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; + 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 (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; + return crc16(key + s + 1, e - s - 1) & 0x3FFF; } -static void __redisClusterSetError(redisClusterContext *cc, int type, const char *str) { +static void __redisClusterSetError(redisClusterContext *cc, int type, + const char *str) { size_t len; - if(cc == NULL){ + 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); + 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! */ @@ -217,43 +211,34 @@ static void __redisClusterSetError(redisClusterContext *cc, int type, const char } } -static int cluster_reply_error_type(redisReply *reply) -{ +static int cluster_reply_error_type(redisReply *reply) { - if(reply == NULL) - { + 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) - { + 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) - { + } 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) - { + } 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) - { + } 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) - { + } 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 - { + } else { return CLUSTER_ERR_SENTINEL; } } @@ -261,12 +246,11 @@ static int cluster_reply_error_type(redisReply *reply) return CLUSTER_NOT_ERR; } -static int cluster_node_init(cluster_node *node) -{ - if(node == NULL){ +static int cluster_node_init(cluster_node *node) { + if (node == NULL) { return REDIS_ERR; } - + node->name = NULL; node->addr = NULL; node->host = NULL; @@ -281,16 +265,14 @@ static int cluster_node_init(cluster_node *node) node->data = NULL; node->migrating = NULL; node->importing = NULL; - + return REDIS_OK; } -static void cluster_node_deinit(cluster_node *node) -{ +static void cluster_node_deinit(cluster_node *node) { copen_slot **oslot; - - if(node == NULL) - { + + if (node == NULL) { return; } @@ -301,133 +283,118 @@ static void cluster_node_deinit(cluster_node *node) node->role = REDIS_ROLE_NULL; node->myself = 0; - if(node->con != NULL) - { + if (node->con != NULL) { redisFree(node->con); } - if(node->acon != NULL) - { + if (node->acon != NULL) { redisAsyncFree(node->acon); } - if(node->slots != NULL) - { + if (node->slots != NULL) { listRelease(node->slots); } - if(node->slaves != NULL) - { + if (node->slaves != NULL) { listRelease(node->slaves); } - if(node->migrating) - { - while(hiarray_n(node->migrating)) - { + 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)) - { + 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) -{ +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) -{ +static cluster_slot *cluster_slot_create(cluster_node *node) { cluster_slot *slot; slot = hi_alloc(sizeof(*slot)); - if(slot == NULL){ + if (slot == NULL) { return NULL; } cluster_slot_init(slot, node); - if(node != NULL){ + if (node != NULL) { ASSERT(node->role == REDIS_ROLE_MASTER); - if(node->slots == NULL){ + if (node->slots == NULL) { node->slots = listCreate(); - if(node->slots == NULL) - { + 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){ +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){ + if (node->role != REDIS_ROLE_MASTER) { return REDIS_ERR; } - - if(node->slots == NULL){ + + if (node->slots == NULL) { node->slots = listCreate(); - if(node->slots == NULL) - { + 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) -{ +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) -{ +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){ + if (oslot == NULL) { return NULL; } @@ -444,90 +411,85 @@ static copen_slot *cluster_open_slot_create(uint32_t slot_num, int migrate, return oslot; } -static void cluster_open_slot_destroy(copen_slot *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){ + 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) -{ + * 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){ + 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."); + 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."); + 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."); + 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"); + if (node == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); goto error; } - + cluster_node_init(node); - if(role == REDIS_ROLE_MASTER){ + if (role == REDIS_ROLE_MASTER) { node->slots = listCreate(); - if(node->slots == NULL){ + if (node->slots == NULL) { hi_free(node); - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "slots for node listCreate error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "slots for node listCreate error"); goto error; } node->slots->free = listClusterSlotDestructor; } - - node->name = NULL; + + 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){ + + if (node != NULL) { hi_free(node); } @@ -535,30 +497,31 @@ static cluster_node *node_get_with_slots( } /** - * Return a new node with the "cluster nodes" command reply. - */ + * 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 *node_infos, int info_count, + uint8_t role) { char *p = NULL; char *port = NULL; cluster_node *node; - if(info_count < 8) { + if (info_count < 8) { return NULL; } node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) { + if (node == NULL) { __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); return NULL; } cluster_node_init(node); - if(role == REDIS_ROLE_MASTER) { + if (role == REDIS_ROLE_MASTER) { node->slots = listCreate(); - if(node->slots == NULL) { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "slots for node listCreate error"); + if (node->slots == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "slots for node listCreate error"); goto error; } @@ -571,7 +534,9 @@ static cluster_node *node_get_with_nodes(redisClusterContext *cc, // Get host part if ((p = strrchr(node_infos[1], IP_PORT_SEPARATOR)) == NULL) { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, port separator missing."); + __redisClusterSetError( + cc, REDIS_ERR_OTHER, + "server address is incorrect, port separator missing."); goto error; } @@ -583,7 +548,7 @@ static cluster_node *node_get_with_nodes(redisClusterContext *cc, if ((port = strchr(p, PORT_CPORT_SEPARATOR)) == NULL) { port_len = strlen(p); } else { - port_len = port-p; + port_len = port - p; } node->port = hi_atoi(p, port_len); @@ -605,38 +570,37 @@ static cluster_node *node_get_with_nodes(redisClusterContext *cc, return NULL; } -static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) -{ +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){ + if (nodes_f == NULL || nodes_t == NULL) { return; } di = dictGetIterator(nodes_t); - while((de_t = dictNext(di)) != NULL){ + while ((de_t = dictNext(di)) != NULL) { node_t = dictGetEntryVal(de_t); - if(node_t == NULL){ + if (node_t == NULL) { continue; } - + de_f = dictFind(nodes_f, node_t->addr); - if(de_f == NULL){ + if (de_f == NULL) { continue; } node_f = dictGetEntryVal(de_f); - if(node_f->con != NULL){ + if (node_f->con != NULL) { c = node_f->con; node_f->con = node_t->con; node_t->con = c; } - if(node_f->acon != NULL){ + if (node_f->acon != NULL) { ac = node_f->acon; node_f->acon = node_t->acon; node_t->acon = ac; @@ -648,89 +612,68 @@ static void cluster_nodes_swap_ctx(dict *nodes_f, dict *nodes_t) } dictReleaseIterator(di); - } -static int -cluster_slot_start_cmp(const void *t1, const void *t2) -{ - const cluster_slot * const *s1 = t1, * const *s2 = t2; +static int cluster_slot_start_cmp(const void *t1, const void *t2) { + const cluster_slot *const *s1 = t1, *const *s2 = t2; - return (*s1)->start > (*s2)->start?1:-1; + 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) -{ +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) - { + if (node == NULL || master_name == NULL) { return REDIS_ERR; } - if(*nodes == NULL) - { - *nodes = dictCreate( - &clusterNodesRefDictType, NULL); + 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"); + 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 - { + } else { node_old = dictGetEntryVal(di); - if(node_old == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "dict get value null"); + 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"); + 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) - { + } 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"); + if (node->slaves == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } - node->slaves->free = - listClusterNodeDestructor; + node->slaves->free = listClusterNodeDestructor; } - - if(node_old->slaves != NULL) - { + + if (node_old->slaves != NULL) { node_old->slaves->free = NULL; - while(listLength(node_old->slaves) > 0) - { + while (listLength(node_old->slaves) > 0) { lnode = listFirst(node_old->slaves); listAddNodeHead(node->slaves, lnode->value); listDelNode(node_old->slaves, lnode); @@ -742,41 +685,31 @@ cluster_master_slave_mapping_with_name(redisClusterContext *cc, listAddNodeHead(node->slaves, node_old); dictSetHashVal(*nodes, di, node); - } - else if(node->role == REDIS_ROLE_SLAVE) - { - if(node_old->slaves == NULL) - { + } 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"); + if (node_old->slaves == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } - node_old->slaves->free = - listClusterNodeDestructor; + node_old->slaves->free = listClusterNodeDestructor; } listAddNodeTail(node_old->slaves, node); - } - else - { + } 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) -{ + * 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; @@ -789,74 +722,76 @@ parse_cluster_slots(redisClusterContext *cc, sds address; uint32_t i, idx; - if(reply == NULL){ + if (reply == NULL) { return NULL; } nodes = dictCreate(&clusterNodesDictType, NULL); - if(nodes == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "out of memory"); + 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."); + + 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 ++){ + 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."); + 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."); + 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){ + // 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, + 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){ + } else if (idx == 1) { elem_slots_end = elem_slots->element[idx]; - if(elem_slots_end->type != REDIS_REPLY_INTEGER){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, + 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, + 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{ + } else { elem_nodes = elem_slots->element[idx]; - if(elem_nodes->type != REDIS_REPLY_ARRAY || - elem_nodes->elements != 3){ - __redisClusterSetError(cc, REDIS_ERR_OTHER, + 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; @@ -865,29 +800,31 @@ parse_cluster_slots(redisClusterContext *cc, 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, + 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){ + // 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){ + // 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, + if (ret != REDIS_OK) { + __redisClusterSetError( + cc, REDIS_ERR_OOM, "Slot ref node failed: out of memory."); goto error; } @@ -897,48 +834,50 @@ parse_cluster_slots(redisClusterContext *cc, } sdsfree(address); - master = node_get_with_slots(cc, elem_ip, - elem_port, REDIS_ROLE_MASTER); - if(master == NULL){ + 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, + 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, + 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){ + } 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){ + if (master->slaves == NULL) { master->slaves = listCreate(); - if(master->slaves == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Out of memory"); + if (master->slaves == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Out of memory"); cluster_node_deinit(slave); goto error; } - master->slaves->free = - listClusterNodeDestructor; + master->slaves->free = listClusterNodeDestructor; } listAddNodeTail(master->slaves, slave); @@ -951,24 +890,22 @@ parse_cluster_slots(redisClusterContext *cc, error: - if(nodes != NULL){ + if (nodes != NULL) { dictRelease(nodes); } - if(slot != NULL){ + 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) -{ + * 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; @@ -985,135 +922,145 @@ parse_cluster_nodes(redisClusterContext *cc, int len; nodes = dictCreate(&clusterNodesDictType, NULL); - if(nodes == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "out of memory"); + 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'){ + 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"); + 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){ + // 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){ + if (sdslen(part[2]) >= 7 && memcmp(part[2], "myself,", 7) == 0) { role_len = sdslen(part[2]) - 7; role = part[2] + 7; myself = 1; - }else{ + } 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, + // 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){ + + 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, + 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){ + 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, + 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])); + } 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 && + } 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] == ']'){ - + 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] == '>'){ + + if (slot_start_end[1][0] == '>') { oslot = cluster_open_slot_create( hi_atoi(slot_start_end[0], - sdslen(slot_start_end[0])), + sdslen(slot_start_end[0])), 1, slot_start_end[2], master); - if(oslot == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, + 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, + + 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; @@ -1121,28 +1068,34 @@ parse_cluster_nodes(redisClusterContext *cc, } oslot_elem = hiarray_push(master->migrating); - if(oslot_elem == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Push migrating array error: out of memory"); + 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, + } 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, + 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; @@ -1150,9 +1103,11 @@ parse_cluster_nodes(redisClusterContext *cc, } oslot_elem = hiarray_push(master->importing); - if(oslot_elem == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "push migrating array error: out of memory"); + if (oslot_elem == NULL) { + __redisClusterSetError( + cc, REDIS_ERR_OTHER, + "push migrating array error: out of " + "memory"); cluster_open_slot_destroy(oslot); goto error; } @@ -1160,104 +1115,104 @@ parse_cluster_nodes(redisClusterContext *cc, *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){ + 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"); + 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; + 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){ + // 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){ + 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) + slave->myself = 1; } - if(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){ + if (nodes_name != NULL) { dictRelease(nodes_name); } - + return nodes; error: - - if(part != NULL){ + + if (part != NULL) { sdsfreesplitres(part, count_part); count_part = 0; part = NULL; } - if(slot_start_end != 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){ + if (nodes != NULL) { dictRelease(nodes); } - if(nodes_name != NULL){ + 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) -{ + * 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; @@ -1271,28 +1226,27 @@ cluster_update_route_by_addr(redisClusterContext *cc, cluster_node *table[REDIS_CLUSTER_SLOTS]; uint32_t j, k; - if(cc == NULL){ + if (cc == NULL) { return REDIS_ERR; } - if(ip == NULL || port <= 0){ - __redisClusterSetError(cc, - REDIS_ERR_OTHER,"Ip or port error!"); + if (ip == NULL || port <= 0) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, "Ip or port error!"); goto error; } - if(cc->connect_timeout){ + if (cc->connect_timeout) { c = redisConnectWithTimeout(ip, port, *cc->connect_timeout); - }else{ + } else { c = redisConnect(ip, port); } - - if (c == NULL){ - __redisClusterSetError(cc,REDIS_ERR_OTHER, - "Init redis context error(return NULL)"); + + 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); + } else if (c->err) { + __redisClusterSetError(cc, c->err, c->errstr); goto error; } @@ -1308,104 +1262,107 @@ cluster_update_route_by_addr(redisClusterContext *cc, } } #endif - if(cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS){ + if (cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS) { reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); - if(reply == NULL){ + if (reply == NULL) { if (c->err == REDIS_ERR_TIMEOUT) { - __redisClusterSetError(cc,c->err, + __redisClusterSetError( + cc, c->err, "Command(cluster slots) reply error(socket timeout)"); } else { - __redisClusterSetError(cc,REDIS_ERR_OTHER, + __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, + } 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){ + if (reply == NULL) { if (c->err == REDIS_ERR_TIMEOUT) { - __redisClusterSetError(cc,c->err, + __redisClusterSetError( + cc, c->err, "Command(cluster nodes) reply error(socket timeout)"); } else { - __redisClusterSetError(cc,REDIS_ERR_OTHER, + __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, + } 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){ + 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"); + + 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"); + if (dit == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Dict get iterator failed: out of memory"); goto error; } - - while((den = dictNext(dit))){ + + while ((den = dictNext(dit))) { master = dictGetEntryVal(den); - if(master->role != REDIS_ROLE_MASTER){ - __redisClusterSetError(cc,REDIS_ERR_OOM, - "Node role must be master"); + if (master->role != REDIS_ROLE_MASTER) { + __redisClusterSetError(cc, REDIS_ERR_OOM, + "Node role must be master"); goto error; } - if(master->slots == NULL){ + if (master->slots == NULL) { continue; } - + lit = listGetIterator(master->slots, AL_START_HEAD); - if(lit == NULL){ + if (lit == NULL) { __redisClusterSetError(cc, REDIS_ERR_OOM, - "List get iterator failed: out of memory"); + "List get iterator failed: out of memory"); goto error; } - - while((lnode = listNext(lit))){ + + while ((lnode = listNext(lit))) { slot = listNodeValue(lnode); - if(slot->start > slot->end || - slot->end >= REDIS_CLUSTER_SLOTS){ + if (slot->start > slot->end || slot->end >= REDIS_CLUSTER_SLOTS) { __redisClusterSetError(cc, REDIS_ERR_OTHER, - "Slot region for node is error"); + "Slot region for node is error"); goto error; } - + slot_elem = hiarray_push(slots); *slot_elem = slot; } @@ -1416,140 +1373,127 @@ cluster_update_route_by_addr(redisClusterContext *cc, dictReleaseIterator(dit); hiarray_sort(slots, cluster_slot_start_cmp); - for(j = 0; j < hiarray_n(slots); j ++){ + 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){ + + 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"); + "Diffent node hold a same slot"); goto error; } - + table[k] = (*slot_elem)->node; } } - + cluster_nodes_swap_ctx(cc->nodes, nodes); - if(cc->nodes != NULL){ + if (cc->nodes != NULL) { dictRelease(cc->nodes); cc->nodes = NULL; } cc->nodes = nodes; - if(cc->slots != NULL) - { + 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 ++; - + memcpy(cc->table, table, REDIS_CLUSTER_SLOTS * sizeof(cluster_node *)); + cc->route_version++; + freeReplyObject(reply); - if(c != NULL){ + if (c != NULL) { redisFree(c); } - + return REDIS_OK; error: - if(dit != NULL){ + if (dit != NULL) { dictReleaseIterator(dit); } - if(lit != NULL){ - listReleaseIterator(lit); + if (lit != NULL) { + listReleaseIterator(lit); } - if(slots != NULL) - { - if(slots == cc->slots) - { + if (slots != NULL) { + if (slots == cc->slots) { cc->slots = NULL; } - + slots->nelem = 0; hiarray_destroy(slots); } - if(nodes != NULL){ - if(nodes == cc->nodes){ + if (nodes != NULL) { + if (nodes == cc->nodes) { cc->nodes = NULL; } dictRelease(nodes); } - if(reply != NULL){ + if (reply != NULL) { freeReplyObject(reply); reply = NULL; } - if(c != NULL){ + if (c != NULL) { redisFree(c); } - + return REDIS_ERR; } -int -cluster_update_route(redisClusterContext *cc) -{ +int cluster_update_route(redisClusterContext *cc) { int ret; int flag_err_not_set = 1; cluster_node *node; dictIterator *it; dictEntry *de; - - if(cc == NULL) - { + + if (cc == NULL) { return REDIS_ERR; } - if(cc->nodes == NULL) - { - if(flag_err_not_set) - { + 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) - { + while ((de = dictNext(it)) != NULL) { node = dictGetEntryVal(de); - if(node == NULL || node->host == NULL || node->port < 0) - { + 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) - { + 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) - { + if (flag_err_not_set) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "no valid server address"); } @@ -1557,8 +1501,7 @@ cluster_update_route(redisClusterContext *cc) } #ifdef DEBUG -static void print_cluster_node_list(redisClusterContext *cc) -{ +static void print_cluster_node_list(redisClusterContext *cc) { dictIterator *di = NULL; dictEntry *de; listIter *it; @@ -1566,8 +1509,7 @@ static void print_cluster_node_list(redisClusterContext *cc) cluster_node *master, *slave; hilist *slaves; - if(cc == NULL) - { + if (cc == NULL) { return; } @@ -1575,24 +1517,22 @@ static void print_cluster_node_list(redisClusterContext *cc) printf("name\taddress\trole\tslaves\n"); - while((de = dictNext(di)) != NULL) { + 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"); + 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) - { + if (slaves == NULL) { continue; } it = listGetIterator(slaves, AL_START_HEAD); - while((ln = listNext(it)) != NULL) - { + 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"); + printf("%s\t%s\t%d\t%s\n", slave->name, slave->addr, slave->role, + slave->slaves ? "hava" : "null"); } listReleaseIterator(it); @@ -1602,8 +1542,7 @@ static void print_cluster_node_list(redisClusterContext *cc) } #endif /* DEBUG */ -int test_cluster_update_route(redisClusterContext *cc) -{ +int test_cluster_update_route(redisClusterContext *cc) { int ret; ret = cluster_update_route(cc); @@ -1618,7 +1557,7 @@ int test_cluster_update_route(redisClusterContext *cc) redisClusterContext *redisClusterContextInit(void) { redisClusterContext *cc; - cc = calloc(1,sizeof(redisClusterContext)); + cc = calloc(1, sizeof(redisClusterContext)); if (cc == NULL) return NULL; @@ -1637,10 +1576,10 @@ redisClusterContext *redisClusterContextInit(void) { cc->route_version = 0LL; - memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + memset(cc->table, 0, REDIS_CLUSTER_SLOTS * sizeof(cluster_node *)); cc->flags |= REDIS_BLOCK; - + #ifdef SSL_SUPPORT cc->ssl = NULL; #endif @@ -1648,120 +1587,108 @@ redisClusterContext *redisClusterContextInit(void) { } void redisClusterFree(redisClusterContext *cc) { - + if (cc == NULL) return; - if (cc->connect_timeout) - { + if (cc->connect_timeout) { free(cc->connect_timeout); } - if (cc->timeout) - { + if (cc->timeout) { free(cc->timeout); } - memset(cc->table, 0, REDIS_CLUSTER_SLOTS*sizeof(cluster_node *)); + memset(cc->table, 0, REDIS_CLUSTER_SLOTS * sizeof(cluster_node *)); - if(cc->slots != NULL) - { + if (cc->slots != NULL) { cc->slots->nelem = 0; hiarray_destroy(cc->slots); cc->slots = NULL; } - if(cc->nodes != NULL) - { + if (cc->nodes != NULL) { dictRelease(cc->nodes); } - if(cc->requests != NULL) - { + if (cc->requests != NULL) { listRelease(cc->requests); } - + free(cc); } /* 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 int _redisClusterConnect2(redisClusterContext *cc) -{ +static int _redisClusterConnect2(redisClusterContext *cc) { - if (cc->nodes == NULL || dictSize(cc->nodes) == 0) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"servers address does not set up"); + if (cc->nodes == NULL || dictSize(cc->nodes) == 0) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "servers address does not set up"); return REDIS_ERR; } - + return cluster_update_route(cc); } /* 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) { +static redisClusterContext *_redisClusterConnect(redisClusterContext *cc, + const char *addrs) { int ret; - + ret = redisClusterSetOptionAddNodes(cc, addrs); - if (ret != REDIS_OK) - { + if (ret != REDIS_OK) { return cc; } - + cluster_update_route(cc); return cc; } -redisClusterContext *redisClusterConnect(const char *addrs, int flags) -{ +redisClusterContext *redisClusterConnect(const char *addrs, int flags) { redisClusterContext *cc; cc = redisClusterContextInit(); - if(cc == NULL) - { + if (cc == NULL) { return NULL; } cc->flags |= REDIS_BLOCK; - if(flags) - { + if (flags) { cc->flags |= flags; } - + return _redisClusterConnect(cc, addrs); } -redisClusterContext *redisClusterConnectWithTimeout( - const char *addrs, const struct timeval tv, int flags) -{ +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, + const struct timeval tv, + int flags) { redisClusterContext *cc; cc = redisClusterContextInit(); - if(cc == NULL) - { + if (cc == NULL) { return NULL; } cc->flags |= REDIS_BLOCK; - if(flags) - { + if (flags) { cc->flags |= flags; } - - if (cc->connect_timeout == NULL) - { + + if (cc->connect_timeout == NULL) { cc->connect_timeout = malloc(sizeof(struct timeval)); } - + memcpy(cc->connect_timeout, &tv, sizeof(struct timeval)); - + return _redisClusterConnect(cc, addrs); } @@ -1771,35 +1698,32 @@ redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags) { cc = redisClusterContextInit(); - if(cc == NULL) - { + if (cc == NULL) { return NULL; } cc->flags &= ~REDIS_BLOCK; - if(flags) - { + if (flags) { cc->flags |= flags; } - + return _redisClusterConnect(cc, addrs); } -int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) -{ +int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) { dictEntry *node_entry; cluster_node *node; sds ip; int port; sds addr_sds = NULL; - if(cc == NULL) { + if (cc == NULL) { return REDIS_ERR; } - if(cc->nodes == NULL) { + if (cc->nodes == NULL) { cc->nodes = dictCreate(&clusterNodesDictType, NULL); - if(cc->nodes == NULL) { + if (cc->nodes == NULL) { return REDIS_ERR; } } @@ -1807,48 +1731,57 @@ int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) addr_sds = sdsnew(addr); node_entry = dictFind(cc->nodes, addr_sds); sdsfree(addr_sds); - if(node_entry == NULL) { + if (node_entry == NULL) { char *p; if ((p = strrchr(addr, IP_PORT_SEPARATOR)) == NULL) { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, port separator missing."); + __redisClusterSetError( + cc, REDIS_ERR_OTHER, + "server address is incorrect, port separator missing."); return REDIS_ERR; } // p includes separator - if (p-addr <= 0) { /* length until separator */ - __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, address part missing."); + if (p - addr <= 0) { /* length until separator */ + __redisClusterSetError( + cc, REDIS_ERR_OTHER, + "server address is incorrect, address part missing."); return REDIS_ERR; } - ip = sdsnewlen(addr, p-addr); + ip = sdsnewlen(addr, p - addr); p++; // remove separator character if (strlen(p) <= 0) { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "server address is incorrect, port part missing."); + __redisClusterSetError( + cc, REDIS_ERR_OTHER, + "server address is incorrect, port part missing."); return REDIS_ERR; } port = hi_atoi(p, strlen(p)); - if(port <= 0) { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "server port is incorrect"); + if (port <= 0) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "server port is incorrect"); return REDIS_ERR; } node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) { + if (node == NULL) { sdsfree(ip); - __redisClusterSetError(cc, REDIS_ERR_OTHER, "alloc cluster node error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "alloc cluster node error"); return REDIS_ERR; } cluster_node_init(node); node->addr = sdsnew(addr); - if(node->addr == NULL) { + if (node->addr == NULL) { sdsfree(ip); hi_free(node); - __redisClusterSetError(cc, REDIS_ERR_OTHER, "new node address error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "new node address error"); return REDIS_ERR; } @@ -1861,31 +1794,28 @@ int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr) return REDIS_OK; } -int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs) -{ +int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs) { int ret; sds *address = NULL; int address_count = 0; int i; - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } - 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)"); + 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 REDIS_ERR; } - for(i = 0; i < address_count; i ++) - { + for (i = 0; i < address_count; i++) { ret = redisClusterSetOptionAddNode(cc, address[i]); - if(ret != REDIS_OK) - { + if (ret != REDIS_OK) { sdsfreesplitres(address, address_count); return REDIS_ERR; } @@ -1896,11 +1826,9 @@ int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs) return REDIS_OK; } -int redisClusterSetOptionConnectBlock(redisClusterContext *cc) -{ +int redisClusterSetOptionConnectBlock(redisClusterContext *cc) { - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } @@ -1909,11 +1837,9 @@ int redisClusterSetOptionConnectBlock(redisClusterContext *cc) return REDIS_OK; } -int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc) -{ +int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc) { - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } @@ -1922,11 +1848,9 @@ int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc) return REDIS_OK; } -int redisClusterSetOptionParseSlaves(redisClusterContext *cc) -{ +int redisClusterSetOptionParseSlaves(redisClusterContext *cc) { - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } @@ -1935,11 +1859,9 @@ int redisClusterSetOptionParseSlaves(redisClusterContext *cc) return REDIS_OK; } -int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc) -{ +int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc) { - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } @@ -1948,11 +1870,9 @@ int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc) return REDIS_OK; } -int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc) -{ +int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc) { - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } @@ -1961,69 +1881,60 @@ int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc) return REDIS_OK; } -int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv) -{ +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, + const struct timeval tv) { - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } - if (cc->connect_timeout == NULL) - { + if (cc->connect_timeout == NULL) { cc->connect_timeout = malloc(sizeof(struct timeval)); } - + memcpy(cc->connect_timeout, &tv, sizeof(struct timeval)); return REDIS_OK; } -int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv) -{ +int redisClusterSetOptionTimeout(redisClusterContext *cc, + const struct timeval tv) { - if(cc == NULL) - { + if (cc == NULL) { return REDIS_ERR; } - if (cc->timeout == NULL) - { + if (cc->timeout == NULL) { cc->timeout = malloc(sizeof(struct timeval)); memcpy(cc->timeout, &tv, sizeof(struct timeval)); - } - else if (cc->timeout->tv_sec != tv.tv_sec || cc->timeout->tv_usec != tv.tv_usec) - { + } else if (cc->timeout->tv_sec != tv.tv_sec || + cc->timeout->tv_usec != tv.tv_usec) { memcpy(cc->timeout, &tv, sizeof(struct timeval)); - if (cc->nodes && dictSize(cc->nodes) > 0) - { + if (cc->nodes && dictSize(cc->nodes) > 0) { dictEntry *de; dictIterator *di; cluster_node *node; di = dictGetIterator(cc->nodes); - while ((de = dictNext(di)) != NULL) - { + while ((de = dictNext(di)) != NULL) { node = dictGetEntryVal(de); - if (node->con && node->con->flags&REDIS_CONNECTED && node->con->err == 0) - { + if (node->con && node->con->flags & REDIS_CONNECTED && + node->con->err == 0) { redisSetTimeout(node->con, tv); } - if (node->slaves && listLength(node->slaves) > 0) - { + if (node->slaves && listLength(node->slaves) > 0) { cluster_node *slave; listIter *li; listNode *ln; - + li = listGetIterator(node->slaves, AL_START_HEAD); - while ((ln = listNext(li)) != NULL) - { + while ((ln = listNext(li)) != NULL) { slave = listNodeValue(ln); - if (slave->con && slave->con->flags&REDIS_CONNECTED && slave->con->err == 0) - { + if (slave->con && slave->con->flags & REDIS_CONNECTED && + slave->con->err == 0) { redisSetTimeout(slave->con, tv); } } @@ -2035,14 +1946,13 @@ int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval t dictReleaseIterator(di); } } - + return REDIS_OK; } -int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count) -{ - if(cc == NULL || max_redirect_count <= 0) - { +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, + int max_redirect_count) { + if (cc == NULL || max_redirect_count <= 0) { return REDIS_ERR; } @@ -2052,8 +1962,9 @@ int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_c } #ifdef SSL_SUPPORT -int redisClusterSetOptionEnableSSL(redisClusterContext *cc, redisSSLContext *ssl) { - if(cc == NULL || ssl == NULL) { +int redisClusterSetOptionEnableSSL(redisClusterContext *cc, + redisSSLContext *ssl) { + if (cc == NULL || ssl == NULL) { return REDIS_ERR; } @@ -2063,30 +1974,24 @@ int redisClusterSetOptionEnableSSL(redisClusterContext *cc, redisSSLContext *ssl } #endif -int redisClusterConnect2(redisClusterContext *cc) -{ - - if(cc == NULL) - { +int redisClusterConnect2(redisClusterContext *cc) { + + if (cc == NULL) { return REDIS_ERR; } - + return _redisClusterConnect2(cc); } -redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) -{ +redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) { redisContext *c = NULL; - if(node == NULL) - { + if (node == NULL) { return NULL; } c = node->con; - if(c != NULL) - { - if(c->err) - { + if (c != NULL) { + if (c->err) { redisReconnect(c); #ifdef SSL_SUPPORT @@ -2105,17 +2010,14 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) return c; } - if(node->host == NULL || node->port <= 0) - { + if (node->host == NULL || node->port <= 0) { return NULL; } - if(cc->connect_timeout) - { - c = redisConnectWithTimeout(node->host, node->port, *cc->connect_timeout); - } - else - { + if (cc->connect_timeout) { + c = redisConnectWithTimeout(node->host, node->port, + *cc->connect_timeout); + } else { c = redisConnect(node->host, node->port); } @@ -2138,63 +2040,52 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) return c; } -static cluster_node *node_get_by_table(redisClusterContext *cc, uint32_t slot_num) -{ - if(cc == 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) - { + if (slot_num >= REDIS_CLUSTER_SLOTS) { return NULL; } return cc->table[slot_num]; - } -static cluster_node *node_get_witch_connected(redisClusterContext *cc) -{ +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) - { + if (cc == NULL || cc->nodes == NULL) { return NULL; } di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { + while ((de = dictNext(di)) != NULL) { node = dictGetEntryVal(de); - if(node == NULL) - { + if (node == NULL) { continue; } - + c = ctx_get_by_node(cc, node); - if(c == NULL || c->err) - { + 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) - { + if (reply != NULL && reply->type == REDIS_REPLY_STATUS && + reply->str != NULL && strcmp(reply->str, "PONG") == 0) { freeReplyObject(reply); reply = NULL; - - dictReleaseIterator(di); - + + dictReleaseIterator(di); + return node; - } - else if(reply != NULL) - { + } else if (reply != NULL) { freeReplyObject(reply); reply = NULL; } @@ -2206,93 +2097,85 @@ static cluster_node *node_get_witch_connected(redisClusterContext *cc) } /* 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) -{ + * 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) - { + 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"); + if (node == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "no reachable node in cluster"); goto error; } c = ctx_get_by_node(cc, node); - + reply = redisCommand(c, "config get %s", config_name); - if(reply == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OTHER, "reply for config get is null"); + 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"); + 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"); + 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, + 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, + 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, + 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; + sub_reply->str = NULL; - if(reply != NULL) - { - freeReplyObject(reply); + if (reply != NULL) { + freeReplyObject(reply); } return config_value; error: - if(reply != NULL) - { - freeReplyObject(reply); + if (reply != NULL) { + freeReplyObject(reply); } return NULL; @@ -2304,77 +2187,66 @@ static char * cluster_config_get(redisClusterContext *cc, * 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) { +static int __redisClusterAppendCommand(redisClusterContext *cc, + struct cmd *command) { cluster_node *node; redisContext *c = NULL; - if(cc == NULL || command == NULL) - { + if (cc == NULL || command == NULL) { return REDIS_ERR; } - + node = node_get_by_table(cc, (uint32_t)command->slot_num); - if(node == NULL) - { + if (node == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by slot error"); return REDIS_ERR; } c = ctx_get_by_node(cc, node); - if(c == NULL) - { + if (c == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); return REDIS_ERR; - } - else if(c->err) - { + } else if (c->err) { __redisClusterSetError(cc, c->err, c->errstr); return REDIS_ERR; } - if (redisAppendFormattedCommand(c, command->cmd, command->clen) != REDIS_OK) - { + if (redisAppendFormattedCommand(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) -{ +static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, + void **reply) { cluster_node *node; redisContext *c; - if(cc == NULL || slot_num < 0 || reply == NULL) - { + 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"); + if (node == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "node get by table is null"); return REDIS_ERR; } c = ctx_get_by_node(cc, node); - if(c == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + 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) - { + } 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; } @@ -2383,57 +2255,45 @@ static int __redisClusterGetReply(redisClusterContext *cc, int slot_num, void ** return REDIS_ERR; } - if(redisGetReply(c, reply) != REDIS_OK) - { + if (redisGetReply(c, reply) != REDIS_OK) { __redisClusterSetError(cc, c->err, c->errstr); return REDIS_ERR; } - - if(cluster_reply_error_type(*reply) == CLUSTER_ERR_MOVED) - { + + 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) -{ +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) - { + 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!"); + 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 (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) - { + if (ip_port != NULL && ip_port_len == 2) { de = dictFind(cc->nodes, part[2]); - if(de == NULL) - { + if (de == NULL) { node = hi_alloc(sizeof(cluster_node)); - if(node == NULL) - { - __redisClusterSetError(cc, - REDIS_ERR_OOM, "Out of memory"); + if (node == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); goto done; } @@ -2444,55 +2304,47 @@ static cluster_node *node_get_by_ask_error_reply( 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); - + dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), + node); + part = NULL; ip_port = NULL; - } - else - { + } else { node = de->val; goto done; } - } - else - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, - "ask error reply address part parse error!"); + } 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!"); + } else { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "ask error reply parse error!"); goto done; } done: - if(part != NULL) - { + if (part != NULL) { sdsfreesplitres(part, part_len); part = NULL; } - if(ip_port != 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) -{ +static void *redis_cluster_command_execute(redisClusterContext *cc, + struct cmd *command) { int ret; void *reply = NULL; cluster_node *node; @@ -2500,45 +2352,38 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, int error_type; retry: - + node = node_get_by_table(cc, (uint32_t)command->slot_num); - if(node == NULL) - { + if (node == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "node get by table error"); return NULL; } c = ctx_get_by_node(cc, node); - if(c == NULL) - { + if (c == NULL) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node is null"); return NULL; - } - else if(c->err) - { + } else if (c->err) { node = node_get_witch_connected(cc); - if(node == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "no reachable node in cluster"); + 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"); + 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(cc, node); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); + if (c == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "ctx get by node error"); return NULL; - } - else if(c->err) - { + } else if (c->err) { __redisClusterSetError(cc, c->err, c->errstr); return NULL; } @@ -2546,51 +2391,46 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, ask_retry: - if (redisAppendFormattedCommand(c,command->cmd, command->clen) != REDIS_OK) - { + if (redisAppendFormattedCommand(c, command->cmd, command->clen) != + REDIS_OK) { __redisClusterSetError(cc, c->err, c->errstr); return NULL; } - + reply = __redisBlockForReply(c); - if(reply == NULL) - { + 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"); + 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) - { + + 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, + 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) - { + if (node == NULL) { freeReplyObject(reply); return NULL; } @@ -2599,27 +2439,24 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, reply = NULL; c = ctx_get_by_node(cc, node); - if(c == NULL) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, "ctx get by node error"); + if (c == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "ctx get by node error"); return NULL; - } - else if(c->err) - { + } else if (c->err) { __redisClusterSetError(cc, c->err, c->errstr); return NULL; } reply = redisCommand(c, REDIS_COMMAND_ASKING); - if(reply == NULL) - { + if (reply == NULL) { __redisClusterSetError(cc, c->err, c->errstr); return NULL; } freeReplyObject(reply); reply = NULL; - + goto ask_retry; break; @@ -2629,21 +2466,20 @@ static void *redis_cluster_command_execute(redisClusterContext *cc, freeReplyObject(reply); reply = NULL; goto retry; - + break; default: break; } } - + return reply; } -static int command_pre_fragment(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ - +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; @@ -2654,46 +2490,40 @@ static int command_pre_fragment(redisClusterContext *cc, struct cmd **sub_commands = NULL; char num_str[12]; uint8_t num_str_len; - - if(command == NULL || commands == NULL) - { + 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"); + 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"); + if (command->frag_seq == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); goto done; } - - - for(i = 0; i < key_count; i ++) - { + + 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"); + 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"); + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); slot_num = -1; goto done; } @@ -2705,11 +2535,11 @@ static int command_pre_fragment(redisClusterContext *cc, sub_kp = hiarray_push(sub_command->keys); if (sub_kp == NULL) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); slot_num = -1; goto done; } - + sub_kp->start = kp->start; sub_kp->end = kp->end; @@ -2723,18 +2553,18 @@ static int command_pre_fragment(redisClusterContext *cc, uint32_t len = 0; char *p; - for (p = sub_kp->end + 1; !isdigit(*p); p++){} - + for (p = sub_kp->end + 1; !isdigit(*p); p++) { + } + p = sub_kp->end + 1; - while(!isdigit(*p)) - { - p ++; + while (!isdigit(*p)) { + p++; } - for (; 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; @@ -2742,29 +2572,29 @@ static int command_pre_fragment(redisClusterContext *cc, } } - for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ + for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ sub_command = sub_commands[i]; if (sub_command == NULL) { continue; } - idx = 0; + 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 ++; + 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"); + 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; } @@ -2774,9 +2604,8 @@ static int command_pre_fragment(redisClusterContext *cc, 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 ++) - { + + 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); @@ -2794,20 +2623,20 @@ static int command_pre_fragment(redisClusterContext *cc, } } 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 ++; + 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"); + 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; } @@ -2818,8 +2647,7 @@ static int command_pre_fragment(redisClusterContext *cc, 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 ++) - { + 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); @@ -2837,22 +2665,22 @@ static int command_pre_fragment(redisClusterContext *cc, } } 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->clen += 3 * sub_command->narg; sub_command->narg *= 2; - 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"); + 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; } @@ -2862,9 +2690,8 @@ static int command_pre_fragment(redisClusterContext *cc, 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 ++) - { + + 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); @@ -2875,17 +2702,18 @@ static int command_pre_fragment(redisClusterContext *cc, 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); + 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); - + // 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); @@ -2893,18 +2721,14 @@ static int command_pre_fragment(redisClusterContext *cc, done: - if(sub_commands != NULL) - { + if (sub_commands != NULL) { hi_free(sub_commands); } - if(slot_num >= 0 && commands != NULL - && listLength(commands) == 1) - { + if (slot_num >= 0 && commands != NULL && listLength(commands) == 1) { listNode *list_node = listFirst(commands); listDelNode(commands, list_node); - if(command->frag_seq) - { + if (command->frag_seq) { hi_free(command->frag_seq); command->frag_seq = NULL; } @@ -2915,60 +2739,57 @@ static int command_pre_fragment(redisClusterContext *cc, return slot_num; } -static void *command_post_fragment(redisClusterContext *cc, - struct cmd *command, hilist *commands) -{ +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) - { + while ((list_node = listNext(list_iter)) != NULL) { sub_command = list_node->value; reply = sub_command->reply; - if(reply == NULL) - { + if (reply == NULL) { return NULL; - } - else if(reply->type == REDIS_REPLY_ERROR) - { + } 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)"); + 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)"); + } 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)"); + } 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 { + } else { NOT_REACHED(); } } - reply = hi_calloc(1,sizeof(*reply)); + reply = hi_calloc(1, sizeof(*reply)); - if (reply == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + if (reply == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); return NULL; } @@ -2984,102 +2805,93 @@ static void *command_post_fragment(redisClusterContext *cc, reply->element = hi_calloc(key_count, sizeof(*reply)); if (reply->element == NULL) { freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + __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 */ + + 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"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "sub reply is null"); return NULL; } - if(sub_reply->type == REDIS_REPLY_STRING) - { + 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) - { + } else if (sub_reply->type == REDIS_REPLY_ARRAY) { + if (sub_reply->elements == 0) { freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OTHER,"sub reply elements error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "sub reply elements error"); return NULL; } - + reply->element[i] = sub_reply->element[sub_reply->elements - 1]; - sub_reply->elements --; + sub_reply->elements--; } } - }else if(command->type == CMD_REQ_REDIS_DEL){ + } else if (command->type == CMD_REQ_REDIS_DEL) { reply->type = REDIS_REPLY_INTEGER; reply->integer = count; - }else if(command->type == CMD_REQ_REDIS_MSET){ + } 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) - { + reply->str = hi_alloc((str_len + 1) * sizeof(char *)); + if (reply->str == NULL) { freeReplyObject(reply); - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + __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 { + } 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. + * 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) -{ +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) - { + 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"); + 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) - { + } 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)"); + 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) - { + } 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; @@ -3090,22 +2902,21 @@ static int command_format_by_slot(redisClusterContext *cc, 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) - { +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) { +void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, + int len) { redisReply *reply = NULL; int slot_num; struct cmd *command = NULL, *sub_command; @@ -3113,31 +2924,27 @@ void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) listNode *list_node; listIter *list_iter = NULL; - if(cc == NULL) - { + if (cc == NULL) { return NULL; } - if(cc->err) - { + 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"); + 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"); + if (commands == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); goto error; } @@ -3145,19 +2952,15 @@ void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) slot_num = command_format_by_slot(cc, command, commands); - if(slot_num < 0) - { + if (slot_num < 0) { goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); + } 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) - { + // all keys belong to one slot + if (listLength(commands) == 0) { reply = redis_cluster_command_execute(cc, command); goto done; } @@ -3165,17 +2968,13 @@ void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) ASSERT(listLength(commands) != 1); list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { + while ((list_node = listNext(list_iter)) != NULL) { sub_command = list_node->value; - + reply = redis_cluster_command_execute(cc, sub_command); - if(reply == NULL) - { + if (reply == NULL) { goto error; - } - else if(reply->type == REDIS_REPLY_ERROR) - { + } else if (reply->type == REDIS_REPLY_ERROR) { goto done; } @@ -3183,68 +2982,63 @@ void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len) } reply = command_post_fragment(cc, command, commands); - + done: command->cmd = NULL; command_destroy(command); - if(commands != NULL) - { + if (commands != NULL) { listRelease(commands); } - if(list_iter != NULL) - { + if (list_iter != NULL) { listReleaseIterator(list_iter); } cc->retry_count = 0; - + return reply; error: - if(command != NULL) - { + if (command != NULL) { command->cmd = NULL; command_destroy(command); } - if(commands != NULL) - { + if (commands != NULL) { listRelease(commands); } - if(list_iter != NULL) - { + if (list_iter != NULL) { listReleaseIterator(list_iter); } cc->retry_count = 0; - + return NULL; } -void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap) { +void *redisClustervCommand(redisClusterContext *cc, const char *format, + va_list ap) { redisReply *reply; char *cmd; int len; - if(cc == NULL) - { + if (cc == NULL) { return NULL; } - len = redisvFormatCommand(&cmd,format,ap); + len = redisvFormatCommand(&cmd, format, ap); if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); return NULL; } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "Invalid format string"); return NULL; - } + } reply = redisClusterFormattedCommand(cc, cmd, len); @@ -3256,25 +3050,26 @@ void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list void *redisClusterCommand(redisClusterContext *cc, const char *format, ...) { va_list ap; redisReply *reply = NULL; - - va_start(ap,format); + + 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) { +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); + len = redisFormatCommandArgv(&cmd, argc, argv, argvlen); if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); return NULL; } - + reply = redisClusterFormattedCommand(cc, cmd, len); free(cmd); @@ -3282,40 +3077,36 @@ void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **ar return reply; } -int redisClusterAppendFormattedCommand(redisClusterContext *cc, - char *cmd, int len) { +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) - { + if (cc->requests == NULL) { cc->requests = listCreate(); - if(cc->requests == NULL) - { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + 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"); + 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"); + if (commands == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); goto error; } @@ -3323,25 +3114,18 @@ int redisClusterAppendFormattedCommand(redisClusterContext *cc, slot_num = command_format_by_slot(cc, command, commands); - if(slot_num < 0) - { + if (slot_num < 0) { goto error; - } - else if(slot_num >= REDIS_CLUSTER_SLOTS) - { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"slot_num is out of range"); + } 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) - { + // all keys belong to one slot + if (listLength(commands) == 0) { + if (__redisClusterAppendCommand(cc, command) == REDIS_OK) { goto done; - } - else - { + } else { goto error; } } @@ -3349,92 +3133,76 @@ int redisClusterAppendFormattedCommand(redisClusterContext *cc, ASSERT(listLength(commands) != 1); list_iter = listGetIterator(commands, AL_START_HEAD); - while((list_node = listNext(list_iter)) != NULL) - { + while ((list_node = listNext(list_iter)) != NULL) { sub_command = list_node->value; - - if(__redisClusterAppendCommand(cc, sub_command) == REDIS_OK) - { + + if (__redisClusterAppendCommand(cc, sub_command) == REDIS_OK) { continue; - } - else - { + } else { goto error; } } done: - if(command->cmd != NULL) - { + if (command->cmd != NULL) { command->cmd = NULL; - } - else - { + } else { goto error; } - if(commands != NULL) - { - if(listLength(commands) > 0) - { + if (commands != NULL) { + if (listLength(commands) > 0) { command->sub_commands = commands; - } - else - { + } else { listRelease(commands); } } - if(list_iter != NULL) - { + if (list_iter != NULL) { listReleaseIterator(list_iter); } listAddNodeTail(cc->requests, command); - + return REDIS_OK; error: - if(command != NULL) - { + if (command != NULL) { command->cmd = NULL; command_destroy(command); } - if(commands != NULL) - { + if (commands != NULL) { listRelease(commands); } - if(list_iter != NULL) - { + if (list_iter != NULL) { listReleaseIterator(list_iter); } - /* Attention: mybe here we must pop the - sub_commands that had append to the nodes. + /* 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 redisClustervAppendCommand(redisClusterContext *cc, const char *format, + va_list ap) { int ret; char *cmd; int len; - - len = redisvFormatCommand(&cmd,format,ap); + + len = redisvFormatCommand(&cmd, format, ap); if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + __redisClusterSetError(cc, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } else if (len == -2) { - __redisClusterSetError(cc,REDIS_ERR_OTHER,"Invalid format string"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "Invalid format string"); return REDIS_ERR; - } + } ret = redisClusterAppendFormattedCommand(cc, cmd, len); @@ -3443,95 +3211,87 @@ int redisClustervAppendCommand(redisClusterContext *cc, return ret; } -int redisClusterAppendCommand(redisClusterContext *cc, - const char *format, ...) { +int redisClusterAppendCommand(redisClusterContext *cc, const char *format, + ...) { int ret; va_list ap; - if(cc == NULL || format == NULL) - { + if (cc == NULL || format == NULL) { return REDIS_ERR; } - - va_start(ap,format); + + 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 redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, + const char **argv, const size_t *argvlen) { int ret; char *cmd; int len; - len = redisFormatCommandArgv(&cmd,argc,argv,argvlen); + len = redisFormatCommandArgv(&cmd, argc, argv, argvlen); if (len == -1) { - __redisClusterSetError(cc,REDIS_ERR_OOM,"Out of memory"); + __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) -{ +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) - { + + if (cc == NULL || cc->nodes == NULL) { return REDIS_ERR; } di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { + while ((de = dictNext(di)) != NULL) { node = dictGetEntryVal(de); - if(node == NULL) - { + if (node == NULL) { continue; } - + c = ctx_get_by_node(cc, node); - if(c == NULL) - { + if (c == NULL) { continue; } if (c->flags & REDIS_BLOCK) { /* Write until done */ do { - if (redisBufferWrite(c,&wdone) == REDIS_ERR) - { + if (redisBufferWrite(c, &wdone) == REDIS_ERR) { dictReleaseIterator(di); return REDIS_ERR; } } while (!wdone); } } - + dictReleaseIterator(di); return REDIS_OK; } -static int redisCLusterClearAll(redisClusterContext *cc) -{ +static int redisCLusterClearAll(redisClusterContext *cc) { dictIterator *di; dictEntry *de; struct cluster_node *node; redisContext *c = NULL; - + if (cc == NULL) { return REDIS_ERR; } @@ -3545,26 +3305,23 @@ static int redisCLusterClearAll(redisClusterContext *cc) return REDIS_ERR; } di = dictGetIterator(cc->nodes); - while((de = dictNext(di)) != NULL) - { + while ((de = dictNext(di)) != NULL) { node = dictGetEntryVal(de); - if(node == NULL) - { + if (node == NULL) { continue; } c = node->con; - if(c == NULL) - { + if (c == NULL) { continue; } redisFree(c); node->con = NULL; } - + dictReleaseIterator(di); - + return REDIS_OK; } @@ -3577,7 +3334,7 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { int slot_num; void *sub_reply; - if(cc == NULL || reply == NULL) + if (cc == NULL || reply == NULL) return REDIS_ERR; cc->err = 0; @@ -3590,59 +3347,50 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { list_command = listFirst(cc->requests); - //no more reply - if(list_command == NULL) - { + // 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"); + 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) - { + 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"); + 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) - { + 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"); + 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"); + 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) - { + + if (__redisClusterGetReply(cc, slot_num, &sub_reply) != REDIS_OK) { goto error; } @@ -3650,8 +3398,7 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { } *reply = command_post_fragment(cc, command, commands); - if(*reply == NULL) - { + if (*reply == NULL) { goto error; } @@ -3664,13 +3411,11 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { return REDIS_ERR; } -void redisClusterReset(redisClusterContext *cc) -{ +void redisClusterReset(redisClusterContext *cc) { int status; void *reply; - - if(cc == NULL || cc->nodes == NULL) - { + + if (cc == NULL || cc->nodes == NULL) { return; } @@ -3678,7 +3423,7 @@ void redisClusterReset(redisClusterContext *cc) redisCLusterClearAll(cc); } else { redisCLusterSendAll(cc); - + do { status = redisClusterGetReply(cc, &reply); if (status == REDIS_OK) { @@ -3687,21 +3432,19 @@ void redisClusterReset(redisClusterContext *cc) redisCLusterClearAll(cc); break; } - } while(reply != NULL); + } while (reply != NULL); } - - if(cc->requests) - { + + if (cc->requests) { listRelease(cc->requests); cc->requests = NULL; } - if(cc->need_update_route) - { + if (cc->need_update_route) { status = cluster_update_route(cc); - if(status != REDIS_OK) - { - __redisClusterSetError(cc, REDIS_ERR_OTHER, + if (status != REDIS_OK) { + __redisClusterSetError( + cc, REDIS_ERR_OTHER, "route update error, please recreate redisClusterContext!"); return; } @@ -3711,16 +3454,16 @@ void redisClusterReset(redisClusterContext *cc) /*############redis cluster async############*/ -static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, - int type, const char *str) { - +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); + 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! */ @@ -3729,11 +3472,11 @@ static void __redisClusterAsyncSetError(redisClusterAsyncContext *acc, } } -static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext *cc) { +static redisClusterAsyncContext * +redisClusterAsyncInitialize(redisClusterContext *cc) { redisClusterAsyncContext *acc; - if(cc == NULL) - { + if (cc == NULL) { return NULL; } @@ -3759,13 +3502,11 @@ static redisClusterAsyncContext *redisClusterAsyncInitialize(redisClusterContext return acc; } -static cluster_async_data *cluster_async_data_get(void) -{ +static cluster_async_data *cluster_async_data_get(void) { cluster_async_data *cad; cad = hi_alloc(sizeof(cluster_async_data)); - if(cad == NULL) - { + if (cad == NULL) { return NULL; } @@ -3778,24 +3519,20 @@ static cluster_async_data *cluster_async_data_get(void) return cad; } -static void cluster_async_data_free(cluster_async_data *cad) -{ - if(cad == NULL) - { +static void cluster_async_data_free(cluster_async_data *cad) { + if (cad == NULL) { return; } - if(cad->command != NULL) - { + if (cad->command != NULL) { command_destroy(cad->command); } - + hi_free(cad); cad = NULL; } -static void unlinkAsyncContextAndNode(void *data) -{ +static void unlinkAsyncContextAndNode(void *data) { cluster_node *node; if (data) { @@ -3804,19 +3541,16 @@ static void unlinkAsyncContextAndNode(void *data) } } -redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, - cluster_node *node) -{ +redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, + cluster_node *node) { redisAsyncContext *ac; - - if(node == NULL) - { + + if (node == NULL) { return NULL; } ac = node->acon; - if(ac != NULL) - { + if (ac != NULL) { if (ac->c.err == 0) { return ac; } else { @@ -3824,94 +3558,85 @@ redisAsyncContext * actx_get_by_node(redisClusterAsyncContext *acc, } } - if(node->host == NULL || node->port <= 0) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); + 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"); + if (ac == NULL) { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "node host or port is error"); return NULL; } #ifdef SSL_SUPPORT - if(acc->cc->ssl) { - if(redisInitiateSSLWithContext(&ac->c, acc->cc->ssl) != REDIS_OK) { + if (acc->cc->ssl) { + if (redisInitiateSSLWithContext(&ac->c, acc->cc->ssl) != REDIS_OK) { __redisClusterAsyncSetError(acc, ac->c.err, ac->c.errstr); return NULL; } } #endif - if(acc->adapter) - { + if (acc->adapter) { acc->attach_fn(ac, acc->adapter); } - if(acc->onConnect) - { + if (acc->onConnect) { redisAsyncSetConnectCallback(ac, acc->onConnect); } - if(acc->onDisconnect) - { + if (acc->onDisconnect) { redisAsyncSetDisconnectCallback(ac, acc->onDisconnect); } ac->data = node; ac->dataCleanup = unlinkAsyncContextAndNode; node->acon = ac; - + return ac; } -static redisAsyncContext *actx_get_after_update_route_by_slot( - redisClusterAsyncContext *acc, int slot_num) -{ +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) - { + if (acc == NULL || slot_num < 0) { return NULL; } cc = acc->cc; - if(cc == NULL) - { + if (cc == NULL) { return NULL; } - + ret = cluster_update_route(cc); - if(ret != REDIS_OK) - { - __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + 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"); + 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"); + if (ac == NULL) { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "actx get by node error"); return NULL; - } - else if(ac->err) - { + } else if (ac->err) { __redisClusterAsyncSetError(acc, ac->err, ac->errstr); return NULL; } @@ -3924,14 +3649,14 @@ redisClusterAsyncContext *redisClusterAsyncContextInit() { redisClusterAsyncContext *acc; cc = redisClusterContextInit(); - if(cc == NULL) { + if (cc == NULL) { return NULL; } cc->flags &= ~REDIS_BLOCK; acc = redisClusterAsyncInitialize(cc); - if(acc == NULL) { + if (acc == NULL) { redisClusterFree(cc); return NULL; } @@ -3939,14 +3664,14 @@ redisClusterAsyncContext *redisClusterAsyncContextInit() { return acc; } -redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) { +redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, + int flags) { redisClusterContext *cc; redisClusterAsyncContext *acc; cc = redisClusterConnectNonBlock(addrs, flags); - if(cc == NULL) - { + if (cc == NULL) { return NULL; } @@ -3959,10 +3684,8 @@ redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags) return acc; } - -int redisClusterAsyncSetConnectCallback( - redisClusterAsyncContext *acc, redisConnectCallback *fn) -{ +int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, + redisConnectCallback *fn) { if (acc->onConnect == NULL) { acc->onConnect = fn; return REDIS_OK; @@ -3970,9 +3693,8 @@ int redisClusterAsyncSetConnectCallback( return REDIS_ERR; } -int redisClusterAsyncSetDisconnectCallback( - redisClusterAsyncContext *acc, redisDisconnectCallback *fn) -{ +int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, + redisDisconnectCallback *fn) { if (acc->onDisconnect == NULL) { acc->onDisconnect = fn; return REDIS_OK; @@ -3980,7 +3702,8 @@ int redisClusterAsyncSetDisconnectCallback( return REDIS_ERR; } -static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *privdata) { +static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, + void *privdata) { int ret; redisReply *reply = r; cluster_async_data *cad = privdata; @@ -3992,108 +3715,94 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv struct cmd *command; int64_t now, next; - if(cad == NULL) - { + if (cad == NULL) { goto error; } acc = cad->acc; - if(acc == NULL) - { + if (acc == NULL) { goto error; } cc = acc->cc; - if(cc == NULL) - { + if (cc == NULL) { goto error; } command = cad->command; - if(command == NULL) - { + 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 - + + 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) - { + + __redisClusterAsyncSetError(acc, ac->err, ac->errstr); + + if (cc->update_route_time != 0) { now = hi_usec_now(); - if(now >= cc->update_route_time) - { + 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!"); + 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) - { + + 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) - { + 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); + + 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); + cluster_timeout = + hi_atoi(cluster_timeout_str, cluster_timeout_str_len); free(cluster_timeout_str); - if(cluster_timeout <= 0) - { - __redisClusterAsyncSetError(acc, - REDIS_ERR_OTHER, + 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"); + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "get now usec time error"); goto done; } next = now + (cluster_timeout * 1000LL); cc->update_route_time = next; - } goto done; @@ -4101,64 +3810,54 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv 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) - { + 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"); + __redisClusterAsyncSetError(acc, + REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT, + "too many cluster redirect"); goto done; } - - switch(error_type) - { + + switch (error_type) { case CLUSTER_ERR_MOVED: - ac_retry = actx_get_after_update_route_by_slot(acc, command->slot_num); - if(ac_retry == NULL) - { + 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); + 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"); + 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); + } 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) - { + 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: @@ -4171,29 +3870,23 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv done: - if(acc->err) - { + if (acc->err) { cad->callback(acc, NULL, cad->privdata); - } - else - { + } else { cad->callback(acc, r, cad->privdata); } - if(cc->err) - { + if (cc->err) { cc->err = 0; memset(cc->errstr, '\0', strlen(cc->errstr)); } - if(acc->err) - { + if (acc->err) { acc->err = 0; memset(acc->errstr, '\0', strlen(acc->errstr)); } - - if(cad != NULL) - { + + if (cad != NULL) { cluster_async_data_free(cad); } @@ -4201,26 +3894,25 @@ static void redisClusterAsyncCallback(redisAsyncContext *ac, void *r, void *priv retry: - ret = redisAsyncFormattedCommand(ac_retry, - redisClusterAsyncCallback,cad,command->cmd,command->clen); - if(ret != REDIS_OK) - { + ret = redisAsyncFormattedCommand(ac_retry, redisClusterAsyncCallback, cad, + command->cmd, command->clen); + if (ret != REDIS_OK) { goto error; } - + return; error: - if(cad != NULL) - { + if (cad != NULL) { cluster_async_data_free(cad); } } -int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, char *cmd, int len) { - +int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, + void *privdata, char *cmd, int len) { + redisClusterContext *cc; int status = REDIS_OK; int slot_num; @@ -4230,45 +3922,39 @@ int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, hilist *commands = NULL; cluster_async_data *cad; - if(acc == NULL) - { + if (acc == NULL) { return REDIS_ERR; } cc = acc->cc; - if(cc->err) - { + if (cc->err) { cc->err = 0; memset(cc->errstr, '\0', strlen(cc->errstr)); } - if(acc->err) - { + 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"); + 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"); + + 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"); + if (commands == NULL) { + __redisClusterAsyncSetError(acc, REDIS_ERR_OOM, "Out of memory"); goto error; } @@ -4276,54 +3962,45 @@ int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, slot_num = command_format_by_slot(cc, command, commands); - if(slot_num < 0) - { - __redisClusterAsyncSetError(acc, - cc->err, cc->errstr); + 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"); + } 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) - { + // all keys not belong to one slot + if (listLength(commands) > 0) { ASSERT(listLength(commands) != 1); - - __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER, + + __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"); + 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"); + if (ac == NULL) { + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "actx get by node error"); goto error; - } - else if(ac->err) - { + } 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"); + if (cad == NULL) { + __redisClusterAsyncSetError(acc, REDIS_ERR_OOM, "Out of memory"); goto error; } @@ -4331,54 +4008,50 @@ int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, cad->command = command; cad->callback = fn; cad->privdata = privdata; - - status = redisAsyncFormattedCommand(ac, - redisClusterAsyncCallback,cad,cmd,len); - if(status != REDIS_OK) - { + + status = redisAsyncFormattedCommand(ac, redisClusterAsyncCallback, cad, cmd, + len); + if (status != REDIS_OK) { goto error; } - if(commands != NULL) - { + if (commands != NULL) { listRelease(commands); } return REDIS_OK; -error: - - if(command != NULL) - { +error: + + if (command != NULL) { command_destroy(command); } - if(commands != NULL) - { + if (commands != NULL) { listRelease(commands); } return REDIS_ERR; } - -int redisClustervAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap) { +int redisClustervAsyncCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, + const char *format, va_list ap) { int ret; char *cmd; int len; - if(acc == NULL) - { + if (acc == NULL) { return REDIS_ERR; } - len = redisvFormatCommand(&cmd,format,ap); + len = redisvFormatCommand(&cmd, format, ap); if (len == -1) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + __redisClusterAsyncSetError(acc, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } else if (len == -2) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OTHER,"Invalid format string"); + __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, + "Invalid format string"); return REDIS_ERR; } @@ -4389,27 +4062,30 @@ int redisClustervAsyncCommand(redisClusterAsyncContext *acc, return ret; } -int redisClusterAsyncCommand(redisClusterAsyncContext *acc, - redisClusterCallbackFn *fn, void *privdata, const char *format, ...) { +int redisClusterAsyncCommand(redisClusterAsyncContext *acc, + redisClusterCallbackFn *fn, void *privdata, + const char *format, ...) { int ret; va_list ap; - va_start(ap,format); + 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 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); + + len = redisFormatCommandArgv(&cmd, argc, argv, argvlen); if (len == -1) { - __redisClusterAsyncSetError(acc,REDIS_ERR_OOM,"Out of memory"); + __redisClusterAsyncSetError(acc, REDIS_ERR_OOM, "Out of memory"); return REDIS_ERR; } @@ -4429,8 +4105,7 @@ void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { dict *nodes; struct cluster_node *node; - if(acc == NULL) - { + if (acc == NULL) { return; } @@ -4438,21 +4113,18 @@ void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { nodes = cc->nodes; - if(nodes == NULL) - { + if (nodes == NULL) { return; } - + di = dictGetIterator(nodes); - while((de = dictNext(di)) != NULL) - { + while ((de = dictNext(di)) != NULL) { node = dictGetEntryVal(de); ac = node->acon; - if(ac == NULL || ac->err) - { + if (ac == NULL || ac->err) { continue; } @@ -4464,12 +4136,10 @@ void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc) { dictReleaseIterator(di); } -void redisClusterAsyncFree(redisClusterAsyncContext *acc) -{ +void redisClusterAsyncFree(redisClusterAsyncContext *acc) { redisClusterContext *cc; - - if(acc == NULL) - { + + if (acc == NULL) { return; } @@ -4479,4 +4149,3 @@ void redisClusterAsyncFree(redisClusterAsyncContext *acc) hi_free(acc); } - diff --git a/hircluster.h b/hircluster.h index 228ebece..b1b9decf 100644 --- a/hircluster.h +++ b/hircluster.h @@ -2,8 +2,8 @@ #ifndef __HIRCLUSTER_H #define __HIRCLUSTER_H -#include #include +#include #define UNUSED(x) (void)(x) #ifdef SSL_SUPPORT @@ -17,60 +17,57 @@ #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 +#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 -{ +typedef struct cluster_node { sds name; sds addr; sds host; int port; uint8_t role; - uint8_t myself; /* myself ? */ + 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; + void *data; /* Not used by hiredis */ + struct hiarray *migrating; /* copen_slot[] */ + struct hiarray *importing; /* copen_slot[] */ +} cluster_node; -typedef struct cluster_slot -{ +typedef struct cluster_slot { uint32_t start; uint32_t end; cluster_node *node; /* master that this slot region belong to */ -}cluster_slot; +} 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 */ +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; +} copen_slot; #ifdef __cplusplus extern "C" { @@ -78,15 +75,15 @@ extern "C" { /* Context for a connection to Redis cluster */ typedef struct redisClusterContext { - int err; /* Error flags, 0 when there is no error */ + int err; /* Error flags, 0 when there is no error */ char errstr[128]; /* String representation of error when applicable */ int flags; struct timeval *connect_timeout; - struct timeval *timeout; /* receive and send timeout. */ - + struct timeval *timeout; /* receive and send timeout. */ + struct hiarray *slots; struct dict *nodes; @@ -109,8 +106,9 @@ typedef struct redisClusterContext { } redisClusterContext; redisClusterContext *redisClusterConnect(const char *addrs, int flags); -redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, - const struct timeval tv, int flags); +redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, + const struct timeval tv, + int flags); redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); redisClusterContext *redisClusterContextInit(void); @@ -123,48 +121,61 @@ int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc); int redisClusterSetOptionParseSlaves(redisClusterContext *cc); int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc); int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); -int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); +int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, + const struct timeval tv); +int redisClusterSetOptionTimeout(redisClusterContext *cc, + const struct timeval tv); +int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, + int max_redirect_count); #ifdef SSL_SUPPORT -int redisClusterSetOptionEnableSSL(redisClusterContext *cc, redisSSLContext *ssl); +int redisClusterSetOptionEnableSSL(redisClusterContext *cc, + redisSSLContext *ssl); #endif int redisClusterConnect2(redisClusterContext *cc); -void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); +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 *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); +void *redisClusterCommandArgv(redisClusterContext *cc, int argc, + const char **argv, const size_t *argvlen); -redisContext *ctx_get_by_node(redisClusterContext *cc, struct cluster_node *node); +redisContext *ctx_get_by_node(redisClusterContext *cc, + struct cluster_node *node); -int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); -int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); +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 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); - +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 int(adapterAttachFn)(redisAsyncContext *, void *); -typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, 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. */ @@ -187,17 +198,30 @@ typedef struct redisClusterAsyncContext { } redisClusterAsyncContext; redisClusterAsyncContext *redisClusterAsyncContextInit(void); -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); +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); +redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, + cluster_node *node); #ifdef __cplusplus } diff --git a/hiutil.c b/hiutil.c index 087d04c7..f8e9908e 100644 --- a/hiutil.c +++ b/hiutil.c @@ -1,27 +1,25 @@ +#include "win32.h" +#include +#include +#include #include #include -#include #include -#include -#include -#include "win32.h" #ifndef WIN32 -#include #include #include +#include #endif -#include #include "hiutil.h" +#include #ifdef HI_HAVE_BACKTRACE -# include +#include #endif #ifndef WIN32 -int -hi_set_blocking(int sd) -{ +int hi_set_blocking(int sd) { int flags; flags = fcntl(sd, F_GETFL, 0); @@ -32,9 +30,7 @@ hi_set_blocking(int sd) return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK); } -int -hi_set_nonblocking(int sd) -{ +int hi_set_nonblocking(int sd) { int flags; flags = fcntl(sd, F_GETFL, 0); @@ -45,9 +41,7 @@ hi_set_nonblocking(int sd) return fcntl(sd, F_SETFL, flags | O_NONBLOCK); } -int -hi_set_reuseaddr(int sd) -{ +int hi_set_reuseaddr(int sd) { int reuse; socklen_t len; @@ -65,9 +59,7 @@ hi_set_reuseaddr(int sd) * 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 hi_set_tcpnodelay(int sd) { int nodelay; socklen_t len; @@ -77,9 +69,7 @@ hi_set_tcpnodelay(int sd) return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); } -int -hi_set_linger(int sd, int timeout) -{ +int hi_set_linger(int sd, int timeout) { struct linger linger; socklen_t len; @@ -91,9 +81,7 @@ hi_set_linger(int sd, int timeout) return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); } -int -hi_set_sndbuf(int sd, int size) -{ +int hi_set_sndbuf(int sd, int size) { socklen_t len; len = sizeof(size); @@ -101,9 +89,7 @@ hi_set_sndbuf(int sd, int size) return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); } -int -hi_set_rcvbuf(int sd, int size) -{ +int hi_set_rcvbuf(int sd, int size) { socklen_t len; len = sizeof(size); @@ -111,9 +97,7 @@ hi_set_rcvbuf(int sd, int size) return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); } -int -hi_get_soerror(int sd) -{ +int hi_get_soerror(int sd) { int status, err; socklen_t len; @@ -128,9 +112,7 @@ hi_get_soerror(int sd) return status; } -int -hi_get_sndbuf(int sd) -{ +int hi_get_sndbuf(int sd) { int status, size; socklen_t len; @@ -145,9 +127,7 @@ hi_get_sndbuf(int sd) return size; } -int -hi_get_rcvbuf(int sd) -{ +int hi_get_rcvbuf(int sd) { int status, size; socklen_t len; @@ -163,9 +143,7 @@ hi_get_rcvbuf(int sd) } #endif -int -_hi_atoi(uint8_t *line, size_t n) -{ +int _hi_atoi(uint8_t *line, size_t n) { int value; if (n == 0) { @@ -187,60 +165,47 @@ _hi_atoi(uint8_t *line, size_t n) return value; } -void -_hi_itoa(uint8_t *s, int num) -{ +void _hi_itoa(uint8_t *s, int num) { uint8_t c; uint8_t sign = 0; - if(s == NULL) - { + if (s == NULL) { return; } uint32_t len, i; len = 0; - if(num < 0) - { + if (num < 0) { sign = 1; num = abs(num); - } - else if(num == 0) - { + } 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]; + while (num % 10 || num / 10) { + c = num % 10 + '0'; + num = num / 10; + s[len + 1] = s[len]; s[len] = c; - len ++; + len++; } - if(sign == 1) - { + if (sign == 1) { s[len++] = '-'; } s[len] = '\0'; - - for(i = 0; i < len/2; i ++) - { + + for (i = 0; i < len / 2; i++) { c = s[i]; - s[i] = s[len - i -1]; - s[len - i -1] = c; + s[i] = s[len - i - 1]; + s[len - i - 1] = c; } - } - -int -hi_valid_port(int n) -{ +int hi_valid_port(int n) { if (n < 1 || n > UINT16_MAX) { return 0; } @@ -248,44 +213,35 @@ hi_valid_port(int n) return 1; } -int _uint_len(uint32_t num) -{ +int _uint_len(uint32_t num) { int n = 0; - if(num == 0) - { + if (num == 0) { return 1; } - while(num != 0) - { - n ++; + while (num != 0) { + n++; num /= 10; } return n; } -void * -_hi_alloc(size_t size, const char *name, int line) -{ +void *_hi_alloc(size_t size, const char *name, int line) { void *p; ASSERT(size != 0); p = malloc(size); - if(name == NULL && line == 1) - { - + if (name == NULL && line == 1) { } return p; } -void * -_hi_zalloc(size_t size, const char *name, int line) -{ +void *_hi_zalloc(size_t size, const char *name, int line) { void *p; p = _hi_alloc(size, name, line); @@ -296,48 +252,34 @@ _hi_zalloc(size_t size, const char *name, int line) return p; } -void * -_hi_calloc(size_t nmemb, size_t size, const char *name, int line) -{ +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 *_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) - { - + if (name == NULL && line == 1) { } - + return p; } -void -_hi_free(void *ptr, const char *name, int line) -{ +void _hi_free(void *ptr, const char *name, int line) { ASSERT(ptr != NULL); - if(name == NULL && line == 1) - { - + if (name == NULL && line == 1) { } free(ptr); } -void -hi_stacktrace(int skip_count) -{ - if(skip_count > 0) - { - +void hi_stacktrace(int skip_count) { + if (skip_count > 0) { } #ifdef HI_HAVE_BACKTRACE @@ -361,12 +303,8 @@ hi_stacktrace(int skip_count) #endif } -void -hi_stacktrace_fd(int fd) -{ - if(fd > 0) - { - +void hi_stacktrace_fd(int fd) { + if (fd > 0) { } #ifdef HI_HAVE_BACKTRACE void *stack[64]; @@ -377,12 +315,10 @@ hi_stacktrace_fd(int fd) #endif } -void -hi_assert(const char *cond, const char *file, int line, int panic) -{ - +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(); @@ -390,9 +326,7 @@ hi_assert(const char *cond, const char *file, int line, int panic) abort(); } -int -_vscnprintf(char *buf, size_t size, const char *fmt, va_list args) -{ +int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args) { int n; n = vsnprintf(buf, size, fmt, args); @@ -411,16 +345,14 @@ _vscnprintf(char *buf, size_t size, const char *fmt, va_list args) return 0; } - if (n < (int) size) { + if (n < (int)size) { return n; } return (int)(size - 1); } -int -_scnprintf(char *buf, size_t size, const char *fmt, ...) -{ +int _scnprintf(char *buf, size_t size, const char *fmt, ...) { va_list args; int n; @@ -435,9 +367,7 @@ _scnprintf(char *buf, size_t size, const char *fmt, ...) /* * Send n bytes on a blocking descriptor */ -ssize_t -_hi_sendn(int sd, const void *vptr, size_t n) -{ +ssize_t _hi_sendn(int sd, const void *vptr, size_t n) { size_t nleft; ssize_t nsend; const char *ptr; @@ -466,9 +396,7 @@ _hi_sendn(int sd, const void *vptr, size_t n) /* * Recv n bytes from a blocking descriptor */ -ssize_t -_hi_recvn(int sd, void *vptr, size_t n) -{ +ssize_t _hi_recvn(int sd, void *vptr, size_t n) { size_t nleft; ssize_t nrecv; char *ptr; @@ -498,15 +426,13 @@ _hi_recvn(int sd, void *vptr, size_t n) /* * Return the current time in microseconds since Epoch */ -int64_t -hi_usec_now(void) -{ +int64_t hi_usec_now(void) { int64_t usec; #ifdef _MSC_VER LARGE_INTEGER counter, frequency; - if (!QueryPerformanceCounter(&counter) || !QueryPerformanceFrequency(&frequency)) - { + if (!QueryPerformanceCounter(&counter) || + !QueryPerformanceFrequency(&frequency)) { return -1; } @@ -529,40 +455,26 @@ hi_usec_now(void) /* * Return the current time in milliseconds since Epoch */ -int64_t -hi_msec_now(void) -{ - return hi_usec_now() / 1000LL; -} +int64_t hi_msec_now(void) { return hi_usec_now() / 1000LL; } -void print_string_with_length(char *s, size_t len) -{ +void print_string_with_length(char *s, size_t len) { char *token; - for(token = s; token <= s + len; 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) -{ +void print_string_with_length_fix_CRLF(char *s, size_t len) { char *token; - for(token = s; token < s + len; token ++) - { - if(*token == CR) - { + for (token = s; token < s + len; token++) { + if (*token == CR) { printf("\\r"); - } - else if(*token == LF) - { + } else if (*token == LF) { printf("\\n"); - } - else - { + } else { printf("%c", *token); } } printf("\n"); } - diff --git a/hiutil.h b/hiutil.h index 128f1407..a7e8b0f1 100644 --- a/hiutil.h +++ b/hiutil.h @@ -9,35 +9,35 @@ typedef SSIZE_T ssize_t; #endif -#define HI_OK 0 -#define HI_ERROR -1 -#define HI_EAGAIN -2 -#define HI_ENOMEM -3 +#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 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 NELEMS(a) ((sizeof(a)) / sizeof((a)[0])) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#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 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 \ +#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 \ +#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 +#define HI_MAXHOSTNAMELEN 256 /* * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral @@ -49,94 +49,89 @@ typedef int rstatus_t; /* return type */ * # 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 +#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)) && \ +#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) \ +#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) \ +#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) \ +#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 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) && \ +#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) && \ +#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) && \ +#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) && \ +#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) && \ +#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) && \ +#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) && \ +#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) && \ +#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) && \ +#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 hi_gethostname(_name, _len) gethostname((char *)_name, (size_t)_len) -#define uint_len(_n) \ - _uint_len((uint32_t)_n) +#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) #ifndef WIN32 int hi_set_blocking(int sd); @@ -158,29 +153,26 @@ 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_alloc(_s) _hi_alloc((size_t)(_s), __FILE__, __LINE__) -#define hi_zalloc(_s) \ - _hi_zalloc((size_t)(_s), __FILE__, __LINE__) +#define hi_zalloc(_s) _hi_zalloc((size_t)(_s), __FILE__, __LINE__) -#define hi_calloc(_n, _s) \ +#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_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) +#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); @@ -188,37 +180,29 @@ 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)); +#define hi_strndup(_s, _n) strndup((char *)(_s), (size_t)(_n)); #ifndef WIN32 /* * 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_sendn(_s, _b, _n) _hi_sendn(_s, _b, (size_t)(_n)) -#define hi_recvn(_s, _b, _n) \ - _hi_recvn(_s, _b, (size_t)(_n)) +#define hi_recvn(_s, _b, _n) _hi_recvn(_s, _b, (size_t)(_n)) #endif /* * 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_read(_d, _b, _n) read(_d, _b, (size_t)(_n)) -#define hi_readv(_d, _b, _n) \ - readv(_d, _b, (int)(_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_write(_d, _b, _n) write(_d, _b, (size_t)(_n)) -#define hi_writev(_d, _b, _n) \ - writev(_d, _b, (int)(_n)) +#define hi_writev(_d, _b, _n) writev(_d, _b, (int)(_n)) #ifndef WIN32 ssize_t _hi_sendn(int sd, const void *vptr, size_t n); @@ -232,21 +216,23 @@ ssize_t _hi_recvn(int sd, void *vptr, size_t n); */ #ifdef HI_ASSERT_PANIC -#define ASSERT(_x) do { \ - if (!(_x)) { \ - hi_assert(#_x, __FILE__, __LINE__, 1); \ - } \ -} while (0) +#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 ASSERT(_x) \ + do { \ + if (!(_x)) { \ + hi_assert(#_x, __FILE__, __LINE__, 0); \ + } \ + } while (0) #define NOT_REACHED() ASSERT(0) diff --git a/tests/main.c b/tests/main.c index 3456e369..1701c514 100644 --- a/tests/main.c +++ b/tests/main.c @@ -1,13 +1,12 @@ +#include "hircluster.h" #include #include -#include "hircluster.h" -int main(int argc, char **argv) -{ +int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); - struct timeval timeout = { 1, 500000 }; // 1.5s + struct timeval timeout = {1, 500000}; // 1.5s redisClusterContext *cc = redisClusterContextInit(); redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); @@ -20,11 +19,12 @@ int main(int argc, char **argv) exit(-1); } - redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + redisReply *reply = + (redisReply *)redisClusterCommand(cc, "SET %s %s", "key", "value"); printf("SET: %s\n", reply->str); freeReplyObject(reply); - redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + redisReply *reply2 = (redisReply *)redisClusterCommand(cc, "GET %s", "key"); printf("GET: %s\n", reply2->str); freeReplyObject(reply2); diff --git a/tests/main_async.c b/tests/main_async.c index cf28d601..3f77ee14 100644 --- a/tests/main_async.c +++ b/tests/main_async.c @@ -1,31 +1,31 @@ +#include "adapters/libevent.h" +#include "hircluster.h" #include #include -#include "hircluster.h" -#include "adapters/libevent.h" void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { - redisReply *reply = (redisReply*)r; + redisReply *reply = (redisReply *)r; if (reply == NULL) { if (cc->errstr) { printf("errstr: %s\n", cc->errstr); } return; } - printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisClusterAsyncDisconnect(cc); } void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { - redisReply *reply = (redisReply*)r; + redisReply *reply = (redisReply *)r; if (reply == NULL) { if (cc->errstr) { printf("errstr: %s\n", cc->errstr); } return; } - printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); } void connectCallback(const redisAsyncContext *ac, int status) { @@ -44,14 +44,13 @@ void disconnectCallback(const redisAsyncContext *ac, int status) { printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); } -int main(int argc, char **argv) -{ +int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); printf("Connecting...\n"); - redisClusterAsyncContext *cc = redisClusterAsyncConnect("127.0.0.1:30001", - HIRCLUSTER_FLAG_NULL); + redisClusterAsyncContext *cc = + redisClusterAsyncConnect("127.0.0.1:30001", HIRCLUSTER_FLAG_NULL); if (cc && cc->err) { printf("Error: %s\n", cc->errstr); return 1; @@ -63,27 +62,27 @@ int main(int argc, char **argv) redisClusterAsyncSetDisconnectCallback(cc, disconnectCallback); int status; - status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key", "value"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, setCallback, (char *)"THE_ID", + "SET %s %s", "key", "value"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } - status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, getCallback, (char *)"THE_ID", + "GET %s", "key"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } - status = redisClusterAsyncCommand(cc, setCallback, (char*)"THE_ID", "SET %s %s", "key2", "value2"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, setCallback, (char *)"THE_ID", + "SET %s %s", "key2", "value2"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } - status = redisClusterAsyncCommand(cc, getCallback, (char*)"THE_ID", "GET %s", "key2"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(cc, getCallback, (char *)"THE_ID", + "GET %s", "key2"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); } diff --git a/tests/main_async_tls.c b/tests/main_async_tls.c index 644f00e4..4ebb0334 100644 --- a/tests/main_async_tls.c +++ b/tests/main_async_tls.c @@ -1,33 +1,33 @@ +#include #include #include -#include -#include "hircluster.h" #include "adapters/libevent.h" +#include "hircluster.h" void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { - redisReply *reply = (redisReply*)r; + redisReply *reply = (redisReply *)r; if (reply == NULL) { if (cc->errstr) { printf("errstr: %s\n", cc->errstr); } return; } - printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); /* Disconnect after receiving the reply to GET */ redisClusterAsyncDisconnect(cc); } void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { - redisReply *reply = (redisReply*)r; + redisReply *reply = (redisReply *)r; if (reply == NULL) { if (cc->errstr) { printf("errstr: %s\n", cc->errstr); } return; } - printf("privdata: %s reply: %s\n", (char*)privdata, reply->str); + printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); } void connectCallback(const redisAsyncContext *ac, int status) { @@ -47,8 +47,7 @@ void disconnectCallback(const redisAsyncContext *ac, int status) { printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); } -int main(int argc, char **argv) -{ +int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); @@ -56,7 +55,8 @@ int main(int argc, char **argv) redisSSLContextError ssl_error; redisInitOpenSSL(); - ssl = redisCreateSSLContext("ca.crt", NULL, "client.crt", "client.key", NULL, &ssl_error); + ssl = redisCreateSSLContext("ca.crt", NULL, "client.crt", "client.key", + NULL, &ssl_error); if (!ssl) { printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error)); exit(1); @@ -80,15 +80,15 @@ int main(int argc, char **argv) redisClusterLibeventAttach(acc, base); int status; - status = redisClusterAsyncCommand(acc, setCallback, (char*)"THE_ID", "SET %s %s", "key", "value"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(acc, setCallback, (char *)"THE_ID", + "SET %s %s", "key", "value"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", acc->err, acc->errstr); } - status = redisClusterAsyncCommand(acc, getCallback, (char*)"THE_ID", "GET %s", "key"); - if(status != REDIS_OK) - { + status = redisClusterAsyncCommand(acc, getCallback, (char *)"THE_ID", + "GET %s", "key"); + if (status != REDIS_OK) { printf("error: err=%d errstr=%s\n", acc->err, acc->errstr); } diff --git a/tests/main_ipv6.c b/tests/main_ipv6.c index 8ca46b99..51ffebe7 100644 --- a/tests/main_ipv6.c +++ b/tests/main_ipv6.c @@ -1,14 +1,13 @@ +#include "hircluster.h" +#include #include #include -#include -#include "hircluster.h" -int main(int argc, char **argv) -{ +int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); - struct timeval timeout = { 1, 500000 }; // 1.5s + struct timeval timeout = {1, 500000}; // 1.5s redisClusterContext *cc = redisClusterContextInit(); assert(cc); @@ -24,11 +23,12 @@ int main(int argc, char **argv) exit(-1); } - redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); + redisReply *reply = + (redisReply *)redisClusterCommand(cc, "SET %s %s", "key", "value"); printf("SET: %s\n", reply->str); freeReplyObject(reply); - redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); + redisReply *reply2 = (redisReply *)redisClusterCommand(cc, "GET %s", "key"); printf("GET: %s\n", reply2->str); freeReplyObject(reply2); diff --git a/tests/main_tls.c b/tests/main_tls.c index cf0b79c4..00954b1d 100644 --- a/tests/main_tls.c +++ b/tests/main_tls.c @@ -1,9 +1,8 @@ +#include "hircluster.h" #include #include -#include "hircluster.h" -int main(int argc, char **argv) -{ +int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); @@ -11,13 +10,14 @@ int main(int argc, char **argv) redisSSLContextError ssl_error; redisInitOpenSSL(); - ssl = redisCreateSSLContext("ca.crt", NULL, "client.crt", "client.key", NULL, &ssl_error); + ssl = redisCreateSSLContext("ca.crt", NULL, "client.crt", "client.key", + NULL, &ssl_error); if (!ssl) { printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error)); exit(1); } - struct timeval timeout = { 1, 500000 }; // 1.5s + struct timeval timeout = {1, 500000}; // 1.5s redisClusterContext *cc = redisClusterContextInit(); redisClusterSetOptionAddNodes(cc, "127.0.0.1:31001"); @@ -32,18 +32,17 @@ int main(int argc, char **argv) exit(-1); } - redisReply *reply = (redisReply*)redisClusterCommand(cc, "SET %s %s", "key", "value"); - if (!reply) - { + redisReply *reply = + (redisReply *)redisClusterCommand(cc, "SET %s %s", "key", "value"); + if (!reply) { printf("Reply missing: %s\n", cc->errstr); exit(-1); } printf("SET: %s\n", reply->str); freeReplyObject(reply); - redisReply *reply2 = (redisReply*)redisClusterCommand(cc, "GET %s", "key"); - if (!reply2) - { + redisReply *reply2 = (redisReply *)redisClusterCommand(cc, "GET %s", "key"); + if (!reply2) { printf("Reply missing: %s\n", cc->errstr); exit(-1); } diff --git a/win32.h b/win32.h index a2ad24cc..7f7aea8e 100644 --- a/win32.h +++ b/win32.h @@ -25,14 +25,14 @@ #endif #ifndef va_copy -#define va_copy(d,s) ((d) = (s)) +#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) -{ +__inline int c99_vsnprintf(char *str, size_t size, const char *format, + va_list ap) { int count = -1; if (size != 0) @@ -43,8 +43,7 @@ __inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list a return count; } -__inline int c99_snprintf(char* str, size_t size, const char* format, ...) -{ +__inline int c99_snprintf(char *str, size_t size, const char *format, ...) { int count; va_list ap; @@ -58,7 +57,7 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...) #endif /* _MSC_VER */ #ifdef _WIN32 -#define strerror_r(errno,buf,len) strerror_s(buf,len,errno) +#define strerror_r(errno, buf, len) strerror_s(buf, len, errno) #endif /* _WIN32 */ #endif /* _WIN32_HELPER_INCLUDE */ From 8042f9a3491b2da69ccffb46fd752d0a51e48c4e Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Tue, 10 Nov 2020 20:28:19 +0100 Subject: [PATCH 074/273] Use both clang and gcc in CI builds Also correcting build warnings --- .github/workflows/ci.yml | 31 +++++++++++++++++++------------ tests/CMakeLists.txt | 7 +++++++ tests/main_async.c | 4 ++-- tests/main_async_tls.c | 4 ++-- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd8a3671..aaec3ea1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,38 +7,45 @@ env: BUILD_TYPE: Release jobs: + checkers: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run clang-format style check (.c and .h) + uses: jidicula/clang-format-action@master + build: - # The CMake configure and build commands are platform agnostic and should work equally - # well on Windows or Mac. You can convert this to a matrix build if you need - # cross-platform coverage. - # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ubuntu-latest + strategy: + matrix: + compiler: [gcc, clang] + steps: - name: Prepare run: sudo apt install libevent-dev - uses: actions/checkout@v2 - - name: Run clang-format style check (.c and .h) - uses: jidicula/clang-format-action@master - - - name: Create out-of-source build folder - run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create build folder + run: cmake -E make_directory build - name: Generate makefiles + env: + CC: ${{ matrix.compiler }} + CXX: ${{ matrix.compiler }} # Use a bash shell so we can use the same syntax for environment variable # access regardless of the host operating system shell: bash - working-directory: ${{runner.workspace}}/build + working-directory: build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE - name: Build - working-directory: ${{runner.workspace}}/build shell: bash + working-directory: build run: cmake --build . --config $BUILD_TYPE # - name: Test - # working-directory: ${{runner.workspace}}/build + # working-directory: build # shell: bash # run: ctest -C $BUILD_TYPE diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f73bb3ac..3fa4494a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,13 @@ endif() find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) +if(MSVC) + # MS Visual: Suppress warnings + add_compile_options("/wd 4267" "/wd 4244") +else() + add_compile_options(-Wall -Wextra -pedantic -Werror) +endif() + # Executable: IPv4 add_executable(example_ipv4 main.c) target_link_libraries(example_ipv4 hiredis_cluster hiredis ${SSL_LIBRARY}) diff --git a/tests/main_async.c b/tests/main_async.c index 3f77ee14..3f41c95c 100644 --- a/tests/main_async.c +++ b/tests/main_async.c @@ -6,7 +6,7 @@ void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply *)r; if (reply == NULL) { - if (cc->errstr) { + if (cc->err) { printf("errstr: %s\n", cc->errstr); } return; @@ -20,7 +20,7 @@ void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply *)r; if (reply == NULL) { - if (cc->errstr) { + if (cc->err) { printf("errstr: %s\n", cc->errstr); } return; diff --git a/tests/main_async_tls.c b/tests/main_async_tls.c index 4ebb0334..adb064ad 100644 --- a/tests/main_async_tls.c +++ b/tests/main_async_tls.c @@ -8,7 +8,7 @@ void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply *)r; if (reply == NULL) { - if (cc->errstr) { + if (cc->err) { printf("errstr: %s\n", cc->errstr); } return; @@ -22,7 +22,7 @@ void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply *)r; if (reply == NULL) { - if (cc->errstr) { + if (cc->err) { printf("errstr: %s\n", cc->errstr); } return; From f8ef12be4da9df71c9b342cc4e41aa2d8352e1a5 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 11 Nov 2020 11:51:38 +0100 Subject: [PATCH 075/273] Enable asserts and backtrace in debug mode --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b666802..f916b048 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,10 @@ if(MSVC) target_compile_options(hiredis_cluster PRIVATE "/wd 4267" "/wd 4244") else() target_compile_options(hiredis_cluster PRIVATE -Wall -Wextra -pedantic -Werror) + + # Add extra defines when CMAKE_BUILD_TYPE is set to Debug + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DHI_ASSERT_PANIC -DHI_HAVE_BACKTRACE") + # Alternative: -DHI_ASSERT_LOG) endif() set_target_properties(hiredis_cluster From 746714d15417a37d413f63064df590046108638e Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 11 Nov 2020 16:14:19 +0100 Subject: [PATCH 076/273] Handle variable number of keys in EXISTS This handles multiple keys by distributing the query to different instances when needed. Uses existing functionality and there is a need to refactor a large chunk of this code.. Also see: https://redis.io/commands/exists --- command.c | 2 +- hiarray.c | 2 +- hircluster.c | 65 +++++++++++++++++++++++++++++++++++++++++--- tests/CMakeLists.txt | 4 +++ tests/ct_commands.c | 55 +++++++++++++++++++++++++++++++++++++ 5 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 tests/ct_commands.c diff --git a/command.c b/command.c index 4936ccd5..628849ff 100644 --- a/command.c +++ b/command.c @@ -30,7 +30,6 @@ static int redis_argz(struct cmd *r) { */ 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: @@ -222,6 +221,7 @@ static int redis_argn(struct cmd *r) { */ static int redis_argx(struct cmd *r) { switch (r->type) { + case CMD_REQ_REDIS_EXISTS: case CMD_REQ_REDIS_MGET: case CMD_REQ_REDIS_DEL: return 1; diff --git a/hiarray.c b/hiarray.c index c4dc6593..dbc6adb4 100644 --- a/hiarray.c +++ b/hiarray.c @@ -147,7 +147,7 @@ void hiarray_sort(struct hiarray *a, hiarray_compare_t compare) { int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) { uint32_t i, nelem; - ASSERT(array_n(a) != 0); + ASSERT(hiarray_n(a) != 0); ASSERT(func != NULL); for (i = 0, nelem = hiarray_n(a); i < nelem; i++) { diff --git a/hircluster.c b/hircluster.c index e2b2b31d..778a67df 100644 --- a/hircluster.c +++ b/hircluster.c @@ -2509,6 +2509,7 @@ static int command_pre_fragment(redisClusterContext *cc, struct cmd *command, goto done; } + // Fill sub_command with key, slot and command length (clen, only keylength) for (i = 0; i < key_count; i++) { kp = hiarray_get(command->keys, i); @@ -2543,6 +2544,7 @@ static int command_pre_fragment(redisClusterContext *cc, struct cmd *command, sub_kp->start = kp->start; sub_kp->end = kp->end; + // Number of characters in key key_len = (uint32_t)(kp->end - kp->start); sub_command->clen += key_len + uint_len(key_len); @@ -2572,7 +2574,8 @@ static int command_pre_fragment(redisClusterContext *cc, struct cmd *command, } } - for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { /* prepend command header */ + /* prepend command header */ + for (i = 0; i < REDIS_CLUSTER_SLOTS; i++) { sub_command = sub_commands[i]; if (sub_command == NULL) { continue; @@ -2647,6 +2650,48 @@ static int command_pre_fragment(redisClusterContext *cc, struct cmd *command, 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_EXISTS) { + //"*%d\r\n$6\r\nexists\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 += 15 + 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$6\r\nexists\r\n", 14); + idx += 14; + 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); @@ -2761,14 +2806,23 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, if (reply->type != REDIS_REPLY_ARRAY) { __redisClusterSetError( cc, REDIS_ERR_OTHER, - "reply type is error(here only can be array)"); + "reply type error"); 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)"); + "reply type error"); + return NULL; + } + + count += reply->integer; + } else if (command->type == CMD_REQ_REDIS_EXISTS) { + if (reply->type != REDIS_REPLY_INTEGER) { + __redisClusterSetError( + cc, REDIS_ERR_OTHER, + "reply type error"); return NULL; } @@ -2778,7 +2832,7 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, strcmp(reply->str, REDIS_STATUS_OK) != 0) { __redisClusterSetError( cc, REDIS_ERR_OTHER, - "reply type is error(here only can be status and ok)"); + "reply type error"); return NULL; } } else { @@ -2835,6 +2889,9 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, } else if (command->type == CMD_REQ_REDIS_DEL) { reply->type = REDIS_REPLY_INTEGER; reply->integer = count; + } else if (command->type == CMD_REQ_REDIS_EXISTS) { + 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); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3fa4494a..53dd5839 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,10 @@ add_executable(example_async main_async.c) target_link_libraries(example_async hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) add_test(NAME example_async COMMAND "$") +add_executable(ct_commands ct_commands.c) +target_link_libraries(ct_commands hiredis_cluster hiredis ${SSL_LIBRARY}) +add_test(NAME ct_commands COMMAND "$") + if(ENABLE_SSL) # Executable: tls add_executable(example_tls main_tls.c) diff --git a/tests/ct_commands.c b/tests/ct_commands.c new file mode 100644 index 00000000..b6fd4b4f --- /dev/null +++ b/tests/ct_commands.c @@ -0,0 +1,55 @@ +#include "hircluster.h" +#include +#include +#include +#include + +#define ASSERT_MSG(_x, _msg) if(!(_x)) {fprintf(stderr, "ERROR: %s\n", _msg); assert(_x); } +#define REPLY(_ctx, _reply) if(!(_reply)) {ASSERT_MSG(_reply, _ctx->errstr); } +#define REPLY_TYPE(_reply, _type) ASSERT_MSG((_reply->type == _type), "Reply type incorrect"); + +#define CHECK_REPLY_OK(_ctx, _reply) { REPLY(_ctx, _reply); REPLY_TYPE(_reply, REDIS_REPLY_STATUS); ASSERT_MSG((strcmp(_reply->str, "OK") == 0), _ctx->errstr); } +#define CHECK_REPLY_INT(_ctx, _reply, _value) { REPLY(_ctx, _reply); REPLY_TYPE(_reply, REDIS_REPLY_INTEGER); ASSERT_MSG((_reply->integer == _value), _ctx->errstr); } + +void test_exists(redisClusterContext *cc) { + redisReply *reply; + reply = (redisReply *)redisClusterCommand(cc, "SET key1 Hello"); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "EXISTS key1"); + CHECK_REPLY_INT(cc, reply, 1); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "EXISTS nosuchkey"); + CHECK_REPLY_INT(cc, reply, 0); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "SET key2 World"); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "EXISTS key1 key2 nosuchkey"); + CHECK_REPLY_INT(cc, reply, 2); + freeReplyObject(reply); +} + +int main(int argc, char **argv) { + UNUSED(argc); + UNUSED(argv); + + struct timeval timeout = {0, 500000}; + + redisClusterContext *cc = redisClusterContextInit(); + assert(cc); + redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); + redisClusterSetOptionConnectTimeout(cc, timeout); + redisClusterSetOptionRouteUseSlots(cc); + redisClusterConnect2(cc); + assert(cc->err == 0); + + test_exists(cc); + + redisClusterFree(cc); + return 0; +} From db21dbe43f9b530fae26e33135ddadd36818d324 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 11 Nov 2020 16:25:47 +0100 Subject: [PATCH 077/273] Formatting corrections --- hircluster.c | 16 ++++------------ tests/ct_commands.c | 28 +++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/hircluster.c b/hircluster.c index 778a67df..197b67fa 100644 --- a/hircluster.c +++ b/hircluster.c @@ -2804,25 +2804,19 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, if (command->type == CMD_REQ_REDIS_MGET) { if (reply->type != REDIS_REPLY_ARRAY) { - __redisClusterSetError( - cc, REDIS_ERR_OTHER, - "reply type error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); return NULL; } } else if (command->type == CMD_REQ_REDIS_DEL) { if (reply->type != REDIS_REPLY_INTEGER) { - __redisClusterSetError( - cc, REDIS_ERR_OTHER, - "reply type error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); return NULL; } count += reply->integer; } else if (command->type == CMD_REQ_REDIS_EXISTS) { if (reply->type != REDIS_REPLY_INTEGER) { - __redisClusterSetError( - cc, REDIS_ERR_OTHER, - "reply type error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); return NULL; } @@ -2830,9 +2824,7 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, } 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 error"); + __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); return NULL; } } else { diff --git a/tests/ct_commands.c b/tests/ct_commands.c index b6fd4b4f..52637a03 100644 --- a/tests/ct_commands.c +++ b/tests/ct_commands.c @@ -4,12 +4,30 @@ #include #include -#define ASSERT_MSG(_x, _msg) if(!(_x)) {fprintf(stderr, "ERROR: %s\n", _msg); assert(_x); } -#define REPLY(_ctx, _reply) if(!(_reply)) {ASSERT_MSG(_reply, _ctx->errstr); } -#define REPLY_TYPE(_reply, _type) ASSERT_MSG((_reply->type == _type), "Reply type incorrect"); +#define ASSERT_MSG(_x, _msg) \ + if (!(_x)) { \ + fprintf(stderr, "ERROR: %s\n", _msg); \ + assert(_x); \ + } +#define REPLY(_ctx, _reply) \ + if (!(_reply)) { \ + ASSERT_MSG(_reply, _ctx->errstr); \ + } +#define REPLY_TYPE(_reply, _type) \ + ASSERT_MSG((_reply->type == _type), "Reply type incorrect"); -#define CHECK_REPLY_OK(_ctx, _reply) { REPLY(_ctx, _reply); REPLY_TYPE(_reply, REDIS_REPLY_STATUS); ASSERT_MSG((strcmp(_reply->str, "OK") == 0), _ctx->errstr); } -#define CHECK_REPLY_INT(_ctx, _reply, _value) { REPLY(_ctx, _reply); REPLY_TYPE(_reply, REDIS_REPLY_INTEGER); ASSERT_MSG((_reply->integer == _value), _ctx->errstr); } +#define CHECK_REPLY_OK(_ctx, _reply) \ + { \ + REPLY(_ctx, _reply); \ + REPLY_TYPE(_reply, REDIS_REPLY_STATUS); \ + ASSERT_MSG((strcmp(_reply->str, "OK") == 0), _ctx->errstr); \ + } +#define CHECK_REPLY_INT(_ctx, _reply, _value) \ + { \ + REPLY(_ctx, _reply); \ + REPLY_TYPE(_reply, REDIS_REPLY_INTEGER); \ + ASSERT_MSG((_reply->integer == _value), _ctx->errstr); \ + } void test_exists(redisClusterContext *cc) { redisReply *reply; From 53f9667e18079321aac3708eff2ccf478dccfb4f Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Fri, 13 Nov 2020 16:43:29 +0100 Subject: [PATCH 078/273] Add support of password authentication (AUTH) (#3) When a password is configured it will be sent right after a connection to a Redis instance is setup, via the AUTH command. Added API: redisClusterSetOptionPassword(cc, "password"); --- hircluster.c | 87 ++++++++++++++++++++- hircluster.h | 6 ++ tests/CMakeLists.txt | 9 +++ tests/ct_connection.c | 176 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 tests/ct_connection.c diff --git a/hircluster.c b/hircluster.c index 197b67fa..aed2f015 100644 --- a/hircluster.c +++ b/hircluster.c @@ -424,6 +424,42 @@ static void cluster_open_slot_destroy(copen_slot *oslot) { hi_free(oslot); } +/** + * Handle password authentication in the synchronous API + */ +static int authenticate(redisClusterContext *cc, redisContext *c) { + if (cc == NULL || c == NULL) { + return REDIS_ERR; + } + + // Skip if no password configured + if (cc->password[0] == '\0') { + return REDIS_OK; + } + + redisReply *reply = redisCommand(c, "AUTH %s", cc->password); + if (reply == NULL) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, + "Command AUTH reply error (NULL)"); + goto error; + } + + if (reply->type == REDIS_REPLY_ERROR) { + __redisClusterSetError(cc, REDIS_ERR_OTHER, reply->str); + goto error; + } + + freeReplyObject(reply); + return REDIS_OK; + +error: + if (reply != NULL) { + freeReplyObject(reply); + reply = NULL; + } + return REDIS_ERR; +} + /** * Return a new node with the "cluster slots" command reply. */ @@ -1262,6 +1298,11 @@ static int cluster_update_route_by_addr(redisClusterContext *cc, const char *ip, } } #endif + + if (authenticate(cc, c) != REDIS_OK) { + goto error; + } + if (cc->flags & HIRCLUSTER_FLAG_ROUTE_USE_SLOTS) { reply = redisCommand(c, REDIS_COMMAND_CLUSTER_SLOTS); if (reply == NULL) { @@ -1583,6 +1624,8 @@ redisClusterContext *redisClusterContextInit(void) { #ifdef SSL_SUPPORT cc->ssl = NULL; #endif + cc->password[0] = '\0'; + return cc; } @@ -1848,6 +1891,27 @@ int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc) { return REDIS_OK; } +/** + * Configure a password used when connecting to password-protected + * Redis instances. (See Redis AUTH command) + */ +int redisClusterSetOptionPassword(redisClusterContext *cc, + const char *password) { + + if (cc == NULL || password == NULL) { + return REDIS_ERR; + } + + if (strlen(password) > CONFIG_AUTHPASS_MAX_LEN) { + return REDIS_ERR; + } + + strncpy(cc->password, password, sizeof(cc->password) - 1); + cc->password[sizeof(cc->password) - 1] = '\0'; + + return REDIS_OK; +} + int redisClusterSetOptionParseSlaves(redisClusterContext *cc) { if (cc == NULL) { @@ -2001,6 +2065,7 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) { } } #endif + authenticate(cc, c); // err and errstr handled in function if (cc->timeout && c->err == 0) { redisSetTimeout(c, *cc->timeout); @@ -2035,6 +2100,11 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) { } #endif + if (authenticate(cc, c) != REDIS_OK) { + redisFree(c); + return NULL; + } + node->con = c; return c; @@ -3593,6 +3663,7 @@ static void unlinkAsyncContextAndNode(void *data) { redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node) { redisAsyncContext *ac; + int ret; if (node == NULL) { return NULL; @@ -3607,6 +3678,8 @@ redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, } } + // No async context exists, perform a connect + if (node->host == NULL || node->port <= 0) { __redisClusterAsyncSetError(acc, REDIS_ERR_OTHER, "node host or port is error"); @@ -3622,13 +3695,25 @@ redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, #ifdef SSL_SUPPORT if (acc->cc->ssl) { - if (redisInitiateSSLWithContext(&ac->c, acc->cc->ssl) != REDIS_OK) { + ret = redisInitiateSSLWithContext(&ac->c, acc->cc->ssl); + if (ret != REDIS_OK) { __redisClusterAsyncSetError(acc, ac->c.err, ac->c.errstr); + redisAsyncFree(ac); return NULL; } } #endif + // Authenticate when needed + if (acc->cc->password[0] != '\0') { + ret = redisAsyncCommand(ac, NULL, NULL, "AUTH %s", acc->cc->password); + if (ret != REDIS_OK) { + __redisClusterAsyncSetError(acc, ac->c.err, ac->c.errstr); + redisAsyncFree(ac); + return NULL; + } + } + if (acc->adapter) { acc->attach_fn(ac, acc->adapter); } diff --git a/hircluster.h b/hircluster.h index b1b9decf..bf37c260 100644 --- a/hircluster.h +++ b/hircluster.h @@ -21,6 +21,8 @@ #define REDIS_ROLE_MASTER 1 #define REDIS_ROLE_SLAVE 2 +#define CONFIG_AUTHPASS_MAX_LEN 512 // Defined in Redis as max characters + #define HIRCLUSTER_FLAG_NULL 0x0 /* The flag to decide whether add slave node in * redisClusterContext->nodes. This is set in the @@ -99,6 +101,8 @@ typedef struct redisClusterContext { int need_update_route; int64_t update_route_time; + char password[CONFIG_AUTHPASS_MAX_LEN + 1]; // Include a null terminator + #ifdef SSL_SUPPORT redisSSLContext *ssl; #endif @@ -118,6 +122,8 @@ int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr); int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); int redisClusterSetOptionConnectBlock(redisClusterContext *cc); int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc); +int redisClusterSetOptionPassword(redisClusterContext *cc, + const char *password); int redisClusterSetOptionParseSlaves(redisClusterContext *cc); int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc); int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 53dd5839..d030a117 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,6 +25,9 @@ else() add_compile_options(-Wall -Wextra -pedantic -Werror) endif() +# Debug mode for tests +set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "" FORCE) + # Executable: IPv4 add_executable(example_ipv4 main.c) target_link_libraries(example_ipv4 hiredis_cluster hiredis ${SSL_LIBRARY}) @@ -43,6 +46,12 @@ add_test(NAME example_async COMMAND "$") add_executable(ct_commands ct_commands.c) target_link_libraries(ct_commands hiredis_cluster hiredis ${SSL_LIBRARY}) add_test(NAME ct_commands COMMAND "$") +set_tests_properties(ct_commands PROPERTIES LABELS "CT") + +add_executable(ct_connection ct_connection.c) +target_link_libraries(ct_connection hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) +add_test(NAME ct_connection COMMAND "$") +set_tests_properties(ct_connection PROPERTIES LABELS "CT") if(ENABLE_SSL) # Executable: tls diff --git a/tests/ct_connection.c b/tests/ct_connection.c new file mode 100644 index 00000000..75c74ccc --- /dev/null +++ b/tests/ct_connection.c @@ -0,0 +1,176 @@ +#include "adapters/libevent.h" +#include "hircluster.h" +#include +#include +#include +#include + +#define CLUSTER_NODE_WITH_PASSWORD "127.0.0.1:30001" +#define CLUSTER_PASSWORD "secretword" + +// Connecting to a password protected cluster and +// providing a correct password. +void test_password_ok() { + redisClusterContext *cc = redisClusterContextInit(); + assert(cc); + redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_WITH_PASSWORD); + redisClusterSetOptionPassword(cc, CLUSTER_PASSWORD); + redisClusterConnect2(cc); + + assert(cc->err == 0); + + // Test connection + redisReply *reply; + reply = (redisReply *)redisClusterCommand(cc, "SET key1 Hello"); + assert(reply); + assert(strcmp(reply->str, "OK") == 0); + freeReplyObject(reply); + + redisClusterFree(cc); +} + +// Connecting to a password protected cluster and +// providing wrong password. +void test_password_wrong() { + redisClusterContext *cc = redisClusterContextInit(); + assert(cc); + redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_WITH_PASSWORD); + redisClusterSetOptionPassword(cc, "wrongpass"); + redisClusterConnect2(cc); + + assert(cc->err == REDIS_ERR_OTHER); + assert(strncmp(cc->errstr, "WRONGPASS", 9) == 0); + + redisClusterFree(cc); +} + +// Connecting to a password protected cluster and +// not providing any password. +void test_password_missing() { + redisClusterContext *cc = redisClusterContextInit(); + assert(cc); + redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_WITH_PASSWORD); + // A password is not configured.. + redisClusterConnect2(cc); + + assert(cc->err == REDIS_ERR_OTHER); + assert(strncmp(cc->errstr, "NOAUTH", 6) == 0); + + redisClusterFree(cc); +} + +//------------------------------------------------------------------------------ +// Async API +//------------------------------------------------------------------------------ + +void callbackExpectOk(const redisAsyncContext *ac, int status) { + UNUSED(ac); + assert(status == REDIS_OK); +} + +void commandCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + UNUSED(r); + UNUSED(privdata); + redisReply *reply = (redisReply *)r; + assert(reply != NULL); + assert(strcmp(reply->str, "OK") == 0); + redisClusterAsyncDisconnect(cc); +} + +// Connecting to a password protected cluster using +// the async API, providing correct password. +void test_async_password_ok() { + redisClusterAsyncContext *acc = redisClusterAsyncContextInit(); + assert(acc); + redisClusterAsyncSetConnectCallback(acc, callbackExpectOk); + redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); + redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE_WITH_PASSWORD); + redisClusterSetOptionPassword(acc->cc, CLUSTER_PASSWORD); + redisClusterConnect2(acc->cc); + + assert(acc->err == 0); + + struct event_base *base = event_base_new(); + redisClusterLibeventAttach(acc, base); + + // Test connection + int status = redisClusterAsyncCommand(acc, commandCallback, + (char *)"THE_ID", "SET key1 Hello"); + assert(status == REDIS_OK); + + event_base_dispatch(base); + + redisClusterAsyncFree(acc); + event_base_free(base); +} + +// Connecting to a password protected cluster using +// the async API, providing wrong password. +void test_async_password_wrong() { + redisClusterAsyncContext *acc = redisClusterAsyncContextInit(); + assert(acc); + redisClusterAsyncSetConnectCallback(acc, callbackExpectOk); + redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); + redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE_WITH_PASSWORD); + redisClusterSetOptionPassword(acc->cc, "wrongpass"); + redisClusterConnect2(acc->cc); + + assert(acc->err == 0); + + struct event_base *base = event_base_new(); + redisClusterLibeventAttach(acc, base); + + // Test connection + int status = redisClusterAsyncCommand(acc, commandCallback, + (char *)"THE_ID", "SET key1 Hello"); + assert(status == REDIS_ERR); + assert(acc->err == REDIS_ERR_OTHER); + assert(strcmp(acc->errstr, "node get by table error") == 0); + + event_base_dispatch(base); + + redisClusterAsyncFree(acc); + event_base_free(base); +} + +// Connecting to a password protected cluster using +// the async API, not providing a password. +void test_async_password_missing() { + redisClusterAsyncContext *acc = redisClusterAsyncContextInit(); + assert(acc); + redisClusterAsyncSetConnectCallback(acc, callbackExpectOk); + redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); + redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE_WITH_PASSWORD); + // Password not configured + redisClusterConnect2(acc->cc); + + assert(acc->err == 0); + + struct event_base *base = event_base_new(); + redisClusterLibeventAttach(acc, base); + + // Test connection + int status = redisClusterAsyncCommand(acc, commandCallback, + (char *)"THE_ID", "SET key1 Hello"); + assert(status == REDIS_ERR); + assert(acc->err == REDIS_ERR_OTHER); + assert(strcmp(acc->errstr, "node get by table error") == 0); + + event_base_dispatch(base); + + redisClusterAsyncFree(acc); + event_base_free(base); +} + +int main() { + + test_password_ok(); + test_password_wrong(); + test_password_missing(); + + test_async_password_ok(); + test_async_password_wrong(); + test_async_password_missing(); + + return 0; +} From 1ab8e342a7a416c3e2199b0d5d5e11517317723a Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 18 Nov 2020 12:12:05 +0100 Subject: [PATCH 079/273] Add pipeline tests (#4) * Rename timeout to command_timeout like in hiredis * Add tests of pipeline, both sync and async API --- hircluster.c | 30 +++++----- hircluster.h | 2 +- tests/CMakeLists.txt | 5 ++ tests/ct_pipeline.c | 136 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 tests/ct_pipeline.c diff --git a/hircluster.c b/hircluster.c index aed2f015..66dc08a0 100644 --- a/hircluster.c +++ b/hircluster.c @@ -1286,8 +1286,8 @@ static int cluster_update_route_by_addr(redisClusterContext *cc, const char *ip, goto error; } - if (cc->timeout) { - redisSetTimeout(c, *cc->timeout); + if (cc->command_timeout) { + redisSetTimeout(c, *cc->command_timeout); } #ifdef SSL_SUPPORT @@ -1606,7 +1606,7 @@ redisClusterContext *redisClusterContextInit(void) { cc->errstr[0] = '\0'; cc->flags = 0; cc->connect_timeout = NULL; - cc->timeout = NULL; + cc->command_timeout = NULL; cc->nodes = NULL; cc->slots = NULL; cc->max_redirect_count = CLUSTER_DEFAULT_MAX_REDIRECT_COUNT; @@ -1638,8 +1638,8 @@ void redisClusterFree(redisClusterContext *cc) { free(cc->connect_timeout); } - if (cc->timeout) { - free(cc->timeout); + if (cc->command_timeout) { + free(cc->command_timeout); } memset(cc->table, 0, REDIS_CLUSTER_SLOTS * sizeof(cluster_node *)); @@ -1968,12 +1968,12 @@ int redisClusterSetOptionTimeout(redisClusterContext *cc, return REDIS_ERR; } - if (cc->timeout == NULL) { - cc->timeout = malloc(sizeof(struct timeval)); - memcpy(cc->timeout, &tv, sizeof(struct timeval)); - } else if (cc->timeout->tv_sec != tv.tv_sec || - cc->timeout->tv_usec != tv.tv_usec) { - memcpy(cc->timeout, &tv, sizeof(struct timeval)); + if (cc->command_timeout == NULL) { + cc->command_timeout = malloc(sizeof(struct timeval)); + memcpy(cc->command_timeout, &tv, sizeof(struct timeval)); + } else if (cc->command_timeout->tv_sec != tv.tv_sec || + cc->command_timeout->tv_usec != tv.tv_usec) { + memcpy(cc->command_timeout, &tv, sizeof(struct timeval)); if (cc->nodes && dictSize(cc->nodes) > 0) { dictEntry *de; @@ -2067,8 +2067,8 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) { #endif authenticate(cc, c); // err and errstr handled in function - if (cc->timeout && c->err == 0) { - redisSetTimeout(c, *cc->timeout); + if (cc->command_timeout && c->err == 0) { + redisSetTimeout(c, *cc->command_timeout); } } @@ -2086,8 +2086,8 @@ redisContext *ctx_get_by_node(redisClusterContext *cc, cluster_node *node) { c = redisConnect(node->host, node->port); } - if (cc->timeout && c != NULL && c->err == 0) { - redisSetTimeout(c, *cc->timeout); + if (cc->command_timeout && c != NULL && c->err == 0) { + redisSetTimeout(c, *cc->command_timeout); } #ifdef SSL_SUPPORT diff --git a/hircluster.h b/hircluster.h index bf37c260..d602b60e 100644 --- a/hircluster.h +++ b/hircluster.h @@ -84,7 +84,7 @@ typedef struct redisClusterContext { struct timeval *connect_timeout; - struct timeval *timeout; /* receive and send timeout. */ + struct timeval *command_timeout; /* receive and send timeout. */ struct hiarray *slots; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d030a117..08c1dfb8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,6 +53,11 @@ target_link_libraries(ct_connection hiredis_cluster hiredis ${SSL_LIBRARY} ${EVE add_test(NAME ct_connection COMMAND "$") set_tests_properties(ct_connection PROPERTIES LABELS "CT") +add_executable(ct_pipeline ct_pipeline.c) +target_link_libraries(ct_pipeline hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) +add_test(NAME ct_pipeline COMMAND "$") +set_tests_properties(ct_pipeline PROPERTIES LABELS "CT") + if(ENABLE_SSL) # Executable: tls add_executable(example_tls main_tls.c) diff --git a/tests/ct_pipeline.c b/tests/ct_pipeline.c new file mode 100644 index 00000000..38337846 --- /dev/null +++ b/tests/ct_pipeline.c @@ -0,0 +1,136 @@ +#include "adapters/libevent.h" +#include "hircluster.h" + +#include +#include +#include +#include +#include + +#define CLUSTER_NODE "127.0.0.1:30001" + +// Test of two pipelines using sync API +void test_pipeline() { + redisClusterContext *cc = redisClusterContextInit(); + assert(cc); + redisClusterSetOptionAddNodes(cc, CLUSTER_NODE); + redisClusterConnect2(cc); + + assert(cc->err == 0); + + int status; + status = redisClusterAppendCommand(cc, "SET foo one"); + assert(status == REDIS_OK); + status = redisClusterAppendCommand(cc, "SET bar two"); + assert(status == REDIS_OK); + status = redisClusterAppendCommand(cc, "GET foo"); + assert(status == REDIS_OK); + status = redisClusterAppendCommand(cc, "GET bar"); + assert(status == REDIS_OK); + + redisReply *reply; + redisClusterGetReply(cc, (void *)&reply); // reply for: SET foo one + assert(reply != NULL); + assert(reply->type == REDIS_REPLY_STATUS); + assert(strcmp(reply->str, "OK") == 0); + freeReplyObject(reply); + + redisClusterGetReply(cc, (void *)&reply); // reply for: SET bar two + assert(reply != NULL); + assert(reply->type == REDIS_REPLY_STATUS); + assert(strcmp(reply->str, "OK") == 0); + freeReplyObject(reply); + + redisClusterGetReply(cc, (void *)&reply); // reply for: GET foo + assert(reply != NULL); + assert(reply->type == REDIS_REPLY_STRING); + assert(strcmp(reply->str, "one") == 0); + freeReplyObject(reply); + + redisClusterGetReply(cc, (void *)&reply); // reply for: GET bar + assert(reply != NULL); + assert(reply->type == REDIS_REPLY_STRING); + assert(strcmp(reply->str, "two") == 0); + freeReplyObject(reply); + + redisClusterFree(cc); +} + +//------------------------------------------------------------------------------ +// Async API +//------------------------------------------------------------------------------ + +typedef struct ExpectedResult { + int type; + char *str; + bool disconnect; +} ExpectedResult; + +// Callback for Redis connects and disconnects +void callbackExpectOk(const redisAsyncContext *ac, int status) { + UNUSED(ac); + assert(status == REDIS_OK); +} + +// Callback for async commands, verifies the redisReply +void commandCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { + redisReply *reply = (redisReply *)r; + ExpectedResult *expect = (ExpectedResult *)privdata; + assert(reply != NULL); + assert(reply->type == expect->type); + assert(strcmp(reply->str, expect->str) == 0); + + if (expect->disconnect) { + redisClusterAsyncDisconnect(cc); + } +} + +// Test of two pipelines using async API +// In an asynchronous context, commands are automatically pipelined due to the +// nature of an event loop. Therefore, unlike the synchronous API, there is only +// a single way to send commands. +void test_async_pipeline() { + redisClusterAsyncContext *acc = redisClusterAsyncContextInit(); + assert(acc); + redisClusterAsyncSetConnectCallback(acc, callbackExpectOk); + redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); + redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE); + redisClusterConnect2(acc->cc); + + assert(acc->err == 0); + + struct event_base *base = event_base_new(); + redisClusterLibeventAttach(acc, base); + + int status; + ExpectedResult r1 = {.type = REDIS_REPLY_STATUS, .str = "OK"}; + status = redisClusterAsyncCommand(acc, commandCallback, &r1, "SET foo six"); + assert(status == REDIS_OK); + + ExpectedResult r2 = {.type = REDIS_REPLY_STATUS, .str = "OK"}; + status = redisClusterAsyncCommand(acc, commandCallback, &r2, "SET bar ten"); + assert(status == REDIS_OK); + + ExpectedResult r3 = {.type = REDIS_REPLY_STRING, .str = "six"}; + status = redisClusterAsyncCommand(acc, commandCallback, &r3, "GET foo"); + assert(status == REDIS_OK); + + ExpectedResult r4 = { + .type = REDIS_REPLY_STRING, .str = "ten", .disconnect = true}; + status = redisClusterAsyncCommand(acc, commandCallback, &r4, "GET bar"); + assert(status == REDIS_OK); + + event_base_dispatch(base); + + redisClusterAsyncFree(acc); + event_base_free(base); +} + +int main() { + + test_pipeline(); + + test_async_pipeline(); + + return 0; +} From 0cf7050bca68de4ff7a403920e8c9d8da0b63149 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 18 Nov 2020 12:41:06 +0100 Subject: [PATCH 080/273] Update README --- README.md | 273 +++++++++++++++++++----------------------------------- 1 file changed, 95 insertions(+), 178 deletions(-) diff --git a/README.md b/README.md index 9aea7cb7..fb2c4f3e 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,92 @@ +# Hiredis-cluster -# HIREDIS-VIP +Hiredis-cluster is a C client library for cluster deployments of the [Redis](http://redis.io/) database. -Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. +Hiredis-cluster is using [Hiredis](https://github.com/redis/hiredis) for the connections to each Redis node. -Hiredis-vip supported redis cluster. +Hiredis-cluster is a fork of Hiredis-vip, with the following improvements: -Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . +* The C library `hiredis` is an external dependency rather than a builtin part of the cluster client, meaning that the latest `hiredis` can be used. +* Support for SSL/TLS introduced in Redis 6 +* Support for IPv6 +* Using CMake as build system +* Code style guide (using clang-format) +* Improved testing +* Memory leak corrections -## CLUSTER SUPPORT +## Features -### FEATURES: +* Redis Cluster + * Connect to a Redis cluster and run commands. -* **`SUPPORT REDIS CLUSTER`**: - * Connect to redis cluster and run commands. - -* **`SUPPORT MULTI-KEY COMMAND`**: +* Multi-key commands * 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. + * Multi-key commands will be processed and sent to slot owning nodes. -### CLUSTER API: +* Pipelining + * Send multiple commands at once to speed up queries. + * Supports multi-key commands described in above bullet. -```c -redisClusterContext *redisClusterContextInit(void); -void redisClusterFree(redisClusterContext *cc); +* Asynchronous API + * Send commands asynchronously and let a callback handle the response. + * Needs an external event loop system that can be attached using an adapter. -int redisClusterSetOptionAddNode(redisClusterContext *cc, const char *addr); -int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); -int redisClusterSetOptionConnectBlock(redisClusterContext *cc); -int redisClusterSetOptionConnectNonBlock(redisClusterContext *cc); -int redisClusterSetOptionParseSlaves(redisClusterContext *cc); -int redisClusterSetOptionParseOpenSlots(redisClusterContext *cc); -int redisClusterSetOptionRouteUseSlots(redisClusterContext *cc); -int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); - -int redisClusterConnect2(redisClusterContext *cc); - -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); -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); +* SSL/TLS + * Connect to Redis nodes using SSL/TLS (supported from Redis 6) -redisContext *ctx_get_by_node(redisClusterContext *cc, struct cluster_node *node); +* IPv6 + * Handles clusters on IPv6 networks -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); +## Build instructions -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -void redisClusterAsyncFree(redisClusterAsyncContext *acc); +Building hiredis-cluster and its test suites requires headerfiles and linkage to (hiredis)[https://github.com/redis/hiredis] +and (libevent)[https://libevent.org/]. The libevent dependency can be avoided by disabling the tests. + +Hiredis-cluster will be built as a shared library and the test suites will additionally depend on the shared library libhiredis.so, +and libhiredis_ssl.so when SSL is enabled. -redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node); +```sh +$ mkdir build; cd build +$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_HIREDIS=ON -DENABLE_SSL=ON .. +$ make ``` -### CLUSTER API (old api, version <= 0.3.0): +### Build options -```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); +The following CMake options are available: -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); +* `DOWNLOAD_HIREDIS` + * `OFF` CMake will search for an already installed hiredis using following (paths)[https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure]. + * `ON` (default) hiredis will be downloaded, built and installed locally in the build folder. +* `ENABLE_SSL` + * `OFF` + * `ON` (default) Enable SSL/TLS support and build its tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). +* `DISABLE_TESTS` + * `OFF` (default) + * `ON` Disable compilation of tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). -void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); -void redisClusterAsyncFree(redisClusterAsyncContext *acc); -``` +### Build details -## Quick usage +The build uses CMake's (find_package)[https://cmake.org/cmake/help/latest/command/find_package.html] to search for a `hiredis` installation. +When building and installing `hiredis` a file called `hiredis-config.cmake` will be installed and this contains relevant information for users. -If you want used but not read the follow, please reference the examples: -https://github.com/vipshop/hiredis-vip/wiki +As described in the CMake docs a specific path can be set using a flag like: `-Dhiredis_DIR:PATH=${MY_DIR}/hiredis/share/hiredis` -## Cluster synchronous API -To consume the synchronous API, there are only a few function calls that need to be introduced: +## Quick usage -```c -redisClusterContext *redisClusterContextInit(void); -int redisClusterSetOptionAddNodes(redisClusterContext *cc, const char *addrs); -int redisClusterSetOptionMaxRedirect(redisClusterContext *cc, int max_redirect_count); -int redisClusterSetOptionConnectTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterSetOptionTimeout(redisClusterContext *cc, const struct timeval tv); -int redisClusterConnect2(redisClusterContext *cc); -void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); -void redisClusterFree(redisClusterContext *cc); -``` +## Cluster synchronous API + +### Connecting -### Cluster connecting - -The function `redisClusterContextInit` is used to create a so-called `redisClusterContext`. -The function `redisClusterSetOptionAddNodes` is used to add the redis cluster address. -The function `redisClusterConnect2` is used to connect to the redis cluser. -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: +The function `redisClusterContextInit` is used to create a `redisClusterContext`. +The function `redisClusterSetOptionAddNodes` is used to add one or many Redis Cluster addresses. +The function `redisClusterConnect2` is used to connect to the Redis Cluster. +The context is where the state for connections is kept. +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 = redisClusterContextInit(); redisClusterSetOptionAddNodes(cc, "127.0.0.1:6379,127.0.0.1:6380"); @@ -142,11 +97,10 @@ if (cc != NULL && cc->err) { } ``` -### Cluster sending commands +### 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: +The function `redisClusterCommand` takes a format similar to printf. +In the simplest form it is used like: ```c reply = redisClusterCommand(clustercontext, "SET foo bar"); ``` @@ -156,7 +110,7 @@ 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 +Internally, hiredis-cluster 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: @@ -164,31 +118,29 @@ anywhere in an argument: reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); ``` -### Cluster multi-key commands +### Sending 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. +Hiredis-cluster supports mget/mset/del multi-key commands. +The command will be splitted per slot and sent to correct Redis nodes. Example: ```c reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); ``` -### Cluster cleaning up +### Teardown 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. +This function closes the sockets and deallocates 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 +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, ...); @@ -217,9 +169,10 @@ 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. +Hiredis-cluster comes with an asynchronous cluster API that works with many event systems. +Currently there are adapters that enables support for libevent and Redis Event Library (ae), +but more can be added. The hiredis library has adapters for additional event systems that +easily can be adapted for hiredis-cluster as well. ### Connecting @@ -246,8 +199,7 @@ On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection w 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. +You dont need to reconnect in the disconnect callback, hiredis-cluster will reconnect by itself when next command for this Redis node is handled. 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: @@ -256,22 +208,21 @@ int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisD ``` ### 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 +In an asynchronous cluster context, commands are automatically pipelined due to the nature of an event loop. +Therefore, unlike the synchronous cluster 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 `privdata` argument can be used to carry 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, ...); +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 @@ -286,62 +237,28 @@ All pending callbacks are called with a `NULL` reply when the context encountere ### Disconnecting -An cluster asynchronous connection can be terminated using: +Asynchronous cluster connections 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 +When this function is called, connections are **not** immediately terminated. Instead, new +commands are no longer accepted and connections are only terminated when all pending commands +have been written to a 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* +### Using 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*. -## Build instructions - -Build the library - -``` -mkdir build -cd build -cmake .. -make -``` - -Options: - -``` -# Dont download hiredis from git -cmake -DDOWNLOAD_HIREDIS=OFF .. - -# Build without tests: -cmake -DDISABLE_TESTS=ON .. -``` - -Build the examples - -``` -mkdir build_examples -cd build_examples -cmake ../examples -make - -# Run -./examples/src/examples-build/examples -./examples/src/examples-build/examples_async -``` - ## AUTHORS -Hiredis-vip was created by vipshop (https://github.com/vipshop/hiredis-vip). - This fork is based on the heronr fork (https://github.com/heronr/hiredis-vip) and uses 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 was originally created by vipshop (https://github.com/vipshop/hiredis-vip). + +The Redis Cluster client library part in hiredis-vip was written by deep (https://github.com/deep011). Hiredis-vip is released under the BSD license. From 4e7a83fc8bda3f3713cbb0f6d7ad95ea5c37e6c0 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 18 Nov 2020 12:43:57 +0100 Subject: [PATCH 081/273] Build option ENABLE_SSL is now default OFF. This now matches the default option in the `hiredis` library. --- .github/workflows/ci.yml | 2 +- CMakeLists.txt | 2 +- README.md | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aaec3ea1..0f44a340 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: # access regardless of the host operating system shell: bash working-directory: build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DENABLE_SSL=ON .. - name: Build shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index f916b048..e6e9a12f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ include(GNUInstallDirs) project(hiredis-cluster) option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) -option(ENABLE_SSL "Enable SSL/TLS support" ON) +option(ENABLE_SSL "Enable SSL/TLS support" OFF) option(DISABLE_TESTS "Disable compilation of test" OFF) macro(getVersionBit name) diff --git a/README.md b/README.md index fb2c4f3e..ad118332 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ and libhiredis_ssl.so when SSL is enabled. ```sh $ mkdir build; cd build -$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_HIREDIS=ON -DENABLE_SSL=ON .. +$ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SSL=ON .. $ make ``` @@ -59,8 +59,8 @@ The following CMake options are available: * `OFF` CMake will search for an already installed hiredis using following (paths)[https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure]. * `ON` (default) hiredis will be downloaded, built and installed locally in the build folder. * `ENABLE_SSL` - * `OFF` - * `ON` (default) Enable SSL/TLS support and build its tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). + * `OFF` (default) + * `ON` Enable SSL/TLS support and build its tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). * `DISABLE_TESTS` * `OFF` (default) * `ON` Disable compilation of tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). From 038ad37de44af69039c36e935bc6d4acb69f179b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20S=C3=B6derqvist?= Date: Tue, 24 Nov 2020 12:54:25 +0100 Subject: [PATCH 082/273] Build and test details in README --- README.md | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ad118332..66b48122 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,15 @@ Hiredis-cluster is a fork of Hiredis-vip, with the following improvements: ## Build instructions -Building hiredis-cluster and its test suites requires headerfiles and linkage to (hiredis)[https://github.com/redis/hiredis] -and (libevent)[https://libevent.org/]. The libevent dependency can be avoided by disabling the tests. +Prerequisites: + +* A C compiler (GCC or Clang) +* CMake and GNU Make +* (hiredis)[https://github.com/redis/hiredis]; downloaded automatically by + default, but see build options below +* (libevent)[https://libevent.org/] (`libevent-dev` in Debian); can be avoided + if building without tests (DISABLE_TESTS=ON) +* OpenSSL (`libssl-dev` in Debian) if building with TLS support Hiredis-cluster will be built as a shared library and the test suites will additionally depend on the shared library libhiredis.so, and libhiredis_ssl.so when SSL is enabled. @@ -56,22 +63,36 @@ $ make The following CMake options are available: * `DOWNLOAD_HIREDIS` - * `OFF` CMake will search for an already installed hiredis using following (paths)[https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure]. - * `ON` (default) hiredis will be downloaded, built and installed locally in the build folder. + * `OFF` CMake will search for an already installed hiredis (for example the + the Debian package `libhiredis-dev`) for header files and linkage. + * `ON` (default) hiredis will be downloaded from + (Github)[https://github.com/redis/hiredis], built and installed locally in + the build folder. * `ENABLE_SSL` * `OFF` (default) - * `ON` Enable SSL/TLS support and build its tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). + * `ON` Enable SSL/TLS support and build its tests (also affect hiredis when + `DOWNLOAD_HIREDIS=ON`). * `DISABLE_TESTS` * `OFF` (default) - * `ON` Disable compilation of tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). + * `ON` Disable compilation of tests (also affect hiredis when + `DOWNLOAD_HIREDIS=ON`). ### Build details -The build uses CMake's (find_package)[https://cmake.org/cmake/help/latest/command/find_package.html] to search for a `hiredis` installation. +The build uses CMake's (find_package)[https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure] to search for a `hiredis` installation. When building and installing `hiredis` a file called `hiredis-config.cmake` will be installed and this contains relevant information for users. As described in the CMake docs a specific path can be set using a flag like: `-Dhiredis_DIR:PATH=${MY_DIR}/hiredis/share/hiredis` +### Running the tests + +The tests and examples binaries are located under `build/tests` after building +using the instructions above. Then run `make start` (WIP) to start a Redis +cluster and then `make test`. If you want to set up the Redis cluster manually, +it should run on localhost, where one of the nodes listens on port 30001 and has +TLS disabled. It should accept both IPv4 and IPv6 for all tests to pass. For the +TLS tests, one node is to listen on port 31001 and all nodes has to have TLS +enabled. ## Quick usage From 10f90f8a39a67a39ad0539a564801dfb3e67905a Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 23 Nov 2020 10:40:47 +0100 Subject: [PATCH 083/273] Add build target to start and stop test clusters `make start` will setup Redis clusters using Docker that will be used by the component tests. `make stop` will terminate the containers again. --- README.md | 9 +++++++++ tests/CMakeLists.txt | 12 ++++++++++++ tests/ct_commands.c | 4 +++- tests/ct_connection.c | 2 +- tests/ct_pipeline.c | 2 +- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 66b48122..2c610ae0 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,15 @@ $ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SSL=ON .. $ make ``` +Some tests needs a Redis cluster and that can be setup by the build targets `start`/`stop` +The clusters will be setup using Docker and it may take a while before initiation and being able to run the tests. + +```sh +$ make start +$ make test +$ make stop +``` + ### Build options The following CMake options are available: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 08c1dfb8..c4988648 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -15,7 +15,19 @@ if(ENABLE_SSL) set(SSL_LIBRARY hiredis_ssl) endif() +# Targets to setup Redis Clusters for testing +find_program(Docker_EXECUTABLE docker) +set(CONTAINER "bjosv/redis-cluster:latest") # When supporting password, change to: grokzen/redis-cluster:latest +add_custom_target(start + COMMAND ${Docker_EXECUTABLE} run --name docker-cluster -d -p 7000-7006:7000-7006 ${CONTAINER} || (exit 0) + COMMAND ${Docker_EXECUTABLE} run --name docker-cluster-passw -d -e INITIAL_PORT=7100 -e PASSWORD="secretword" -p 7100-7106:7100-7106 ${CONTAINER} || (exit 0) +) +add_custom_target(stop + COMMAND ${Docker_EXECUTABLE} rm -f docker-cluster || (exit 0) + COMMAND ${Docker_EXECUTABLE} rm -f docker-cluster-passw || (exit 0) +) +# Find dependencies find_library(EVENT_LIBRARY event HINTS /usr/lib/x86_64-linux-gnu) if(MSVC) diff --git a/tests/ct_commands.c b/tests/ct_commands.c index 52637a03..fe1767fe 100644 --- a/tests/ct_commands.c +++ b/tests/ct_commands.c @@ -4,6 +4,8 @@ #include #include +#define CLUSTER_NODE "127.0.0.1:7000" + #define ASSERT_MSG(_x, _msg) \ if (!(_x)) { \ fprintf(stderr, "ERROR: %s\n", _msg); \ @@ -60,7 +62,7 @@ int main(int argc, char **argv) { redisClusterContext *cc = redisClusterContextInit(); assert(cc); - redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); + redisClusterSetOptionAddNodes(cc, CLUSTER_NODE); redisClusterSetOptionConnectTimeout(cc, timeout); redisClusterSetOptionRouteUseSlots(cc); redisClusterConnect2(cc); diff --git a/tests/ct_connection.c b/tests/ct_connection.c index 75c74ccc..d612b150 100644 --- a/tests/ct_connection.c +++ b/tests/ct_connection.c @@ -5,7 +5,7 @@ #include #include -#define CLUSTER_NODE_WITH_PASSWORD "127.0.0.1:30001" +#define CLUSTER_NODE_WITH_PASSWORD "127.0.0.1:7100" #define CLUSTER_PASSWORD "secretword" // Connecting to a password protected cluster and diff --git a/tests/ct_pipeline.c b/tests/ct_pipeline.c index 38337846..c2b99a32 100644 --- a/tests/ct_pipeline.c +++ b/tests/ct_pipeline.c @@ -7,7 +7,7 @@ #include #include -#define CLUSTER_NODE "127.0.0.1:30001" +#define CLUSTER_NODE "127.0.0.1:7000" // Test of two pipelines using sync API void test_pipeline() { From e10f66a7a0f79be779fc410516ea32bf442494fb Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 23 Nov 2020 10:47:24 +0100 Subject: [PATCH 084/273] Rename async test and remove redundant example binaries --- tests/CMakeLists.txt | 24 ++++++++-------------- tests/{main_async.c => ct_async.c} | 4 +++- tests/main.c | 33 ------------------------------ 3 files changed, 11 insertions(+), 50 deletions(-) rename tests/{main_async.c => ct_async.c} (96%) delete mode 100644 tests/main.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c4988648..e1ee950f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,20 +40,10 @@ endif() # Debug mode for tests set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "" FORCE) -# Executable: IPv4 -add_executable(example_ipv4 main.c) -target_link_libraries(example_ipv4 hiredis_cluster hiredis ${SSL_LIBRARY}) -add_test(NAME example_ipv4 COMMAND "$") - -# Executable: IPv6 -add_executable(example_ipv6 main_ipv6.c) -target_link_libraries(example_ipv6 hiredis_cluster hiredis ${SSL_LIBRARY}) -add_test(NAME example_ipv6 COMMAND "$") - -# Executable: async -add_executable(example_async main_async.c) -target_link_libraries(example_async hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) -add_test(NAME example_async COMMAND "$") +add_executable(ct_async ct_async.c) +target_link_libraries(ct_async hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) +add_test(NAME ct_async COMMAND "$") +set_tests_properties(ct_async PROPERTIES LABELS "CT") add_executable(ct_commands ct_commands.c) target_link_libraries(ct_commands hiredis_cluster hiredis ${SSL_LIBRARY}) @@ -70,16 +60,18 @@ target_link_libraries(ct_pipeline hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT add_test(NAME ct_pipeline COMMAND "$") set_tests_properties(ct_pipeline PROPERTIES LABELS "CT") +# Executable: IPv6 +add_executable(example_ipv6 main_ipv6.c) +target_link_libraries(example_ipv6 hiredis_cluster hiredis ${SSL_LIBRARY}) + if(ENABLE_SSL) # Executable: tls add_executable(example_tls main_tls.c) target_link_libraries(example_tls hiredis_cluster hiredis ${SSL_LIBRARY}) add_dependencies(example_tls generate_tls_configs) - add_test(NAME example_tls COMMAND "$") # Executable: async tls add_executable(example_async_tls main_async_tls.c) target_link_libraries(example_async_tls hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) add_dependencies(example_async_tls generate_tls_configs) - add_test(NAME example_async_tls COMMAND "$") endif() diff --git a/tests/main_async.c b/tests/ct_async.c similarity index 96% rename from tests/main_async.c rename to tests/ct_async.c index 3f41c95c..39d88898 100644 --- a/tests/main_async.c +++ b/tests/ct_async.c @@ -3,6 +3,8 @@ #include #include +#define CLUSTER_NODE "127.0.0.1:7000" + void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply *)r; if (reply == NULL) { @@ -50,7 +52,7 @@ int main(int argc, char **argv) { printf("Connecting...\n"); redisClusterAsyncContext *cc = - redisClusterAsyncConnect("127.0.0.1:30001", HIRCLUSTER_FLAG_NULL); + redisClusterAsyncConnect(CLUSTER_NODE, HIRCLUSTER_FLAG_NULL); if (cc && cc->err) { printf("Error: %s\n", cc->errstr); return 1; diff --git a/tests/main.c b/tests/main.c deleted file mode 100644 index 1701c514..00000000 --- a/tests/main.c +++ /dev/null @@ -1,33 +0,0 @@ -#include "hircluster.h" -#include -#include - -int main(int argc, char **argv) { - UNUSED(argc); - UNUSED(argv); - - struct timeval timeout = {1, 500000}; // 1.5s - - redisClusterContext *cc = redisClusterContextInit(); - redisClusterSetOptionAddNodes(cc, "127.0.0.1:30001"); - redisClusterSetOptionConnectTimeout(cc, timeout); - redisClusterSetOptionRouteUseSlots(cc); - redisClusterConnect2(cc); - if (cc && cc->err) { - printf("Error: %s\n", cc->errstr); - // handle error - exit(-1); - } - - redisReply *reply = - (redisReply *)redisClusterCommand(cc, "SET %s %s", "key", "value"); - printf("SET: %s\n", reply->str); - freeReplyObject(reply); - - redisReply *reply2 = (redisReply *)redisClusterCommand(cc, "GET %s", "key"); - printf("GET: %s\n", reply2->str); - freeReplyObject(reply2); - - redisClusterFree(cc); - return 0; -} From cff5ba1e9f438b96c88d5d636d068a2f99d1e913 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Mon, 23 Nov 2020 12:08:26 +0100 Subject: [PATCH 085/273] Use common macros in tests --- tests/ct_async.c | 98 ++++++++++++++++--------------------------- tests/ct_commands.c | 38 +++-------------- tests/ct_connection.c | 66 ++++++++++++++++++----------- tests/ct_pipeline.c | 52 +++++++++++------------ tests/test_utils.h | 39 +++++++++++++++++ 5 files changed, 146 insertions(+), 147 deletions(-) create mode 100644 tests/test_utils.h diff --git a/tests/ct_async.c b/tests/ct_async.c index 39d88898..6b2912f2 100644 --- a/tests/ct_async.c +++ b/tests/ct_async.c @@ -1,98 +1,70 @@ #include "adapters/libevent.h" #include "hircluster.h" +#include "test_utils.h" +#include #include #include #define CLUSTER_NODE "127.0.0.1:7000" -void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { +void getCallback(redisClusterAsyncContext *acc, void *r, void *privdata) { + UNUSED(privdata); redisReply *reply = (redisReply *)r; - if (reply == NULL) { - if (cc->err) { - printf("errstr: %s\n", cc->errstr); - } - return; - } - printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); + ASSERT_MSG(reply != NULL, acc->errstr); - /* Disconnect after receiving the reply to GET */ - redisClusterAsyncDisconnect(cc); + /* Disconnect after receiving the first reply to GET */ + redisClusterAsyncDisconnect(acc); } -void setCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { +void setCallback(redisClusterAsyncContext *acc, void *r, void *privdata) { + UNUSED(privdata); redisReply *reply = (redisReply *)r; - if (reply == NULL) { - if (cc->err) { - printf("errstr: %s\n", cc->errstr); - } - return; - } - printf("privdata: %s reply: %s\n", (char *)privdata, reply->str); + ASSERT_MSG(reply != NULL, acc->errstr); } void connectCallback(const redisAsyncContext *ac, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", ac->errstr); - return; - } + ASSERT_MSG(status == REDIS_OK, ac->errstr); printf("Connected to %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); } void disconnectCallback(const redisAsyncContext *ac, int status) { - if (status != REDIS_OK) { - printf("Error: %s\n", ac->errstr); - return; - } + ASSERT_MSG(status == REDIS_OK, ac->errstr); printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port); } -int main(int argc, char **argv) { - UNUSED(argc); - UNUSED(argv); - - printf("Connecting...\n"); - redisClusterAsyncContext *cc = +int main() { + redisClusterAsyncContext *acc = redisClusterAsyncConnect(CLUSTER_NODE, HIRCLUSTER_FLAG_NULL); - if (cc && cc->err) { - printf("Error: %s\n", cc->errstr); - return 1; - } + assert(acc); + ASSERT_MSG(acc->err == 0, acc->errstr); + int status; struct event_base *base = event_base_new(); - redisClusterLibeventAttach(cc, base); - redisClusterAsyncSetConnectCallback(cc, connectCallback); - redisClusterAsyncSetDisconnectCallback(cc, disconnectCallback); + status = redisClusterLibeventAttach(acc, base); + assert(status == REDIS_OK); - int status; - status = redisClusterAsyncCommand(cc, setCallback, (char *)"THE_ID", - "SET %s %s", "key", "value"); - if (status != REDIS_OK) { - printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); - } + redisClusterAsyncSetConnectCallback(acc, connectCallback); + redisClusterAsyncSetDisconnectCallback(acc, disconnectCallback); + + status = redisClusterAsyncCommand(acc, setCallback, (char *)"ID", + "SET key12345 value"); + ASSERT_MSG(status == REDIS_OK, acc->errstr); - status = redisClusterAsyncCommand(cc, getCallback, (char *)"THE_ID", - "GET %s", "key"); - if (status != REDIS_OK) { - printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); - } + status = redisClusterAsyncCommand(acc, getCallback, (char *)"ID", + "GET key12345"); + ASSERT_MSG(status == REDIS_OK, acc->errstr); - status = redisClusterAsyncCommand(cc, setCallback, (char *)"THE_ID", - "SET %s %s", "key2", "value2"); - if (status != REDIS_OK) { - printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); - } + status = redisClusterAsyncCommand(acc, setCallback, (char *)"ID", + "SET key23456 value2"); + ASSERT_MSG(status == REDIS_OK, acc->errstr); - status = redisClusterAsyncCommand(cc, getCallback, (char *)"THE_ID", - "GET %s", "key2"); - if (status != REDIS_OK) { - printf("error: err=%d errstr=%s\n", cc->err, cc->errstr); - } + status = redisClusterAsyncCommand(acc, getCallback, (char *)"ID", + "GET key23456"); + ASSERT_MSG(status == REDIS_OK, acc->errstr); - printf("Dispatch..\n"); event_base_dispatch(base); - printf("Done..\n"); - redisClusterAsyncFree(cc); + redisClusterAsyncFree(acc); event_base_free(base); return 0; } diff --git a/tests/ct_commands.c b/tests/ct_commands.c index fe1767fe..687ff161 100644 --- a/tests/ct_commands.c +++ b/tests/ct_commands.c @@ -1,4 +1,5 @@ #include "hircluster.h" +#include "test_utils.h" #include #include #include @@ -6,31 +7,6 @@ #define CLUSTER_NODE "127.0.0.1:7000" -#define ASSERT_MSG(_x, _msg) \ - if (!(_x)) { \ - fprintf(stderr, "ERROR: %s\n", _msg); \ - assert(_x); \ - } -#define REPLY(_ctx, _reply) \ - if (!(_reply)) { \ - ASSERT_MSG(_reply, _ctx->errstr); \ - } -#define REPLY_TYPE(_reply, _type) \ - ASSERT_MSG((_reply->type == _type), "Reply type incorrect"); - -#define CHECK_REPLY_OK(_ctx, _reply) \ - { \ - REPLY(_ctx, _reply); \ - REPLY_TYPE(_reply, REDIS_REPLY_STATUS); \ - ASSERT_MSG((strcmp(_reply->str, "OK") == 0), _ctx->errstr); \ - } -#define CHECK_REPLY_INT(_ctx, _reply, _value) \ - { \ - REPLY(_ctx, _reply); \ - REPLY_TYPE(_reply, REDIS_REPLY_INTEGER); \ - ASSERT_MSG((_reply->integer == _value), _ctx->errstr); \ - } - void test_exists(redisClusterContext *cc) { redisReply *reply; reply = (redisReply *)redisClusterCommand(cc, "SET key1 Hello"); @@ -54,19 +30,17 @@ void test_exists(redisClusterContext *cc) { freeReplyObject(reply); } -int main(int argc, char **argv) { - UNUSED(argc); - UNUSED(argv); - +int main() { struct timeval timeout = {0, 500000}; redisClusterContext *cc = redisClusterContextInit(); assert(cc); redisClusterSetOptionAddNodes(cc, CLUSTER_NODE); redisClusterSetOptionConnectTimeout(cc, timeout); - redisClusterSetOptionRouteUseSlots(cc); - redisClusterConnect2(cc); - assert(cc->err == 0); + + int status; + status = redisClusterConnect2(cc); + ASSERT_MSG(status == REDIS_OK, cc->errstr); test_exists(cc); diff --git a/tests/ct_connection.c b/tests/ct_connection.c index d612b150..b2ef4f65 100644 --- a/tests/ct_connection.c +++ b/tests/ct_connection.c @@ -1,5 +1,6 @@ #include "adapters/libevent.h" #include "hircluster.h" +#include "test_utils.h" #include #include #include @@ -15,15 +16,15 @@ void test_password_ok() { assert(cc); redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_WITH_PASSWORD); redisClusterSetOptionPassword(cc, CLUSTER_PASSWORD); - redisClusterConnect2(cc); - assert(cc->err == 0); + int status; + status = redisClusterConnect2(cc); + ASSERT_MSG(status == REDIS_OK, cc->errstr); // Test connection redisReply *reply; reply = (redisReply *)redisClusterCommand(cc, "SET key1 Hello"); - assert(reply); - assert(strcmp(reply->str, "OK") == 0); + CHECK_REPLY_OK(cc, reply); freeReplyObject(reply); redisClusterFree(cc); @@ -35,8 +36,11 @@ void test_password_wrong() { redisClusterContext *cc = redisClusterContextInit(); assert(cc); redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_WITH_PASSWORD); - redisClusterSetOptionPassword(cc, "wrongpass"); - redisClusterConnect2(cc); + redisClusterSetOptionPassword(cc, "faultypass"); + + int status; + status = redisClusterConnect2(cc); + assert(status == REDIS_ERR); assert(cc->err == REDIS_ERR_OTHER); assert(strncmp(cc->errstr, "WRONGPASS", 9) == 0); @@ -50,8 +54,11 @@ void test_password_missing() { redisClusterContext *cc = redisClusterContextInit(); assert(cc); redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_WITH_PASSWORD); + // A password is not configured.. - redisClusterConnect2(cc); + int status; + status = redisClusterConnect2(cc); + assert(status == REDIS_ERR); assert(cc->err == REDIS_ERR_OTHER); assert(strncmp(cc->errstr, "NOAUTH", 6) == 0); @@ -86,16 +93,19 @@ void test_async_password_ok() { redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE_WITH_PASSWORD); redisClusterSetOptionPassword(acc->cc, CLUSTER_PASSWORD); - redisClusterConnect2(acc->cc); - - assert(acc->err == 0); struct event_base *base = event_base_new(); redisClusterLibeventAttach(acc, base); + int status; + status = redisClusterConnect2(acc->cc); + assert(status == REDIS_OK); + assert(acc->err == 0); + assert(acc->cc->err == 0); + // Test connection - int status = redisClusterAsyncCommand(acc, commandCallback, - (char *)"THE_ID", "SET key1 Hello"); + status = redisClusterAsyncCommand(acc, commandCallback, (char *)"THE_ID", + "SET key1 Hello"); assert(status == REDIS_OK); event_base_dispatch(base); @@ -112,17 +122,21 @@ void test_async_password_wrong() { redisClusterAsyncSetConnectCallback(acc, callbackExpectOk); redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE_WITH_PASSWORD); - redisClusterSetOptionPassword(acc->cc, "wrongpass"); - redisClusterConnect2(acc->cc); - - assert(acc->err == 0); + redisClusterSetOptionPassword(acc->cc, "faultypass"); struct event_base *base = event_base_new(); redisClusterLibeventAttach(acc, base); + int status; + status = redisClusterConnect2(acc->cc); + assert(status == REDIS_ERR); + assert(acc->err == REDIS_OK); // TODO: This must be wrong! + assert(acc->cc->err == REDIS_ERR_OTHER); + assert(strncmp(acc->cc->errstr, "WRONGPASS", 6) == 0); + // Test connection - int status = redisClusterAsyncCommand(acc, commandCallback, - (char *)"THE_ID", "SET key1 Hello"); + status = redisClusterAsyncCommand(acc, commandCallback, (char *)"THE_ID", + "SET key1 Hello"); assert(status == REDIS_ERR); assert(acc->err == REDIS_ERR_OTHER); assert(strcmp(acc->errstr, "node get by table error") == 0); @@ -142,16 +156,20 @@ void test_async_password_missing() { redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE_WITH_PASSWORD); // Password not configured - redisClusterConnect2(acc->cc); - - assert(acc->err == 0); struct event_base *base = event_base_new(); redisClusterLibeventAttach(acc, base); - // Test connection - int status = redisClusterAsyncCommand(acc, commandCallback, - (char *)"THE_ID", "SET key1 Hello"); + int status; + status = redisClusterConnect2(acc->cc); + assert(status == REDIS_ERR); + assert(acc->err == REDIS_OK); // TODO: This must be wrong! + assert(acc->cc->err == REDIS_ERR_OTHER); + assert(strncmp(acc->cc->errstr, "NOAUTH", 6) == 0); + + // No connection + status = redisClusterAsyncCommand(acc, commandCallback, (char *)"THE_ID", + "SET key1 Hello"); assert(status == REDIS_ERR); assert(acc->err == REDIS_ERR_OTHER); assert(strcmp(acc->errstr, "node get by table error") == 0); diff --git a/tests/ct_pipeline.c b/tests/ct_pipeline.c index c2b99a32..774e995c 100644 --- a/tests/ct_pipeline.c +++ b/tests/ct_pipeline.c @@ -1,5 +1,6 @@ #include "adapters/libevent.h" #include "hircluster.h" +#include "test_utils.h" #include #include @@ -13,44 +14,38 @@ void test_pipeline() { redisClusterContext *cc = redisClusterContextInit(); assert(cc); - redisClusterSetOptionAddNodes(cc, CLUSTER_NODE); - redisClusterConnect2(cc); - - assert(cc->err == 0); int status; + status = redisClusterSetOptionAddNodes(cc, CLUSTER_NODE); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + status = redisClusterConnect2(cc); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + status = redisClusterAppendCommand(cc, "SET foo one"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, cc->errstr); status = redisClusterAppendCommand(cc, "SET bar two"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, cc->errstr); status = redisClusterAppendCommand(cc, "GET foo"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, cc->errstr); status = redisClusterAppendCommand(cc, "GET bar"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, cc->errstr); redisReply *reply; redisClusterGetReply(cc, (void *)&reply); // reply for: SET foo one - assert(reply != NULL); - assert(reply->type == REDIS_REPLY_STATUS); - assert(strcmp(reply->str, "OK") == 0); + CHECK_REPLY_OK(cc, reply); freeReplyObject(reply); redisClusterGetReply(cc, (void *)&reply); // reply for: SET bar two - assert(reply != NULL); - assert(reply->type == REDIS_REPLY_STATUS); - assert(strcmp(reply->str, "OK") == 0); + CHECK_REPLY_OK(cc, reply); freeReplyObject(reply); redisClusterGetReply(cc, (void *)&reply); // reply for: GET foo - assert(reply != NULL); - assert(reply->type == REDIS_REPLY_STRING); - assert(strcmp(reply->str, "one") == 0); + CHECK_REPLY_STR(cc, reply, "one"); freeReplyObject(reply); redisClusterGetReply(cc, (void *)&reply); // reply for: GET bar - assert(reply != NULL); - assert(reply->type == REDIS_REPLY_STRING); - assert(strcmp(reply->str, "two") == 0); + CHECK_REPLY_STR(cc, reply, "two"); freeReplyObject(reply); redisClusterFree(cc); @@ -95,30 +90,31 @@ void test_async_pipeline() { redisClusterAsyncSetConnectCallback(acc, callbackExpectOk); redisClusterAsyncSetDisconnectCallback(acc, callbackExpectOk); redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE); - redisClusterConnect2(acc->cc); - assert(acc->err == 0); + int status; + status = redisClusterConnect2(acc->cc); + ASSERT_MSG(status == REDIS_OK, acc->errstr); struct event_base *base = event_base_new(); - redisClusterLibeventAttach(acc, base); + status = redisClusterLibeventAttach(acc, base); + assert(status == REDIS_OK); - int status; ExpectedResult r1 = {.type = REDIS_REPLY_STATUS, .str = "OK"}; status = redisClusterAsyncCommand(acc, commandCallback, &r1, "SET foo six"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, acc->errstr); ExpectedResult r2 = {.type = REDIS_REPLY_STATUS, .str = "OK"}; status = redisClusterAsyncCommand(acc, commandCallback, &r2, "SET bar ten"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, acc->errstr); ExpectedResult r3 = {.type = REDIS_REPLY_STRING, .str = "six"}; status = redisClusterAsyncCommand(acc, commandCallback, &r3, "GET foo"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, acc->errstr); ExpectedResult r4 = { .type = REDIS_REPLY_STRING, .str = "ten", .disconnect = true}; status = redisClusterAsyncCommand(acc, commandCallback, &r4, "GET bar"); - assert(status == REDIS_OK); + ASSERT_MSG(status == REDIS_OK, acc->errstr); event_base_dispatch(base); diff --git a/tests/test_utils.h b/tests/test_utils.h new file mode 100644 index 00000000..e3f0a916 --- /dev/null +++ b/tests/test_utils.h @@ -0,0 +1,39 @@ +#ifndef __TEST_UTILS_H__ +#define __TEST_UTILS_H__ + +#define ASSERT_MSG(_x, _msg) \ + if (!(_x)) { \ + fprintf(stderr, "ERROR: %s (%s)\n", _msg, #_x); \ + assert(_x); \ + } + +#define CHECK_REPLY(_ctx, _reply) \ + if (!(_reply)) { \ + ASSERT_MSG(_reply, _ctx->errstr); \ + } + +#define CHECK_REPLY_TYPE(_reply, _type) \ + { ASSERT_MSG((_reply->type == _type), "Reply type incorrect"); } + +#define CHECK_REPLY_OK(_ctx, _reply) \ + { \ + CHECK_REPLY(_ctx, _reply); \ + CHECK_REPLY_TYPE(_reply, REDIS_REPLY_STATUS); \ + ASSERT_MSG((strcmp(_reply->str, "OK") == 0), _ctx->errstr); \ + } + +#define CHECK_REPLY_INT(_ctx, _reply, _value) \ + { \ + CHECK_REPLY(_ctx, _reply); \ + CHECK_REPLY_TYPE(_reply, REDIS_REPLY_INTEGER); \ + ASSERT_MSG((_reply->integer == _value), _ctx->errstr); \ + } + +#define CHECK_REPLY_STR(_ctx, _reply, _str) \ + { \ + CHECK_REPLY(_ctx, _reply); \ + CHECK_REPLY_TYPE(_reply, REDIS_REPLY_STRING); \ + ASSERT_MSG((strcmp(_reply->str, _str) == 0), _ctx->errstr); \ + } + +#endif From f40d20d13e7d766f87ad0c5eb42132c40eb76d20 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Tue, 24 Nov 2020 12:38:15 +0100 Subject: [PATCH 086/273] Enable the IPv6 tests to run towards a setup cluster --- CMakeLists.txt | 14 +++++------- README.md | 13 +++++++---- tests/CMakeLists.txt | 20 ++++++++++++---- tests/ct_connection_ipv6.c | 47 ++++++++++++++++++++++++++++++++++++++ tests/main_ipv6.c | 37 ------------------------------ 5 files changed, 77 insertions(+), 54 deletions(-) create mode 100644 tests/ct_connection_ipv6.c delete mode 100644 tests/main_ipv6.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e6e9a12f..621d7cf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,10 @@ cmake_minimum_required(VERSION 3.14) include(GNUInstallDirs) project(hiredis-cluster) -option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) -option(ENABLE_SSL "Enable SSL/TLS support" OFF) -option(DISABLE_TESTS "Disable compilation of test" OFF) +option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) +option(ENABLE_SSL "Enable SSL/TLS support" OFF) +option(DISABLE_TESTS "Disable compilation of test" OFF) +option(ENABLE_IPV6_TESTS "Enable IPv6 tests requiring special prerequisites" OFF) macro(getVersionBit name) set(VERSION_REGEX "^#define ${name} (.+)$") @@ -112,12 +113,9 @@ if(WIN32 OR MINGW) TARGET_LINK_LIBRARIES(hiredis_cluster PRIVATE ws2_32 hiredis::hiredis) endif() -if (NOT DISABLE_TESTS) +if(NOT DISABLE_TESTS) include(CTest) - - if(BUILD_TESTING) - add_subdirectory(tests) - endif() + add_subdirectory(tests) endif() configure_file(hiredis_cluster.pc.in hiredis_cluster.pc @ONLY) diff --git a/README.md b/README.md index 2c610ae0..8d03044f 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ Prerequisites: * A C compiler (GCC or Clang) * CMake and GNU Make -* (hiredis)[https://github.com/redis/hiredis]; downloaded automatically by +* [hiredis](https://github.com/redis/hiredis); downloaded automatically by default, but see build options below -* (libevent)[https://libevent.org/] (`libevent-dev` in Debian); can be avoided +* [libevent](https://libevent.org/) (`libevent-dev` in Debian); can be avoided if building without tests (DISABLE_TESTS=ON) * OpenSSL (`libssl-dev` in Debian) if building with TLS support @@ -58,7 +58,7 @@ $ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SSL=ON .. $ make ``` -Some tests needs a Redis cluster and that can be setup by the build targets `start`/`stop` +Some tests needs a Redis cluster and that can be setup by the make targets `start`/`stop` The clusters will be setup using Docker and it may take a while before initiation and being able to run the tests. ```sh @@ -75,7 +75,7 @@ The following CMake options are available: * `OFF` CMake will search for an already installed hiredis (for example the the Debian package `libhiredis-dev`) for header files and linkage. * `ON` (default) hiredis will be downloaded from - (Github)[https://github.com/redis/hiredis], built and installed locally in + [Github](https://github.com/redis/hiredis), built and installed locally in the build folder. * `ENABLE_SSL` * `OFF` (default) @@ -85,10 +85,13 @@ The following CMake options are available: * `OFF` (default) * `ON` Disable compilation of tests (also affect hiredis when `DOWNLOAD_HIREDIS=ON`). +* `ENABLE_IPV6_TESTS` + * `OFF` (default) + * `ON` Enable IPv6 tests. Requires that IPv6 is [setup](https://docs.docker.com/config/daemon/ipv6/) in Docker. ### Build details -The build uses CMake's (find_package)[https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure] to search for a `hiredis` installation. +The build uses CMake's [find_package](https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure) to search for a `hiredis` installation. When building and installing `hiredis` a file called `hiredis-config.cmake` will be installed and this contains relevant information for users. As described in the CMake docs a specific path can be set using a flag like: `-Dhiredis_DIR:PATH=${MY_DIR}/hiredis/share/hiredis` diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e1ee950f..cc276eca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,15 +16,24 @@ if(ENABLE_SSL) endif() # Targets to setup Redis Clusters for testing +if(ENABLE_IPV6_TESTS) + set(NO_IPV6 "") +else() + set(NO_IPV6 "true") # Ignore command +endif() + find_program(Docker_EXECUTABLE docker) -set(CONTAINER "bjosv/redis-cluster:latest") # When supporting password, change to: grokzen/redis-cluster:latest +set(CONTAINER "bjosv/redis-cluster:latest") # When supporting password and IPv6, change to: grokzen/redis-cluster:vX.X + add_custom_target(start COMMAND ${Docker_EXECUTABLE} run --name docker-cluster -d -p 7000-7006:7000-7006 ${CONTAINER} || (exit 0) COMMAND ${Docker_EXECUTABLE} run --name docker-cluster-passw -d -e INITIAL_PORT=7100 -e PASSWORD="secretword" -p 7100-7106:7100-7106 ${CONTAINER} || (exit 0) + COMMAND ${NO_IPV6} ${Docker_EXECUTABLE} run --name docker-cluster-ipv6 -d -e INITIAL_PORT=7200 -e IP=:: -e BIND_ADDRESS=::1 --network host ${CONTAINER} || (exit 0) ) add_custom_target(stop COMMAND ${Docker_EXECUTABLE} rm -f docker-cluster || (exit 0) COMMAND ${Docker_EXECUTABLE} rm -f docker-cluster-passw || (exit 0) + COMMAND ${NO_IPV6} ${Docker_EXECUTABLE} rm -f docker-cluster-ipv6 || (exit 0) ) # Find dependencies @@ -60,9 +69,12 @@ target_link_libraries(ct_pipeline hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT add_test(NAME ct_pipeline COMMAND "$") set_tests_properties(ct_pipeline PROPERTIES LABELS "CT") -# Executable: IPv6 -add_executable(example_ipv6 main_ipv6.c) -target_link_libraries(example_ipv6 hiredis_cluster hiredis ${SSL_LIBRARY}) +add_executable(ct_connection_ipv6 ct_connection_ipv6.c) +target_link_libraries(ct_connection_ipv6 hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) +if(ENABLE_IPV6_TESTS) + add_test(NAME ct_connection_ipv6 COMMAND "$") + set_tests_properties(ct_connection_ipv6 PROPERTIES LABELS "CT") +endif() if(ENABLE_SSL) # Executable: tls diff --git a/tests/ct_connection_ipv6.c b/tests/ct_connection_ipv6.c new file mode 100644 index 00000000..bc490138 --- /dev/null +++ b/tests/ct_connection_ipv6.c @@ -0,0 +1,47 @@ +#include "hircluster.h" +#include "test_utils.h" +#include +#include +#include +#include + +#define CLUSTER_NODE_IPV6 "::1:7200" + +// Successful connection an IPv6 cluster +void test_successful_ipv6_connection() { + + redisClusterContext *cc = redisClusterContextInit(); + assert(cc); + + int status; + struct timeval timeout = {0, 500000}; // 0.5s + status = redisClusterSetOptionConnectTimeout(cc, timeout); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + status = redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_IPV6); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + status = redisClusterSetOptionRouteUseSlots(cc); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + status = redisClusterConnect2(cc); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + redisReply *reply; + reply = (redisReply *)redisClusterCommand(cc, "SET key_ipv6 value"); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "GET key_ipv6"); + CHECK_REPLY_STR(cc, reply, "value"); + freeReplyObject(reply); + + redisClusterFree(cc); +} + +int main() { + + test_successful_ipv6_connection(); + + return 0; +} diff --git a/tests/main_ipv6.c b/tests/main_ipv6.c deleted file mode 100644 index 51ffebe7..00000000 --- a/tests/main_ipv6.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "hircluster.h" -#include -#include -#include - -int main(int argc, char **argv) { - UNUSED(argc); - UNUSED(argv); - - struct timeval timeout = {1, 500000}; // 1.5s - - redisClusterContext *cc = redisClusterContextInit(); - assert(cc); - redisClusterSetOptionConnectTimeout(cc, timeout); - redisClusterSetOptionRouteUseSlots(cc); - - if (redisClusterSetOptionAddNodes(cc, "::1:30001") != REDIS_OK) { - printf("Error: %s\n", cc->errstr); - exit(-1); - } - if (redisClusterConnect2(cc) != REDIS_OK) { - printf("Error: %s\n", cc->errstr); - exit(-1); - } - - redisReply *reply = - (redisReply *)redisClusterCommand(cc, "SET %s %s", "key", "value"); - printf("SET: %s\n", reply->str); - freeReplyObject(reply); - - redisReply *reply2 = (redisReply *)redisClusterCommand(cc, "GET %s", "key"); - printf("GET: %s\n", reply2->str); - freeReplyObject(reply2); - - redisClusterFree(cc); - return 0; -} From e55a547b370699edf35a2cfabc24aa86ec5cac70 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Tue, 24 Nov 2020 16:39:41 +0100 Subject: [PATCH 087/273] Cleanup of README --- README.md | 62 +++++++++++++++++++++++++----------------- tests/main_async_tls.c | 4 ++- tests/main_tls.c | 4 ++- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 8d03044f..1527172e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # Hiredis-cluster -Hiredis-cluster is a C client library for cluster deployments of the [Redis](http://redis.io/) database. +Hiredis-cluster is a C client library for cluster deployments of the +[Redis](http://redis.io/) database. -Hiredis-cluster is using [Hiredis](https://github.com/redis/hiredis) for the connections to each Redis node. +Hiredis-cluster is using [Hiredis](https://github.com/redis/hiredis) for the +connections to each Redis node. Hiredis-cluster is a fork of Hiredis-vip, with the following improvements: -* The C library `hiredis` is an external dependency rather than a builtin part of the cluster client, meaning that the latest `hiredis` can be used. +* The C library `hiredis` is an external dependency rather than a builtin part + of the cluster client, meaning that the latest `hiredis` can be used. * Support for SSL/TLS introduced in Redis 6 * Support for IPv6 * Using CMake as build system @@ -49,8 +52,9 @@ Prerequisites: if building without tests (DISABLE_TESTS=ON) * OpenSSL (`libssl-dev` in Debian) if building with TLS support -Hiredis-cluster will be built as a shared library and the test suites will additionally depend on the shared library libhiredis.so, -and libhiredis_ssl.so when SSL is enabled. +Hiredis-cluster will be built as a shared library and the test suites will +additionally depend on the shared library libhiredis.so, and libhiredis_ssl.so +when SSL is enabled. ```sh $ mkdir build; cd build @@ -58,15 +62,6 @@ $ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SSL=ON .. $ make ``` -Some tests needs a Redis cluster and that can be setup by the make targets `start`/`stop` -The clusters will be setup using Docker and it may take a while before initiation and being able to run the tests. - -```sh -$ make start -$ make test -$ make stop -``` - ### Build options The following CMake options are available: @@ -87,24 +82,41 @@ The following CMake options are available: `DOWNLOAD_HIREDIS=ON`). * `ENABLE_IPV6_TESTS` * `OFF` (default) - * `ON` Enable IPv6 tests. Requires that IPv6 is [setup](https://docs.docker.com/config/daemon/ipv6/) in Docker. + * `ON` Enable IPv6 tests. Requires that IPv6 is + [setup](https://docs.docker.com/config/daemon/ipv6/) in Docker. ### Build details -The build uses CMake's [find_package](https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure) to search for a `hiredis` installation. -When building and installing `hiredis` a file called `hiredis-config.cmake` will be installed and this contains relevant information for users. +The build uses CMake's [find_package](https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure) +to search for a `hiredis` installation. When building and installing `hiredis` a +file called `hiredis-config.cmake` will be installed and this contains relevant information for users. -As described in the CMake docs a specific path can be set using a flag like: `-Dhiredis_DIR:PATH=${MY_DIR}/hiredis/share/hiredis` +As described in the CMake docs a specific path can be set using a flag like: +`-Dhiredis_DIR:PATH=${MY_DIR}/hiredis/share/hiredis` ### Running the tests -The tests and examples binaries are located under `build/tests` after building -using the instructions above. Then run `make start` (WIP) to start a Redis -cluster and then `make test`. If you want to set up the Redis cluster manually, -it should run on localhost, where one of the nodes listens on port 30001 and has -TLS disabled. It should accept both IPv4 and IPv6 for all tests to pass. For the -TLS tests, one node is to listen on port 31001 and all nodes has to have TLS -enabled. +Some tests needs a Redis cluster and that can be setup by the make targets +`start`/`stop`. The clusters will be setup using Docker and it may take a while +for them to be ready and accepting requests. Run `make start` to start the +clusters and then wait a few seconds before running `make test`. +To stop the running cluster containers run `make stop`. + +```sh +$ make start +$ make test +$ make stop +``` + +If you want to set up the Redis clusters manually they should run on localhost +using following access ports: + +| Cluster type | Access port | +| ---------------------------------- | -------: | +| IPv4 | 7000 | +| IPv4, authentication needed, password: `secretword` | 7100 | +| IPv6 | 7200 | +| IPv4, using TLS/SSL | 7300 | ## Quick usage diff --git a/tests/main_async_tls.c b/tests/main_async_tls.c index adb064ad..8ffad901 100644 --- a/tests/main_async_tls.c +++ b/tests/main_async_tls.c @@ -5,6 +5,8 @@ #include "adapters/libevent.h" #include "hircluster.h" +#define CLUSTER_NODE_TLS "127.0.0.1:7300" + void getCallback(redisClusterAsyncContext *cc, void *r, void *privdata) { redisReply *reply = (redisReply *)r; if (reply == NULL) { @@ -66,7 +68,7 @@ int main(int argc, char **argv) { assert(acc); redisClusterAsyncSetConnectCallback(acc, connectCallback); redisClusterAsyncSetDisconnectCallback(acc, disconnectCallback); - redisClusterSetOptionAddNodes(acc->cc, "127.0.0.1:31001"); + redisClusterSetOptionAddNodes(acc->cc, CLUSTER_NODE_TLS); redisClusterSetOptionRouteUseSlots(acc->cc); redisClusterSetOptionParseSlaves(acc->cc); redisClusterSetOptionEnableSSL(acc->cc, ssl); diff --git a/tests/main_tls.c b/tests/main_tls.c index 00954b1d..1a08f3d6 100644 --- a/tests/main_tls.c +++ b/tests/main_tls.c @@ -2,6 +2,8 @@ #include #include +#define CLUSTER_NODE_TLS "127.0.0.1:7300" + int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); @@ -20,7 +22,7 @@ int main(int argc, char **argv) { struct timeval timeout = {1, 500000}; // 1.5s redisClusterContext *cc = redisClusterContextInit(); - redisClusterSetOptionAddNodes(cc, "127.0.0.1:31001"); + redisClusterSetOptionAddNodes(cc, CLUSTER_NODE_TLS); redisClusterSetOptionConnectTimeout(cc, timeout); redisClusterSetOptionRouteUseSlots(cc); redisClusterSetOptionParseSlaves(cc); From 93d3595bdde25fa6baa0b36114ecf4242e1fdd6b Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Tue, 24 Nov 2020 23:04:00 +0100 Subject: [PATCH 088/273] Run tests in CI --- .github/workflows/ci.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f44a340..bd7ae08e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,20 @@ jobs: working-directory: build run: cmake --build . --config $BUILD_TYPE - # - name: Test - # working-directory: build - # shell: bash - # run: ctest -C $BUILD_TYPE + # Run CI tests + - name: Setup clusters + shell: bash + working-directory: build + run: make start + - name: Wait for clusters to start.. + uses: jakejarvis/wait-action@master + with: + time: '20s' + - name: Run tests + shell: bash + working-directory: build + run: make test + - name: Teardown clusters + working-directory: build + shell: bash + run: make stop From faa913d2551275f1cc77687df8240a90174951c1 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 25 Nov 2020 17:16:20 +0100 Subject: [PATCH 089/273] Add code formatting target All code can be formatted according to the rules in .clang-format by running `make format`. --- CMakeLists.txt | 9 +++++++++ CONTRIBUTING.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 621d7cf9..ec0713e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,15 @@ if(NOT DISABLE_TESTS) add_subdirectory(tests) endif() +# Code formatting target +find_program(CLANG_FORMAT "clang-format") +file(GLOB_RECURSE FILES_TO_FORMAT + ${PROJECT_SOURCE_DIR}/*.[ch] +) +add_custom_target(format + COMMAND ${CLANG_FORMAT} -i ${FILES_TO_FORMAT} +) + configure_file(hiredis_cluster.pc.in hiredis_cluster.pc @ONLY) install(TARGETS hiredis_cluster diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..0a32d733 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +:tada:Thanks for taking the time to contribute!:tada: + +The following is a set of guidelines for contributing to hiredis-cluster. + +The basics about setting up the project, building and testing is covered in +the [README](README.md). + +## Coding conventions + +### Code style + +Adhere to the existing coding style and make sure to mimic best possible. + +### Code formatting + +To have a common look-and-feel [clang-format](https://clang.llvm.org/docs/ClangFormat.html) +is used for code formatting. The formatting rules can be applied to the +source code by running the following make target in your build directory: + +```sh +$ make format +``` + +## Submitting changes + +* Run the formatter before committing when contributing to this project (`make format`). +* Cover new behaviour with tests when possible. + +## Links + +* [clang-format](https://apt.llvm.org/) for code formatting From 4875ea67890b6328bbad9a9fcb5f20414ea225f7 Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 25 Nov 2020 18:06:45 +0100 Subject: [PATCH 090/273] Add option to build using sanitizers The option is set when generating makefiles, like the other configs: `cmake -DDOWNLOAD_HIREDIS=ON -DUSE_SANITIZER=leak ..` * Build and test using sanitizers in CI as a new job * Leak found so sanitizers `leak` and `address` temporary disabled in CI --- .github/workflows/ci.yml | 52 +++++++++++++++++++++++++++++++--------- CMakeLists.txt | 5 ++++ README.md | 4 ++++ tests/CMakeLists.txt | 2 ++ 4 files changed, 52 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd7ae08e..c1f4b609 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,44 +8,74 @@ env: jobs: checkers: + name: Run static checkers runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Run clang-format style check (.c and .h) uses: jidicula/clang-format-action@master - build: + sanitizers: + name: Test with -fsanitizer=${{ matrix.sanitizer }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: [thread, undefined] # LEAK TO BE CORRECTED: leak, address + steps: + - name: Prepare + run: sudo apt install libevent-dev + - uses: actions/checkout@v2 + - name: Create build folder + run: cmake -E make_directory build + - name: Generate makefiles + shell: bash + working-directory: build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DENABLE_SSL=ON -DUSE_SANITIZER=${{ matrix.sanitizer }} .. + - name: Build + shell: bash + working-directory: build + run: VERBOSE=1 make + - name: Setup clusters + shell: bash + working-directory: build + run: make start + - name: Wait for clusters to start.. + uses: jakejarvis/wait-action@master + with: + time: '20s' + - name: Run tests + shell: bash + working-directory: build + run: make CTEST_OUTPUT_ON_FAILURE=1 test + - name: Teardown clusters + working-directory: build + shell: bash + run: make stop + build: + name: Build with ${{ matrix.compiler }} + runs-on: ubuntu-latest strategy: matrix: compiler: [gcc, clang] - steps: - name: Prepare run: sudo apt install libevent-dev - - uses: actions/checkout@v2 - - name: Create build folder run: cmake -E make_directory build - - name: Generate makefiles env: CC: ${{ matrix.compiler }} CXX: ${{ matrix.compiler }} - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system shell: bash working-directory: build run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DENABLE_SSL=ON .. - - name: Build shell: bash working-directory: build run: cmake --build . --config $BUILD_TYPE - - # Run CI tests - name: Setup clusters shell: bash working-directory: build @@ -57,7 +87,7 @@ jobs: - name: Run tests shell: bash working-directory: build - run: make test + run: make CTEST_OUTPUT_ON_FAILURE=1 test - name: Teardown clusters working-directory: build shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index ec0713e0..1880fbc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,11 @@ getVersionBit(HIREDIS_CLUSTER_SONAME) set(VERSION "${HIREDIS_CLUSTER_MAJOR}.${HIREDIS_CLUSTER_MINOR}.${HIREDIS_CLUSTER_PATCH}") message("Detected version: ${VERSION}") +# Build using a sanitizer +if(USE_SANITIZER) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -fsanitize=${USE_SANITIZER}") +endif() + project(hiredis-cluster VERSION "${VERSION}" LANGUAGES C) diff --git a/README.md b/README.md index 1527172e..6711692c 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,10 @@ The following CMake options are available: * `OFF` (default) * `ON` Enable IPv6 tests. Requires that IPv6 is [setup](https://docs.docker.com/config/daemon/ipv6/) in Docker. +* `USE_SANITIZER` + Compile with a sanitizer. Options depends on + [compiler](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#index-fsanitize_003daddress), + but usually: `address`, `thread`, `undefined`, `leak` ### Build details diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cc276eca..6de0f392 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,6 +48,8 @@ endif() # Debug mode for tests set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "" FORCE) +# Make sure ctest gives the output when tests fail +list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") add_executable(ct_async ct_async.c) target_link_libraries(ct_async hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) From f9b977ef28c1d8f99c3394cd065bafeefe17392d Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Thu, 26 Nov 2020 14:56:08 +0100 Subject: [PATCH 091/273] Correcting iterator leaks Leaks in multi-command handling: - redisClusterGetReply() - command_post_fragment() --- .github/workflows/ci.yml | 48 +++++----------------------------------- CMakeLists.txt | 1 + README.md | 11 ++++++--- hircluster.c | 16 ++++++++++++-- tests/ct_commands.c | 44 ++++++++++++++++++++++++++++++++++++ tests/ct_pipeline.c | 35 +++++++++++++++++++++++++++++ tests/test_utils.h | 7 ++++++ 7 files changed, 115 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1f4b609..4c7f9f21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,49 +15,13 @@ jobs: - name: Run clang-format style check (.c and .h) uses: jidicula/clang-format-action@master - sanitizers: - name: Test with -fsanitizer=${{ matrix.sanitizer }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - sanitizer: [thread, undefined] # LEAK TO BE CORRECTED: leak, address - steps: - - name: Prepare - run: sudo apt install libevent-dev - - uses: actions/checkout@v2 - - name: Create build folder - run: cmake -E make_directory build - - name: Generate makefiles - shell: bash - working-directory: build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DENABLE_SSL=ON -DUSE_SANITIZER=${{ matrix.sanitizer }} .. - - name: Build - shell: bash - working-directory: build - run: VERBOSE=1 make - - name: Setup clusters - shell: bash - working-directory: build - run: make start - - name: Wait for clusters to start.. - uses: jakejarvis/wait-action@master - with: - time: '20s' - - name: Run tests - shell: bash - working-directory: build - run: make CTEST_OUTPUT_ON_FAILURE=1 test - - name: Teardown clusters - working-directory: build - shell: bash - run: make stop - build: - name: Build with ${{ matrix.compiler }} + name: Build [${{ matrix.compiler }}, sanitizer="${{ matrix.sanitizer }}"] runs-on: ubuntu-latest strategy: + fail-fast: false matrix: + sanitizer: ["", thread, undefined, leak, address] compiler: [gcc, clang] steps: - name: Prepare @@ -66,16 +30,16 @@ jobs: - name: Create build folder run: cmake -E make_directory build - name: Generate makefiles + shell: bash env: CC: ${{ matrix.compiler }} CXX: ${{ matrix.compiler }} - shell: bash working-directory: build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DENABLE_SSL=ON .. + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DENABLE_SSL=ON -DUSE_SANITIZER=${{ matrix.sanitizer }} .. - name: Build shell: bash working-directory: build - run: cmake --build . --config $BUILD_TYPE + run: VERBOSE=1 make - name: Setup clusters shell: bash working-directory: build diff --git a/CMakeLists.txt b/CMakeLists.txt index 1880fbc4..1f1f3e83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.14) include(GNUInstallDirs) project(hiredis-cluster) +# Options option(DOWNLOAD_HIREDIS "Download the dependency hiredis from GitHub" ON) option(ENABLE_SSL "Enable SSL/TLS support" OFF) option(DISABLE_TESTS "Disable compilation of test" OFF) diff --git a/README.md b/README.md index 6711692c..a490085e 100644 --- a/README.md +++ b/README.md @@ -85,9 +85,14 @@ The following CMake options are available: * `ON` Enable IPv6 tests. Requires that IPv6 is [setup](https://docs.docker.com/config/daemon/ipv6/) in Docker. * `USE_SANITIZER` - Compile with a sanitizer. Options depends on - [compiler](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#index-fsanitize_003daddress), - but usually: `address`, `thread`, `undefined`, `leak` + Compile using a specific sanitizer that detect issues. The value of this + option specifies which sanitizer to activate, but it depends on support in the + [compiler](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html#index-fsanitize_003daddress). + Common option values are: `address`, `thread`, `undefined`, `leak` + +Options needs to be set with the `-D` flag when generating makefiles, e.g. + +`cmake -DENABLE_SSL=ON -DUSE_SANITIZER=address ..` ### Build details diff --git a/hircluster.c b/hircluster.c index 66dc08a0..464cf60b 100644 --- a/hircluster.c +++ b/hircluster.c @@ -2867,19 +2867,23 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, sub_command = list_node->value; reply = sub_command->reply; if (reply == NULL) { + listReleaseIterator(list_iter); return NULL; } else if (reply->type == REDIS_REPLY_ERROR) { + listReleaseIterator(list_iter); return reply; } if (command->type == CMD_REQ_REDIS_MGET) { if (reply->type != REDIS_REPLY_ARRAY) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); + listReleaseIterator(list_iter); return NULL; } } else if (command->type == CMD_REQ_REDIS_DEL) { if (reply->type != REDIS_REPLY_INTEGER) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); + listReleaseIterator(list_iter); return NULL; } @@ -2887,6 +2891,7 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, } else if (command->type == CMD_REQ_REDIS_EXISTS) { if (reply->type != REDIS_REPLY_INTEGER) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); + listReleaseIterator(list_iter); return NULL; } @@ -2895,12 +2900,14 @@ static void *command_post_fragment(redisClusterContext *cc, struct cmd *command, if (reply->type != REDIS_REPLY_STATUS || reply->len != 2 || strcmp(reply->str, REDIS_STATUS_OK) != 0) { __redisClusterSetError(cc, REDIS_ERR_OTHER, "reply type error"); + listReleaseIterator(list_iter); return NULL; } } else { NOT_REACHED(); } } + listReleaseIterator(list_iter); reply = hi_calloc(1, sizeof(*reply)); @@ -3449,7 +3456,7 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { struct cmd *command, *sub_command; hilist *commands = NULL; listNode *list_command, *list_sub_command; - listIter *list_iter; + listIter *list_iter = NULL; int slot_num; void *sub_reply; @@ -3462,7 +3469,7 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { *reply = NULL; if (cc->requests == NULL) - return REDIS_ERR; + return REDIS_ERR; // No queued requests list_command = listFirst(cc->requests); @@ -3515,6 +3522,7 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { sub_command->reply = sub_reply; } + listReleaseIterator(list_iter); *reply = command_post_fragment(cc, command, commands); if (*reply == NULL) { @@ -3526,6 +3534,10 @@ int redisClusterGetReply(redisClusterContext *cc, void **reply) { error: + if (list_iter != NULL) { + listReleaseIterator(list_iter); + } + listDelNode(cc->requests, list_command); return REDIS_ERR; } diff --git a/tests/ct_commands.c b/tests/ct_commands.c index 687ff161..34be823c 100644 --- a/tests/ct_commands.c +++ b/tests/ct_commands.c @@ -30,6 +30,48 @@ void test_exists(redisClusterContext *cc) { freeReplyObject(reply); } +void test_mset(redisClusterContext *cc) { + redisReply *reply; + reply = (redisReply *)redisClusterCommand( + cc, "MSET key1 mset1 key2 mset2 key3 mset3"); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "GET key1"); + CHECK_REPLY_STR(cc, reply, "mset1"); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "GET key2"); + CHECK_REPLY_STR(cc, reply, "mset2"); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "GET key3"); + CHECK_REPLY_STR(cc, reply, "mset3"); + freeReplyObject(reply); +} + +void test_mget(redisClusterContext *cc) { + redisReply *reply; + reply = (redisReply *)redisClusterCommand(cc, "SET key1 mget1"); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "SET key2 mget2"); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "SET key3 mget3"); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + reply = (redisReply *)redisClusterCommand(cc, "MGET key1 key2 key3"); + CHECK_REPLY_ARRAY(cc, reply, 3); + CHECK_REPLY_STR(cc, reply->element[0], "mget1"); + CHECK_REPLY_STR(cc, reply->element[1], "mget2"); + CHECK_REPLY_STR(cc, reply->element[2], "mget3"); + freeReplyObject(reply); +} + int main() { struct timeval timeout = {0, 500000}; @@ -43,6 +85,8 @@ int main() { ASSERT_MSG(status == REDIS_OK, cc->errstr); test_exists(cc); + test_mset(cc); + test_mget(cc); redisClusterFree(cc); return 0; diff --git a/tests/ct_pipeline.c b/tests/ct_pipeline.c index 774e995c..f999ced6 100644 --- a/tests/ct_pipeline.c +++ b/tests/ct_pipeline.c @@ -51,6 +51,39 @@ void test_pipeline() { redisClusterFree(cc); } +// Test of pipelines containing multi-node commands +void test_pipeline_with_multinode_commands() { + redisClusterContext *cc = redisClusterContextInit(); + assert(cc); + + int status; + status = redisClusterSetOptionAddNodes(cc, CLUSTER_NODE); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + status = redisClusterConnect2(cc); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + status = redisClusterAppendCommand(cc, "MSET key1 Hello key2 World key3 !"); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + status = redisClusterAppendCommand(cc, "MGET key1 key2 key3"); + ASSERT_MSG(status == REDIS_OK, cc->errstr); + + redisReply *reply; + redisClusterGetReply(cc, (void *)&reply); + CHECK_REPLY_OK(cc, reply); + freeReplyObject(reply); + + redisClusterGetReply(cc, (void *)&reply); + CHECK_REPLY_ARRAY(cc, reply, 3); + CHECK_REPLY_STR(cc, reply->element[0], "Hello"); + CHECK_REPLY_STR(cc, reply->element[1], "World"); + CHECK_REPLY_STR(cc, reply->element[2], "!"); + freeReplyObject(reply); + + redisClusterFree(cc); +} + //------------------------------------------------------------------------------ // Async API //------------------------------------------------------------------------------ @@ -125,8 +158,10 @@ void test_async_pipeline() { int main() { test_pipeline(); + test_pipeline_with_multinode_commands(); test_async_pipeline(); + // Asynchronous API does not support multi-key commands return 0; } diff --git a/tests/test_utils.h b/tests/test_utils.h index e3f0a916..fa655572 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -36,4 +36,11 @@ ASSERT_MSG((strcmp(_reply->str, _str) == 0), _ctx->errstr); \ } +#define CHECK_REPLY_ARRAY(_ctx, _reply, _num_of_elements) \ + { \ + CHECK_REPLY(_ctx, _reply); \ + CHECK_REPLY_TYPE(_reply, REDIS_REPLY_ARRAY); \ + ASSERT_MSG(_reply->elements == _num_of_elements, _ctx->errstr); \ + } + #endif From 01103018423086b1b973e4a3723d50cdf3b95c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20S=C3=B6derqvist?= Date: Wed, 25 Nov 2020 16:40:08 +0100 Subject: [PATCH 092/273] Tests using simulated Redis node Shell scripts and a simple Redis node traffic simulator in Perl is used in test cases for hard-to-test things such as ASK redirects. --- tests/CMakeLists.txt | 10 ++ tests/clusterclient.c | 46 ++++++ tests/scripts/ask-redirect-test.sh | 64 ++++++++ tests/scripts/set-get-test.sh | 52 +++++++ tests/scripts/simulated-redis.pl | 233 +++++++++++++++++++++++++++++ 5 files changed, 405 insertions(+) create mode 100644 tests/clusterclient.c create mode 100755 tests/scripts/ask-redirect-test.sh create mode 100755 tests/scripts/set-get-test.sh create mode 100755 tests/scripts/simulated-redis.pl diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6de0f392..28a23a4f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -89,3 +89,13 @@ if(ENABLE_SSL) target_link_libraries(example_async_tls hiredis_cluster hiredis ${SSL_LIBRARY} ${EVENT_LIBRARY}) add_dependencies(example_async_tls generate_tls_configs) endif() + +# Tests using simulated redis node +add_executable(clusterclient clusterclient.c) +target_link_libraries(clusterclient hiredis_cluster hiredis ${SSL_LIBRARY}) +add_test(NAME set-get-test + COMMAND "${CMAKE_SOURCE_DIR}/tests/scripts/set-get-test.sh" "$" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/tests/scripts/") +add_test(NAME ask-redirect-test + COMMAND "${CMAKE_SOURCE_DIR}/tests/scripts/ask-redirect-test.sh" "$" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/tests/scripts/") diff --git a/tests/clusterclient.c b/tests/clusterclient.c new file mode 100644 index 00000000..94377cf3 --- /dev/null +++ b/tests/clusterclient.c @@ -0,0 +1,46 @@ +/* + * This program connects to a cluster and then reads commands from stdin, such + * as "SET foo bar", one per line and prints the results to stdout. + */ + +#include "hircluster.h" +#include +#include +#include + +int main(int argc, char **argv) { + if (argc < 1) { + fprintf(stderr, "Usage: clusterclient HOST:PORT\n"); + exit(1); + } + const char *initnode = argv[1]; + + struct timeval timeout = {1, 500000}; // 1.5s + + redisClusterContext *cc = redisClusterContextInit(); + redisClusterSetOptionAddNodes(cc, initnode); + redisClusterSetOptionConnectTimeout(cc, timeout); + redisClusterSetOptionRouteUseSlots(cc); + redisClusterConnect2(cc); + if (cc && cc->err) { + fprintf(stderr, "Connect error: %s\n", cc->errstr); + exit(100); + } + + char command[256]; + while (fgets(command, 256, stdin)) { + size_t len = strlen(command); + if (command[len - 1] == '\n') // Chop trailing line break + command[len - 1] = '\0'; + redisReply *reply = (redisReply *)redisClusterCommand(cc, command); + if (cc->err) { + fprintf(stderr, "redisClusterCommand error: %s\n", cc->errstr); + exit(101); + } + printf("%s\n", reply->str); + freeReplyObject(reply); + } + + redisClusterFree(cc); + return 0; +} diff --git a/tests/scripts/ask-redirect-test.sh b/tests/scripts/ask-redirect-test.sh new file mode 100755 index 00000000..f1e1bb99 --- /dev/null +++ b/tests/scripts/ask-redirect-test.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +# Usage: $0 /path/to/clusterclient-binary + +clientprog=${1:-./clusterclient} +testname=ask-redirect-test + +# Sync processes waiting for CONT signals. +perl -we 'use sigtrap "handler", sub{exit}, "CONT"; sleep 1; die "timeout"' & +syncpid1=$!; +perl -we 'use sigtrap "handler", sub{exit}, "CONT"; sleep 1; die "timeout"' & +syncpid2=$!; + +# Start simulated redis node #1 +timeout 5s ./simulated-redis.pl -p 7401 -d --sigcont $syncpid1 <<'EOF' & +EXPECT ["CLUSTER", "SLOTS"] +SEND [[0, 16383, ["127.0.0.1", 7401, "nodeid123"]]] +EXPECT RECONNECT +EXPECT ["GET", "foo"] +SEND -ASK 12182 127.0.0.1:7402 +EXPECT CLOSE +EOF +server1=$! + +# Start simulated redis node #2 +timeout 5s ./simulated-redis.pl -p 7402 -d --sigcont $syncpid2 <<'EOF' & +EXPECT ["ASKING"] +SEND +OK +EXPECT ["GET", "foo"] +SEND "bar" +EXPECT CLOSE +EOF +server2=$! + +# Wait until both nodes are ready to accept client connections +wait $syncpid1 $syncpid2; + +# Run client +echo 'GET foo' | timeout 3s "$clientprog" 127.0.0.1:7401 > "$testname.out" +clientexit=$? + +# Wait for servers to exit +wait $server1; server1exit=$? +wait $server2; server2exit=$? + +# Check exit statuses +if [ $server1exit -ne 0 ]; then + echo "Simulated server #1 exited with status $server1exit" + exit $server1exit +fi +if [ $server2exit -ne 0 ]; then + echo "Simulated server #2 exited with status $server2exit" + exit $server2exit +fi +if [ $clientexit -ne 0 ]; then + echo "$clientprog exited with status $clientexit" + exit $clientexit +fi + +# Check the output from clusterclient +echo 'bar' | cmp "$testname.out" - || exit 99 + +# Clean up +rm "$testname.out" diff --git a/tests/scripts/set-get-test.sh b/tests/scripts/set-get-test.sh new file mode 100755 index 00000000..e9a05297 --- /dev/null +++ b/tests/scripts/set-get-test.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +# Usage: $0 /path/to/clusterclient-binary + +clientprog=${1:-./clusterclient} +testname=set-get-test + +# Sync process just waiting for server to be ready to accept connection. +perl -we 'use sigtrap "handler", sub{exit}, "CONT"; sleep 1; die "timeout"' & +syncpid=$! + +# Start simulated server +timeout 5s ./simulated-redis.pl -p 7400 -d --sigcont $syncpid <<'EOF' & +EXPECT ["CLUSTER", "SLOTS"] +SEND [[0, 16383, ["127.0.0.1", 7400, "nodeid123"]]] +EXPECT RECONNECT +EXPECT ["SET", "foo", "bar"] +SEND +OK +EXPECT ["GET", "foo"] +SEND "bar" +EXPECT CLOSE +EOF +server=$! + +# Wait until server is ready to accept client connection +wait $syncpid; + +# Run client +timeout 3s "$clientprog" 127.0.0.1:7400 > "$testname.out" <<'EOF' +SET foo bar +GET foo +EOF +clientexit=$? + +# Wait for server to exit +wait $server; serverexit=$? + +# Check exit statuses +if [ $serverexit -ne 0 ]; then + echo "Simulated server exited with status $serverexit" + exit $serverexit +fi +if [ $clientexit -ne 0 ]; then + echo "$clientprog exited with status $clientexit" + exit $clientexit +fi + +# Check the output from clusterclient +printf 'OK\nbar\n' | cmp "$testname.out" - || exit 99 + +# Clean up +rm "$testname.out" diff --git a/tests/scripts/simulated-redis.pl b/tests/scripts/simulated-redis.pl new file mode 100755 index 00000000..6c5ed793 --- /dev/null +++ b/tests/scripts/simulated-redis.pl @@ -0,0 +1,233 @@ +#!/usr/bin/perl + +# A tool for simulating the traffic of a Redis node. +# +# Copyright 2020 Ericsson Software Technology +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without any warranty. + +use strict; +use warnings; +use Socket; + +my $port = 7000; +my $debug = 0; +my ($sig, $sig_pid); + +# Parse command line args +while ($_ = shift) { + if (/^(?:-p|--port)$/) { + $port = shift; + die "Bad port: $port\n" unless $port > 0; + } elsif (/^--sig(cont|alrm|int|hup|term)$/) { + $sig = uc $1; + $sig_pid = shift; + die "Pid expected after $_\n" unless $sig_pid; + } elsif (/^(?:-d|--debug)$/) { + $debug = 1; + } elsif (/^(?:-h|--help)$/) { + map { print "$_\n" } + "Usage: $0 [ OPTIONS ]", + "", + "Acts as a Redis node, communicating with a single client according to", + "expected traffic provided as events on stdin.", + "", + "Options:", + "", + " -p PORT, --port PORT TCP port to use.", + " --sigSIG PID Send SIG to PID when ready to accept a", + " client connection.", + " -d, --debug Enable debug printouts.", + " -h, --help Help.", + "", + "Expected traffic is to be provided on stdin, one event per line,", + "where the following events are accepted:", + "", + " SEND response Send response to the client.", + " EXPECT command Receive expected command from the client.", + " CLOSE Close the connection to the client.", + " EXPECT CLOSE Wait for the client to close the connection.", + " EXPECT RECONNECT Wait for the client to close and reconnect.", + " SLEEP n Sleep n seconds.", + "", + "The command and response in the events above is provided in a subset", + "of Perl syntax. Examples:", + "", + " EXPECT [\"GET\", \"foo\"]", + " SEND \"bar\"", + "", + "The response (after SEND) can also be represented in raw backslash-", + "escaped Redis protocol data, optionally without the final '\\r\\n'.", + "Examples:", + "", + " SEND +OK", + " SEND -ERR Some error", + " SEND \$3\\r\\nfoo\\r\\n"; + exit; + } else { + die "Bad option: $_\n"; + } +} + +my $listener; # Listener socket +my $connection; # Connection socket + +END { + close $listener if $listener; +} + +socket($listener, PF_INET, SOCK_STREAM, getprotobyname("tcp")) + or die "socket: $!\n"; +setsockopt($listener, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) + or die "setsockopt: $!\n"; +bind($listener, sockaddr_in($port, INADDR_ANY)) + or die "bind: $!\n"; +listen($listener, 5) + or die "listen: $!\n"; +print "(port $port) Listening.\n" if $debug; +kill $sig, $sig_pid if $sig_pid; +my $peer_addr = accept($connection, $listener); +my($client_port, $client_addr) = sockaddr_in($peer_addr); +my $name = gethostbyaddr($client_addr, AF_INET); +print "(port $port) Connection from $name [", inet_ntoa($client_addr), "]", + " on client port $client_port.\n" if $debug; + +# Loop over events on stdin. +while (<>) { + next if /^#/; # skip commented-out events + print "(port $port) $_" if $debug; + if (/^SEND (.*)/) { + my $data = $1; + if ($data =~ /^[-+\$\*:]/) { + # Redis protocol with character escapes + # e.g. '-ERR Unknown command: FOO\r\n' + $data = unescape($data); + $data .= "\r\n" unless $data =~ /\r\n$/; + } else { + # e.g. '["foo", "bar", 42]' + $data = redis_encode(eval $1); + } + print $connection $data; + flush $connection; + } elsif (/^CLOSE$/) { + close $connection; + } elsif (/^EXPECT CLOSE$/) { + my $buffer; + my $bytes_read = read $connection, $buffer, 1; + die "(port $port) Data received from peer when close is expected.\n" + if $bytes_read; + print "(port $port) Client disconnected.\n" if $debug; + close $connection; + } elsif (/^EXPECT RECONNECT$/) { + my $buffer; + my $bytes_read = read $connection, $buffer, 1; + die "(port $port) Data received from peer when reconnect is expected.\n" + if $bytes_read; + close $connection; + print "(port $port) Client disconnected.\n" if $debug; + my $peer_addr = accept($connection, $listener); + my($client_port, $client_addr) = sockaddr_in($peer_addr); + my $name = gethostbyaddr($client_addr, AF_INET); + print "(port $port) Reconnect from $name [", inet_ntoa($client_addr), + "] on client port $client_port.\n" if $debug; + } elsif (/^EXPECT ([\[\"].*)/) { + my $expected = eval $1; + my $received = recv_command($connection); + my $expected_str = pretty_format_command($expected); + my $received_str = pretty_format_command($received); + if ($expected_str ne $received_str) { + die "(port $port) Unexpected $received_str received.\n" . + "Expected $expected_str.\n"; + } + } elsif (/^SLEEP (\d+)$/) { + sleep $1; + } else { + die "(port $port) Unexpected event: $_\n"; + } +} +print "(port $port) Done.\n" if $debug; +exit; + +sub redis_encode { + my $x = shift; + return ("*" . @$x . "\r\n" . join '', map { redis_encode($_) } @$x) + if ref $x eq "ARRAY"; + return ":$x\r\n" + unless ($x ^ $x) ne "0"; # hack to check if int or string + return "\$" . length($x) . "\r\n$x\r\n"; +} + +sub recv_command { + my $connection = shift; + my $old_rec_sep = $/; + $/ = "\r\n"; + $_ = <$connection>; + die "(port $port) The peer has closed the connection\n" if !defined $_; + my $result; + if (/^\*(\d+)\r\n$/) { + my $n = $1; + $result = []; + for (my $i = 0; $i < $n; $i++) { + push @$result, recv_command($connection); + } + } elsif (/^\$0\r\n$/) { + $result = ""; + $_ = <$connection>; + die "(port $port) Expected \\r\\n after empty string\n" + unless /^\r\n$/; + } elsif (/^\$(\d+)\r\n$/) { + my $remaining = $1; + $result = ""; + my $buffer; + do { + my $read = read $connection, $buffer, $remaining; + die "(port $port) Unexpected EOF while receiving command\n" + unless $read; + $result .= $buffer; + $remaining -= $read; + } while ($remaining > 0); + $_ = <$connection>; + die "(port $port) Expected \\r\\n after string\n" unless /^\r\n$/ + } else { + die "Unexpected command: $_\n"; + } + $/ = $old_rec_sep; + return $result; +} + +# ["GET", "foo\tbar"] => '["GET", "foo\tbar"]' +sub pretty_format_command { + my $x = shift; + return "[" . join(", ", map { pretty_format_command($_) } @$x) . "]" + if ref $x eq "ARRAY"; + return '"' . escape($x) . '"' + if ref $x eq ""; + die "Can't serialize " . ref $x . " $x\n"; +} + +# Escape special chars to binary data readable +# "hello\r\n" => "hello\\r\\n" +sub escape { + $_ = shift; + s/([^A-Za-z0-9\/\-_.,:;!?~\*'(){}\^\$])/ escape_char($1) /eg; + return $_; +} +sub escape_char { + $_ = shift; + return "\\r" if /\r/; # nicer than \x0d, etc. + return "\\n" if /\n/; + return "\\t" if /\t/; + return '\\"' if /\"/; + return "\\\\" if /\\/; + return sprintf "\\x%02x", ord $_; +} + +# "hello\\r\\n" => "hello\r\n" +sub unescape { + $_ = shift; + s/\\([tnrfbae\\]|x\{[0-9a-fA-F]+\}|x[0-9a-fA-F]{1,2}|0[0-7]{0,2})/eval "\"\\$1\""/eg; + return $_; +} From e9f8480877d245d1df7276d1151f35882188df8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20S=C3=B6derqvist?= Date: Tue, 1 Dec 2020 17:40:25 +0100 Subject: [PATCH 093/273] Fix memory leak in ASK redirect handling --- hircluster.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hircluster.c b/hircluster.c index 464cf60b..3306d9ad 100644 --- a/hircluster.c +++ b/hircluster.c @@ -2377,8 +2377,8 @@ static cluster_node *node_get_by_ask_error_reply(redisClusterContext *cc, dictAdd(cc->nodes, sdsnewlen(node->addr, sdslen(node->addr)), node); - part = NULL; - ip_port = NULL; + part[1] = NULL; + ip_port[0] = NULL; } else { node = de->val; From 7afec210bc8cf9e624e257af7fd3a4618649cc9a Mon Sep 17 00:00:00 2001 From: Bjorn Svensson Date: Wed, 2 Dec 2020 00:11:05 +0100 Subject: [PATCH 094/273] Add command tests for hset/hget/hdel/hexists and eval --- command.c | 15 ++++- hircluster.h | 2 + tests/ct_commands.c | 131 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_utils.h | 14 +++++ 4 files changed, 159 insertions(+), 3 deletions(-) diff --git a/command.c b/command.c index 628849ff..2f880ed3 100644 --- a/command.c +++ b/command.c @@ -381,6 +381,7 @@ void redis_parse_cmd(struct cmd *r) { break; case SW_REQ_TYPE_LEN: + // Parse length of request/command if (token == NULL) { if (ch != '$') { goto error; @@ -393,7 +394,7 @@ void redis_parse_cmd(struct cmd *r) { if (rlen == 0 || rnarg == 0) { goto error; } - rnarg--; + rnarg--; // Remove command from argument counter token = NULL; state = SW_REQ_TYPE_LEN_LF; } else { @@ -415,6 +416,7 @@ void redis_parse_cmd(struct cmd *r) { break; case SW_REQ_TYPE: + // Parse request/command type if (token == NULL) { token = p; } @@ -1188,7 +1190,7 @@ void redis_parse_cmd(struct cmd *r) { if ((p - token) <= 1 || rnarg == 0) { goto error; } - rnarg--; + rnarg--; // Remove first argument from argument counter token = NULL; /* @@ -1230,8 +1232,9 @@ void redis_parse_cmd(struct cmd *r) { break; case SW_ARG1: - m = p + rlen; + m = p + rlen; // Move forward given length ($ in protocol) if (m >= cmd_end) { + // Moving past the end, not good.. // rlen -= (uint32_t)(b->last - p); // m = b->last - 1; // p = m; @@ -1251,6 +1254,9 @@ void redis_parse_cmd(struct cmd *r) { break; case SW_ARG1_LF: + // Check that the command parser has enough + // arguments left to be acceptable + // rnarg is the number of arguments after the first argument switch (ch) { case LF: if (redis_arg1(r)) { @@ -1274,6 +1280,9 @@ void redis_parse_cmd(struct cmd *r) { } state = SW_ARGN_LEN; } else if (redis_argeval(r)) { + // hiredis-cluster needs atleast one key in eval + // to know which instance to use. Normally one argument + // should be accepted (i.e rnarg < 1) if (rnarg < 2) { goto error; } diff --git a/hircluster.h b/hircluster.h index d602b60e..20ff2b92 100644 --- a/hircluster.h +++ b/hircluster.h @@ -109,6 +109,7 @@ typedef struct redisClusterContext { } redisClusterContext; +/* Synchronous API */ redisClusterContext *redisClusterConnect(const char *addrs, int flags); redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, @@ -203,6 +204,7 @@ typedef struct redisClusterAsyncContext { } redisClusterAsyncContext; +/* Async API */ redisClusterAsyncContext *redisClusterAsyncContextInit(void); redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); diff --git a/tests/ct_commands.c b/tests/ct_commands.c index 34be823c..32c7dea3 100644 --- a/tests/ct_commands.c +++ b/tests/ct_commands.c @@ -72,6 +72,135 @@ void test_mget(redisClusterContext *cc) { freeReplyObject(reply); } +void test_hset_hget_hdel_hexists(redisClusterContext *cc) { + redisReply *reply; + + // Prepare + reply = (redisReply *)redisClusterCommand(cc, "HDEL myhash field1"); + CHECK_REPLY(cc, reply); + freeReplyObject(reply); + reply = (redisReply *)redisClusterCommand(cc, "HDEL myhash field2"); + CHECK_REPLY(cc, reply); + freeReplyObject(reply); + + // Set hash field + reply = + (redisReply *)redisClusterCommand(cc, "HSET myhash field1 hsetvalue"); + CHECK_REPLY_INT(cc, reply, 1); // Set 1 field + freeReplyObject(reply); + + // Set second hash field + reply = + (redisReply *)redisClusterCommand(cc, "HSET myhash field3 hsetvalue3"); + CHECK_REPLY_INT(cc, reply, 1); // Set 1 field + freeReplyObject(reply); + + // Get field value + reply = (redisReply *)redisClusterCommand(cc, "HGET myhash field1"); + CHECK_REPLY_STR(cc, reply, "hsetvalue"); + freeReplyObject(reply); + + // Get field that is not present + reply = (redisReply *)redisClusterCommand(cc, "HGET myhash field2"); + CHECK_REPLY_NIL(cc, reply); + freeReplyObject(reply); + + // Delete a field + reply = (redisReply *)redisClusterCommand(cc, "HDEL myhash field1"); + CHECK_REPLY_INT(cc, reply, 1); // Delete 1 field + freeReplyObject(reply); + + // Delete a field that is not present + reply = (redisReply *)redisClusterCommand(cc, "HDEL myhash field2"); + CHECK_REPLY_INT(cc, reply, 0); // Nothing to delete + freeReplyObject(reply); + + // Check if field exists + reply = (redisReply *)redisClusterCommand(cc, "HEXISTS myhash field3"); + CHECK_REPLY_INT(cc, reply, 1); // exists + freeReplyObject(reply); + + // Delete multiple fields at once + reply = (redisReply *)redisClusterCommand( + cc, "HDEL myhash field1 field2 field3"); + CHECK_REPLY_INT(cc, reply, 1); // field3 deleted + freeReplyObject(reply); + + // Make sure field3 is deleted now + reply = (redisReply *)redisClusterCommand(cc, "HEXISTS myhash field3"); + CHECK_REPLY_INT(cc, reply, 0); // no field + freeReplyObject(reply); + + // As of Redis 4.0.0, HSET is variadic i.e. multiple field/value, + // but this is currently not supported. + reply = (redisReply *)redisClusterCommand( + cc, "HSET myhash field1 hsetvalue1 field2 hsetvalue2"); + assert(reply == NULL); +} + +// Command layout: +// eval