48 changed files with 15788 additions and 5 deletions
@ -0,0 +1,7 @@
|
||||
/hiredis-test |
||||
/examples/hiredis-example* |
||||
/*.o |
||||
/*.so |
||||
/*.dylib |
||||
/*.a |
||||
/*.pc |
||||
@ -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 |
||||
@ -0,0 +1,16 @@
|
||||
### 0.3.0 - Dec 07, 2016 |
||||
|
||||
* 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) |
||||
|
||||
### 0.2.1 - Nov 24, 2015 |
||||
|
||||
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) |
||||
@ -0,0 +1,29 @@
|
||||
Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
|
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are met: |
||||
|
||||
* Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, |
||||
this list of conditions and the following disclaimer in the documentation |
||||
and/or other materials provided with the distribution. |
||||
|
||||
* Neither the name of Redis nor the names of its contributors may be used |
||||
to endorse or promote products derived from this software without specific |
||||
prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
@ -0,0 +1,205 @@
|
||||
# Hiredis Makefile
|
||||
# Copyright (C) 2010-2011 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ=net.o hiredis.o sds.o async.o read.o hiarray.o hiutil.o command.o crc16.o adlist.o hircluster.o
|
||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
|
||||
TESTS=hiredis-test
|
||||
LIBNAME=libhiredis_vip
|
||||
PKGCONFNAME=hiredis.pc
|
||||
|
||||
HIREDIS_VIP_MAJOR=$(shell grep HIREDIS_VIP_MAJOR hircluster.h | awk '{print $$3}')
|
||||
HIREDIS_VIP_MINOR=$(shell grep HIREDIS_VIP_MINOR hircluster.h | awk '{print $$3}')
|
||||
HIREDIS_VIP_PATCH=$(shell grep HIREDIS_VIP_PATCH hircluster.h | awk '{print $$3}')
|
||||
|
||||
# Installation related variables and target
|
||||
PREFIX?=/usr/local
|
||||
INCLUDE_PATH?=include/hiredis-vip
|
||||
LIBRARY_PATH?=lib
|
||||
PKGCONF_PATH?=pkgconfig
|
||||
INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
|
||||
INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
|
||||
INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)
|
||||
|
||||
# redis-server configuration used for testing
|
||||
REDIS_PORT=56379
|
||||
REDIS_SERVER=redis-server
|
||||
define REDIS_TEST_CONFIG |
||||
daemonize yes
|
||||
pidfile /tmp/hiredis-test-redis.pid
|
||||
port $(REDIS_PORT)
|
||||
bind 127.0.0.1
|
||||
unixsocket /tmp/hiredis-test-redis.sock
|
||||
endef |
||||
export REDIS_TEST_CONFIG |
||||
|
||||
# Fallback to gcc when $CC is not in $PATH.
|
||||
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
||||
DEBUG?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH)
|
||||
REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
STLIBSUFFIX=a
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_VIP_MAJOR)
|
||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
|
||||
|
||||
# Platform-specific overrides
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
ifeq ($(uname_S),SunOS) |
||||
REAL_LDFLAGS+= -ldl -lnsl -lsocket
|
||||
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
|
||||
INSTALL= cp -r
|
||||
endif |
||||
ifeq ($(uname_S),Darwin) |
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_VIP_MAJOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
endif |
||||
|
||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) |
||||
|
||||
# Deps (use make dep to generate this)
|
||||
|
||||
adlist.o: adlist.c adlist.h hiutil.h |
||||
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h |
||||
command.o: command.c command.h hiredis.h read.h sds.h adlist.h hiutil.h hiarray.h |
||||
crc16.o: crc16.c hiutil.h |
||||
dict.o: dict.c fmacros.h dict.h |
||||
hiarray.o: hiarray.c hiarray.h hiutil.h |
||||
hircluster.o: hircluster.c fmacros.h hircluster.h hiredis.h read.h sds.h adlist.h hiarray.h hiutil.h async.h command.h dict.c dict.h |
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h |
||||
hiutil.o: hiutil.c hiutil.h |
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h |
||||
read.o: read.c fmacros.h read.h sds.h |
||||
sds.o: sds.c sds.h |
||||
test.o: test.c fmacros.h hiredis.h read.h sds.h net.h |
||||
|
||||
$(DYLIBNAME): $(OBJ) |
||||
$(DYLIB_MAKE_CMD) $(OBJ)
|
||||
|
||||
$(STLIBNAME): $(OBJ) |
||||
$(STLIB_MAKE_CMD) $(OBJ)
|
||||
|
||||
dynamic: $(DYLIBNAME) |
||||
static: $(STLIBNAME) |
||||
|
||||
# Binaries:
|
||||
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) |
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
|
||||
|
||||
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) |
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
|
||||
|
||||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) |
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
|
||||
|
||||
ifndef AE_DIR |
||||
hiredis-example-ae: |
||||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||
@false
|
||||
else |
||||
hiredis-example-ae: examples/example-ae.c adapters/ae.h $(STLIBNAME) |
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(AE_DIR) $< $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o $(AE_DIR)/../deps/jemalloc/lib/libjemalloc.a -pthread $(STLIBNAME)
|
||||
endif |
||||
|
||||
ifndef LIBUV_DIR |
||||
hiredis-example-libuv: |
||||
@echo "Please specify LIBUV_DIR (e.g. ../libuv/)"
|
||||
@false
|
||||
else |
||||
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) |
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME)
|
||||
endif |
||||
|
||||
hiredis-example: examples/example.c $(STLIBNAME) |
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
|
||||
|
||||
examples: $(EXAMPLES) |
||||
|
||||
hiredis-test: test.o $(STLIBNAME) |
||||
|
||||
hiredis-%: %.o $(STLIBNAME) |
||||
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
|
||||
|
||||
test: hiredis-test |
||||
./hiredis-test
|
||||
|
||||
check: hiredis-test |
||||
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
|
||||
$(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
|
||||
( kill `cat /tmp/hiredis-test-redis.pid` && false )
|
||||
kill `cat /tmp/hiredis-test-redis.pid`
|
||||
|
||||
.c.o: |
||||
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
|
||||
|
||||
clean: |
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep: |
||||
$(CC) -MM *.c
|
||||
|
||||
ifeq ($(uname_S),SunOS) |
||||
INSTALL?= cp -r
|
||||
endif |
||||
|
||||
INSTALL?= cp -a
|
||||
|
||||
$(PKGCONFNAME): hiredis.h |
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@echo prefix=$(PREFIX) > $@
|
||||
@echo exec_prefix=\$${prefix} >> $@
|
||||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||
@echo >> $@
|
||||
@echo Name: hiredis >> $@
|
||||
@echo Description: Minimalistic C client library for Redis. >> $@
|
||||
@echo Version: $(HIREDIS_VIP_MAJOR).$(HIREDIS_VIP_MINOR).$(HIREDIS_VIP_PATCH) >> $@
|
||||
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) |
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h hiutil.h hiarray.h dict.h dict.c adlist.h fmacros.h hircluster.h adapters $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME)
|
||||
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||
mkdir -p $(INSTALL_PKGCONF_PATH)
|
||||
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
|
||||
|
||||
32bit: |
||||
@echo ""
|
||||
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
|
||||
|
||||
32bit-vars: |
||||
$(eval CFLAGS=-m32)
|
||||
$(eval LDFLAGS=-m32)
|
||||
|
||||
gprof: |
||||
$(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
|
||||
|
||||
gcov: |
||||
$(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
|
||||
|
||||
coverage: gcov |
||||
make check
|
||||
mkdir -p tmp/lcov
|
||||
lcov -d . -c -o tmp/lcov/hiredis.info
|
||||
genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
|
||||
|
||||
noopt: |
||||
$(MAKE) OPTIMIZATION=""
|
||||
|
||||
.PHONY: all test check clean dep install 32bit gprof gcov noopt |
||||
@ -0,0 +1,255 @@
|
||||
|
||||
# HIREDIS-VIP |
||||
|
||||
Hiredis-vip is a C client library for the [Redis](http://redis.io/) database. |
||||
|
||||
Hiredis-vip supported redis cluster. |
||||
|
||||
Hiredis-vip fully contained and based on [Hiredis](https://github.com/redis/hiredis) . |
||||
|
||||
## CLUSTER SUPPORT |
||||
|
||||
### FEATURES: |
||||
|
||||
* **`SUPPORT REDIS CLUSTER`**: |
||||
* Connect to redis cluster and run commands. |
||||
|
||||
* **`SUPPORT MULTI-KEY COMMAND`**: |
||||
* Support `MSET`, `MGET` and `DEL`. |
||||
|
||||
* **`SUPPORT PIPELING`**: |
||||
* Support redis pipeline and can contain multi-key command like above. |
||||
|
||||
* **`SUPPORT Asynchronous API`**: |
||||
* User can run commands with asynchronous mode. |
||||
|
||||
### CLUSTER API: |
||||
|
||||
```c |
||||
redisClusterContext *redisClusterConnect(const char *addrs, int flags); |
||||
redisClusterContext *redisClusterConnectWithTimeout(const char *addrs, const struct timeval tv, int flags); |
||||
redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); |
||||
void redisClusterFree(redisClusterContext *cc); |
||||
void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); |
||||
void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); |
||||
void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); |
||||
void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); |
||||
void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); |
||||
redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); |
||||
int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); |
||||
int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); |
||||
int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); |
||||
int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); |
||||
int redisClusterGetReply(redisClusterContext *cc, void **reply); |
||||
void redisClusterReset(redisClusterContext *cc); |
||||
|
||||
redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); |
||||
int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); |
||||
int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); |
||||
int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); |
||||
int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); |
||||
int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); |
||||
int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); |
||||
|
||||
void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); |
||||
void redisClusterAsyncFree(redisClusterAsyncContext *acc); |
||||
``` |
||||
|
||||
## Quick usage |
||||
|
||||
If you want used but not read the follow, please reference the examples: |
||||
https://github.com/vipshop/hiredis-vip/wiki |
||||
|
||||
## Cluster synchronous API |
||||
|
||||
To consume the synchronous API, there are only a few function calls that need to be introduced: |
||||
|
||||
```c |
||||
redisClusterContext *redisClusterConnect(const char *addrs, int flags); |
||||
void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); |
||||
void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); |
||||
void redisClusterFree(redisClusterContext *cc); |
||||
``` |
||||
|
||||
### Cluster connecting |
||||
|
||||
The function `redisClusterConnect` is used to create a so-called `redisClusterContext`. The |
||||
context is where Hiredis-vip Cluster holds state for connections. The `redisClusterContext` |
||||
struct has an integer `err` field that is non-zero when the connection is in |
||||
an error state. The field `errstr` will contain a string with a description of |
||||
the error. |
||||
After trying to connect to Redis using `redisClusterContext` you should |
||||
check the `err` field to see if establishing the connection was successful: |
||||
```c |
||||
redisClusterContext *cc = redisClusterConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); |
||||
if (cc != NULL && cc->err) { |
||||
printf("Error: %s\n", cc->errstr); |
||||
// handle error |
||||
} |
||||
``` |
||||
|
||||
### Cluster sending commands |
||||
|
||||
The next that will be introduced is `redisClusterCommand`. |
||||
This function takes a format similar to printf. In the simplest form, |
||||
it is used like this: |
||||
```c |
||||
reply = redisClusterCommand(clustercontext, "SET foo bar"); |
||||
``` |
||||
|
||||
The specifier `%s` interpolates a string in the command, and uses `strlen` to |
||||
determine the length of the string: |
||||
```c |
||||
reply = redisClusterCommand(clustercontext, "SET foo %s", value); |
||||
``` |
||||
Internally, Hiredis-vip splits the command in different arguments and will |
||||
convert it to the protocol used to communicate with Redis. |
||||
One or more spaces separates arguments, so you can use the specifiers |
||||
anywhere in an argument: |
||||
```c |
||||
reply = redisClusterCommand(clustercontext, "SET key:%s %s", myid, value); |
||||
``` |
||||
|
||||
### Cluster multi-key commands |
||||
|
||||
Hiredis-vip supports mget/mset/del multi-key commands. |
||||
Those multi-key commands is highly effective. |
||||
Millions of keys in one mget command just used several seconds. |
||||
|
||||
Example: |
||||
```c |
||||
reply = redisClusterCommand(clustercontext, "mget %s %s %s %s", key1, key2, key3, key4); |
||||
``` |
||||
|
||||
### Cluster cleaning up |
||||
|
||||
To disconnect and free the context the following function can be used: |
||||
```c |
||||
void redisClusterFree(redisClusterContext *cc); |
||||
``` |
||||
This function immediately closes the socket and then frees the allocations done in |
||||
creating the context. |
||||
|
||||
### Cluster pipelining |
||||
|
||||
The function `redisClusterGetReply` is exported as part of the Hiredis API and can be used |
||||
when a reply is expected on the socket. To pipeline commands, the only things that needs |
||||
to be done is filling up the output buffer. For this cause, two commands can be used that |
||||
are identical to the `redisClusterCommand` family, apart from not returning a reply: |
||||
```c |
||||
int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); |
||||
int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv); |
||||
``` |
||||
After calling either function one or more times, `redisClusterGetReply` can be used to receive the |
||||
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where |
||||
the latter means an error occurred while reading a reply. Just as with the other commands, |
||||
the `err` field in the context can be used to find out what the cause of this error is. |
||||
```c |
||||
void redisClusterReset(redisClusterContext *cc); |
||||
``` |
||||
Warning: You must call `redisClusterReset` function after one pipelining anyway. |
||||
|
||||
The following examples shows a simple cluster pipeline: |
||||
```c |
||||
redisReply *reply; |
||||
redisClusterAppendCommand(clusterContext,"SET foo bar"); |
||||
redisClusterAppendCommand(clusterContext,"GET foo"); |
||||
redisClusterGetReply(clusterContext,&reply); // reply for SET |
||||
freeReplyObject(reply); |
||||
redisClusterGetReply(clusterContext,&reply); // reply for GET |
||||
freeReplyObject(reply); |
||||
redisClusterReset(clusterContext); |
||||
``` |
||||
|
||||
## Cluster asynchronous API |
||||
|
||||
Hiredis-vip comes with an cluster asynchronous API that works easily with any event library. |
||||
Now we just support and test for libevent and redis ae, if you need for other event libraries, |
||||
please contact with us, and we will support it quickly. |
||||
|
||||
### Connecting |
||||
|
||||
The function `redisAsyncConnect` can be used to establish a non-blocking connection to |
||||
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field |
||||
should be checked after creation to see if there were errors creating the connection. |
||||
Because the connection that will be created is non-blocking, the kernel is not able to |
||||
instantly return if the specified host and port is able to accept a connection. |
||||
```c |
||||
redisClusterAsyncContext *acc = redisClusterAsyncConnect("127.0.0.1:6379", HIRCLUSTER_FLAG_NULL); |
||||
if (acc->err) { |
||||
printf("Error: %s\n", acc->errstr); |
||||
// handle error |
||||
} |
||||
``` |
||||
|
||||
The cluster asynchronous context can hold a disconnect callback function that is called when the |
||||
connection is disconnected (either because of an error or per user request). This function should |
||||
have the following prototype: |
||||
```c |
||||
void(const redisAsyncContext *c, int status); |
||||
``` |
||||
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the |
||||
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err` |
||||
field in the context can be accessed to find out the cause of the error. |
||||
|
||||
You not need to reconnect in the disconnect callback, hiredis-vip will reconnect this connection itself |
||||
when commands come to this redis node. |
||||
|
||||
Setting the disconnect callback can only be done once per context. For subsequent calls it will |
||||
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype: |
||||
```c |
||||
int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); |
||||
``` |
||||
### Sending commands and their callbacks |
||||
|
||||
In an cluster asynchronous context, commands are automatically pipelined due to the nature of an event loop. |
||||
Therefore, unlike the cluster synchronous API, there is only a single way to send commands. |
||||
Because commands are sent to Redis cluster asynchronously, issuing a command requires a callback function |
||||
that is called when the reply is received. Reply callbacks should have the following prototype: |
||||
```c |
||||
void(redisClusterAsyncContext *acc, void *reply, void *privdata); |
||||
``` |
||||
The `privdata` argument can be used to curry arbitrary data to the callback from the point where |
||||
the command is initially queued for execution. |
||||
|
||||
The functions that can be used to issue commands in an asynchronous context are: |
||||
```c |
||||
int redisClusterAsyncCommand( |
||||
redisClusterAsyncContext *acc, |
||||
redisClusterCallbackFn *fn, |
||||
void *privdata, const char *format, ...); |
||||
``` |
||||
This function work like their blocking counterparts. The return value is `REDIS_OK` when the command |
||||
was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection |
||||
is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is |
||||
returned on calls to the `redisClusterAsyncCommand` family. |
||||
|
||||
If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback |
||||
for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only |
||||
valid for the duration of the callback. |
||||
|
||||
All pending callbacks are called with a `NULL` reply when the context encountered an error. |
||||
|
||||
### Disconnecting |
||||
|
||||
An cluster asynchronous connection can be terminated using: |
||||
```c |
||||
void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); |
||||
``` |
||||
When this function is called, the connection is **not** immediately terminated. Instead, new |
||||
commands are no longer accepted and the connection is only terminated when all pending commands |
||||
have been written to the socket, their respective replies have been read and their respective |
||||
callbacks have been executed. After this, the disconnection callback is executed with the |
||||
`REDIS_OK` status and the context object is freed. |
||||
|
||||
### Hooking it up to event library *X* |
||||
|
||||
There are a few hooks that need to be set on the cluster context object after it is created. |
||||
See the `adapters/` directory for bindings to *ae* and *libevent*. |
||||
|
||||
## AUTHORS |
||||
|
||||
Hiredis-vip was maintained and used at vipshop(https://github.com/vipshop). |
||||
The redis client library part in hiredis-vip is same as hiredis(https://github.com/redis/hiredis). |
||||
The redis cluster client library part in hiredis-vip is written by deep(https://github.com/deep011). |
||||
Hiredis-vip is released under the BSD license. |
||||
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef __HIREDIS_AE_H__ |
||||
#define __HIREDIS_AE_H__ |
||||
#include <sys/types.h> |
||||
#include <ae.h> |
||||
#include "../hiredis.h" |
||||
#include "../async.h" |
||||
|
||||
#if 1 //shenzheng 2015-11-5 redis cluster
|
||||
#include "../hircluster.h" |
||||
#endif //shenzheng 2015-11-5 redis cluster
|
||||
|
||||
typedef struct redisAeEvents { |
||||
redisAsyncContext *context; |
||||
aeEventLoop *loop; |
||||
int fd; |
||||
int reading, writing; |
||||
} redisAeEvents; |
||||
|
||||
static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) { |
||||
((void)el); ((void)fd); ((void)mask); |
||||
|
||||
redisAeEvents *e = (redisAeEvents*)privdata; |
||||
redisAsyncHandleRead(e->context); |
||||
} |
||||
|
||||
static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) { |
||||
((void)el); ((void)fd); ((void)mask); |
||||
|
||||
redisAeEvents *e = (redisAeEvents*)privdata; |
||||
redisAsyncHandleWrite(e->context); |
||||
} |
||||
|
||||
static void redisAeAddRead(void *privdata) { |
||||
redisAeEvents *e = (redisAeEvents*)privdata; |
||||
aeEventLoop *loop = e->loop; |
||||
if (!e->reading) { |
||||
e->reading = 1; |
||||
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e); |
||||
} |
||||
} |
||||
|
||||
static void redisAeDelRead(void *privdata) { |
||||
redisAeEvents *e = (redisAeEvents*)privdata; |
||||
aeEventLoop *loop = e->loop; |
||||
if (e->reading) { |
||||
e->reading = 0; |
||||
aeDeleteFileEvent(loop,e->fd,AE_READABLE); |
||||
} |
||||
} |
||||
|
||||
static void redisAeAddWrite(void *privdata) { |
||||
redisAeEvents *e = (redisAeEvents*)privdata; |
||||
aeEventLoop *loop = e->loop; |
||||
if (!e->writing) { |
||||
e->writing = 1; |
||||
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e); |
||||
} |
||||
} |
||||
|
||||
static void redisAeDelWrite(void *privdata) { |
||||
redisAeEvents *e = (redisAeEvents*)privdata; |
||||
aeEventLoop *loop = e->loop; |
||||
if (e->writing) { |
||||
e->writing = 0; |
||||
aeDeleteFileEvent(loop,e->fd,AE_WRITABLE); |
||||
} |
||||
} |
||||
|
||||
static void redisAeCleanup(void *privdata) { |
||||
redisAeEvents *e = (redisAeEvents*)privdata; |
||||
redisAeDelRead(privdata); |
||||
redisAeDelWrite(privdata); |
||||
free(e); |
||||
} |
||||
|
||||
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
redisAeEvents *e; |
||||
|
||||
/* Nothing should be attached when something is already attached */ |
||||
if (ac->ev.data != NULL) |
||||
return REDIS_ERR; |
||||
|
||||
/* Create container for context and r/w events */ |
||||
e = (redisAeEvents*)malloc(sizeof(*e)); |
||||
e->context = ac; |
||||
e->loop = loop; |
||||
e->fd = c->fd; |
||||
e->reading = e->writing = 0; |
||||
|
||||
/* Register functions to start/stop listening for events */ |
||||
ac->ev.addRead = redisAeAddRead; |
||||
ac->ev.delRead = redisAeDelRead; |
||||
ac->ev.addWrite = redisAeAddWrite; |
||||
ac->ev.delWrite = redisAeDelWrite; |
||||
ac->ev.cleanup = redisAeCleanup; |
||||
ac->ev.data = e; |
||||
|
||||
return REDIS_OK; |
||||
} |
||||
|
||||
#if 1 //shenzheng 2015-11-5 redis cluster
|
||||
|
||||
static int redisAeAttach_link(redisAsyncContext *ac, void *base) |
||||
{ |
||||
redisAeAttach((aeEventLoop *)base, ac); |
||||
} |
||||
|
||||
static int redisClusterAeAttach(aeEventLoop *loop, redisClusterAsyncContext *acc) { |
||||
|
||||
if(acc == NULL || loop == NULL) |
||||
{ |
||||
return REDIS_ERR; |
||||
} |
||||
|
||||
acc->adapter = loop; |
||||
acc->attach_fn = redisAeAttach_link; |
||||
|
||||
return REDIS_OK; |
||||
} |
||||
|
||||
#endif //shenzheng 2015-11-5 redis cluster
|
||||
|
||||
#endif |
||||
@ -0,0 +1,153 @@
|
||||
#ifndef __HIREDIS_GLIB_H__ |
||||
#define __HIREDIS_GLIB_H__ |
||||
|
||||
#include <glib.h> |
||||
|
||||
#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__ */ |
||||
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef __HIREDIS_LIBEV_H__ |
||||
#define __HIREDIS_LIBEV_H__ |
||||
#include <stdlib.h> |
||||
#include <sys/types.h> |
||||
#include <ev.h> |
||||
#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 |
||||
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef __HIREDIS_LIBEVENT_H__ |
||||
#define __HIREDIS_LIBEVENT_H__ |
||||
#include <event.h> |
||||
#include "../hiredis.h" |
||||
#include "../async.h" |
||||
|
||||
#if 1 //shenzheng 2015-9-21 redis cluster
|
||||
#include "../hircluster.h" |
||||
#endif //shenzheng 2015-9-21 redis cluster
|
||||
|
||||
typedef struct redisLibeventEvents { |
||||
redisAsyncContext *context; |
||||
struct event rev, wev; |
||||
} redisLibeventEvents; |
||||
|
||||
static void redisLibeventReadEvent(int fd, short event, void *arg) { |
||||
((void)fd); ((void)event); |
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg; |
||||
redisAsyncHandleRead(e->context); |
||||
} |
||||
|
||||
static void redisLibeventWriteEvent(int fd, short event, void *arg) { |
||||
((void)fd); ((void)event); |
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg; |
||||
redisAsyncHandleWrite(e->context); |
||||
} |
||||
|
||||
static void redisLibeventAddRead(void *privdata) { |
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
||||
event_add(&e->rev,NULL); |
||||
} |
||||
|
||||
static void redisLibeventDelRead(void *privdata) { |
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
||||
event_del(&e->rev); |
||||
} |
||||
|
||||
static void redisLibeventAddWrite(void *privdata) { |
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
||||
event_add(&e->wev,NULL); |
||||
} |
||||
|
||||
static void redisLibeventDelWrite(void *privdata) { |
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
||||
event_del(&e->wev); |
||||
} |
||||
|
||||
static void redisLibeventCleanup(void *privdata) { |
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata; |
||||
event_del(&e->rev); |
||||
event_del(&e->wev); |
||||
free(e); |
||||
} |
||||
|
||||
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { |
||||
redisContext *c = &(ac->c); |
||||
redisLibeventEvents *e; |
||||
|
||||
/* Nothing should be attached when something is already attached */ |
||||
if (ac->ev.data != NULL) |
||||
return REDIS_ERR; |
||||
|
||||
/* Create container for context and r/w events */ |
||||
e = (redisLibeventEvents*)malloc(sizeof(*e)); |
||||
e->context = ac; |
||||
|
||||
/* Register functions to start/stop listening for events */ |
||||
ac->ev.addRead = redisLibeventAddRead; |
||||
ac->ev.delRead = redisLibeventDelRead; |
||||
ac->ev.addWrite = redisLibeventAddWrite; |
||||
ac->ev.delWrite = redisLibeventDelWrite; |
||||
ac->ev.cleanup = redisLibeventCleanup; |
||||
ac->ev.data = e; |
||||
|
||||
/* Initialize and install read/write events */ |
||||
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e); |
||||
event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e); |
||||
event_base_set(base,&e->rev); |
||||
event_base_set(base,&e->wev); |
||||
return REDIS_OK; |
||||
} |
||||
|
||||
#if 1 //shenzheng 2015-9-21 redis cluster
|
||||
|
||||
static int redisLibeventAttach_link(redisAsyncContext *ac, void *base) |
||||
{ |
||||
redisLibeventAttach(ac, (struct event_base *)base); |
||||
} |
||||
|
||||
static int redisClusterLibeventAttach(redisClusterAsyncContext *acc, struct event_base *base) { |
||||
|
||||
if(acc == NULL || base == NULL) |
||||
{ |
||||
return REDIS_ERR; |
||||
} |
||||
|
||||
acc->adapter = base; |
||||
acc->attach_fn = redisLibeventAttach_link; |
||||
|
||||
return REDIS_OK; |
||||
} |
||||
|
||||
#endif //shenzheng 2015-9-21 redis cluster
|
||||
|
||||
#endif |
||||
@ -0,0 +1,122 @@
|
||||
#ifndef __HIREDIS_LIBUV_H__ |
||||
#define __HIREDIS_LIBUV_H__ |
||||
#include <stdlib.h> |
||||
#include <uv.h> |
||||
#include "../hiredis.h" |
||||
#include "../async.h" |
||||
#include <string.h> |
||||
|
||||
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 |
||||
@ -0,0 +1,341 @@
|
||||
/* adlist.c - A generic doubly linked list implementation
|
||||
* |
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
|
||||
#include <stdlib.h> |
||||
#include "adlist.h" |
||||
#include "hiutil.h" |
||||
|
||||
/* Create a new list. The created list can be freed with
|
||||
* AlFreeList(), but private value of every node need to be freed |
||||
* by the user before to call AlFreeList(). |
||||
* |
||||
* On error, NULL is returned. Otherwise the pointer to the new list. */ |
||||
hilist *listCreate(void) |
||||
{ |
||||
struct hilist *list; |
||||
|
||||
if ((list = hi_alloc(sizeof(*list))) == NULL) |
||||
return NULL; |
||||
list->head = list->tail = NULL; |
||||
list->len = 0; |
||||
list->dup = NULL; |
||||
list->free = NULL; |
||||
list->match = NULL; |
||||
return list; |
||||
} |
||||
|
||||
/* Free the whole list.
|
||||
* |
||||
* This function can't fail. */ |
||||
void listRelease(hilist *list) |
||||
{ |
||||
unsigned long len; |
||||
listNode *current, *next; |
||||
|
||||
current = list->head; |
||||
len = list->len; |
||||
while(len--) { |
||||
next = current->next; |
||||
if (list->free) list->free(current->value); |
||||
hi_free(current); |
||||
current = next; |
||||
} |
||||
hi_free(list); |
||||
} |
||||
|
||||
/* Add a new node to the list, to head, containing the specified 'value'
|
||||
* pointer as value. |
||||
* |
||||
* On error, NULL is returned and no operation is performed (i.e. the |
||||
* list remains unaltered). |
||||
* On success the 'list' pointer you pass to the function is returned. */ |
||||
hilist *listAddNodeHead(hilist *list, void *value) |
||||
{ |
||||
listNode *node; |
||||
|
||||
if ((node = hi_alloc(sizeof(*node))) == NULL) |
||||
return NULL; |
||||
node->value = value; |
||||
if (list->len == 0) { |
||||
list->head = list->tail = node; |
||||
node->prev = node->next = NULL; |
||||
} else { |
||||
node->prev = NULL; |
||||
node->next = list->head; |
||||
list->head->prev = node; |
||||
list->head = node; |
||||
} |
||||
list->len++; |
||||
return list; |
||||
} |
||||
|
||||
/* Add a new node to the list, to tail, containing the specified 'value'
|
||||
* pointer as value. |
||||
* |
||||
* On error, NULL is returned and no operation is performed (i.e. the |
||||
* list remains unaltered). |
||||
* On success the 'list' pointer you pass to the function is returned. */ |
||||
hilist *listAddNodeTail(hilist *list, void *value) |
||||
{ |
||||
listNode *node; |
||||
|
||||
if ((node = hi_alloc(sizeof(*node))) == NULL) |
||||
return NULL; |
||||
node->value = value; |
||||
if (list->len == 0) { |
||||
list->head = list->tail = node; |
||||
node->prev = node->next = NULL; |
||||
} else { |
||||
node->prev = list->tail; |
||||
node->next = NULL; |
||||
list->tail->next = node; |
||||
list->tail = node; |
||||
} |
||||
list->len++; |
||||
return list; |
||||
} |
||||
|
||||
hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after) { |
||||
listNode *node; |
||||
|
||||
if ((node = hi_alloc(sizeof(*node))) == NULL) |
||||
return NULL; |
||||
node->value = value; |
||||
if (after) { |
||||
node->prev = old_node; |
||||
node->next = old_node->next; |
||||
if (list->tail == old_node) { |
||||
list->tail = node; |
||||
} |
||||
} else { |
||||
node->next = old_node; |
||||
node->prev = old_node->prev; |
||||
if (list->head == old_node) { |
||||
list->head = node; |
||||
} |
||||
} |
||||
if (node->prev != NULL) { |
||||
node->prev->next = node; |
||||
} |
||||
if (node->next != NULL) { |
||||
node->next->prev = node; |
||||
} |
||||
list->len++; |
||||
return list; |
||||
} |
||||
|
||||
/* Remove the specified node from the specified list.
|
||||
* It's up to the caller to free the private value of the node. |
||||
* |
||||
* This function can't fail. */ |
||||
void listDelNode(hilist *list, listNode *node) |
||||
{ |
||||
if (node->prev) |
||||
node->prev->next = node->next; |
||||
else |
||||
list->head = node->next; |
||||
if (node->next) |
||||
node->next->prev = node->prev; |
||||
else |
||||
list->tail = node->prev; |
||||
if (list->free) list->free(node->value); |
||||
hi_free(node); |
||||
list->len--; |
||||
} |
||||
|
||||
/* Returns a list iterator 'iter'. After the initialization every
|
||||
* call to listNext() will return the next element of the list. |
||||
* |
||||
* This function can't fail. */ |
||||
listIter *listGetIterator(hilist *list, int direction) |
||||
{ |
||||
listIter *iter; |
||||
|
||||
if ((iter = hi_alloc(sizeof(*iter))) == NULL) return NULL; |
||||
if (direction == AL_START_HEAD) |
||||
iter->next = list->head; |
||||
else |
||||
iter->next = list->tail; |
||||
iter->direction = direction; |
||||
return iter; |
||||
} |
||||
|
||||
/* Release the iterator memory */ |
||||
void listReleaseIterator(listIter *iter) { |
||||
hi_free(iter); |
||||
} |
||||
|
||||
/* Create an iterator in the list private iterator structure */ |
||||
void listRewind(hilist *list, listIter *li) { |
||||
li->next = list->head; |
||||
li->direction = AL_START_HEAD; |
||||
} |
||||
|
||||
void listRewindTail(hilist *list, listIter *li) { |
||||
li->next = list->tail; |
||||
li->direction = AL_START_TAIL; |
||||
} |
||||
|
||||
/* Return the next element of an iterator.
|
||||
* It's valid to remove the currently returned element using |
||||
* listDelNode(), but not to remove other elements. |
||||
* |
||||
* The function returns a pointer to the next element of the list, |
||||
* or NULL if there are no more elements, so the classical usage patter |
||||
* is: |
||||
* |
||||
* iter = listGetIterator(list,<direction>); |
||||
* while ((node = listNext(iter)) != NULL) { |
||||
* doSomethingWith(listNodeValue(node)); |
||||
* } |
||||
* |
||||
* */ |
||||
listNode *listNext(listIter *iter) |
||||
{ |
||||
listNode *current = iter->next; |
||||
|
||||
if (current != NULL) { |
||||
if (iter->direction == AL_START_HEAD) |
||||
iter->next = current->next; |
||||
else |
||||
iter->next = current->prev; |
||||
} |
||||
return current; |
||||
} |
||||
|
||||
/* Duplicate the whole list. On out of memory NULL is returned.
|
||||
* On success a copy of the original list is returned. |
||||
* |
||||
* The 'Dup' method set with listSetDupMethod() function is used |
||||
* to copy the node value. Otherwise the same pointer value of |
||||
* the original node is used as value of the copied node. |
||||
* |
||||
* The original list both on success or error is never modified. */ |
||||
hilist *listDup(hilist *orig) |
||||
{ |
||||
hilist *copy; |
||||
listIter *iter; |
||||
listNode *node; |
||||
|
||||
if ((copy = listCreate()) == NULL) |
||||
return NULL; |
||||
copy->dup = orig->dup; |
||||
copy->free = orig->free; |
||||
copy->match = orig->match; |
||||
iter = listGetIterator(orig, AL_START_HEAD); |
||||
while((node = listNext(iter)) != NULL) { |
||||
void *value; |
||||
|
||||
if (copy->dup) { |
||||
value = copy->dup(node->value); |
||||
if (value == NULL) { |
||||
listRelease(copy); |
||||
listReleaseIterator(iter); |
||||
return NULL; |
||||
} |
||||
} else |
||||
value = node->value; |
||||
if (listAddNodeTail(copy, value) == NULL) { |
||||
listRelease(copy); |
||||
listReleaseIterator(iter); |
||||
return NULL; |
||||
} |
||||
} |
||||
listReleaseIterator(iter); |
||||
return copy; |
||||
} |
||||
|
||||
/* Search the list for a node matching a given key.
|
||||
* The match is performed using the 'match' method |
||||
* set with listSetMatchMethod(). If no 'match' method |
||||
* is set, the 'value' pointer of every node is directly |
||||
* compared with the 'key' pointer. |
||||
* |
||||
* On success the first matching node pointer is returned |
||||
* (search starts from head). If no matching node exists |
||||
* NULL is returned. */ |
||||
listNode *listSearchKey(hilist *list, void *key) |
||||
{ |
||||
listIter *iter; |
||||
listNode *node; |
||||
|
||||
iter = listGetIterator(list, AL_START_HEAD); |
||||
while((node = listNext(iter)) != NULL) { |
||||
if (list->match) { |
||||
if (list->match(node->value, key)) { |
||||
listReleaseIterator(iter); |
||||
return node; |
||||
} |
||||
} else { |
||||
if (key == node->value) { |
||||
listReleaseIterator(iter); |
||||
return node; |
||||
} |
||||
} |
||||
} |
||||
listReleaseIterator(iter); |
||||
return NULL; |
||||
} |
||||
|
||||
/* Return the element at the specified zero-based index
|
||||
* where 0 is the head, 1 is the element next to head |
||||
* and so on. Negative integers are used in order to count |
||||
* from the tail, -1 is the last element, -2 the penultimate |
||||
* and so on. If the index is out of range NULL is returned. */ |
||||
listNode *listIndex(hilist *list, long index) { |
||||
listNode *n; |
||||
|
||||
if (index < 0) { |
||||
index = (-index)-1; |
||||
n = list->tail; |
||||
while(index-- && n) n = n->prev; |
||||
} else { |
||||
n = list->head; |
||||
while(index-- && n) n = n->next; |
||||
} |
||||
return n; |
||||
} |
||||
|
||||
/* Rotate the list removing the tail node and inserting it to the head. */ |
||||
void listRotate(hilist *list) { |
||||
listNode *tail = list->tail; |
||||
|
||||
if (listLength(list) <= 1) return; |
||||
|
||||
/* Detach current tail */ |
||||
list->tail = tail->prev; |
||||
list->tail->next = NULL; |
||||
/* Move it as head */ |
||||
list->head->prev = tail; |
||||
tail->prev = NULL; |
||||
tail->next = list->head; |
||||
list->head = tail; |
||||
} |
||||
@ -0,0 +1,93 @@
|
||||
/* adlist.h - A generic doubly linked list implementation
|
||||
* |
||||
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef __ADLIST_H__ |
||||
#define __ADLIST_H__ |
||||
|
||||
/* Node, List, and Iterator are the only data structures used currently. */ |
||||
|
||||
typedef struct listNode { |
||||
struct listNode *prev; |
||||
struct listNode *next; |
||||
void *value; |
||||
} listNode; |
||||
|
||||
typedef struct listIter { |
||||
listNode *next; |
||||
int direction; |
||||
} listIter; |
||||
|
||||
typedef struct hilist { |
||||
listNode *head; |
||||
listNode *tail; |
||||
void *(*dup)(void *ptr); |
||||
void (*free)(void *ptr); |
||||
int (*match)(void *ptr, void *key); |
||||
unsigned long len; |
||||
} hilist; |
||||
|
||||
/* Functions implemented as macros */ |
||||
#define listLength(l) ((l)->len) |
||||
#define listFirst(l) ((l)->head) |
||||
#define listLast(l) ((l)->tail) |
||||
#define listPrevNode(n) ((n)->prev) |
||||
#define listNextNode(n) ((n)->next) |
||||
#define listNodeValue(n) ((n)->value) |
||||
|
||||
#define listSetDupMethod(l,m) ((l)->dup = (m)) |
||||
#define listSetFreeMethod(l,m) ((l)->free = (m)) |
||||
#define listSetMatchMethod(l,m) ((l)->match = (m)) |
||||
|
||||
#define listGetDupMethod(l) ((l)->dup) |
||||
#define listGetFree(l) ((l)->free) |
||||
#define listGetMatchMethod(l) ((l)->match) |
||||
|
||||
/* Prototypes */ |
||||
hilist *listCreate(void); |
||||
void listRelease(hilist *list); |
||||
hilist *listAddNodeHead(hilist *list, void *value); |
||||
hilist *listAddNodeTail(hilist *list, void *value); |
||||
hilist *listInsertNode(hilist *list, listNode *old_node, void *value, int after); |
||||
void listDelNode(hilist *list, listNode *node); |
||||
listIter *listGetIterator(hilist *list, int direction); |
||||
listNode *listNext(listIter *iter); |
||||
void listReleaseIterator(listIter *iter); |
||||
hilist *listDup(hilist *orig); |
||||
listNode *listSearchKey(hilist *list, void *key); |
||||
listNode *listIndex(hilist *list, long index); |
||||
void listRewind(hilist *list, listIter *li); |
||||
void listRewindTail(hilist *list, listIter *li); |
||||
void listRotate(hilist *list); |
||||
|
||||
/* Directions for iterators */ |
||||
#define AL_START_HEAD 0 |
||||
#define AL_START_TAIL 1 |
||||
|
||||
#endif /* __ADLIST_H__ */ |
||||
@ -0,0 +1,691 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#include "fmacros.h" |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <strings.h> |
||||
#include <assert.h> |
||||
#include <ctype.h> |
||||
#include <errno.h> |
||||
#include "async.h" |
||||
#include "net.h" |
||||
#include "dict.c" |
||||
#include "sds.h" |
||||
|
||||
#define _EL_ADD_READ(ctx) do { \ |
||||
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
|
||||
} while(0) |
||||
#define _EL_DEL_READ(ctx) do { \ |
||||
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
|
||||
} while(0) |
||||
#define _EL_ADD_WRITE(ctx) do { \ |
||||
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
|
||||
} while(0) |
||||
#define _EL_DEL_WRITE(ctx) do { \ |
||||
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
|
||||
} while(0) |
||||
#define _EL_CLEANUP(ctx) do { \ |
||||
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
|
||||
} while(0); |
||||
|
||||
/* Forward declaration of function in hiredis.c */ |
||||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); |
||||
|
||||
/* Functions managing dictionary of callbacks for pub/sub. */ |
||||
static unsigned int callbackHash(const void *key) { |
||||
return dictGenHashFunction((const unsigned char *)key, |
||||
sdslen((const sds)key)); |
||||
} |
||||
|
||||
static void *callbackValDup(void *privdata, const void *src) { |
||||
((void) privdata); |
||||
redisCallback *dup = malloc(sizeof(*dup)); |
||||
memcpy(dup,src,sizeof(*dup)); |
||||
return dup; |
||||
} |
||||
|
||||
static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) { |
||||
int l1, l2; |
||||
((void) privdata); |
||||
|
||||
l1 = sdslen((const sds)key1); |
||||
l2 = sdslen((const sds)key2); |
||||
if (l1 != l2) return 0; |
||||
return memcmp(key1,key2,l1) == 0; |
||||
} |
||||
|
||||
static void callbackKeyDestructor(void *privdata, void *key) { |
||||
((void) privdata); |
||||
sdsfree((sds)key); |
||||
} |
||||
|
||||
static void callbackValDestructor(void *privdata, void *val) { |
||||
((void) privdata); |
||||
free(val); |
||||
} |
||||
|
||||
static dictType callbackDict = { |
||||
callbackHash, |
||||
NULL, |
||||
callbackValDup, |
||||
callbackKeyCompare, |
||||
callbackKeyDestructor, |
||||
callbackValDestructor |
||||
}; |
||||
|
||||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) { |
||||
redisAsyncContext *ac; |
||||
|
||||
ac = realloc(c,sizeof(redisAsyncContext)); |
||||
if (ac == NULL) |
||||
return NULL; |
||||
|
||||
c = &(ac->c); |
||||
|
||||
/* The regular connect functions will always set the flag REDIS_CONNECTED.
|
||||
* For the async API, we want to wait until the first write event is |
||||
* received up before setting this flag, so reset it here. */ |
||||
c->flags &= ~REDIS_CONNECTED; |
||||
|
||||
ac->err = 0; |
||||
ac->errstr = NULL; |
||||
ac->data = NULL; |
||||
ac->dataHandler = NULL; |
||||
|
||||
ac->ev.data = NULL; |
||||
ac->ev.addRead = NULL; |
||||
ac->ev.delRead = NULL; |
||||
ac->ev.addWrite = NULL; |
||||
ac->ev.delWrite = NULL; |
||||
ac->ev.cleanup = NULL; |
||||
|
||||
ac->onConnect = NULL; |
||||
ac->onDisconnect = NULL; |
||||
|
||||
ac->replies.head = NULL; |
||||
ac->replies.tail = NULL; |
||||
ac->sub.invalid.head = NULL; |
||||
ac->sub.invalid.tail = NULL; |
||||
ac->sub.channels = dictCreate(&callbackDict,NULL); |
||||
ac->sub.patterns = dictCreate(&callbackDict,NULL); |
||||
return ac; |
||||
} |
||||
|
||||
/* We want the error field to be accessible directly instead of requiring
|
||||
* an indirection to the redisContext struct. */ |
||||
static void __redisAsyncCopyError(redisAsyncContext *ac) { |
||||
if (!ac) |
||||
return; |
||||
|
||||
redisContext *c = &(ac->c); |
||||
ac->err = c->err; |
||||
ac->errstr = c->errstr; |
||||
} |
||||
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { |
||||
redisContext *c; |
||||
redisAsyncContext *ac; |
||||
|
||||
c = redisConnectNonBlock(ip,port); |
||||
if (c == NULL) |
||||
return NULL; |
||||
|
||||
ac = redisAsyncInitialize(c); |
||||
if (ac == NULL) { |
||||
redisFree(c); |
||||
return NULL; |
||||
} |
||||
|
||||
__redisAsyncCopyError(ac); |
||||
return ac; |
||||
} |
||||
|
||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, |
||||
const char *source_addr) { |
||||
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); |
||||
redisAsyncContext *ac = redisAsyncInitialize(c); |
||||
__redisAsyncCopyError(ac); |
||||
return ac; |
||||
} |
||||
|
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, |
||||
const char *source_addr) { |
||||
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); |
||||
redisAsyncContext *ac = redisAsyncInitialize(c); |
||||
__redisAsyncCopyError(ac); |
||||
return ac; |
||||
} |
||||
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path) { |
||||
redisContext *c; |
||||
redisAsyncContext *ac; |
||||
|
||||
c = redisConnectUnixNonBlock(path); |
||||
if (c == NULL) |
||||
return NULL; |
||||
|
||||
ac = redisAsyncInitialize(c); |
||||
if (ac == NULL) { |
||||
redisFree(c); |
||||
return NULL; |
||||
} |
||||
|
||||
__redisAsyncCopyError(ac); |
||||
return ac; |
||||
} |
||||
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) { |
||||
if (ac->onConnect == NULL) { |
||||
ac->onConnect = fn; |
||||
|
||||
/* The common way to detect an established connection is to wait for
|
||||
* the first write event to be fired. This assumes the related event |
||||
* library functions are already set. */ |
||||
_EL_ADD_WRITE(ac); |
||||
return REDIS_OK; |
||||
} |
||||
return REDIS_ERR; |
||||
} |
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) { |
||||
if (ac->onDisconnect == NULL) { |
||||
ac->onDisconnect = fn; |
||||
return REDIS_OK; |
||||
} |
||||
return REDIS_ERR; |
||||
} |
||||
|
||||
/* Helper functions to push/shift callbacks */ |
||||
static int __redisPushCallback(redisCallbackList *list, redisCallback *source) { |
||||
redisCallback *cb; |
||||
|
||||
/* Copy callback from stack to heap */ |
||||
cb = malloc(sizeof(*cb)); |
||||
if (cb == NULL) |
||||
return REDIS_ERR_OOM; |
||||
|
||||
if (source != NULL) { |
||||
memcpy(cb,source,sizeof(*cb)); |
||||
cb->next = NULL; |
||||
} |
||||
|
||||
/* Store callback in list */ |
||||
if (list->head == NULL) |
||||
list->head = cb; |
||||
if (list->tail != NULL) |
||||
list->tail->next = cb; |
||||
list->tail = cb; |
||||
return REDIS_OK; |
||||
} |
||||
|
||||
static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) { |
||||
redisCallback *cb = list->head; |
||||
if (cb != NULL) { |
||||
list->head = cb->next; |
||||
if (cb == list->tail) |
||||
list->tail = NULL; |
||||
|
||||
/* Copy callback from heap to stack */ |
||||
if (target != NULL) |
||||
memcpy(target,cb,sizeof(*cb)); |
||||
free(cb); |
||||
return REDIS_OK; |
||||
} |
||||
return REDIS_ERR; |
||||
} |
||||
|
||||
static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) { |
||||
redisContext *c = &(ac->c); |
||||
if (cb->fn != NULL) { |
||||
c->flags |= REDIS_IN_CALLBACK; |
||||
cb->fn(ac,reply,cb->privdata); |
||||
c->flags &= ~REDIS_IN_CALLBACK; |
||||
} |
||||
} |
||||
|
||||
/* Helper function to free the context. */ |
||||
static void __redisAsyncFree(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
redisCallback cb; |
||||
dictIterator *it; |
||||
dictEntry *de; |
||||
|
||||
/* Execute pending callbacks with NULL reply. */ |
||||
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) |
||||
__redisRunCallback(ac,&cb,NULL); |
||||
|
||||
/* Execute callbacks for invalid commands */ |
||||
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) |
||||
__redisRunCallback(ac,&cb,NULL); |
||||
|
||||
/* Run subscription callbacks callbacks with NULL reply */ |
||||
it = dictGetIterator(ac->sub.channels); |
||||
while ((de = dictNext(it)) != NULL) |
||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL); |
||||
dictReleaseIterator(it); |
||||
dictRelease(ac->sub.channels); |
||||
|
||||
it = dictGetIterator(ac->sub.patterns); |
||||
while ((de = dictNext(it)) != NULL) |
||||
__redisRunCallback(ac,dictGetEntryVal(de),NULL); |
||||
dictReleaseIterator(it); |
||||
dictRelease(ac->sub.patterns); |
||||
|
||||
/* Signal event lib to clean up */ |
||||
_EL_CLEANUP(ac); |
||||
|
||||
/* Execute disconnect callback. When redisAsyncFree() initiated destroying
|
||||
* this context, the status will always be REDIS_OK. */ |
||||
if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) { |
||||
if (c->flags & REDIS_FREEING) { |
||||
ac->onDisconnect(ac,REDIS_OK); |
||||
} else { |
||||
ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR); |
||||
} |
||||
} |
||||
|
||||
if (ac->dataHandler) { |
||||
ac->dataHandler(ac); |
||||
} |
||||
|
||||
/* Cleanup self */ |
||||
redisFree(c); |
||||
} |
||||
|
||||
/* Free the async context. When this function is called from a callback,
|
||||
* control needs to be returned to redisProcessCallbacks() before actual |
||||
* free'ing. To do so, a flag is set on the context which is picked up by |
||||
* redisProcessCallbacks(). Otherwise, the context is immediately free'd. */ |
||||
void redisAsyncFree(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
c->flags |= REDIS_FREEING; |
||||
if (!(c->flags & REDIS_IN_CALLBACK)) |
||||
__redisAsyncFree(ac); |
||||
} |
||||
|
||||
/* Helper function to make the disconnect happen and clean up. */ |
||||
static void __redisAsyncDisconnect(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
|
||||
/* Make sure error is accessible if there is any */ |
||||
__redisAsyncCopyError(ac); |
||||
|
||||
if (ac->err == 0) { |
||||
/* For clean disconnects, there should be no pending callbacks. */ |
||||
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); |
||||
} else { |
||||
/* Disconnection is caused by an error, make sure that pending
|
||||
* callbacks cannot call new commands. */ |
||||
c->flags |= REDIS_DISCONNECTING; |
||||
} |
||||
|
||||
/* For non-clean disconnects, __redisAsyncFree() will execute pending
|
||||
* callbacks with a NULL-reply. */ |
||||
__redisAsyncFree(ac); |
||||
} |
||||
|
||||
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
|
||||
* from being issued, but tries to flush the output buffer and execute |
||||
* callbacks for all remaining replies. When this function is called from a |
||||
* callback, there might be more replies and we can safely defer disconnecting |
||||
* to redisProcessCallbacks(). Otherwise, we can only disconnect immediately |
||||
* when there are no pending callbacks. */ |
||||
void redisAsyncDisconnect(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
c->flags |= REDIS_DISCONNECTING; |
||||
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) |
||||
__redisAsyncDisconnect(ac); |
||||
} |
||||
|
||||
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { |
||||
redisContext *c = &(ac->c); |
||||
dict *callbacks; |
||||
dictEntry *de; |
||||
int pvariant; |
||||
char *stype; |
||||
sds sname; |
||||
|
||||
/* Custom reply functions are not supported for pub/sub. This will fail
|
||||
* very hard when they are used... */ |
||||
if (reply->type == REDIS_REPLY_ARRAY) { |
||||
assert(reply->elements >= 2); |
||||
assert(reply->element[0]->type == REDIS_REPLY_STRING); |
||||
stype = reply->element[0]->str; |
||||
pvariant = (tolower(stype[0]) == 'p') ? 1 : 0; |
||||
|
||||
if (pvariant) |
||||
callbacks = ac->sub.patterns; |
||||
else |
||||
callbacks = ac->sub.channels; |
||||
|
||||
/* Locate the right callback */ |
||||
assert(reply->element[1]->type == REDIS_REPLY_STRING); |
||||
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); |
||||
de = dictFind(callbacks,sname); |
||||
if (de != NULL) { |
||||
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); |
||||
|
||||
/* If this is an unsubscribe message, remove it. */ |
||||
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { |
||||
dictDelete(callbacks,sname); |
||||
|
||||
/* If this was the last unsubscribe message, revert to
|
||||
* non-subscribe mode. */ |
||||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER); |
||||
if (reply->element[2]->integer == 0) |
||||
c->flags &= ~REDIS_SUBSCRIBED; |
||||
} |
||||
} |
||||
sdsfree(sname); |
||||
} else { |
||||
/* Shift callback for invalid commands. */ |
||||
__redisShiftCallback(&ac->sub.invalid,dstcb); |
||||
} |
||||
return REDIS_OK; |
||||
} |
||||
|
||||
void redisProcessCallbacks(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
redisCallback cb = {NULL, NULL, NULL}; |
||||
void *reply = NULL; |
||||
int status; |
||||
|
||||
while((status = redisGetReply(c,&reply)) == REDIS_OK) { |
||||
if (reply == NULL) { |
||||
/* When the connection is being disconnected and there are
|
||||
* no more replies, this is the cue to really disconnect. */ |
||||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) { |
||||
__redisAsyncDisconnect(ac); |
||||
return; |
||||
} |
||||
|
||||
/* If monitor mode, repush callback */ |
||||
if(c->flags & REDIS_MONITORING) { |
||||
__redisPushCallback(&ac->replies,&cb); |
||||
} |
||||
|
||||
/* When the connection is not being disconnected, simply stop
|
||||
* trying to get replies and wait for the next loop tick. */ |
||||
break; |
||||
} |
||||
|
||||
/* Even if the context is subscribed, pending regular callbacks will
|
||||
* get a reply before pub/sub messages arrive. */ |
||||
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { |
||||
/*
|
||||
* A spontaneous reply in a not-subscribed context can be the error |
||||
* reply that is sent when a new connection exceeds the maximum |
||||
* number of allowed connections on the server side. |
||||
* |
||||
* This is seen as an error instead of a regular reply because the |
||||
* server closes the connection after sending it. |
||||
* |
||||
* To prevent the error from being overwritten by an EOF error the |
||||
* connection is closed here. See issue #43. |
||||
* |
||||
* Another possibility is that the server is loading its dataset. |
||||
* In this case we also want to close the connection, and have the |
||||
* user wait until the server is ready to take our request. |
||||
*/ |
||||
if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) { |
||||
c->err = REDIS_ERR_OTHER; |
||||
snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str); |
||||
c->reader->fn->freeObject(reply); |
||||
__redisAsyncDisconnect(ac); |
||||
return; |
||||
} |
||||
/* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */ |
||||
assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING)); |
||||
if(c->flags & REDIS_SUBSCRIBED) |
||||
__redisGetSubscribeCallback(ac,reply,&cb); |
||||
} |
||||
|
||||
if (cb.fn != NULL) { |
||||
__redisRunCallback(ac,&cb,reply); |
||||
c->reader->fn->freeObject(reply); |
||||
|
||||
/* Proceed with free'ing when redisAsyncFree() was called. */ |
||||
if (c->flags & REDIS_FREEING) { |
||||
__redisAsyncFree(ac); |
||||
return; |
||||
} |
||||
} else { |
||||
/* No callback for this reply. This can either be a NULL callback,
|
||||
* or there were no callbacks to begin with. Either way, don't |
||||
* abort with an error, but simply ignore it because the client |
||||
* doesn't know what the server will spit out over the wire. */ |
||||
c->reader->fn->freeObject(reply); |
||||
} |
||||
} |
||||
|
||||
/* Disconnect when there was an error reading the reply */ |
||||
if (status != REDIS_OK) |
||||
__redisAsyncDisconnect(ac); |
||||
} |
||||
|
||||
/* Internal helper function to detect socket status the first time a read or
|
||||
* write event fires. When connecting was not succesful, the connect callback |
||||
* is called with a REDIS_ERR status and the context is free'd. */ |
||||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
|
||||
if (redisCheckSocketError(c) == REDIS_ERR) { |
||||
/* Try again later when connect(2) is still in progress. */ |
||||
if (errno == EINPROGRESS) |
||||
return REDIS_OK; |
||||
|
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR); |
||||
__redisAsyncDisconnect(ac); |
||||
return REDIS_ERR; |
||||
} |
||||
|
||||
/* Mark context as connected. */ |
||||
c->flags |= REDIS_CONNECTED; |
||||
if (ac->onConnect) ac->onConnect(ac,REDIS_OK); |
||||
return REDIS_OK; |
||||
} |
||||
|
||||
/* This function should be called when the socket is readable.
|
||||
* It processes all replies that can be read and executes their callbacks. |
||||
*/ |
||||
void redisAsyncHandleRead(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) { |
||||
/* Abort connect was not successful. */ |
||||
if (__redisAsyncHandleConnect(ac) != REDIS_OK) |
||||
return; |
||||
/* Try again later when the context is still not connected. */ |
||||
if (!(c->flags & REDIS_CONNECTED)) |
||||
return; |
||||
} |
||||
|
||||
if (redisBufferRead(c) == REDIS_ERR) { |
||||
__redisAsyncDisconnect(ac); |
||||
} else { |
||||
/* Always re-schedule reads */ |
||||
_EL_ADD_READ(ac); |
||||
redisProcessCallbacks(ac); |
||||
} |
||||
} |
||||
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) { |
||||
redisContext *c = &(ac->c); |
||||
int done = 0; |
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) { |
||||
/* Abort connect was not successful. */ |
||||
if (__redisAsyncHandleConnect(ac) != REDIS_OK) |
||||
return; |
||||
/* Try again later when the context is still not connected. */ |
||||
if (!(c->flags & REDIS_CONNECTED)) |
||||
return; |
||||
} |
||||
|
||||
if (redisBufferWrite(c,&done) == REDIS_ERR) { |
||||
__redisAsyncDisconnect(ac); |
||||
} else { |
||||
/* Continue writing when not done, stop writing otherwise */ |
||||
if (!done) |
||||
_EL_ADD_WRITE(ac); |
||||
else |
||||
_EL_DEL_WRITE(ac); |
||||
|
||||
/* Always schedule reads after writes */ |
||||
_EL_ADD_READ(ac); |
||||
} |
||||
} |
||||
|
||||
/* Sets a pointer to the first argument and its length starting at p. Returns
|
||||
* the number of bytes to skip to get to the following argument. */ |
||||
static const char *nextArgument(const char *start, const char **str, size_t *len) { |
||||
const char *p = start; |
||||
if (p[0] != '$') { |
||||
p = strchr(p,'$'); |
||||
if (p == NULL) return NULL; |
||||
} |
||||
|
||||
*len = (int)strtol(p+1,NULL,10); |
||||
p = strchr(p,'\r'); |
||||
assert(p); |
||||
*str = p+2; |
||||
return p+2+(*len)+2; |
||||
} |
||||
|
||||
/* Helper function for the redisAsyncCommand* family of functions. Writes a
|
||||
* formatted command to the output buffer and registers the provided callback |
||||
* function with the context. */ |
||||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { |
||||
redisContext *c = &(ac->c); |
||||
redisCallback cb; |
||||
int pvariant, hasnext; |
||||
const char *cstr, *astr; |
||||
size_t clen, alen; |
||||
const char *p; |
||||
sds sname; |
||||
int ret; |
||||
|
||||
/* Don't accept new commands when the connection is about to be closed. */ |
||||
if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR; |
||||
|
||||
/* Setup callback */ |
||||
cb.fn = fn; |
||||
cb.privdata = privdata; |
||||
|
||||
/* Find out which command will be appended. */ |
||||
p = nextArgument(cmd,&cstr,&clen); |
||||
assert(p != NULL); |
||||
hasnext = (p[0] == '$'); |
||||
pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0; |
||||
cstr += pvariant; |
||||
clen -= pvariant; |
||||
|
||||
if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) { |
||||
c->flags |= REDIS_SUBSCRIBED; |
||||
|
||||
/* Add every channel/pattern to the list of subscription callbacks. */ |
||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) { |
||||
sname = sdsnewlen(astr,alen); |
||||
if (pvariant) |
||||
ret = dictReplace(ac->sub.patterns,sname,&cb); |
||||
else |
||||
ret = dictReplace(ac->sub.channels,sname,&cb); |
||||
|
||||
if (ret == 0) sdsfree(sname); |
||||
} |
||||
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { |
||||
/* It is only useful to call (P)UNSUBSCRIBE when the context is
|
||||
* subscribed to one or more channels or patterns. */ |
||||
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR; |
||||
|
||||
/* (P)UNSUBSCRIBE does not have its own response: every channel or
|
||||
* pattern that is unsubscribed will receive a message. This means we |
||||
* should not append a callback function for this command. */ |
||||
} else if(strncasecmp(cstr,"monitor\r\n",9) == 0) { |
||||
/* Set monitor flag and push callback */ |
||||
c->flags |= REDIS_MONITORING; |
||||
__redisPushCallback(&ac->replies,&cb); |
||||
} else { |
||||
if (c->flags & REDIS_SUBSCRIBED) |
||||
/* This will likely result in an error reply, but it needs to be
|
||||
* received and passed to the callback. */ |
||||
__redisPushCallback(&ac->sub.invalid,&cb); |
||||
else |
||||
__redisPushCallback(&ac->replies,&cb); |
||||
} |
||||
|
||||
__redisAppendCommand(c,cmd,len); |
||||
|
||||
/* Always schedule a write when the write buffer is non-empty */ |
||||
_EL_ADD_WRITE(ac); |
||||
|
||||
return REDIS_OK; |
||||
} |
||||
|
||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { |
||||
char *cmd; |
||||
int len; |
||||
int status; |
||||
len = redisvFormatCommand(&cmd,format,ap); |
||||
|
||||
/* We don't want to pass -1 or -2 to future functions as a length. */ |
||||
if (len < 0) |
||||
return REDIS_ERR; |
||||
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); |
||||
free(cmd); |
||||
return status; |
||||
} |
||||
|
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) { |
||||
va_list ap; |
||||
int status; |
||||
va_start(ap,format); |
||||
status = redisvAsyncCommand(ac,fn,privdata,format,ap); |
||||
va_end(ap); |
||||
return status; |
||||
} |
||||
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { |
||||
sds cmd; |
||||
int len; |
||||
int status; |
||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); |
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); |
||||
sdsfree(cmd); |
||||
return status; |
||||
} |
||||
|
||||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { |
||||
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); |
||||
return status; |
||||
} |
||||
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef __HIREDIS_ASYNC_H |
||||
#define __HIREDIS_ASYNC_H |
||||
#include "hiredis.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */ |
||||
struct dict; /* dictionary header is included in async.c */ |
||||
|
||||
/* Reply callback prototype and container */ |
||||
typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); |
||||
typedef struct redisCallback { |
||||
struct redisCallback *next; /* simple singly linked list */ |
||||
redisCallbackFn *fn; |
||||
void *privdata; |
||||
} redisCallback; |
||||
|
||||
/* List of callbacks for either regular replies or pub/sub */ |
||||
typedef struct redisCallbackList { |
||||
redisCallback *head, *tail; |
||||
} redisCallbackList; |
||||
|
||||
/* Connection callback prototypes */ |
||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); |
||||
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); |
||||
|
||||
/* Context for an async connection to Redis */ |
||||
typedef struct redisAsyncContext { |
||||
/* Hold the regular context, so it can be realloc'ed. */ |
||||
redisContext c; |
||||
|
||||
/* Setup error flags so they can be used directly. */ |
||||
int err; |
||||
char *errstr; |
||||
|
||||
/* Not used by hiredis */ |
||||
void *data; |
||||
void (*dataHandler)(struct redisAsyncContext* ac); |
||||
|
||||
/* Event library data and hooks */ |
||||
struct { |
||||
void *data; |
||||
|
||||
/* Hooks that are called when the library expects to start
|
||||
* reading/writing. These functions should be idempotent. */ |
||||
void (*addRead)(void *privdata); |
||||
void (*delRead)(void *privdata); |
||||
void (*addWrite)(void *privdata); |
||||
void (*delWrite)(void *privdata); |
||||
void (*cleanup)(void *privdata); |
||||
} ev; |
||||
|
||||
/* Called when either the connection is terminated due to an error or per
|
||||
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ |
||||
redisDisconnectCallback *onDisconnect; |
||||
|
||||
/* Called when the first write event was received. */ |
||||
redisConnectCallback *onConnect; |
||||
|
||||
/* Regular command callbacks */ |
||||
redisCallbackList replies; |
||||
|
||||
/* Subscription callbacks */ |
||||
struct { |
||||
redisCallbackList invalid; |
||||
struct dict *channels; |
||||
struct dict *patterns; |
||||
} sub; |
||||
} redisAsyncContext; |
||||
|
||||
/* Functions that proxy to hiredis */ |
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port); |
||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); |
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, |
||||
const char *source_addr); |
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path); |
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); |
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); |
||||
void redisAsyncDisconnect(redisAsyncContext *ac); |
||||
void redisAsyncFree(redisAsyncContext *ac); |
||||
|
||||
/* Handle read/write events */ |
||||
void redisAsyncHandleRead(redisAsyncContext *ac); |
||||
void redisAsyncHandleWrite(redisAsyncContext *ac); |
||||
|
||||
/* Command functions for an async context. Write the command to the
|
||||
* output buffer and register the provided callback. */ |
||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap); |
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...); |
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); |
||||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,179 @@
|
||||
#ifndef __COMMAND_H_ |
||||
#define __COMMAND_H_ |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include "hiredis.h" |
||||
#include "adlist.h" |
||||
|
||||
typedef enum cmd_parse_result { |
||||
CMD_PARSE_OK, /* parsing ok */ |
||||
CMD_PARSE_ENOMEM, /* out of memory */ |
||||
CMD_PARSE_ERROR, /* parsing error */ |
||||
CMD_PARSE_REPAIR, /* more to parse -> repair parsed & unparsed data */ |
||||
CMD_PARSE_AGAIN, /* incomplete -> parse again */ |
||||
} cmd_parse_result_t; |
||||
|
||||
#define CMD_TYPE_CODEC(ACTION) \ |
||||
ACTION( UNKNOWN ) \
|
||||
ACTION( REQ_REDIS_DEL ) /* redis commands - keys */ \
|
||||
ACTION( REQ_REDIS_EXISTS ) \
|
||||
ACTION( REQ_REDIS_EXPIRE ) \
|
||||
ACTION( REQ_REDIS_EXPIREAT ) \
|
||||
ACTION( REQ_REDIS_PEXPIRE ) \
|
||||
ACTION( REQ_REDIS_PEXPIREAT ) \
|
||||
ACTION( REQ_REDIS_PERSIST ) \
|
||||
ACTION( REQ_REDIS_PTTL ) \
|
||||
ACTION( REQ_REDIS_SORT ) \
|
||||
ACTION( REQ_REDIS_TTL ) \
|
||||
ACTION( REQ_REDIS_TYPE ) \
|
||||
ACTION( REQ_REDIS_APPEND ) /* redis requests - string */ \
|
||||
ACTION( REQ_REDIS_BITCOUNT ) \
|
||||
ACTION( REQ_REDIS_DECR ) \
|
||||
ACTION( REQ_REDIS_DECRBY ) \
|
||||
ACTION( REQ_REDIS_DUMP ) \
|
||||
ACTION( REQ_REDIS_GET ) \
|
||||
ACTION( REQ_REDIS_GETBIT ) \
|
||||
ACTION( REQ_REDIS_GETRANGE ) \
|
||||
ACTION( REQ_REDIS_GETSET ) \
|
||||
ACTION( REQ_REDIS_INCR ) \
|
||||
ACTION( REQ_REDIS_INCRBY ) \
|
||||
ACTION( REQ_REDIS_INCRBYFLOAT ) \
|
||||
ACTION( REQ_REDIS_MGET ) \
|
||||
ACTION( REQ_REDIS_MSET ) \
|
||||
ACTION( REQ_REDIS_PSETEX ) \
|
||||
ACTION( REQ_REDIS_RESTORE ) \
|
||||
ACTION( REQ_REDIS_SET ) \
|
||||
ACTION( REQ_REDIS_SETBIT ) \
|
||||
ACTION( REQ_REDIS_SETEX ) \
|
||||
ACTION( REQ_REDIS_SETNX ) \
|
||||
ACTION( REQ_REDIS_SETRANGE ) \
|
||||
ACTION( REQ_REDIS_STRLEN ) \
|
||||
ACTION( REQ_REDIS_HDEL ) /* redis requests - hashes */ \
|
||||
ACTION( REQ_REDIS_HEXISTS ) \
|
||||
ACTION( REQ_REDIS_HGET ) \
|
||||
ACTION( REQ_REDIS_HGETALL ) \
|
||||
ACTION( REQ_REDIS_HINCRBY ) \
|
||||
ACTION( REQ_REDIS_HINCRBYFLOAT ) \
|
||||
ACTION( REQ_REDIS_HKEYS ) \
|
||||
ACTION( REQ_REDIS_HLEN ) \
|
||||
ACTION( REQ_REDIS_HMGET ) \
|
||||
ACTION( REQ_REDIS_HMSET ) \
|
||||
ACTION( REQ_REDIS_HSET ) \
|
||||
ACTION( REQ_REDIS_HSETNX ) \
|
||||
ACTION( REQ_REDIS_HSCAN) \
|
||||
ACTION( REQ_REDIS_HVALS ) \
|
||||
ACTION( REQ_REDIS_LINDEX ) /* redis requests - lists */ \
|
||||
ACTION( REQ_REDIS_LINSERT ) \
|
||||
ACTION( REQ_REDIS_LLEN ) \
|
||||
ACTION( REQ_REDIS_LPOP ) \
|
||||
ACTION( REQ_REDIS_LPUSH ) \
|
||||
ACTION( REQ_REDIS_LPUSHX ) \
|
||||
ACTION( REQ_REDIS_LRANGE ) \
|
||||
ACTION( REQ_REDIS_LREM ) \
|
||||
ACTION( REQ_REDIS_LSET ) \
|
||||
ACTION( REQ_REDIS_LTRIM ) \
|
||||
ACTION( REQ_REDIS_PFADD ) /* redis requests - hyperloglog */ \
|
||||
ACTION( REQ_REDIS_PFCOUNT ) \
|
||||
ACTION( REQ_REDIS_PFMERGE ) \
|
||||
ACTION( REQ_REDIS_RPOP ) \
|
||||
ACTION( REQ_REDIS_RPOPLPUSH ) \
|
||||
ACTION( REQ_REDIS_RPUSH ) \
|
||||
ACTION( REQ_REDIS_RPUSHX ) \
|
||||
ACTION( REQ_REDIS_SADD ) /* redis requests - sets */ \
|
||||
ACTION( REQ_REDIS_SCARD ) \
|
||||
ACTION( REQ_REDIS_SDIFF ) \
|
||||
ACTION( REQ_REDIS_SDIFFSTORE ) \
|
||||
ACTION( REQ_REDIS_SINTER ) \
|
||||
ACTION( REQ_REDIS_SINTERSTORE ) \
|
||||
ACTION( REQ_REDIS_SISMEMBER ) \
|
||||
ACTION( REQ_REDIS_SMEMBERS ) \
|
||||
ACTION( REQ_REDIS_SMOVE ) \
|
||||
ACTION( REQ_REDIS_SPOP ) \
|
||||
ACTION( REQ_REDIS_SRANDMEMBER ) \
|
||||
ACTION( REQ_REDIS_SREM ) \
|
||||
ACTION( REQ_REDIS_SUNION ) \
|
||||
ACTION( REQ_REDIS_SUNIONSTORE ) \
|
||||
ACTION( REQ_REDIS_SSCAN) \
|
||||
ACTION( REQ_REDIS_ZADD ) /* redis requests - sorted sets */ \
|
||||
ACTION( REQ_REDIS_ZCARD ) \
|
||||
ACTION( REQ_REDIS_ZCOUNT ) \
|
||||
ACTION( REQ_REDIS_ZINCRBY ) \
|
||||
ACTION( REQ_REDIS_ZINTERSTORE ) \
|
||||
ACTION( REQ_REDIS_ZLEXCOUNT ) \
|
||||
ACTION( REQ_REDIS_ZRANGE ) \
|
||||
ACTION( REQ_REDIS_ZRANGEBYLEX ) \
|
||||
ACTION( REQ_REDIS_ZRANGEBYSCORE ) \
|
||||
ACTION( REQ_REDIS_ZRANK ) \
|
||||
ACTION( REQ_REDIS_ZREM ) \
|
||||
ACTION( REQ_REDIS_ZREMRANGEBYRANK ) \
|
||||
ACTION( REQ_REDIS_ZREMRANGEBYLEX ) \
|
||||
ACTION( REQ_REDIS_ZREMRANGEBYSCORE ) \
|
||||
ACTION( REQ_REDIS_ZREVRANGE ) \
|
||||
ACTION( REQ_REDIS_ZREVRANGEBYSCORE ) \
|
||||
ACTION( REQ_REDIS_ZREVRANK ) \
|
||||
ACTION( REQ_REDIS_ZSCORE ) \
|
||||
ACTION( REQ_REDIS_ZUNIONSTORE ) \
|
||||
ACTION( REQ_REDIS_ZSCAN) \
|
||||
ACTION( REQ_REDIS_EVAL ) /* redis requests - eval */ \
|
||||
ACTION( REQ_REDIS_EVALSHA ) \
|
||||
ACTION( REQ_REDIS_PING ) /* redis requests - ping/quit */ \
|
||||
ACTION( REQ_REDIS_QUIT) \
|
||||
ACTION( REQ_REDIS_AUTH) \
|
||||
ACTION( RSP_REDIS_STATUS ) /* redis response */ \
|
||||
ACTION( RSP_REDIS_ERROR ) \
|
||||
ACTION( RSP_REDIS_INTEGER ) \
|
||||
ACTION( RSP_REDIS_BULK ) \
|
||||
ACTION( RSP_REDIS_MULTIBULK ) \
|
||||
ACTION( SENTINEL ) \
|
||||
|
||||
|
||||
#define DEFINE_ACTION(_name) CMD_##_name, |
||||
typedef enum cmd_type { |
||||
CMD_TYPE_CODEC(DEFINE_ACTION) |
||||
} cmd_type_t; |
||||
#undef DEFINE_ACTION |
||||
|
||||
|
||||
struct keypos { |
||||
char *start; /* key start pos */ |
||||
char *end; /* key end pos */ |
||||
uint32_t remain_len; /* remain length after keypos->end for more key-value pairs in command, like mset */ |
||||
}; |
||||
|
||||
struct cmd { |
||||
|
||||
uint64_t id; /* command id */ |
||||
|
||||
cmd_parse_result_t result; /* command parsing result */ |
||||
char *errstr; /* error info when the command parse failed */ |
||||
|
||||
cmd_type_t type; /* command type */ |
||||
|
||||
char *cmd; |
||||
uint32_t clen; /* command length */ |
||||
|
||||
struct hiarray *keys; /* array of keypos, for req */ |
||||
|
||||
char *narg_start; /* narg start (redis) */ |
||||
char *narg_end; /* narg end (redis) */ |
||||
uint32_t narg; /* # arguments (redis) */ |
||||
|
||||
unsigned quit:1; /* quit request? */ |
||||
unsigned noforward:1; /* not need forward (example: ping) */ |
||||
|
||||
int slot_num; /* this command should send to witch slot?
|
||||
* -1:the keys in this command cross different slots*/ |
||||
struct cmd **frag_seq; /* sequence of fragment command, map from keys to fragments*/ |
||||
|
||||
redisReply *reply; |
||||
|
||||
hilist *sub_commands; /* just for pipeline and multi-key commands */ |
||||
}; |
||||
|
||||
void redis_parse_cmd(struct cmd *r); |
||||
|
||||
struct cmd *command_get(void); |
||||
void command_destroy(struct cmd *command); |
||||
|
||||
#endif |
||||
@ -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; |
||||
} |
||||
@ -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 <antirez at gmail dot com> |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#include "fmacros.h" |
||||
#include <stdlib.h> |
||||
#include <assert.h> |
||||
#include <limits.h> |
||||
#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; |
||||
} |
||||
|
||||
@ -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 <antirez at gmail dot com> |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#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 */ |
||||
@ -0,0 +1,62 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <signal.h> |
||||
|
||||
#include <hiredis.h> |
||||
#include <async.h> |
||||
#include <adapters/ae.h> |
||||
|
||||
/* 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; |
||||
} |
||||
|
||||
@ -0,0 +1,73 @@
|
||||
#include <stdlib.h> |
||||
|
||||
#include <hiredis.h> |
||||
#include <async.h> |
||||
#include <adapters/glib.h> |
||||
|
||||
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; |
||||
} |
||||
@ -0,0 +1,52 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <signal.h> |
||||
|
||||
#include <hiredis.h> |
||||
#include <async.h> |
||||
#include <adapters/libev.h> |
||||
|
||||
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; |
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <signal.h> |
||||
|
||||
#include <hiredis.h> |
||||
#include <async.h> |
||||
#include <adapters/libevent.h> |
||||
|
||||
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; |
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <signal.h> |
||||
|
||||
#include <hiredis.h> |
||||
#include <async.h> |
||||
#include <adapters/libuv.h> |
||||
|
||||
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; |
||||
} |
||||
@ -0,0 +1,78 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
|
||||
#include <hiredis.h> |
||||
|
||||
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; |
||||
} |
||||
@ -0,0 +1,23 @@
|
||||
#ifndef __HIREDIS_FMACRO_H |
||||
#define __HIREDIS_FMACRO_H |
||||
|
||||
#if defined(__linux__) |
||||
#ifndef _BSD_SOURCE |
||||
#define _BSD_SOURCE |
||||
#endif |
||||
#define _DEFAULT_SOURCE |
||||
#endif |
||||
|
||||
#if defined(__sun__) |
||||
#define _POSIX_C_SOURCE 200112L |
||||
#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) |
||||
#define _XOPEN_SOURCE 600 |
||||
#else |
||||
#define _XOPEN_SOURCE |
||||
#endif |
||||
|
||||
#if __APPLE__ && __MACH__ |
||||
#define _OSX |
||||
#endif |
||||
|
||||
#endif |
||||
@ -0,0 +1,188 @@
|
||||
#include <stdlib.h> |
||||
|
||||
#include "hiutil.h" |
||||
#include "hiarray.h" |
||||
|
||||
struct hiarray * |
||||
hiarray_create(uint32_t n, size_t size) |
||||
{ |
||||
struct hiarray *a; |
||||
|
||||
ASSERT(n != 0 && size != 0); |
||||
|
||||
a = hi_alloc(sizeof(*a)); |
||||
if (a == NULL) { |
||||
return NULL; |
||||
} |
||||
|
||||
a->elem = hi_alloc(n * size); |
||||
if (a->elem == NULL) { |
||||
hi_free(a); |
||||
return NULL; |
||||
} |
||||
|
||||
a->nelem = 0; |
||||
a->size = size; |
||||
a->nalloc = n; |
||||
|
||||
return a; |
||||
} |
||||
|
||||
void |
||||
hiarray_destroy(struct hiarray *a) |
||||
{ |
||||
hiarray_deinit(a); |
||||
hi_free(a); |
||||
} |
||||
|
||||
int |
||||
hiarray_init(struct hiarray *a, uint32_t n, size_t size) |
||||
{ |
||||
ASSERT(n != 0 && size != 0); |
||||
|
||||
a->elem = hi_alloc(n * size); |
||||
if (a->elem == NULL) { |
||||
return HI_ENOMEM; |
||||
} |
||||
|
||||
a->nelem = 0; |
||||
a->size = size; |
||||
a->nalloc = n; |
||||
|
||||
return HI_OK; |
||||
} |
||||
|
||||
void |
||||
hiarray_deinit(struct hiarray *a) |
||||
{ |
||||
ASSERT(a->nelem == 0); |
||||
|
||||
if (a->elem != NULL) { |
||||
hi_free(a->elem); |
||||
} |
||||
} |
||||
|
||||
uint32_t |
||||
hiarray_idx(struct hiarray *a, void *elem) |
||||
{ |
||||
uint8_t *p, *q; |
||||
uint32_t off, idx; |
||||
|
||||
ASSERT(elem >= a->elem); |
||||
|
||||
p = a->elem; |
||||
q = elem; |
||||
off = (uint32_t)(q - p); |
||||
|
||||
ASSERT(off % (uint32_t)a->size == 0); |
||||
|
||||
idx = off / (uint32_t)a->size; |
||||
|
||||
return idx; |
||||
} |
||||
|
||||
void * |
||||
hiarray_push(struct hiarray *a) |
||||
{ |
||||
void *elem, *new; |
||||
size_t size; |
||||
|
||||
if (a->nelem == a->nalloc) { |
||||
|
||||
/* the array is full; allocate new array */ |
||||
size = a->size * a->nalloc; |
||||
new = hi_realloc(a->elem, 2 * size); |
||||
if (new == NULL) { |
||||
return NULL; |
||||
} |
||||
|
||||
a->elem = new; |
||||
a->nalloc *= 2; |
||||
} |
||||
|
||||
elem = (uint8_t *)a->elem + a->size * a->nelem; |
||||
a->nelem++; |
||||
|
||||
return elem; |
||||
} |
||||
|
||||
void * |
||||
hiarray_pop(struct hiarray *a) |
||||
{ |
||||
void *elem; |
||||
|
||||
ASSERT(a->nelem != 0); |
||||
|
||||
a->nelem--; |
||||
elem = (uint8_t *)a->elem + a->size * a->nelem; |
||||
|
||||
return elem; |
||||
} |
||||
|
||||
void * |
||||
hiarray_get(struct hiarray *a, uint32_t idx) |
||||
{ |
||||
void *elem; |
||||
|
||||
ASSERT(a->nelem != 0); |
||||
ASSERT(idx < a->nelem); |
||||
|
||||
elem = (uint8_t *)a->elem + (a->size * idx); |
||||
|
||||
return elem; |
||||
} |
||||
|
||||
void * |
||||
hiarray_top(struct hiarray *a) |
||||
{ |
||||
ASSERT(a->nelem != 0); |
||||
|
||||
return hiarray_get(a, a->nelem - 1); |
||||
} |
||||
|
||||
void |
||||
hiarray_swap(struct hiarray *a, struct hiarray *b) |
||||
{ |
||||
struct hiarray tmp; |
||||
|
||||
tmp = *a; |
||||
*a = *b; |
||||
*b = tmp; |
||||
} |
||||
|
||||
/*
|
||||
* Sort nelem elements of the array in ascending order based on the |
||||
* compare comparator. |
||||
*/ |
||||
void |
||||
hiarray_sort(struct hiarray *a, hiarray_compare_t compare) |
||||
{ |
||||
ASSERT(a->nelem != 0); |
||||
|
||||
qsort(a->elem, a->nelem, a->size, compare); |
||||
} |
||||
|
||||
/*
|
||||
* Calls the func once for each element in the array as long as func returns |
||||
* success. On failure short-circuits and returns the error status. |
||||
*/ |
||||
int |
||||
hiarray_each(struct hiarray *a, hiarray_each_t func, void *data) |
||||
{ |
||||
uint32_t i, nelem; |
||||
|
||||
ASSERT(array_n(a) != 0); |
||||
ASSERT(func != NULL); |
||||
|
||||
for (i = 0, nelem = hiarray_n(a); i < nelem; i++) { |
||||
void *elem = hiarray_get(a, i); |
||||
rstatus_t status; |
||||
|
||||
status = func(elem, data); |
||||
if (status != HI_OK) { |
||||
return status; |
||||
} |
||||
} |
||||
|
||||
return HI_OK; |
||||
} |
||||
@ -0,0 +1,56 @@
|
||||
#ifndef __HIARRAY_H_ |
||||
#define __HIARRAY_H_ |
||||
|
||||
#include <stdio.h> |
||||
|
||||
typedef int (*hiarray_compare_t)(const void *, const void *); |
||||
typedef int (*hiarray_each_t)(void *, void *); |
||||
|
||||
struct hiarray { |
||||
uint32_t nelem; /* # element */ |
||||
void *elem; /* element */ |
||||
size_t size; /* element size */ |
||||
uint32_t nalloc; /* # allocated element */ |
||||
}; |
||||
|
||||
#define null_hiarray { 0, NULL, 0, 0 } |
||||
|
||||
static inline void |
||||
hiarray_null(struct hiarray *a) |
||||
{ |
||||
a->nelem = 0; |
||||
a->elem = NULL; |
||||
a->size = 0; |
||||
a->nalloc = 0; |
||||
} |
||||
|
||||
static inline void |
||||
hiarray_set(struct hiarray *a, void *elem, size_t size, uint32_t nalloc) |
||||
{ |
||||
a->nelem = 0; |
||||
a->elem = elem; |
||||
a->size = size; |
||||
a->nalloc = nalloc; |
||||
} |
||||
|
||||
static inline uint32_t |
||||
hiarray_n(const struct hiarray *a) |
||||
{ |
||||
return a->nelem; |
||||
} |
||||
|
||||
struct hiarray *hiarray_create(uint32_t n, size_t size); |
||||
void hiarray_destroy(struct hiarray *a); |
||||
int hiarray_init(struct hiarray *a, uint32_t n, size_t size); |
||||
void hiarray_deinit(struct hiarray *a); |
||||
|
||||
uint32_t hiarray_idx(struct hiarray *a, void *elem); |
||||
void *hiarray_push(struct hiarray *a); |
||||
void *hiarray_pop(struct hiarray *a); |
||||
void *hiarray_get(struct hiarray *a, uint32_t idx); |
||||
void *hiarray_top(struct hiarray *a); |
||||
void hiarray_swap(struct hiarray *a, struct hiarray *b); |
||||
void hiarray_sort(struct hiarray *a, hiarray_compare_t compare); |
||||
int hiarray_each(struct hiarray *a, hiarray_each_t func, void *data); |
||||
|
||||
#endif |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,178 @@
|
||||
|
||||
#ifndef __HIRCLUSTER_H |
||||
#define __HIRCLUSTER_H |
||||
|
||||
#include "hiredis.h" |
||||
#include "async.h" |
||||
|
||||
#define HIREDIS_VIP_MAJOR 0 |
||||
#define HIREDIS_VIP_MINOR 3 |
||||
#define HIREDIS_VIP_PATCH 0 |
||||
|
||||
#define REDIS_CLUSTER_SLOTS 16384 |
||||
|
||||
#define REDIS_ROLE_NULL 0 |
||||
#define REDIS_ROLE_MASTER 1 |
||||
#define REDIS_ROLE_SLAVE 2 |
||||
|
||||
|
||||
#define HIRCLUSTER_FLAG_NULL 0x0 |
||||
/* The flag to decide whether add slave node in
|
||||
* redisClusterContext->nodes. This is set in the |
||||
* least significant bit of the flags field in
|
||||
* redisClusterContext. (1000000000000) */ |
||||
#define HIRCLUSTER_FLAG_ADD_SLAVE 0x1000 |
||||
/* The flag to decide whether add open slot
|
||||
* for master node. (10000000000000) */ |
||||
#define HIRCLUSTER_FLAG_ADD_OPENSLOT 0x2000 |
||||
/* The flag to decide whether get the route
|
||||
* table by 'cluster slots' command. Default
|
||||
* is 'cluster nodes' command.*/ |
||||
#define HIRCLUSTER_FLAG_ROUTE_USE_SLOTS 0x4000 |
||||
|
||||
struct dict; |
||||
struct hilist; |
||||
|
||||
typedef struct cluster_node |
||||
{ |
||||
sds name; |
||||
sds addr; |
||||
sds host; |
||||
int port; |
||||
uint8_t role; |
||||
uint8_t myself; /* myself ? */ |
||||
redisContext *con; |
||||
redisAsyncContext *acon; |
||||
struct hilist *slots; |
||||
struct hilist *slaves; |
||||
int failure_count; |
||||
void *data; /* Not used by hiredis */ |
||||
struct hiarray *migrating; /* copen_slot[] */ |
||||
struct hiarray *importing; /* copen_slot[] */ |
||||
}cluster_node; |
||||
|
||||
typedef struct cluster_slot |
||||
{ |
||||
uint32_t start; |
||||
uint32_t end; |
||||
cluster_node *node; /* master that this slot region belong to */ |
||||
}cluster_slot; |
||||
|
||||
typedef struct copen_slot |
||||
{ |
||||
uint32_t slot_num; /* slot number */ |
||||
int migrate; /* migrating or importing? */ |
||||
sds remote_name; /* name for the node that this slot migrating to/importing from */ |
||||
cluster_node *node; /* master that this slot belong to */ |
||||
}copen_slot; |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/* Context for a connection to Redis cluster */ |
||||
typedef struct redisClusterContext { |
||||
int err; /* Error flags, 0 when there is no error */ |
||||
char errstr[128]; /* String representation of error when applicable */ |
||||
sds ip; |
||||
int port; |
||||
|
||||
int flags; |
||||
|
||||
enum redisConnectionType connection_type; |
||||
struct timeval *timeout; |
||||
|
||||
struct hiarray *slots; |
||||
|
||||
struct dict *nodes; |
||||
cluster_node *table[REDIS_CLUSTER_SLOTS]; |
||||
|
||||
uint64_t route_version; |
||||
|
||||
int max_redirect_count; |
||||
int retry_count; |
||||
|
||||
struct hilist *requests; |
||||
|
||||
int need_update_route; |
||||
int64_t update_route_time; |
||||
} redisClusterContext; |
||||
|
||||
redisClusterContext *redisClusterConnect(const char *addrs, int flags); |
||||
redisClusterContext *redisClusterConnectWithTimeout(const char *addrs,
|
||||
const struct timeval tv, int flags); |
||||
redisClusterContext *redisClusterConnectNonBlock(const char *addrs, int flags); |
||||
|
||||
void redisClusterFree(redisClusterContext *cc); |
||||
|
||||
void redisClusterSetMaxRedirect(redisClusterContext *cc, int max_redirect_count); |
||||
|
||||
void *redisClusterFormattedCommand(redisClusterContext *cc, char *cmd, int len); |
||||
void *redisClustervCommand(redisClusterContext *cc, const char *format, va_list ap); |
||||
void *redisClusterCommand(redisClusterContext *cc, const char *format, ...); |
||||
void *redisClusterCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); |
||||
|
||||
redisContext *ctx_get_by_node(struct cluster_node *node, const struct timeval *timeout, int flags); |
||||
|
||||
int redisClusterAppendFormattedCommand(redisClusterContext *cc, char *cmd, int len); |
||||
int redisClustervAppendCommand(redisClusterContext *cc, const char *format, va_list ap); |
||||
int redisClusterAppendCommand(redisClusterContext *cc, const char *format, ...); |
||||
int redisClusterAppendCommandArgv(redisClusterContext *cc, int argc, const char **argv, const size_t *argvlen); |
||||
int redisClusterGetReply(redisClusterContext *cc, void **reply); |
||||
void redisClusterReset(redisClusterContext *cc); |
||||
|
||||
int cluster_update_route(redisClusterContext *cc); |
||||
int test_cluster_update_route(redisClusterContext *cc); |
||||
struct dict *parse_cluster_nodes(redisClusterContext *cc, char *str, int str_len, int flags); |
||||
struct dict *parse_cluster_slots(redisClusterContext *cc, redisReply *reply, int flags); |
||||
|
||||
|
||||
/*############redis cluster async############*/ |
||||
|
||||
struct redisClusterAsyncContext; |
||||
|
||||
typedef int (adapterAttachFn)(redisAsyncContext*, void*); |
||||
|
||||
typedef void (redisClusterCallbackFn)(struct redisClusterAsyncContext*, void*, void*); |
||||
|
||||
/* Context for an async connection to Redis */ |
||||
typedef struct redisClusterAsyncContext { |
||||
|
||||
redisClusterContext *cc; |
||||
|
||||
/* Setup error flags so they can be used directly. */ |
||||
int err; |
||||
char errstr[128]; /* String representation of error when applicable */ |
||||
|
||||
/* Not used by hiredis */ |
||||
void *data; |
||||
|
||||
void *adapter; |
||||
adapterAttachFn *attach_fn; |
||||
|
||||
/* Called when either the connection is terminated due to an error or per
|
||||
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */ |
||||
redisDisconnectCallback *onDisconnect; |
||||
|
||||
/* Called when the first write event was received. */ |
||||
redisConnectCallback *onConnect; |
||||
|
||||
} redisClusterAsyncContext; |
||||
|
||||
redisClusterAsyncContext *redisClusterAsyncConnect(const char *addrs, int flags); |
||||
int redisClusterAsyncSetConnectCallback(redisClusterAsyncContext *acc, redisConnectCallback *fn); |
||||
int redisClusterAsyncSetDisconnectCallback(redisClusterAsyncContext *acc, redisDisconnectCallback *fn); |
||||
int redisClusterAsyncFormattedCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, char *cmd, int len); |
||||
int redisClustervAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, va_list ap); |
||||
int redisClusterAsyncCommand(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, const char *format, ...); |
||||
int redisClusterAsyncCommandArgv(redisClusterAsyncContext *acc, redisClusterCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen); |
||||
void redisClusterAsyncDisconnect(redisClusterAsyncContext *acc); |
||||
void redisClusterAsyncFree(redisClusterAsyncContext *acc); |
||||
|
||||
redisAsyncContext *actx_get_by_node(redisClusterAsyncContext *acc, cluster_node *node); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, |
||||
* Jan-Erik Rediger <janerik at fnordig dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef __HIREDIS_H |
||||
#define __HIREDIS_H |
||||
#include "read.h" |
||||
#include <stdarg.h> /* for va_list */ |
||||
#include <sys/time.h> /* for struct timeval */ |
||||
#include <stdint.h> /* 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 |
||||
@ -0,0 +1,554 @@
|
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <stdarg.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <errno.h> |
||||
|
||||
#include <sys/time.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include <netinet/in.h> |
||||
#include <netinet/tcp.h> |
||||
|
||||
|
||||
#include "hiutil.h" |
||||
|
||||
#ifdef HI_HAVE_BACKTRACE |
||||
# include <execinfo.h> |
||||
#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"); |
||||
} |
||||
|
||||
@ -0,0 +1,265 @@
|
||||
#ifndef __HIUTIL_H_ |
||||
#define __HIUTIL_H_ |
||||
|
||||
#include <stdarg.h> |
||||
#include <stdint.h> |
||||
#include <sys/types.h> |
||||
|
||||
#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 |
||||
@ -0,0 +1,458 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
* |
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, |
||||
* Jan-Erik Rediger <janerik at fnordig dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#include "fmacros.h" |
||||
#include <sys/types.h> |
||||
#include <sys/socket.h> |
||||
#include <sys/select.h> |
||||
#include <sys/un.h> |
||||
#include <netinet/in.h> |
||||
#include <netinet/tcp.h> |
||||
#include <arpa/inet.h> |
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <string.h> |
||||
#include <netdb.h> |
||||
#include <errno.h> |
||||
#include <stdarg.h> |
||||
#include <stdio.h> |
||||
#include <poll.h> |
||||
#include <limits.h> |
||||
#include <stdlib.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
* |
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>, |
||||
* Jan-Erik Rediger <janerik at fnordig dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#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 |
||||
@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
|
||||
#include "fmacros.h" |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#ifndef _MSC_VER |
||||
#include <unistd.h> |
||||
#endif |
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
#include <ctype.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
|
||||
#ifndef __HIREDIS_READ_H |
||||
#define __HIREDIS_READ_H |
||||
#include <stdio.h> /* for size_t */ |
||||
|
||||
#define REDIS_ERR -1 |
||||
#define REDIS_OK 0 |
||||
|
||||
/* When an error occurs, the err flag in a context is set to hold the type of
|
||||
* error that occured. REDIS_ERR_IO means there was an I/O error and you |
||||
* should use the "errno" variable to find out what is wrong. |
||||
* For other values, the "errstr" field will hold a description. */ |
||||
#define REDIS_ERR_IO 1 /* Error in read or write */ |
||||
#define REDIS_ERR_EOF 3 /* End of file */ |
||||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */ |
||||
#define REDIS_ERR_OOM 5 /* Out of memory */ |
||||
#define REDIS_ERR_OTHER 2 /* Everything else... */ |
||||
#if 1 //shenzheng 2015-8-10 redis cluster
|
||||
#define REDIS_ERR_CLUSTER_TOO_MANY_REDIRECT 6 |
||||
#endif //shenzheng 2015-8-10 redis cluster
|
||||
|
||||
#define REDIS_REPLY_STRING 1 |
||||
#define REDIS_REPLY_ARRAY 2 |
||||
#define REDIS_REPLY_INTEGER 3 |
||||
#define REDIS_REPLY_NIL 4 |
||||
#define REDIS_REPLY_STATUS 5 |
||||
#define REDIS_REPLY_ERROR 6 |
||||
|
||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ |
||||
|
||||
#if 1 //shenzheng 2015-8-22 redis cluster
|
||||
#define REDIS_ERROR_MOVED "MOVED" |
||||
#define REDIS_ERROR_ASK "ASK" |
||||
#define REDIS_ERROR_TRYAGAIN "TRYAGAIN" |
||||
#define REDIS_ERROR_CROSSSLOT "CROSSSLOT" |
||||
#define REDIS_ERROR_CLUSTERDOWN "CLUSTERDOWN" |
||||
|
||||
#define REDIS_STATUS_OK "OK" |
||||
#endif //shenzheng 2015-9-24 redis cluster
|
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
typedef struct redisReadTask { |
||||
int type; |
||||
int elements; /* number of elements in multibulk container */ |
||||
int idx; /* index in parent (array) object */ |
||||
void *obj; /* holds user-generated value for a read task */ |
||||
struct redisReadTask *parent; /* parent task */ |
||||
void *privdata; /* user-settable arbitrary field */ |
||||
} redisReadTask; |
||||
|
||||
typedef struct redisReplyObjectFunctions { |
||||
void *(*createString)(const redisReadTask*, char*, size_t); |
||||
void *(*createArray)(const redisReadTask*, int); |
||||
void *(*createInteger)(const redisReadTask*, long long); |
||||
void *(*createNil)(const redisReadTask*); |
||||
void (*freeObject)(void*); |
||||
} redisReplyObjectFunctions; |
||||
|
||||
typedef struct redisReader { |
||||
int err; /* Error flags, 0 when there is no error */ |
||||
char errstr[128]; /* String representation of error when applicable */ |
||||
|
||||
char *buf; /* Read buffer */ |
||||
size_t pos; /* Buffer cursor */ |
||||
size_t len; /* Buffer length */ |
||||
size_t maxbuf; /* Max length of unused buffer */ |
||||
|
||||
redisReadTask rstack[9]; |
||||
int ridx; /* Index of current read task */ |
||||
void *reply; /* Temporary reply pointer */ |
||||
|
||||
redisReplyObjectFunctions *fn; |
||||
void *privdata; |
||||
} redisReader; |
||||
|
||||
/* Public API for the protocol parser. */ |
||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn); |
||||
void redisReaderFree(redisReader *r); |
||||
int redisReaderFeed(redisReader *r, const char *buf, size_t len); |
||||
int redisReaderGetReply(redisReader *r, void **reply); |
||||
|
||||
/* Backwards compatibility, can be removed on big version bump. */ |
||||
#define redisReplyReaderCreate redisReaderCreate |
||||
#define redisReplyReaderFree redisReaderFree |
||||
#define redisReplyReaderFeed redisReaderFeed |
||||
#define redisReplyReaderGetReply redisReaderGetReply |
||||
#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p)) |
||||
#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply) |
||||
#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr) |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
@ -0,0 +1,105 @@
|
||||
/* SDS (Simple Dynamic Strings), A C dynamic strings library.
|
||||
* |
||||
* Copyright (c) 2006-2014, Salvatore Sanfilippo <antirez at gmail dot com> |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright notice, |
||||
* this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* * Neither the name of Redis nor the names of its contributors may be used |
||||
* to endorse or promote products derived from this software without |
||||
* specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||
* POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
#ifndef __SDS_H |
||||
#define __SDS_H |
||||
|
||||
#define SDS_MAX_PREALLOC (1024*1024) |
||||
|
||||
#include <sys/types.h> |
||||
#include <stdarg.h> |
||||
#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 |
||||
@ -0,0 +1,806 @@
|
||||
#include "fmacros.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <strings.h> |
||||
#include <sys/time.h> |
||||
#include <assert.h> |
||||
#include <unistd.h> |
||||
#include <signal.h> |
||||
#include <errno.h> |
||||
#include <limits.h> |
||||
|
||||
#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; |
||||
} |
||||
@ -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 |
||||
Loading…
Reference in new issue