add redis dependencies for central controller

This commit is contained in:
Grant Limberg 2020-06-08 11:23:41 -07:00
parent d18c33d6df
commit 2bceabdfa5
No known key found for this signature in database
GPG key ID: 2BA62CCABBB4095A
118 changed files with 31744 additions and 0 deletions

View file

@ -0,0 +1,7 @@
/hiredis-test
/examples/hiredis-example*
/*.o
/*.so
/*.dylib
/*.a
/*.pc

View file

@ -0,0 +1,45 @@
language: c
sudo: false
compiler:
- gcc
- clang
os:
- linux
- osx
branches:
only:
- staging
- trying
- master
before_script:
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
addons:
apt:
packages:
- libc6-dbg
- libc6-dev
- libc6:i386
- libc6-dev-i386
- libc6-dbg:i386
- gcc-multilib
- valgrind
env:
- CFLAGS="-Werror"
- PRE="valgrind --track-origins=yes --leak-check=full"
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
- TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
matrix:
exclude:
- os: osx
env: PRE="valgrind --track-origins=yes --leak-check=full"
- os: osx
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example

View file

@ -0,0 +1,190 @@
**NOTE: BREAKING CHANGES upgrading from 0.13.x to 0.14.x **:
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
User code should compare this to `size_t` values as well. If it was used to
compare to other values, casting might be necessary or can be removed, if
casting was applied before.
### 0.14.1 (2020-03-13)
* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder)
### 0.14.0 (2018-09-25)
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
* Fix libevent leak (zfz [515228])
* Clean up GCC warning (Ichito Nagata [2ec774])
* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
* Solaris compilation fix (Donald Whyte [41b07d])
* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
* libuv use after free fix (Paul Scott [cbb956])
* Properly close socket fd on reconnect attempt (WSL [64d1ec])
* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
* Update libevent (Chris Xin [386802])
* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
* Compatibility fix for strerror_r (Tom Lee [bb1747])
* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
* Catch a buffer overflow when formatting the error message
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
* Fix warnings, when compiled with -Wshadow
* Make hiredis compile in Cygwin on Windows, now CI-tested
**BREAKING CHANGES**:
* Remove backwards compatibility macro's
This removes the following old function aliases, use the new name now:
| Old | New |
| --------------------------- | ---------------------- |
| redisReplyReaderCreate | redisReaderCreate |
| redisReplyReaderCreate | redisReaderCreate |
| redisReplyReaderFree | redisReaderFree |
| redisReplyReaderFeed | redisReaderFeed |
| redisReplyReaderGetReply | redisReaderGetReply |
| redisReplyReaderSetPrivdata | redisReaderSetPrivdata |
| redisReplyReaderGetObject | redisReaderGetObject |
| redisReplyReaderGetError | redisReaderGetError |
* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS`
Previously it broke some builds for people that had `DEBUG` set to some arbitrary value,
due to debugging other software.
By renaming we avoid unintentional name clashes.
Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again.
### 0.13.3 (2015-09-16)
* Revert "Clear `REDIS_CONNECTED` flag when connection is closed".
* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni)
If the `REDIS_CONNECTED` flag is cleared,
the async onDisconnect callback function will never be called.
This causes problems as the disconnect is never reported back to the user.
### 0.13.2 (2015-08-25)
* Prevent crash on pending replies in async code (Thanks, @switch-st)
* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs)
* Add MacOS X addapter (Thanks, @dizzus)
* Add Qt adapter (Thanks, Pietro Cerutti)
* Add Ivykis adapter (Thanks, Gergely Nagy)
All adapters are provided as is and are only tested where possible.
### 0.13.1 (2015-05-03)
This is a bug fix release.
The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code.
Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects.
Other non-C99 code can now use hiredis as usual again.
Sorry for the inconvenience.
* Fix memory leak in async reply handling (Salvatore Sanfilippo)
* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa)
### 0.13.0 (2015-04-16)
This release adds a minimal Windows compatibility layer.
The parser, standalone since v0.12.0, can now be compiled on Windows
(and thus used in other client libraries as well)
* Windows compatibility layer for parser code (tzickel)
* Properly escape data printed to PKGCONF file (Dan Skorupski)
* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff)
* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra)
### 0.12.1 (2015-01-26)
* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location
* Fix `make test` as 32 bit build on 64 bit platform
### 0.12.0 (2015-01-22)
* Add optional KeepAlive support
* Try again on EINTR errors
* Add libuv adapter
* Add IPv6 support
* Remove possibility of multiple close on same fd
* Add ability to bind source address on connect
* Add redisConnectFd() and redisFreeKeepFd()
* Fix getaddrinfo() memory leak
* Free string if it is unused (fixes memory leak)
* Improve redisAppendCommandArgv performance 2.5x
* Add support for SO_REUSEADDR
* Fix redisvFormatCommand format parsing
* Add GLib 2.0 adapter
* Refactor reading code into read.c
* Fix errno error buffers to not clobber errors
* Generate pkgconf during build
* Silence _BSD_SOURCE warnings
* Improve digit counting for multibulk creation
### 0.11.0
* Increase the maximum multi-bulk reply depth to 7.
* Increase the read buffer size from 2k to 16k.
* Use poll(2) instead of select(2) to support large fds (>= 1024).
### 0.10.1
* Makefile overhaul. Important to check out if you override one or more
variables using environment variables or via arguments to the "make" tool.
* Issue #45: Fix potential memory leak for a multi bulk reply with 0 elements
being created by the default reply object functions.
* Issue #43: Don't crash in an asynchronous context when Redis returns an error
reply after the connection has been made (this happens when the maximum
number of connections is reached).
### 0.10.0
* See commit log.

View file

@ -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.

View file

@ -0,0 +1,214 @@
# 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 alloc.o
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
TESTS=hiredis-test
LIBNAME=libhiredis
PKGCONFNAME=hiredis.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}')
HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}')
# Installation related variables and target
PREFIX?=/usr/local
INCLUDE_PATH?=include/hiredis
LIBRARY_PATH?=lib
PKGCONF_PATH?=pkgconfig
INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)
# redis-server configuration used for testing
REDIS_PORT=56379
REDIS_SERVER=redis-server
define REDIS_TEST_CONFIG
daemonize yes
pidfile /tmp/hiredis-test-redis.pid
port $(REDIS_PORT)
bind 127.0.0.1
unixsocket /tmp/hiredis-test-redis.sock
endef
export REDIS_TEST_CONFIG
# Fallback to gcc when $CC is not in $PATH.
CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
DEBUG_FLAGS?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
REAL_LDFLAGS=$(LDFLAGS)
DYLIBSUFFIX=so
STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
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)
endif
ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
# Deps (use make dep to generate this)
alloc.o: alloc.c fmacros.h alloc.h
async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
dict.o: dict.c fmacros.h alloc.h dict.h
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h
read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c sds.h sdsalloc.h
test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.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) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME)
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(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 -lrt $(STLIBNAME)
endif
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
hiredis-example-qt:
@echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR"
@false
else
hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
$(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
$(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
$(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
$(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
$(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore
endif
hiredis-example: examples/example.c $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
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
INSTALL?= cp -pPR
$(PKGCONFNAME): hiredis.h
@echo "Generating $@ for pkgconfig..."
@echo prefix=$(PREFIX) > $@
@echo exec_prefix=\$${prefix} >> $@
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
@echo >> $@
@echo Name: hiredis >> $@
@echo Description: Minimalistic C client library for Redis. >> $@
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
@echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
mkdir -p $(INSTALL_PKGCONF_PATH)
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
32bit:
@echo ""
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"
@echo ""
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
32bit-vars:
$(eval CFLAGS=-m32)
$(eval LDFLAGS=-m32)
gprof:
$(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
gcov:
$(MAKE) CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs"
coverage: gcov
make check
mkdir -p tmp/lcov
lcov -d . -c -o tmp/lcov/hiredis.info
genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
noopt:
$(MAKE) OPTIMIZATION=""
.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt

View file

@ -0,0 +1,410 @@
[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis)
**This Readme reflects the latest changed in the master branch. See [v0.14.1](https://github.com/redis/hiredis/tree/v0.14.1) for the Readme and documentation for the latest release.**
# HIREDIS
Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database.
It is minimalistic because it just adds minimal support for the protocol, but
at the same time it uses a high level printf-alike API in order to make it
much higher level than otherwise suggested by its minimal code base and the
lack of explicit bindings for every Redis command.
Apart from supporting sending commands and receiving replies, it comes with
a reply parser that is decoupled from the I/O layer. It
is a stream parser designed for easy reusability, which can for instance be used
in higher level language bindings for efficient reply parsing.
Hiredis only supports the binary-safe Redis protocol, so you can use it with any
Redis version >= 1.2.0.
The library comes with multiple APIs. There is the
*synchronous API*, the *asynchronous API* and the *reply parsing API*.
## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x
Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
Change `redisReply.len` to `size_t`, as it denotes the the size of a string
User code should compare this to `size_t` values as well. If it was used to
compare to other values, casting might be necessary or can be removed, if
casting was applied before.
For a detailed list of changes please view our [Changelog](CHANGELOG.md).
## Synchronous API
To consume the synchronous API, there are only a few function calls that need to be introduced:
```c
redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);
```
### Connecting
The function `redisConnect` is used to create a so-called `redisContext`. The
context is where Hiredis holds state for a connection. The `redisContext`
struct has an integer `err` field that is non-zero when the connection is in
an error state. The field `errstr` will contain a string with a description of
the error. More information on errors can be found in the **Errors** section.
After trying to connect to Redis using `redisConnect` you should
check the `err` field to see if establishing the connection was successful:
```c
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
if (c) {
printf("Error: %s\n", c->errstr);
// handle error
} else {
printf("Can't allocate redis context\n");
}
}
```
*Note: A `redisContext` is not thread-safe.*
### Sending commands
There are several ways to issue commands to Redis. The first that will be introduced is
`redisCommand`. This function takes a format similar to printf. In the simplest form,
it is used like this:
```c
reply = redisCommand(context, "SET foo bar");
```
The specifier `%s` interpolates a string in the command, and uses `strlen` to
determine the length of the string:
```c
reply = redisCommand(context, "SET foo %s", value);
```
When you need to pass binary safe strings in a command, the `%b` specifier can be
used. Together with a pointer to the string, it requires a `size_t` length argument
of the string:
```c
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
```
Internally, Hiredis splits the command in different arguments and will
convert it to the protocol used to communicate with Redis.
One or more spaces separates arguments, so you can use the specifiers
anywhere in an argument:
```c
reply = redisCommand(context, "SET key:%s %s", myid, value);
```
### Using replies
The return value of `redisCommand` holds a reply when the command was
successfully executed. When an error occurs, the return value is `NULL` and
the `err` field in the context will be set (see section on **Errors**).
Once an error is returned the context cannot be reused and you should set up
a new connection.
The standard replies that `redisCommand` are of the type `redisReply`. The
`type` field in the `redisReply` should be used to test what kind of reply
was received:
* **`REDIS_REPLY_STATUS`**:
* The command replied with a status reply. The status string can be accessed using `reply->str`.
The length of this string can be accessed using `reply->len`.
* **`REDIS_REPLY_ERROR`**:
* The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
* **`REDIS_REPLY_INTEGER`**:
* The command replied with an integer. The integer value can be accessed using the
`reply->integer` field of type `long long`.
* **`REDIS_REPLY_NIL`**:
* The command replied with a **nil** object. There is no data to access.
* **`REDIS_REPLY_STRING`**:
* A bulk (string) reply. The value of the reply can be accessed using `reply->str`.
The length of this string can be accessed using `reply->len`.
* **`REDIS_REPLY_ARRAY`**:
* A multi bulk reply. The number of elements in the multi bulk reply is stored in
`reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well
and can be accessed via `reply->element[..index..]`.
Redis may reply with nested arrays but this is fully supported.
Replies should be freed using the `freeReplyObject()` function.
Note that this function will take care of freeing sub-reply objects
contained in arrays and nested arrays, so there is no need for the user to
free the sub replies (it is actually harmful and will corrupt the memory).
**Important:** the current version of hiredis (0.10.0) frees replies when the
asynchronous API is used. This means you should not call `freeReplyObject` when
you use this API. The reply is cleaned up by hiredis _after_ the callback
returns. This behavior will probably change in future releases, so make sure to
keep an eye on the changelog when upgrading (see issue #39).
### Cleaning up
To disconnect and free the context the following function can be used:
```c
void redisFree(redisContext *c);
```
This function immediately closes the socket and then frees the allocations done in
creating the context.
### Sending commands (cont'd)
Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
It has the following prototype:
```c
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
```
It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
need to be binary safe, the entire array of lengths `argvlen` should be provided.
The return value has the same semantic as `redisCommand`.
### Pipelining
To explain how Hiredis supports pipelining in a blocking connection, there needs to be
understanding of the internal execution flow.
When any of the functions in the `redisCommand` family is called, Hiredis first formats the
command according to the Redis protocol. The formatted command is then put in the output buffer
of the context. This output buffer is dynamic, so it can hold any number of commands.
After the command is put in the output buffer, `redisGetReply` is called. This function has the
following two execution paths:
1. The input buffer is non-empty:
* Try to parse a single reply from the input buffer and return it
* If no reply could be parsed, continue at *2*
2. The input buffer is empty:
* Write the **entire** output buffer to the socket
* Read from the socket until a single reply could be parsed
The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
is expected on the socket. To pipeline commands, the only things that needs to be done is
filling up the output buffer. For this cause, two commands can be used that are identical
to the `redisCommand` family, apart from not returning a reply:
```c
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
```
After calling either function one or more times, `redisGetReply` can be used to receive the
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
the latter means an error occurred while reading a reply. Just as with the other commands,
the `err` field in the context can be used to find out what the cause of this error is.
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
a single call to `read(2)`):
```c
redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET
freeReplyObject(reply);
```
This API can also be used to implement a blocking subscriber:
```c
reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK) {
// consume message
freeReplyObject(reply);
}
```
### Errors
When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
returned. The `err` field inside the context will be non-zero and set to one of the
following constants:
* **`REDIS_ERR_IO`**:
There was an I/O error while creating the connection, trying to write
to the socket or read from the socket. If you included `errno.h` in your
application, you can use the global `errno` variable to find out what is
wrong.
* **`REDIS_ERR_EOF`**:
The server closed the connection which resulted in an empty read.
* **`REDIS_ERR_PROTOCOL`**:
There was an error while parsing the protocol.
* **`REDIS_ERR_OTHER`**:
Any other error. Currently, it is only used when a specified hostname to connect
to cannot be resolved.
In every case, the `errstr` field in the context will be set to hold a string representation
of the error.
## Asynchronous API
Hiredis comes with an asynchronous API that works easily with any event library.
Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)
and [libevent](http://monkey.org/~provos/libevent/).
### Connecting
The function `redisAsyncConnect` can be used to establish a non-blocking connection to
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
should be checked after creation to see if there were errors creating the connection.
Because the connection that will be created is non-blocking, the kernel is not able to
instantly return if the specified host and port is able to accept a connection.
*Note: A `redisAsyncContext` is not thread-safe.*
```c
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
printf("Error: %s\n", c->errstr);
// handle error
}
```
The asynchronous context can hold a disconnect callback function that is called when the
connection is disconnected (either because of an error or per user request). This function should
have the following prototype:
```c
void(const redisAsyncContext *c, int status);
```
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
field in the context can be accessed to find out the cause of the error.
The context object is always freed after the disconnect callback fired. When a reconnect is needed,
the disconnect callback is a good point to do so.
Setting the disconnect callback can only be done once per context. For subsequent calls it will
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
```c
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
```
### Sending commands and their callbacks
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
Therefore, unlike the synchronous API, there is only a single way to send commands.
Because commands are sent to Redis asynchronously, issuing a command requires a callback function
that is called when the reply is received. Reply callbacks should have the following prototype:
```c
void(redisAsyncContext *c, void *reply, void *privdata);
```
The `privdata` argument can be used to curry arbitrary data to the callback from the point where
the command is initially queued for execution.
The functions that can be used to issue commands in an asynchronous context are:
```c
int redisAsyncCommand(
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
const char *format, ...);
int redisAsyncCommandArgv(
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
int argc, const char **argv, const size_t *argvlen);
```
Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
returned on calls to the `redisAsyncCommand` family.
If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback
for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only
valid for the duration of the callback.
All pending callbacks are called with a `NULL` reply when the context encountered an error.
### Disconnecting
An asynchronous connection can be terminated using:
```c
void redisAsyncDisconnect(redisAsyncContext *ac);
```
When this function is called, the connection is **not** immediately terminated. Instead, new
commands are no longer accepted and the connection is only terminated when all pending commands
have been written to the socket, their respective replies have been read and their respective
callbacks have been executed. After this, the disconnection callback is executed with the
`REDIS_OK` status and the context object is freed.
### Hooking it up to event library *X*
There are a few hooks that need to be set on the context object after it is created.
See the `adapters/` directory for bindings to *libev* and *libevent*.
## Reply parsing API
Hiredis comes with a reply parsing API that makes it easy for writing higher
level language bindings.
The reply parsing API consists of the following functions:
```c
redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *reader);
int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
int redisReaderGetReply(redisReader *reader, void **reply);
```
The same set of functions are used internally by hiredis when creating a
normal Redis context, the above API just exposes it to the user for a direct
usage.
### Usage
The function `redisReaderCreate` creates a `redisReader` structure that holds a
buffer with unparsed data and state for the protocol parser.
Incoming data -- most likely from a socket -- can be placed in the internal
buffer of the `redisReader` using `redisReaderFeed`. This function will make a
copy of the buffer pointed to by `buf` for `len` bytes. This data is parsed
when `redisReaderGetReply` is called. This function returns an integer status
and a reply object (as described above) via `void **reply`. The returned status
can be either `REDIS_OK` or `REDIS_ERR`, where the latter means something went
wrong (either a protocol error, or an out of memory error).
The parser limits the level of nesting for multi bulk payloads to 7. If the
multi bulk nesting level is higher than this, the parser returns an error.
### Customizing replies
The function `redisReaderGetReply` creates `redisReply` and makes the function
argument `reply` point to the created `redisReply` variable. For instance, if
the response of type `REDIS_REPLY_STATUS` then the `str` field of `redisReply`
will hold the status as a vanilla C string. However, the functions that are
responsible for creating instances of the `redisReply` can be customized by
setting the `fn` field on the `redisReader` struct. This should be done
immediately after creating the `redisReader`.
For example, [hiredis-rb](https://github.com/pietern/hiredis-rb/blob/master/ext/hiredis_ext/reader.c)
uses customized reply object functions to create Ruby objects.
### Reader max buffer
Both when using the Reader API directly or when using it indirectly via a
normal Redis context, the redisReader structure uses a buffer in order to
accumulate data from the server.
Usually this buffer is destroyed when it is empty and is larger than 16
KiB in order to avoid wasting memory in unused buffers
However when working with very big payloads destroying the buffer may slow
down performances considerably, so it is possible to modify the max size of
an idle buffer changing the value of the `maxbuf` field of the reader structure
to the desired value. The special value of 0 means that there is no maximum
value for an idle buffer, so the buffer will never get freed.
For instance if you have a normal Redis context you can set the maximum idle
buffer to zero (unlimited) just with:
```c
context->reader->maxbuf = 0;
```
This should be done only in order to maximize performances when working with
large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again
as soon as possible in order to prevent allocation of useless memory.
## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
Jan-Erik Rediger (janerik at fnordig dot com)

View file

@ -0,0 +1,127 @@
/*
* 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"
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*)hi_malloc(sizeof(*e));
e->context = ac;
e->loop = loop;
e->fd = c->fd;
e->reading = e->writing = 0;
/* Register functions to start/stop listening for events */
ac->ev.addRead = redisAeAddRead;
ac->ev.delRead = redisAeDelRead;
ac->ev.addWrite = redisAeAddWrite;
ac->ev.delWrite = redisAeDelWrite;
ac->ev.cleanup = redisAeCleanup;
ac->ev.data = e;
return REDIS_OK;
}
#endif

View file

@ -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 = (RedisSource *)data;
g_return_if_fail(source);
source->poll_fd.events |= G_IO_IN;
g_main_context_wakeup(g_source_get_context((GSource *)data));
}
static void
redis_source_del_read (gpointer data)
{
RedisSource *source = (RedisSource *)data;
g_return_if_fail(source);
source->poll_fd.events &= ~G_IO_IN;
g_main_context_wakeup(g_source_get_context((GSource *)data));
}
static void
redis_source_add_write (gpointer data)
{
RedisSource *source = (RedisSource *)data;
g_return_if_fail(source);
source->poll_fd.events |= G_IO_OUT;
g_main_context_wakeup(g_source_get_context((GSource *)data));
}
static void
redis_source_del_write (gpointer data)
{
RedisSource *source = (RedisSource *)data;
g_return_if_fail(source);
source->poll_fd.events &= ~G_IO_OUT;
g_main_context_wakeup(g_source_get_context((GSource *)data));
}
static void
redis_source_cleanup (gpointer data)
{
RedisSource *source = (RedisSource *)data;
g_return_if_fail(source);
redis_source_del_read(source);
redis_source_del_write(source);
/*
* It is not our responsibility to remove ourself from the
* current main loop. However, we will remove the GPollFD.
*/
if (source->poll_fd.fd >= 0) {
g_source_remove_poll((GSource *)data, &source->poll_fd);
source->poll_fd.fd = -1;
}
}
static gboolean
redis_source_prepare (GSource *source,
gint *timeout_)
{
RedisSource *redis = (RedisSource *)source;
*timeout_ = -1;
return !!(redis->poll_fd.events & redis->poll_fd.revents);
}
static gboolean
redis_source_check (GSource *source)
{
RedisSource *redis = (RedisSource *)source;
return !!(redis->poll_fd.events & redis->poll_fd.revents);
}
static gboolean
redis_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
RedisSource *redis = (RedisSource *)source;
if ((redis->poll_fd.revents & G_IO_OUT)) {
redisAsyncHandleWrite(redis->ac);
redis->poll_fd.revents &= ~G_IO_OUT;
}
if ((redis->poll_fd.revents & G_IO_IN)) {
redisAsyncHandleRead(redis->ac);
redis->poll_fd.revents &= ~G_IO_IN;
}
if (callback) {
return callback(user_data);
}
return TRUE;
}
static void
redis_source_finalize (GSource *source)
{
RedisSource *redis = (RedisSource *)source;
if (redis->poll_fd.fd >= 0) {
g_source_remove_poll(source, &redis->poll_fd);
redis->poll_fd.fd = -1;
}
}
static GSource *
redis_source_new (redisAsyncContext *ac)
{
static GSourceFuncs source_funcs = {
.prepare = redis_source_prepare,
.check = redis_source_check,
.dispatch = redis_source_dispatch,
.finalize = redis_source_finalize,
};
redisContext *c = &ac->c;
RedisSource *source;
g_return_val_if_fail(ac != NULL, NULL);
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
source->ac = ac;
source->poll_fd.fd = c->fd;
source->poll_fd.events = 0;
source->poll_fd.revents = 0;
g_source_add_poll((GSource *)source, &source->poll_fd);
ac->ev.addRead = redis_source_add_read;
ac->ev.delRead = redis_source_del_read;
ac->ev.addWrite = redis_source_add_write;
ac->ev.delWrite = redis_source_del_write;
ac->ev.cleanup = redis_source_cleanup;
ac->ev.data = source;
return (GSource *)source;
}
#endif /* __HIREDIS_GLIB_H__ */

View file

@ -0,0 +1,81 @@
#ifndef __HIREDIS_IVYKIS_H__
#define __HIREDIS_IVYKIS_H__
#include <iv.h>
#include "../hiredis.h"
#include "../async.h"
typedef struct redisIvykisEvents {
redisAsyncContext *context;
struct iv_fd fd;
} redisIvykisEvents;
static void redisIvykisReadEvent(void *arg) {
redisAsyncContext *context = (redisAsyncContext *)arg;
redisAsyncHandleRead(context);
}
static void redisIvykisWriteEvent(void *arg) {
redisAsyncContext *context = (redisAsyncContext *)arg;
redisAsyncHandleWrite(context);
}
static void redisIvykisAddRead(void *privdata) {
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent);
}
static void redisIvykisDelRead(void *privdata) {
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
iv_fd_set_handler_in(&e->fd, NULL);
}
static void redisIvykisAddWrite(void *privdata) {
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent);
}
static void redisIvykisDelWrite(void *privdata) {
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
iv_fd_set_handler_out(&e->fd, NULL);
}
static void redisIvykisCleanup(void *privdata) {
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
iv_fd_unregister(&e->fd);
free(e);
}
static int redisIvykisAttach(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisIvykisEvents *e;
/* Nothing should be attached when something is already attached */
if (ac->ev.data != NULL)
return REDIS_ERR;
/* Create container for context and r/w events */
e = (redisIvykisEvents*)hi_malloc(sizeof(*e));
e->context = ac;
/* Register functions to start/stop listening for events */
ac->ev.addRead = redisIvykisAddRead;
ac->ev.delRead = redisIvykisDelRead;
ac->ev.addWrite = redisIvykisAddWrite;
ac->ev.delWrite = redisIvykisDelWrite;
ac->ev.cleanup = redisIvykisCleanup;
ac->ev.data = e;
/* Initialize and install read/write events */
IV_FD_INIT(&e->fd);
e->fd.fd = c->fd;
e->fd.handler_in = redisIvykisReadEvent;
e->fd.handler_out = redisIvykisWriteEvent;
e->fd.handler_err = NULL;
e->fd.cookie = e->context;
iv_fd_register(&e->fd);
return REDIS_OK;
}
#endif

View file

@ -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*)hi_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

View file

@ -0,0 +1,108 @@
/*
* 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 <event2/event.h>
#include "../hiredis.h"
#include "../async.h"
typedef struct redisLibeventEvents {
redisAsyncContext *context;
struct event *rev, *wev;
} redisLibeventEvents;
static void redisLibeventReadEvent(int fd, short event, void *arg) {
((void)fd); ((void)event);
redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleRead(e->context);
}
static void redisLibeventWriteEvent(int fd, short event, void *arg) {
((void)fd); ((void)event);
redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleWrite(e->context);
}
static void redisLibeventAddRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_add(e->rev,NULL);
}
static void redisLibeventDelRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(e->rev);
}
static void redisLibeventAddWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_add(e->wev,NULL);
}
static void redisLibeventDelWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(e->wev);
}
static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_free(e->rev);
event_free(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*)hi_calloc(1, sizeof(*e));
e->context = ac;
/* Register functions to start/stop listening for events */
ac->ev.addRead = redisLibeventAddRead;
ac->ev.delRead = redisLibeventDelRead;
ac->ev.addWrite = redisLibeventAddWrite;
ac->ev.delWrite = redisLibeventDelWrite;
ac->ev.cleanup = redisLibeventCleanup;
ac->ev.data = e;
/* Initialize and install read/write events */
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
event_add(e->rev, NULL);
event_add(e->wev, NULL);
return REDIS_OK;
}
#endif

View file

@ -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 (p->context != NULL && (events & UV_READABLE)) {
redisAsyncHandleRead(p->context);
}
if (p->context != NULL && (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;
p->context = NULL; // indicate that context might no longer exist
uv_close((uv_handle_t*)&p->handle, on_close);
}
static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {
redisContext *c = &(ac->c);
if (ac->ev.data != NULL) {
return REDIS_ERR;
}
ac->ev.addRead = redisLibuvAddRead;
ac->ev.delRead = redisLibuvDelRead;
ac->ev.addWrite = redisLibuvAddWrite;
ac->ev.delWrite = redisLibuvDelWrite;
ac->ev.cleanup = redisLibuvCleanup;
redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p));
if (!p) {
return REDIS_ERR;
}
memset(p, 0, sizeof(*p));
if (uv_poll_init(loop, &p->handle, c->fd) != 0) {
return REDIS_ERR;
}
ac->ev.data = p;
p->handle.data = p;
p->context = ac;
return REDIS_OK;
}
#endif

View file

@ -0,0 +1,114 @@
//
// Created by Дмитрий Бахвалов on 13.07.15.
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
//
#ifndef __HIREDIS_MACOSX_H__
#define __HIREDIS_MACOSX_H__
#include <CoreFoundation/CoreFoundation.h>
#include "../hiredis.h"
#include "../async.h"
typedef struct {
redisAsyncContext *context;
CFSocketRef socketRef;
CFRunLoopSourceRef sourceRef;
} RedisRunLoop;
static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
if( redisRunLoop != NULL ) {
if( redisRunLoop->sourceRef != NULL ) {
CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);
CFRelease(redisRunLoop->sourceRef);
}
if( redisRunLoop->socketRef != NULL ) {
CFSocketInvalidate(redisRunLoop->socketRef);
CFRelease(redisRunLoop->socketRef);
}
free(redisRunLoop);
}
return REDIS_ERR;
}
static void redisMacOSAddRead(void *privdata) {
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
}
static void redisMacOSDelRead(void *privdata) {
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
}
static void redisMacOSAddWrite(void *privdata) {
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
}
static void redisMacOSDelWrite(void *privdata) {
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
}
static void redisMacOSCleanup(void *privdata) {
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
freeRedisRunLoop(redisRunLoop);
}
static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) {
redisAsyncContext* context = (redisAsyncContext*) info;
switch (callbackType) {
case kCFSocketReadCallBack:
redisAsyncHandleRead(context);
break;
case kCFSocketWriteCallBack:
redisAsyncHandleWrite(context);
break;
default:
break;
}
}
static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) {
redisContext *redisCtx = &(redisAsyncCtx->c);
/* Nothing should be attached when something is already attached */
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop));
if( !redisRunLoop ) return REDIS_ERR;
/* Setup redis stuff */
redisRunLoop->context = redisAsyncCtx;
redisAsyncCtx->ev.addRead = redisMacOSAddRead;
redisAsyncCtx->ev.delRead = redisMacOSDelRead;
redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;
redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;
redisAsyncCtx->ev.cleanup = redisMacOSCleanup;
redisAsyncCtx->ev.data = redisRunLoop;
/* Initialize and install read/write events */
CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL };
redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd,
kCFSocketReadCallBack | kCFSocketWriteCallBack,
redisMacOSAsyncCallback,
&socketCtx);
if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop);
redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);
if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop);
CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);
return REDIS_OK;
}
#endif

View file

@ -0,0 +1,135 @@
/*-
* Copyright (C) 2014 Pietro Cerutti <gahr@gahr.ch>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef __HIREDIS_QT_H__
#define __HIREDIS_QT_H__
#include <QSocketNotifier>
#include "../async.h"
static void RedisQtAddRead(void *);
static void RedisQtDelRead(void *);
static void RedisQtAddWrite(void *);
static void RedisQtDelWrite(void *);
static void RedisQtCleanup(void *);
class RedisQtAdapter : public QObject {
Q_OBJECT
friend
void RedisQtAddRead(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->addRead();
}
friend
void RedisQtDelRead(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->delRead();
}
friend
void RedisQtAddWrite(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->addWrite();
}
friend
void RedisQtDelWrite(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->delWrite();
}
friend
void RedisQtCleanup(void * adapter) {
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
a->cleanup();
}
public:
RedisQtAdapter(QObject * parent = 0)
: QObject(parent), m_ctx(0), m_read(0), m_write(0) { }
~RedisQtAdapter() {
if (m_ctx != 0) {
m_ctx->ev.data = NULL;
}
}
int setContext(redisAsyncContext * ac) {
if (ac->ev.data != NULL) {
return REDIS_ERR;
}
m_ctx = ac;
m_ctx->ev.data = this;
m_ctx->ev.addRead = RedisQtAddRead;
m_ctx->ev.delRead = RedisQtDelRead;
m_ctx->ev.addWrite = RedisQtAddWrite;
m_ctx->ev.delWrite = RedisQtDelWrite;
m_ctx->ev.cleanup = RedisQtCleanup;
return REDIS_OK;
}
private:
void addRead() {
if (m_read) return;
m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);
connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));
}
void delRead() {
if (!m_read) return;
delete m_read;
m_read = 0;
}
void addWrite() {
if (m_write) return;
m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);
connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));
}
void delWrite() {
if (!m_write) return;
delete m_write;
m_write = 0;
}
void cleanup() {
delRead();
delWrite();
}
private slots:
void read() { redisAsyncHandleRead(m_ctx); }
void write() { redisAsyncHandleWrite(m_ctx); }
private:
redisAsyncContext * m_ctx;
QSocketNotifier * m_read;
QSocketNotifier * m_write;
};
#endif /* !__HIREDIS_QT_H__ */

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder 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 "alloc.h"
#include <string.h>
void *hi_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL)
HIREDIS_OOM_HANDLER;
return ptr;
}
void *hi_calloc(size_t nmemb, size_t size) {
void *ptr = calloc(nmemb, size);
if (ptr == NULL)
HIREDIS_OOM_HANDLER;
return ptr;
}
void *hi_realloc(void *ptr, size_t size) {
void *newptr = realloc(ptr, size);
if (newptr == NULL)
HIREDIS_OOM_HANDLER;
return newptr;
}
char *hi_strdup(const char *str) {
char *newstr = strdup(str);
if (newstr == NULL)
HIREDIS_OOM_HANDLER;
return newstr;
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder 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_ALLOC_H
#define HIREDIS_ALLOC_H
#include <stdlib.h> /* for size_t */
#ifndef HIREDIS_OOM_HANDLER
#define HIREDIS_OOM_HANDLER abort()
#endif
#ifdef __cplusplus
extern "C" {
#endif
void *hi_malloc(size_t size);
void *hi_calloc(size_t nmemb, size_t size);
void *hi_realloc(void *ptr, size_t size);
char *hi_strdup(const char *str);
#ifdef __cplusplus
}
#endif
#endif /* HIREDIS_ALLOC_H */

View file

@ -0,0 +1,23 @@
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
environment:
matrix:
- CYG_BASH: C:\cygwin64\bin\bash
CC: gcc
- CYG_BASH: C:\cygwin\bin\bash
CC: gcc
TARGET: 32bit
TARGET_VARS: 32bit-vars
clone_depth: 1
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
init:
- git config --global core.autocrlf input
# Install needed build dependencies
install:
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
build_script:
- 'echo building...'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'

View file

@ -0,0 +1,717 @@
/*
* 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 "alloc.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 = hi_malloc(sizeof(*dup));
memcpy(dup,src,sizeof(*dup));
return dup;
}
static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
int l1, l2;
((void) privdata);
l1 = sdslen((const sds)key1);
l2 = sdslen((const sds)key2);
if (l1 != l2) return 0;
return memcmp(key1,key2,l1) == 0;
}
static void callbackKeyDestructor(void *privdata, void *key) {
((void) privdata);
sdsfree((sds)key);
}
static void callbackValDestructor(void *privdata, void *val) {
((void) privdata);
free(val);
}
static dictType callbackDict = {
callbackHash,
NULL,
callbackValDup,
callbackKeyCompare,
callbackKeyDestructor,
callbackValDestructor
};
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
redisAsyncContext *ac;
ac = realloc(c,sizeof(redisAsyncContext));
if (ac == NULL)
return NULL;
c = &(ac->c);
/* The regular connect functions will always set the flag REDIS_CONNECTED.
* For the async API, we want to wait until the first write event is
* received up before setting this flag, so reset it here. */
c->flags &= ~REDIS_CONNECTED;
ac->err = 0;
ac->errstr = NULL;
ac->data = NULL;
ac->ev.data = NULL;
ac->ev.addRead = NULL;
ac->ev.delRead = NULL;
ac->ev.addWrite = NULL;
ac->ev.delWrite = NULL;
ac->ev.cleanup = NULL;
ac->onConnect = NULL;
ac->onDisconnect = NULL;
ac->replies.head = NULL;
ac->replies.tail = NULL;
ac->sub.invalid.head = NULL;
ac->sub.invalid.tail = NULL;
ac->sub.channels = dictCreate(&callbackDict,NULL);
ac->sub.patterns = dictCreate(&callbackDict,NULL);
return ac;
}
/* We want the error field to be accessible directly instead of requiring
* an indirection to the redisContext struct. */
static void __redisAsyncCopyError(redisAsyncContext *ac) {
if (!ac)
return;
redisContext *c = &(ac->c);
ac->err = c->err;
ac->errstr = c->errstr;
}
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
redisContext *c;
redisAsyncContext *ac;
c = redisConnectNonBlock(ip,port);
if (c == NULL)
return NULL;
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
__redisAsyncCopyError(ac);
return ac;
}
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) {
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
redisAsyncContext *ac = redisAsyncInitialize(c);
__redisAsyncCopyError(ac);
return ac;
}
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr) {
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
redisAsyncContext *ac = redisAsyncInitialize(c);
__redisAsyncCopyError(ac);
return ac;
}
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
redisContext *c;
redisAsyncContext *ac;
c = redisConnectUnixNonBlock(path);
if (c == NULL)
return NULL;
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
__redisAsyncCopyError(ac);
return ac;
}
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
if (ac->onConnect == NULL) {
ac->onConnect = fn;
/* The common way to detect an established connection is to wait for
* the first write event to be fired. This assumes the related event
* library functions are already set. */
_EL_ADD_WRITE(ac);
return REDIS_OK;
}
return REDIS_ERR;
}
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
if (ac->onDisconnect == NULL) {
ac->onDisconnect = fn;
return REDIS_OK;
}
return REDIS_ERR;
}
/* Helper functions to push/shift callbacks */
static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
redisCallback *cb;
/* Copy callback from stack to heap */
cb = malloc(sizeof(*cb));
if (cb == NULL)
return REDIS_ERR_OOM;
if (source != NULL) {
memcpy(cb,source,sizeof(*cb));
cb->next = NULL;
}
/* Store callback in list */
if (list->head == NULL)
list->head = cb;
if (list->tail != NULL)
list->tail->next = cb;
list->tail = cb;
return REDIS_OK;
}
static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
redisCallback *cb = list->head;
if (cb != NULL) {
list->head = cb->next;
if (cb == list->tail)
list->tail = NULL;
/* Copy callback from heap to stack */
if (target != NULL)
memcpy(target,cb,sizeof(*cb));
free(cb);
return REDIS_OK;
}
return REDIS_ERR;
}
static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
redisContext *c = &(ac->c);
if (cb->fn != NULL) {
c->flags |= REDIS_IN_CALLBACK;
cb->fn(ac,reply,cb->privdata);
c->flags &= ~REDIS_IN_CALLBACK;
}
}
/* Helper function to free the context. */
static void __redisAsyncFree(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisCallback cb;
dictIterator *it;
dictEntry *de;
/* Execute pending callbacks with NULL reply. */
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
__redisRunCallback(ac,&cb,NULL);
/* Execute callbacks for invalid commands */
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
__redisRunCallback(ac,&cb,NULL);
/* Run subscription callbacks callbacks with NULL reply */
it = dictGetIterator(ac->sub.channels);
while ((de = dictNext(it)) != NULL)
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
dictReleaseIterator(it);
dictRelease(ac->sub.channels);
it = dictGetIterator(ac->sub.patterns);
while ((de = dictNext(it)) != NULL)
__redisRunCallback(ac,dictGetEntryVal(de),NULL);
dictReleaseIterator(it);
dictRelease(ac->sub.patterns);
/* Signal event lib to clean up */
_EL_CLEANUP(ac);
/* Execute disconnect callback. When redisAsyncFree() initiated destroying
* this context, the status will always be REDIS_OK. */
if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
if (c->flags & REDIS_FREEING) {
ac->onDisconnect(ac,REDIS_OK);
} else {
ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
}
}
/* Cleanup self */
redisFree(c);
}
/* Free the async context. When this function is called from a callback,
* control needs to be returned to redisProcessCallbacks() before actual
* free'ing. To do so, a flag is set on the context which is picked up by
* redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
void redisAsyncFree(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
c->flags |= REDIS_FREEING;
if (!(c->flags & REDIS_IN_CALLBACK))
__redisAsyncFree(ac);
}
/* Helper function to make the disconnect happen and clean up. */
static void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */
__redisAsyncCopyError(ac);
if (ac->err == 0) {
/* For clean disconnects, there should be no pending callbacks. */
int ret = __redisShiftCallback(&ac->replies,NULL);
assert(ret == REDIS_ERR);
} else {
/* Disconnection is caused by an error, make sure that pending
* callbacks cannot call new commands. */
c->flags |= REDIS_DISCONNECTING;
}
/* 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;
redisCallback *cb;
dictEntry *de;
int pvariant;
char *stype;
sds sname;
/* Custom reply functions are not supported for pub/sub. This will fail
* very hard when they are used... */
if (reply->type == REDIS_REPLY_ARRAY) {
assert(reply->elements >= 2);
assert(reply->element[0]->type == REDIS_REPLY_STRING);
stype = reply->element[0]->str;
pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
if (pvariant)
callbacks = ac->sub.patterns;
else
callbacks = ac->sub.channels;
/* Locate the right callback */
assert(reply->element[1]->type == REDIS_REPLY_STRING);
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
de = dictFind(callbacks,sname);
if (de != NULL) {
cb = dictGetEntryVal(de);
/* If this is an subscribe reply decrease pending counter. */
if (strcasecmp(stype+pvariant,"subscribe") == 0) {
cb->pending_subs -= 1;
}
memcpy(dstcb,cb,sizeof(*dstcb));
/* If this is an unsubscribe message, remove it. */
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
if (cb->pending_subs == 0)
dictDelete(callbacks,sname);
/* If this was the last unsubscribe message, revert to
* non-subscribe mode. */
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
/* Unset subscribed flag only when no pipelined pending subscribe. */
if (reply->element[2]->integer == 0
&& dictSize(ac->sub.channels) == 0
&& dictSize(ac->sub.patterns) == 0)
c->flags &= ~REDIS_SUBSCRIBED;
}
}
sdsfree(sname);
} else {
/* Shift callback for invalid commands. */
__redisShiftCallback(&ac->sub.invalid,dstcb);
}
return REDIS_OK;
}
void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisCallback cb = {NULL, NULL, 0, NULL};
void *reply = NULL;
int status;
while((status = redisGetReply(c,&reply)) == REDIS_OK) {
if (reply == NULL) {
/* When the connection is being disconnected and there are
* no more replies, this is the cue to really disconnect. */
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
&& ac->replies.head == NULL) {
__redisAsyncDisconnect(ac);
return;
}
/* If monitor mode, repush callback */
if(c->flags & REDIS_MONITORING) {
__redisPushCallback(&ac->replies,&cb);
}
/* When the connection is not being disconnected, simply stop
* trying to get replies and wait for the next loop tick. */
break;
}
/* Even if the context is subscribed, pending regular callbacks will
* get a reply before pub/sub messages arrive. */
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
/*
* A spontaneous reply in a not-subscribed context can be the error
* reply that is sent when a new connection exceeds the maximum
* number of allowed connections on the server side.
*
* This is seen as an error instead of a regular reply because the
* server closes the connection after sending it.
*
* To prevent the error from being overwritten by an EOF error the
* connection is closed here. See issue #43.
*
* Another possibility is that the server is loading its dataset.
* In this case we also want to close the connection, and have the
* user wait until the server is ready to take our request.
*/
if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
c->err = REDIS_ERR_OTHER;
snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
c->reader->fn->freeObject(reply);
__redisAsyncDisconnect(ac);
return;
}
/* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
if(c->flags & REDIS_SUBSCRIBED)
__redisGetSubscribeCallback(ac,reply,&cb);
}
if (cb.fn != NULL) {
__redisRunCallback(ac,&cb,reply);
c->reader->fn->freeObject(reply);
/* Proceed with free'ing when redisAsyncFree() was called. */
if (c->flags & REDIS_FREEING) {
__redisAsyncFree(ac);
return;
}
} else {
/* No callback for this reply. This can either be a NULL callback,
* or there were no callbacks to begin with. Either way, don't
* abort with an error, but simply ignore it because the client
* doesn't know what the server will spit out over the wire. */
c->reader->fn->freeObject(reply);
}
}
/* Disconnect when there was an error reading the reply */
if (status != REDIS_OK)
__redisAsyncDisconnect(ac);
}
/* Internal helper function to detect socket status the first time a read or
* write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
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;
struct dict *cbdict;
dictEntry *de;
redisCallback *existcb;
int pvariant, hasnext;
const char *cstr, *astr;
size_t clen, alen;
const char *p;
sds sname;
int ret;
/* Don't accept new commands when the connection is about to be closed. */
if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
/* Setup callback */
cb.fn = fn;
cb.privdata = privdata;
cb.pending_subs = 1;
/* Find out which command will be appended. */
p = nextArgument(cmd,&cstr,&clen);
assert(p != NULL);
hasnext = (p[0] == '$');
pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
cstr += pvariant;
clen -= pvariant;
if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
c->flags |= REDIS_SUBSCRIBED;
/* Add every channel/pattern to the list of subscription callbacks. */
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen);
if (pvariant)
cbdict = ac->sub.patterns;
else
cbdict = ac->sub.channels;
de = dictFind(cbdict,sname);
if (de != NULL) {
existcb = dictGetEntryVal(de);
cb.pending_subs = existcb->pending_subs + 1;
}
ret = dictReplace(cbdict,sname,&cb);
if (ret == 0) sdsfree(sname);
}
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
/* It is only useful to call (P)UNSUBSCRIBE when the context is
* subscribed to one or more channels or patterns. */
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
/* (P)UNSUBSCRIBE does not have its own response: every channel or
* pattern that is unsubscribed will receive a message. This means we
* should not append a callback function for this command. */
} else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
/* Set monitor flag and push callback */
c->flags |= REDIS_MONITORING;
__redisPushCallback(&ac->replies,&cb);
} else {
if (c->flags & REDIS_SUBSCRIBED)
/* This will likely result in an error reply, but it needs to be
* received and passed to the callback. */
__redisPushCallback(&ac->sub.invalid,&cb);
else
__redisPushCallback(&ac->replies,&cb);
}
__redisAppendCommand(c,cmd,len);
/* Always schedule a write when the write buffer is non-empty */
_EL_ADD_WRITE(ac);
return REDIS_OK;
}
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
char *cmd;
int len;
int status;
len = redisvFormatCommand(&cmd,format,ap);
/* We don't want to pass -1 or -2 to future functions as a length. */
if (len < 0)
return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
free(cmd);
return status;
}
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
va_list ap;
int status;
va_start(ap,format);
status = redisvAsyncCommand(ac,fn,privdata,format,ap);
va_end(ap);
return status;
}
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
sds cmd;
int len;
int status;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len < 0)
return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
sdsfree(cmd);
return status;
}
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status;
}

View file

@ -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;
int pending_subs;
void *privdata;
} redisCallback;
/* List of callbacks for either regular replies or pub/sub */
typedef struct redisCallbackList {
redisCallback *head, *tail;
} redisCallbackList;
/* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
/* Context for an async connection to Redis */
typedef struct redisAsyncContext {
/* Hold the regular context, so it can be realloc'ed. */
redisContext c;
/* Setup error flags so they can be used directly. */
int err;
char *errstr;
/* Not used by hiredis */
void *data;
/* Event library data and hooks */
struct {
void *data;
/* Hooks that are called when the library expects to start
* reading/writing. These functions should be idempotent. */
void (*addRead)(void *privdata);
void (*delRead)(void *privdata);
void (*addWrite)(void *privdata);
void (*delWrite)(void *privdata);
void (*cleanup)(void *privdata);
} ev;
/* Called when either the connection is terminated due to an error or per
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
redisDisconnectCallback *onDisconnect;
/* Called when the first write event was received. */
redisConnectCallback *onConnect;
/* Regular command callbacks */
redisCallbackList replies;
/* Subscription callbacks */
struct {
redisCallbackList invalid;
struct dict *channels;
struct dict *patterns;
} sub;
} redisAsyncContext;
/* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr);
redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac);
/* Handle read/write events */
void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,339 @@
/* 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 "alloc.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 = hi_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 = hi_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 succeed. */
if (dictAdd(ht, key, val) == DICT_OK)
return 1;
/* It already exists, get the entry */
entry = dictFind(ht, key);
/* Free the old value and set the new one */
/* Set the new value and free the old one. Note that it is important
* to do that in this order, as the value may just be exactly the same
* as the previous one. In this context, think to reference counting,
* you want to increment (set), and then decrement (free), and not the
* reverse. */
auxentry = *entry;
dictSetHashVal(ht, entry, val);
dictFreeEntryVal(ht, &auxentry);
return 0;
}
/* Search and remove an element */
static int dictDelete(dict *ht, const void *key) {
unsigned int h;
dictEntry *de, *prevde;
if (ht->size == 0)
return DICT_ERR;
h = dictHashKey(ht, key) & ht->sizemask;
de = ht->table[h];
prevde = NULL;
while(de) {
if (dictCompareHashKeys(ht,key,de->key)) {
/* Unlink the element from the list */
if (prevde)
prevde->next = de->next;
else
ht->table[h] = de->next;
dictFreeEntryKey(ht,de);
dictFreeEntryVal(ht,de);
free(de);
ht->used--;
return DICT_OK;
}
prevde = de;
de = de->next;
}
return DICT_ERR; /* not found */
}
/* Destroy an entire hash table */
static int _dictClear(dict *ht) {
unsigned long i;
/* 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 = hi_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 initial 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;
}

View file

@ -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 */

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1,58 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/ivykis.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);
iv_init();
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisIvykisAttach(c);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
iv_main();
iv_deinit();
return 0;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -0,0 +1,66 @@
//
// Created by Дмитрий Бахвалов on 13.07.15.
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
//
#include <stdio.h>
#include <hiredis.h>
#include <async.h>
#include <adapters/macosx.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;
}
CFRunLoopStop(CFRunLoopGetCurrent());
printf("Disconnected...\n");
}
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
CFRunLoopRef loop = CFRunLoopGetCurrent();
if( !loop ) {
printf("Error: Cannot get current run loop\n");
return 1;
}
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
redisMacOSAttach(c, loop);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
CFRunLoopRun();
return 0;
}

View file

@ -0,0 +1,46 @@
#include <iostream>
using namespace std;
#include <QCoreApplication>
#include <QTimer>
#include "example-qt.h"
void getCallback(redisAsyncContext *, void * r, void * privdata) {
redisReply * reply = static_cast<redisReply *>(r);
ExampleQt * ex = static_cast<ExampleQt *>(privdata);
if (reply == nullptr || ex == nullptr) return;
cout << "key: " << reply->str << endl;
ex->finish();
}
void ExampleQt::run() {
m_ctx = redisAsyncConnect("localhost", 6379);
if (m_ctx->err) {
cerr << "Error: " << m_ctx->errstr << endl;
redisAsyncFree(m_ctx);
emit finished();
}
m_adapter.setContext(m_ctx);
redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value);
redisAsyncCommand(m_ctx, getCallback, this, "GET key");
}
int main (int argc, char **argv) {
QCoreApplication app(argc, argv);
ExampleQt example(argv[argc-1]);
QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit()));
QTimer::singleShot(0, &example, SLOT(run()));
return app.exec();
}

View file

@ -0,0 +1,32 @@
#ifndef __HIREDIS_EXAMPLE_QT_H
#define __HIREDIS_EXAMPLE_QT_H
#include <adapters/qt.h>
class ExampleQt : public QObject {
Q_OBJECT
public:
ExampleQt(const char * value, QObject * parent = 0)
: QObject(parent), m_value(value) {}
signals:
void finished();
public slots:
void run();
private:
void finish() { emit finished(); }
private:
const char * m_value;
redisAsyncContext * m_ctx;
RedisQtAdapter m_adapter;
friend
void getCallback(redisAsyncContext *, void *, void *);
};
#endif /* !__HIREDIS_EXAMPLE_QT_H */

View file

@ -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,"%u",j);
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
freeReplyObject(reply);
}
/* Let's check what we have inside the list */
reply = redisCommand(c,"LRANGE mylist 0 -1");
if (reply->type == REDIS_REPLY_ARRAY) {
for (j = 0; j < reply->elements; j++) {
printf("%u) %s\n", j, reply->element[j]->str);
}
}
freeReplyObject(reply);
/* Disconnects and frees the context */
redisFree(c);
return 0;
}

View file

@ -0,0 +1,12 @@
#ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H
#define _XOPEN_SOURCE 600
#define _POSIX_C_SOURCE 200112L
#if defined(__APPLE__) && defined(__MACH__)
/* Enable TCP_KEEPALIVE */
#define _DARWIN_C_SOURCE
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,200 @@
/*
* 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 */
#include "alloc.h" /* for allocation wrappers */
#define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 14
#define HIREDIS_PATCH 1
#define HIREDIS_SONAME 0.14
/* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */
#define REDIS_BLOCK 0x1
/* Connection may be disconnected before being free'd. The second bit
* in the flags field is set when the context is connected. */
#define REDIS_CONNECTED 0x2
/* The async API might try to disconnect cleanly and flush the output
* buffer and read all subsequent replies before disconnecting.
* This flag means no new commands can come in and the connection
* should be terminated once all replies have been read. */
#define REDIS_DISCONNECTING 0x4
/* Flag specific to the async API which means that the context should be clean
* up as soon as possible. */
#define REDIS_FREEING 0x8
/* Flag that is set when an async callback is executed. */
#define REDIS_IN_CALLBACK 0x10
/* Flag that is set when the async context has one or more subscriptions. */
#define REDIS_SUBSCRIBED 0x20
/* Flag that is set when monitor mode is active */
#define REDIS_MONITORING 0x40
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
* SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10
#ifdef __cplusplus
extern "C" {
#endif
/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
size_t 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 successful connect or REDIS_ERR otherwise.
*/
int redisReconnect(redisContext *c);
int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c);
int redisFreeKeepFd(redisContext *c);
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

View file

@ -0,0 +1,477 @@
/* 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) {
int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 };
size_t len = 0;
if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
__redisSetError(c,type,buf);
}
static int redisSetReuseAddr(redisContext *c) {
int on = 1;
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
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;
#if defined(__APPLE__) && defined(__MACH__)
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#else
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
val = interval/3;
if (val == 0) val = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
val = 3;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#endif
#endif
return REDIS_OK;
}
static int redisSetTcpNoDelay(redisContext *c) {
int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
redisContextCloseFd(c);
return REDIS_ERR;
}
return REDIS_OK;
}
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
static int redisContextTimeoutMsec(redisContext *c, long *result)
{
const struct timeval *timeout = c->timeout;
long msec = -1;
/* Only use timeout when not NULL. */
if (timeout != NULL) {
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
*result = msec;
return REDIS_ERR;
}
msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
if (msec < 0 || msec > INT_MAX) {
msec = INT_MAX;
}
}
*result = msec;
return REDIS_OK;
}
static int redisContextWaitReady(redisContext *c, long msec) {
struct pollfd wfd[1];
wfd[0].fd = c->fd;
wfd[0].events = POLLOUT;
if (errno == EINPROGRESS) {
int res;
if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
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;
long timeout_msec = -1;
servinfo = NULL;
c->connection_type = REDIS_CONN_TCP;
c->tcp.port = port;
/* We need to take possession of the passed parameters
* to make them reusable for a reconnect.
* We also carefully check we don't free data we already own,
* as in the case of the reconnect method.
*
* This is a bit ugly, but atleast it works and doesn't leak memory.
**/
if (c->tcp.host != addr) {
free(c->tcp.host);
c->tcp.host = hi_strdup(addr);
}
if (timeout) {
if (c->timeout != timeout) {
if (c->timeout == NULL)
c->timeout = hi_malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
free(c->timeout);
c->timeout = NULL;
}
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
goto error;
}
if (source_addr == NULL) {
free(c->tcp.source_addr);
c->tcp.source_addr = NULL;
} else if (c->tcp.source_addr != source_addr) {
free(c->tcp.source_addr);
c->tcp.source_addr = hi_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) {
freeaddrinfo(bservinfo);
goto error;
}
}
for (b = bservinfo; b != NULL; b = b->ai_next) {
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
bound = 1;
break;
}
}
freeaddrinfo(bservinfo);
if (!bound) {
char buf[128];
snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
__redisSetError(c,REDIS_ERR_OTHER,buf);
goto error;
}
}
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 {
redisContextCloseFd(c);
goto addrretry;
}
} else {
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error;
}
}
if (blocking && redisSetBlocking(c,1) != REDIS_OK)
goto error;
if (redisSetTcpNoDelay(c) != REDIS_OK)
goto error;
c->flags |= REDIS_CONNECTED;
rv = REDIS_OK;
goto end;
}
if (p == NULL) {
char buf[128];
snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
__redisSetError(c,REDIS_ERR_OTHER,buf);
goto error;
}
error:
rv = REDIS_ERR;
end:
if(servinfo) {
freeaddrinfo(servinfo);
}
return rv; // Need to return REDIS_OK if alright
}
int redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout) {
return _redisContextConnectTcp(c, addr, port, timeout, NULL);
}
int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout,
const char *source_addr) {
return _redisContextConnectTcp(c, addr, port, timeout, source_addr);
}
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
int blocking = (c->flags & REDIS_BLOCK);
struct sockaddr_un sa;
long timeout_msec = -1;
if (redisCreateSocket(c,AF_UNIX) < 0)
return REDIS_ERR;
if (redisSetBlocking(c,0) != REDIS_OK)
return REDIS_ERR;
c->connection_type = REDIS_CONN_UNIX;
if (c->unix_sock.path != path)
c->unix_sock.path = hi_strdup(path);
if (timeout) {
if (c->timeout != timeout) {
if (c->timeout == NULL)
c->timeout = hi_malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
free(c->timeout);
c->timeout = NULL;
}
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR;
sa.sun_family = AF_UNIX;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
return REDIS_ERR;
}
}
/* Reset socket to be blocking after connect(2). */
if (blocking && redisSetBlocking(c,1) != REDIS_OK)
return REDIS_ERR;
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
}

View file

@ -0,0 +1,49 @@
/* 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"
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

View file

@ -0,0 +1,598 @@
/*
* 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 <limits.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. */
sdsfree(r->buf);
r->buf = NULL;
r->pos = r->len = 0;
/* Reset task stack. */
r->ridx = -1;
/* Set error. */
r->err = type;
len = strlen(str);
len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
memcpy(r->errstr,str,len);
r->errstr[len] = '\0';
}
static size_t chrtos(char *buf, size_t size, char byte) {
size_t len = 0;
switch(byte) {
case '\\':
case '"':
len = snprintf(buf,size,"\"\\%c\"",byte);
break;
case '\n': len = snprintf(buf,size,"\"\\n\""); break;
case '\r': len = snprintf(buf,size,"\"\\r\""); break;
case '\t': len = snprintf(buf,size,"\"\\t\""); break;
case '\a': len = snprintf(buf,size,"\"\\a\""); break;
case '\b': len = snprintf(buf,size,"\"\\b\""); break;
default:
if (isprint(byte))
len = snprintf(buf,size,"\"%c\"",byte);
else
len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
break;
}
return len;
}
static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
char cbuf[8], sbuf[128];
chrtos(cbuf,sizeof(cbuf),byte);
snprintf(sbuf,sizeof(sbuf),
"Protocol error, got %s as reply type byte", cbuf);
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
}
static void __redisReaderSetErrorOOM(redisReader *r) {
__redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
}
static char *readBytes(redisReader *r, unsigned int bytes) {
char *p;
if (r->len-r->pos >= bytes) {
p = r->buf+r->pos;
r->pos += bytes;
return p;
}
return NULL;
}
/* Find pointer to \r\n. */
static char *seekNewline(char *s, size_t len) {
int pos = 0;
int _len = len-1;
/* Position should be < len-1 because the character at "pos" should be
* followed by a \n. Note that strchr cannot be used because it doesn't
* allow to search a limited length and the buffer that is being searched
* might not have a trailing NULL character. */
while (pos < _len) {
while(pos < _len && s[pos] != '\r') pos++;
if (pos==_len) {
/* Not found. */
return NULL;
} else {
if (s[pos+1] == '\n') {
/* Found. */
return s+pos;
} else {
/* Continue searching. */
pos++;
}
}
}
return NULL;
}
/* Convert a string into a long long. Returns REDIS_OK if the string could be
* parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
* will be set to the parsed value when appropriate.
*
* Note that this function demands that the string strictly represents
* a long long: no spaces or other characters before or after the string
* representing the number are accepted, nor zeroes at the start if not
* for the string "0" representing the zero number.
*
* Because of its strictness, it is safe to use this function to check if
* you can convert a string into a long long, and obtain back the string
* from the number without any loss in the string representation. */
static int string2ll(const char *s, size_t slen, long long *value) {
const char *p = s;
size_t plen = 0;
int negative = 0;
unsigned long long v;
if (plen == slen)
return REDIS_ERR;
/* Special case: first and only digit is 0. */
if (slen == 1 && p[0] == '0') {
if (value != NULL) *value = 0;
return REDIS_OK;
}
if (p[0] == '-') {
negative = 1;
p++; plen++;
/* Abort on only a negative sign. */
if (plen == slen)
return REDIS_ERR;
}
/* First digit should be 1-9, otherwise the string should just be 0. */
if (p[0] >= '1' && p[0] <= '9') {
v = p[0]-'0';
p++; plen++;
} else if (p[0] == '0' && slen == 1) {
*value = 0;
return REDIS_OK;
} else {
return REDIS_ERR;
}
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
if (v > (ULLONG_MAX / 10)) /* Overflow. */
return REDIS_ERR;
v *= 10;
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
return REDIS_ERR;
v += p[0]-'0';
p++; plen++;
}
/* Return if not all bytes were used. */
if (plen < slen)
return REDIS_ERR;
if (negative) {
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
return REDIS_ERR;
if (value != NULL) *value = -v;
} else {
if (v > LLONG_MAX) /* Overflow. */
return REDIS_ERR;
if (value != NULL) *value = v;
}
return REDIS_OK;
}
static char *readLine(redisReader *r, int *_len) {
char *p, *s;
int len;
p = r->buf+r->pos;
s = seekNewline(p,(r->len-r->pos));
if (s != NULL) {
len = s-(r->buf+r->pos);
r->pos += len+2; /* skip \r\n */
if (_len) *_len = len;
return p;
}
return NULL;
}
static void moveToNextTask(redisReader *r) {
redisReadTask *cur, *prv;
while (r->ridx >= 0) {
/* Return a.s.a.p. when the stack is now empty. */
if (r->ridx == 0) {
r->ridx--;
return;
}
cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]);
assert(prv->type == REDIS_REPLY_ARRAY);
if (cur->idx == prv->elements-1) {
r->ridx--;
} else {
/* Reset the type because the next item can be anything */
assert(cur->idx < prv->elements);
cur->type = -1;
cur->elements = -1;
cur->idx++;
return;
}
}
}
static int processLineItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
int len;
if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) {
if (r->fn && r->fn->createInteger) {
long long v;
if (string2ll(p, len, &v) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad integer value");
return REDIS_ERR;
}
obj = r->fn->createInteger(cur,v);
} else {
obj = (void*)REDIS_REPLY_INTEGER;
}
} else {
/* Type will be error or status. */
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,p,len);
else
obj = (void*)(size_t)(cur->type);
}
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return REDIS_OK;
}
return REDIS_ERR;
}
static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj = NULL;
char *p, *s;
long long len;
unsigned long bytelen;
int success = 0;
p = r->buf+r->pos;
s = seekNewline(p,r->len-r->pos);
if (s != NULL) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bulk string length");
return REDIS_ERR;
}
if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bulk string length out of range");
return REDIS_ERR;
}
if (len == -1) {
/* The nil object can always be created. */
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
success = 1;
} else {
/* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= r->len) {
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,s+2,len);
else
obj = (void*)REDIS_REPLY_STRING;
success = 1;
}
}
/* Proceed when obj was created. */
if (success) {
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->pos += bytelen;
/* Set reply if this is the root object. */
if (r->ridx == 0) r->reply = obj;
moveToNextTask(r);
return REDIS_OK;
}
}
return REDIS_ERR;
}
static int processMultiBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
long long elements;
int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"No support for nested multi bulk replies with depth > 7");
return REDIS_ERR;
}
if ((p = readLine(r,&len)) != NULL) {
if (string2ll(p, len, &elements) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad multi-bulk length");
return REDIS_ERR;
}
root = (r->ridx == 0);
if (elements < -1 || elements > INT_MAX) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range");
return REDIS_ERR;
}
if (elements == -1) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
moveToNextTask(r);
} else {
if (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(1,sizeof(redisReader));
if (r == NULL)
return NULL;
r->fn = fn;
r->buf = sdsempty();
r->maxbuf = REDIS_READER_MAX_BUF;
if (r->buf == NULL) {
free(r);
return NULL;
}
r->ridx = -1;
return r;
}
void redisReaderFree(redisReader *r) {
if (r == NULL)
return;
if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply);
sdsfree(r->buf);
free(r);
}
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
sds newbuf;
/* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR;
/* Copy the provided buffer. */
if (buf != NULL && len >= 1) {
/* Destroy internal buffer when it is empty and is quite large. */
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
sdsfree(r->buf);
r->buf = sdsempty();
r->pos = 0;
/* r->buf should not be NULL since we just free'd a larger one. */
assert(r->buf != NULL);
}
newbuf = sdscatlen(r->buf,buf,len);
if (newbuf == NULL) {
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->buf = newbuf;
r->len = sdslen(r->buf);
}
return REDIS_OK;
}
int redisReaderGetReply(redisReader *r, void **reply) {
/* Default target pointer to NULL. */
if (reply != NULL)
*reply = NULL;
/* Return early when this reader is in an erroneous state. */
if (r->err)
return REDIS_ERR;
/* When the buffer is empty, there will never be a reply. */
if (r->len == 0)
return REDIS_OK;
/* Set first item to process when the stack is empty. */
if (r->ridx == -1) {
r->rstack[0].type = -1;
r->rstack[0].elements = -1;
r->rstack[0].idx = -1;
r->rstack[0].obj = NULL;
r->rstack[0].parent = NULL;
r->rstack[0].privdata = r->privdata;
r->ridx = 0;
}
/* Process items in reply. */
while (r->ridx >= 0)
if (processItem(r) != REDIS_OK)
break;
/* Return ASAP when an error occurred. */
if (r->err)
return REDIS_ERR;
/* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) {
sdsrange(r->buf,r->pos,-1);
r->pos = 0;
r->len = sdslen(r->buf);
}
/* Emit a reply when there is one. */
if (r->ridx == -1) {
if (reply != NULL)
*reply = r->reply;
r->reply = NULL;
}
return REDIS_OK;
}

View file

@ -0,0 +1,111 @@
/*
* 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 occurred. REDIS_ERR_IO means there was an I/O error and you
* should use the "errno" variable to find out what is wrong.
* For other values, the "errstr" field will hold a description. */
#define REDIS_ERR_IO 1 /* Error in read or write */
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_OTHER 2 /* Everything else... */
#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
#ifdef __cplusplus
extern "C" {
#endif
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
void *privdata; /* user-settable arbitrary field */
} redisReadTask;
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createNil)(const redisReadTask*);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
typedef struct redisReader {
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
char *buf; /* Read buffer */
size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */
redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */
redisReplyObjectFunctions *fn;
void *privdata;
} redisReader;
/* Public API for the protocol parser. */
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
void redisReaderFree(redisReader *r);
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
int redisReaderGetReply(redisReader *r, void **reply);
#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
#ifdef __cplusplus
}
#endif
#endif

1272
controller/thirdparty/hiredis-0.14.1/sds.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,273 @@
/* SDSLib 2.0 -- A C dynamic strings library
*
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2015, Oran Agra
* Copyright (c) 2015, Redis Labs, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __SDS_H
#define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024)
#include <sys/types.h>
#include <stdarg.h>
#include <stdint.h>
typedef char *sds;
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
#define SDS_TYPE_5 0
#define SDS_TYPE_8 1
#define SDS_TYPE_16 2
#define SDS_TYPE_32 3
#define SDS_TYPE_64 4
#define SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
static inline size_t sdslen(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->len;
case SDS_TYPE_16:
return SDS_HDR(16,s)->len;
case SDS_TYPE_32:
return SDS_HDR(32,s)->len;
case SDS_TYPE_64:
return SDS_HDR(64,s)->len;
}
return 0;
}
static inline size_t sdsavail(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5: {
return 0;
}
case SDS_TYPE_8: {
SDS_HDR_VAR(8,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_16: {
SDS_HDR_VAR(16,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_32: {
SDS_HDR_VAR(32,s);
return sh->alloc - sh->len;
}
case SDS_TYPE_64: {
SDS_HDR_VAR(64,s);
return sh->alloc - sh->len;
}
}
return 0;
}
static inline void sdssetlen(sds s, size_t newlen) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len = newlen;
break;
}
}
static inline void sdsinclen(sds s, size_t inc) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->len += inc;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->len += inc;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->len += inc;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->len += inc;
break;
}
}
/* sdsalloc() = sdsavail() + sdslen() */
static inline size_t sdsalloc(const sds s) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8:
return SDS_HDR(8,s)->alloc;
case SDS_TYPE_16:
return SDS_HDR(16,s)->alloc;
case SDS_TYPE_32:
return SDS_HDR(32,s)->alloc;
case SDS_TYPE_64:
return SDS_HDR(64,s)->alloc;
}
return 0;
}
static inline void sdssetalloc(sds s, size_t newlen) {
unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) {
case SDS_TYPE_5:
/* Nothing to do, this type has no total allocation info. */
break;
case SDS_TYPE_8:
SDS_HDR(8,s)->alloc = newlen;
break;
case SDS_TYPE_16:
SDS_HDR(16,s)->alloc = newlen;
break;
case SDS_TYPE_32:
SDS_HDR(32,s)->alloc = newlen;
break;
case SDS_TYPE_64:
SDS_HDR(64,s)->alloc = newlen;
break;
}
}
sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty(void);
sds sdsdup(const sds s);
void sdsfree(sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t);
sds sdscatsds(sds s, const sds t);
sds sdscpylen(sds s, const char *t, size_t len);
sds sdscpy(sds s, const char *t);
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
sds sdscatprintf(sds s, const char *fmt, ...);
#endif
sds sdscatfmt(sds s, char const *fmt, ...);
sds sdstrim(sds s, const char *cset);
void sdsrange(sds s, int start, int end);
void sdsupdatelen(sds s);
void sdsclear(sds s);
int sdscmp(const sds s1, const sds s2);
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
void sdsfreesplitres(sds *tokens, int count);
void sdstolower(sds s);
void sdstoupper(sds s);
sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen);
sds sdsjoin(char **argv, int argc, char *sep);
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen);
/* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen);
void sdsIncrLen(sds s, int incr);
sds sdsRemoveFreeSpace(sds s);
size_t sdsAllocSize(sds s);
void *sdsAllocPtr(sds s);
/* Export the allocator used by SDS to the program using SDS.
* Sometimes the program SDS is linked to, may use a different set of
* allocators, but may want to allocate or free things that SDS will
* respectively free or allocate. */
void *sds_malloc(size_t size);
void *sds_realloc(void *ptr, size_t size);
void sds_free(void *ptr);
#ifdef REDIS_TEST
int sdsTest(int argc, char *argv[]);
#endif
#endif

View file

@ -0,0 +1,42 @@
/* SDSLib 2.0 -- A C dynamic strings library
*
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2015, Oran Agra
* Copyright (c) 2015, Redis Labs, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* SDS allocator selection.
*
* This file is used in order to change the SDS allocator at compile time.
* Just define the following defines to what you want to use. Also add
* the include of your alternate allocator if needed (not needed in order
* to use the default libc allocator). */
#define s_malloc malloc
#define s_realloc realloc
#define s_free free

View file

@ -0,0 +1,923 @@
#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_sock;
};
/* 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_sock.path);
} else if (config.type == CONN_FD) {
/* Create a dummy connection just to get an fd to inherit */
redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
if (dummy_ctx) {
int fd = disconnect(dummy_ctx, 1);
printf("Connecting to inherited fd %d\n", fd);
c = redisConnectFd(fd);
}
} else {
assert(NULL);
}
if (c == NULL) {
printf("Connection error: can't allocate redis context\n");
exit(1);
} else if (c->err) {
printf("Connection error: %s\n", c->errstr);
redisFree(c);
exit(1);
}
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);
sds sds_cmd;
sds_cmd = sdsempty();
test("Format command into sds by passing argc/argv without lengths: ");
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
sdsfree(sds_cmd);
sds_cmd = sdsempty();
test("Format command into sds by passing argc/argv with lengths: ");
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
sdsfree(sds_cmd);
}
static void test_append_formatted_commands(struct config config) {
redisContext *c;
redisReply *reply;
char *cmd;
int len;
c = connect(config);
test("Append format command: ");
len = redisFormatCommand(&cmd, "SET foo bar");
test_cond(redisAppendFormattedCommand(c, cmd, len) == REDIS_OK);
assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
free(cmd);
freeReplyObject(reply);
disconnect(c, 0);
}
static void test_reply_reader(void) {
redisReader *reader;
void *reply;
int ret;
int i;
test("Error handling in reply parser: ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)"@foo\r\n",6);
ret = redisReaderGetReply(reader,NULL);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
redisReaderFree(reader);
/* when the reply already contains multiple items, they must be free'd
* on an error. valgrind will bark when this doesn't happen. */
test("Memory cleanup in reply parser: ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)"*2\r\n",4);
redisReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
redisReaderFeed(reader,(char*)"@foo\r\n",6);
ret = redisReaderGetReply(reader,NULL);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
redisReaderFree(reader);
test("Set error on nested multi bulks with depth > 7: ");
reader = redisReaderCreate();
for (i = 0; i < 9; i++) {
redisReaderFeed(reader,(char*)"*1\r\n",4);
}
ret = redisReaderGetReply(reader,NULL);
test_cond(ret == REDIS_ERR &&
strncasecmp(reader->errstr,"No support for",14) == 0);
redisReaderFree(reader);
test("Correctly parses LLONG_MAX: ");
reader = redisReaderCreate();
redisReaderFeed(reader, ":9223372036854775807\r\n",22);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
((redisReply*)reply)->integer == LLONG_MAX);
freeReplyObject(reply);
redisReaderFree(reader);
test("Set error when > LLONG_MAX: ");
reader = redisReaderCreate();
redisReaderFeed(reader, ":9223372036854775808\r\n",22);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Bad integer value") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
test("Correctly parses LLONG_MIN: ");
reader = redisReaderCreate();
redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
((redisReply*)reply)->integer == LLONG_MIN);
freeReplyObject(reply);
redisReaderFree(reader);
test("Set error when < LLONG_MIN: ");
reader = redisReaderCreate();
redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Bad integer value") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
test("Set error when array < -1: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
test("Set error when bulk < -1: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
test("Set error when array > INT_MAX: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
#if LLONG_MAX > SIZE_MAX
test("Set error when bulk > SIZE_MAX: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
#endif
test("Works with NULL functions for reply: ");
reader = redisReaderCreate();
reader->fn = NULL;
redisReaderFeed(reader,(char*)"+OK\r\n",5);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
redisReaderFree(reader);
test("Works when a single newline (\\r\\n) covers two calls to feed: ");
reader = redisReaderCreate();
reader->fn = NULL;
redisReaderFeed(reader,(char*)"+OK\r",4);
ret = redisReaderGetReply(reader,&reply);
assert(ret == REDIS_OK && reply == NULL);
redisReaderFeed(reader,(char*)"\n",1);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
redisReaderFree(reader);
test("Don't reset state after protocol error: ");
reader = redisReaderCreate();
reader->fn = NULL;
redisReaderFeed(reader,(char*)"x",1);
ret = redisReaderGetReply(reader,&reply);
assert(ret == REDIS_ERR);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR && reply == NULL);
redisReaderFree(reader);
/* Regression test for issue #45 on GitHub. */
test("Don't do empty allocation for empty multi bulk: ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)"*0\r\n",4);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 0);
freeReplyObject(reply);
redisReaderFree(reader);
}
static void test_free_null(void) {
void *redisCtx = NULL;
void *reply = NULL;
test("Don't fail when redisFree is passed a NULL value: ");
redisFree(redisCtx);
test_cond(redisCtx == NULL);
test("Don't fail when freeReplyObject is passed a NULL value: ");
freeReplyObject(reply);
test_cond(reply == NULL);
}
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,"hostname nor servname provided, or not known") == 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_sock socket path doesn't accept connections: ");
c = redisConnectUnix((char*)"/tmp/idontexist.sock");
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
redisFree(c);
}
static void test_blocking_connection(struct config config) {
redisContext *c;
redisReply *reply;
c = 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_sock.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 && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c);
test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
config.tcp.timeout.tv_usec = 0;
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c);
}
static void test_throughput(struct config config) {
redisContext *c = connect(config);
redisReply **replies;
int i, num;
long long t1, t2;
test("Throughput:\n");
for (i = 0; i < 500; i++)
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
num = 1000;
replies = malloc(sizeof(redisReply*)*num);
t1 = usec();
for (i = 0; i < num; i++) {
replies[i] = redisCommand(c,"PING");
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
}
t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies);
printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num);
t1 = usec();
for (i = 0; i < num; i++) {
replies[i] = redisCommand(c,"LRANGE mylist 0 499");
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
assert(replies[i] != NULL && replies[i]->elements == 500);
}
t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies);
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num);
t1 = usec();
for (i = 0; i < num; i++) {
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
}
t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies);
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
num = 10000;
replies = malloc(sizeof(redisReply*)*num);
for (i = 0; i < num; i++)
redisAppendCommand(c,"PING");
t1 = usec();
for (i = 0; i < num; i++) {
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
}
t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies);
printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num);
for (i = 0; i < num; i++)
redisAppendCommand(c,"LRANGE mylist 0 499");
t1 = usec();
for (i = 0; i < num; i++) {
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
assert(replies[i] != NULL && replies[i]->elements == 500);
}
t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies);
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num);
for (i = 0; i < num; i++)
redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
t1 = usec();
for (i = 0; i < num; i++) {
assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
}
t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies);
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
disconnect(c, 0);
}
// static long __test_callback_flags = 0;
// static void __test_callback(redisContext *c, void *privdata) {
// ((void)c);
// /* Shift to detect execution order */
// __test_callback_flags <<= 8;
// __test_callback_flags |= (long)privdata;
// }
//
// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
// ((void)c);
// /* Shift to detect execution order */
// __test_callback_flags <<= 8;
// __test_callback_flags |= (long)privdata;
// if (reply) freeReplyObject(reply);
// }
//
// static redisContext *__connect_nonblock() {
// /* Reset callback flags */
// __test_callback_flags = 0;
// return redisConnectNonBlock("127.0.0.1", port, NULL);
// }
//
// static void test_nonblocking_connection() {
// redisContext *c;
// int wdone = 0;
//
// test("Calls command callback when command is issued: ");
// c = __connect_nonblock();
// redisSetCommandCallback(c,__test_callback,(void*)1);
// redisCommand(c,"PING");
// test_cond(__test_callback_flags == 1);
// redisFree(c);
//
// test("Calls disconnect callback on redisDisconnect: ");
// c = __connect_nonblock();
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
// redisDisconnect(c);
// test_cond(__test_callback_flags == 2);
// redisFree(c);
//
// test("Calls disconnect callback and free callback on redisFree: ");
// c = __connect_nonblock();
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
// redisSetFreeCallback(c,__test_callback,(void*)4);
// redisFree(c);
// test_cond(__test_callback_flags == ((2 << 8) | 4));
//
// test("redisBufferWrite against empty write buffer: ");
// c = __connect_nonblock();
// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
// redisFree(c);
//
// test("redisBufferWrite against not yet connected fd: ");
// c = __connect_nonblock();
// redisCommand(c,"PING");
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
// strncmp(c->error,"write:",6) == 0);
// redisFree(c);
//
// test("redisBufferWrite against closed fd: ");
// c = __connect_nonblock();
// redisCommand(c,"PING");
// redisDisconnect(c);
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
// strncmp(c->error,"write:",6) == 0);
// redisFree(c);
//
// test("Process callbacks in the right sequence: ");
// c = __connect_nonblock();
// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
//
// /* Write output buffer */
// wdone = 0;
// while(!wdone) {
// usleep(500);
// redisBufferWrite(c,&wdone);
// }
//
// /* Read until at least one callback is executed (the 3 replies will
// * arrive in a single packet, causing all callbacks to be executed in
// * a single pass). */
// while(__test_callback_flags == 0) {
// assert(redisBufferRead(c) == REDIS_OK);
// redisProcessCallbacks(c);
// }
// test_cond(__test_callback_flags == 0x010203);
// redisFree(c);
//
// test("redisDisconnect executes pending callbacks with NULL reply: ");
// c = __connect_nonblock();
// redisSetDisconnectCallback(c,__test_callback,(void*)1);
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
// redisDisconnect(c);
// test_cond(__test_callback_flags == 0x0201);
// redisFree(c);
// }
int main(int argc, char **argv) {
struct config cfg = {
.tcp = {
.host = "127.0.0.1",
.port = 6379
},
.unix_sock = {
.path = "/tmp/redis.sock"
}
};
int throughput = 1;
int test_inherit_fd = 1;
/* Ignore broken pipe signal (for I/O error tests). */
signal(SIGPIPE, SIG_IGN);
/* Parse command line options. */
argv++; argc--;
while (argc) {
if (argc >= 2 && !strcmp(argv[0],"-h")) {
argv++; argc--;
cfg.tcp.host = argv[0];
} else if (argc >= 2 && !strcmp(argv[0],"-p")) {
argv++; argc--;
cfg.tcp.port = atoi(argv[0]);
} else if (argc >= 2 && !strcmp(argv[0],"-s")) {
argv++; argc--;
cfg.unix_sock.path = argv[0];
} else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
throughput = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
test_inherit_fd = 0;
} else {
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
exit(1);
}
argv++; argc--;
}
test_format_commands();
test_reply_reader();
test_blocking_connection_errors();
test_free_null();
printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
cfg.type = CONN_TCP;
test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg);
if (throughput) test_throughput(cfg);
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_UNIX;
test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg);
if (throughput) test_throughput(cfg);
if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_FD;
test_blocking_connection(cfg);
}
if (fails) {
printf("*** %d TESTS FAILED ***\n", fails);
return 1;
}
printf("ALL TESTS PASSED\n");
return 0;
}

View file

@ -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

View file

@ -0,0 +1,32 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app

View file

@ -0,0 +1,50 @@
project(redis++)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
cmake_minimum_required(VERSION 3.0.0)
else()
cmake_minimum_required(VERSION 2.8.0)
endif()
set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -W -Werror -fPIC")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis++)
file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp")
set(STATIC_LIB static)
set(SHARED_LIB shared)
add_library(${STATIC_LIB} STATIC ${PROJECT_SOURCE_FILES})
add_library(${SHARED_LIB} SHARED ${PROJECT_SOURCE_FILES})
# hiredis dependency
find_path(HIREDIS_HEADER hiredis)
target_include_directories(${STATIC_LIB} PUBLIC ${HIREDIS_HEADER})
target_include_directories(${SHARED_LIB} PUBLIC ${HIREDIS_HEADER})
find_library(HIREDIS_LIB hiredis)
target_link_libraries(${SHARED_LIB} ${HIREDIS_LIB})
set_target_properties(${STATIC_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
set_target_properties(${SHARED_LIB} PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
set_target_properties(${STATIC_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1)
set_target_properties(${SHARED_LIB} PROPERTIES CLEAN_DIRECT_OUTPUT 1)
add_subdirectory(test)
# Install static lib.
install(TARGETS ${STATIC_LIB}
ARCHIVE DESTINATION lib)
# Install shared lib.
install(TARGETS ${SHARED_LIB}
LIBRARY DESTINATION lib)
#Install headers.
set(HEADER_PATH "sw/redis++")
file(GLOB HEADERS "${PROJECT_SOURCE_DIR}/*.h*")
install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${HEADER_PATH})

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,376 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "command.h"
#include <cassert>
namespace sw {
namespace redis {
namespace cmd {
// KEY commands.
void restore(Connection &connection,
const StringView &key,
const StringView &val,
long long ttl,
bool replace) {
CmdArgs args;
args << "RESTORE" << key << ttl << val;
if (replace) {
args << "REPLACE";
}
connection.send(args);
}
// STRING commands.
void bitop(Connection &connection,
BitOp op,
const StringView &destination,
const StringView &key) {
CmdArgs args;
detail::set_bitop(args, op);
args << destination << key;
connection.send(args);
}
void set(Connection &connection,
const StringView &key,
const StringView &val,
long long ttl,
UpdateType type) {
CmdArgs args;
args << "SET" << key << val;
if (ttl > 0) {
args << "PX" << ttl;
}
detail::set_update_type(args, type);
connection.send(args);
}
// LIST commands.
void linsert(Connection &connection,
const StringView &key,
InsertPosition position,
const StringView &pivot,
const StringView &val) {
std::string pos;
switch (position) {
case InsertPosition::BEFORE:
pos = "BEFORE";
break;
case InsertPosition::AFTER:
pos = "AFTER";
break;
default:
assert(false);
}
connection.send("LINSERT %b %s %b %b",
key.data(), key.size(),
pos.c_str(),
pivot.data(), pivot.size(),
val.data(), val.size());
}
// GEO commands.
void geodist(Connection &connection,
const StringView &key,
const StringView &member1,
const StringView &member2,
GeoUnit unit) {
CmdArgs args;
args << "GEODIST" << key << member1 << member2;
detail::set_geo_unit(args, unit);
connection.send(args);
}
void georadius_store(Connection &connection,
const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
CmdArgs args;
args << "GEORADIUS" << key << loc.first << loc.second;
detail::set_georadius_store_parameters(args,
radius,
unit,
destination,
store_dist,
count);
connection.send(args);
}
void georadius(Connection &connection,
const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
long long count,
bool asc,
bool with_coord,
bool with_dist,
bool with_hash) {
CmdArgs args;
args << "GEORADIUS" << key << loc.first << loc.second;
detail::set_georadius_parameters(args,
radius,
unit,
count,
asc,
with_coord,
with_dist,
with_hash);
connection.send(args);
}
void georadiusbymember(Connection &connection,
const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
long long count,
bool asc,
bool with_coord,
bool with_dist,
bool with_hash) {
CmdArgs args;
args << "GEORADIUSBYMEMBER" << key << member;
detail::set_georadius_parameters(args,
radius,
unit,
count,
asc,
with_coord,
with_dist,
with_hash);
connection.send(args);
}
void georadiusbymember_store(Connection &connection,
const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
CmdArgs args;
args << "GEORADIUSBYMEMBER" << key << member;
detail::set_georadius_store_parameters(args,
radius,
unit,
destination,
store_dist,
count);
connection.send(args);
}
// Stream commands.
void xtrim(Connection &connection, const StringView &key, long long count, bool approx) {
CmdArgs args;
args << "XTRIM" << key << "MAXLEN";
if (approx) {
args << "~";
}
args << count;
connection.send(args);
}
namespace detail {
void set_bitop(CmdArgs &args, BitOp op) {
args << "BITOP";
switch (op) {
case BitOp::AND:
args << "AND";
break;
case BitOp::OR:
args << "OR";
break;
case BitOp::XOR:
args << "XOR";
break;
case BitOp::NOT:
args << "NOT";
break;
default:
throw Error("Unknown bit operations");
}
}
void set_update_type(CmdArgs &args, UpdateType type) {
switch (type) {
case UpdateType::EXIST:
args << "XX";
break;
case UpdateType::NOT_EXIST:
args << "NX";
break;
case UpdateType::ALWAYS:
// Do nothing.
break;
default:
throw Error("Unknown update type");
}
}
void set_aggregation_type(CmdArgs &args, Aggregation aggr) {
args << "AGGREGATE";
switch (aggr) {
case Aggregation::SUM:
args << "SUM";
break;
case Aggregation::MIN:
args << "MIN";
break;
case Aggregation::MAX:
args << "MAX";
break;
default:
throw Error("Unknown aggregation type");
}
}
void set_geo_unit(CmdArgs &args, GeoUnit unit) {
switch (unit) {
case GeoUnit::M:
args << "m";
break;
case GeoUnit::KM:
args << "km";
break;
case GeoUnit::MI:
args << "mi";
break;
case GeoUnit::FT:
args << "ft";
break;
default:
throw Error("Unknown geo unit type");
break;
}
}
void set_georadius_store_parameters(CmdArgs &args,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
args << radius;
detail::set_geo_unit(args, unit);
args << "COUNT" << count;
if (store_dist) {
args << "STOREDIST";
} else {
args << "STORE";
}
args << destination;
}
void set_georadius_parameters(CmdArgs &args,
double radius,
GeoUnit unit,
long long count,
bool asc,
bool with_coord,
bool with_dist,
bool with_hash) {
args << radius;
detail::set_geo_unit(args, unit);
if (with_coord) {
args << "WITHCOORD";
}
if (with_dist) {
args << "WITHDIST";
}
if (with_hash) {
args << "WITHHASH";
}
args << "COUNT" << count;
if (asc) {
args << "ASC";
} else {
args << "DESC";
}
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,180 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
#define SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H
#include <vector>
#include <list>
#include <string>
#include <tuple>
#include "utils.h"
namespace sw {
namespace redis {
class CmdArgs {
public:
template <typename Arg>
CmdArgs& append(Arg &&arg);
template <typename Arg, typename ...Args>
CmdArgs& append(Arg &&arg, Args &&...args);
// All overloads of operator<< are for internal use only.
CmdArgs& operator<<(const StringView &arg);
template <typename T,
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
int>::type = 0>
CmdArgs& operator<<(T &&arg);
template <typename Iter>
CmdArgs& operator<<(const std::pair<Iter, Iter> &range);
template <std::size_t N, typename ...Args>
auto operator<<(const std::tuple<Args...> &) ->
typename std::enable_if<N == sizeof...(Args), CmdArgs&>::type {
return *this;
}
template <std::size_t N = 0, typename ...Args>
auto operator<<(const std::tuple<Args...> &arg) ->
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type;
const char** argv() {
return _argv.data();
}
const std::size_t* argv_len() {
return _argv_len.data();
}
std::size_t size() const {
return _argv.size();
}
private:
// Deep copy.
CmdArgs& _append(std::string arg);
// Shallow copy.
CmdArgs& _append(const StringView &arg);
// Shallow copy.
CmdArgs& _append(const char *arg);
template <typename T,
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
int>::type = 0>
CmdArgs& _append(T &&arg) {
return operator<<(std::forward<T>(arg));
}
template <typename Iter>
CmdArgs& _append(std::true_type, const std::pair<Iter, Iter> &range);
template <typename Iter>
CmdArgs& _append(std::false_type, const std::pair<Iter, Iter> &range);
std::vector<const char *> _argv;
std::vector<std::size_t> _argv_len;
std::list<std::string> _args;
};
template <typename Arg>
inline CmdArgs& CmdArgs::append(Arg &&arg) {
return _append(std::forward<Arg>(arg));
}
template <typename Arg, typename ...Args>
inline CmdArgs& CmdArgs::append(Arg &&arg, Args &&...args) {
_append(std::forward<Arg>(arg));
return append(std::forward<Args>(args)...);
}
inline CmdArgs& CmdArgs::operator<<(const StringView &arg) {
_argv.push_back(arg.data());
_argv_len.push_back(arg.size());
return *this;
}
template <typename Iter>
inline CmdArgs& CmdArgs::operator<<(const std::pair<Iter, Iter> &range) {
return _append(IsKvPair<typename std::decay<decltype(*std::declval<Iter>())>::type>(), range);
}
template <typename T,
typename std::enable_if<std::is_arithmetic<typename std::decay<T>::type>::value,
int>::type>
inline CmdArgs& CmdArgs::operator<<(T &&arg) {
return _append(std::to_string(std::forward<T>(arg)));
}
template <std::size_t N, typename ...Args>
auto CmdArgs::operator<<(const std::tuple<Args...> &arg) ->
typename std::enable_if<N < sizeof...(Args), CmdArgs&>::type {
operator<<(std::get<N>(arg));
return operator<<<N + 1, Args...>(arg);
}
inline CmdArgs& CmdArgs::_append(std::string arg) {
_args.push_back(std::move(arg));
return operator<<(_args.back());
}
inline CmdArgs& CmdArgs::_append(const StringView &arg) {
return operator<<(arg);
}
inline CmdArgs& CmdArgs::_append(const char *arg) {
return operator<<(arg);
}
template <typename Iter>
CmdArgs& CmdArgs::_append(std::false_type, const std::pair<Iter, Iter> &range) {
auto first = range.first;
auto last = range.second;
while (first != last) {
*this << *first;
++first;
}
return *this;
}
template <typename Iter>
CmdArgs& CmdArgs::_append(std::true_type, const std::pair<Iter, Iter> &range) {
auto first = range.first;
auto last = range.second;
while (first != last) {
*this << first->first << first->second;
++first;
}
return *this;
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_ARGS_H

View file

@ -0,0 +1,201 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "command_options.h"
#include "errors.h"
namespace {
const std::string NEGATIVE_INFINITY_NUMERIC = "-inf";
const std::string POSITIVE_INFINITY_NUMERIC = "+inf";
const std::string NEGATIVE_INFINITY_STRING = "-";
const std::string POSITIVE_INFINITY_STRING = "+";
std::string unbound(const std::string &bnd);
std::string bound(const std::string &bnd);
}
namespace sw {
namespace redis {
const std::string& UnboundedInterval<double>::min() const {
return NEGATIVE_INFINITY_NUMERIC;
}
const std::string& UnboundedInterval<double>::max() const {
return POSITIVE_INFINITY_NUMERIC;
}
BoundedInterval<double>::BoundedInterval(double min, double max, BoundType type) :
_min(std::to_string(min)),
_max(std::to_string(max)) {
switch (type) {
case BoundType::CLOSED:
// Do nothing
break;
case BoundType::OPEN:
_min = unbound(_min);
_max = unbound(_max);
break;
case BoundType::LEFT_OPEN:
_min = unbound(_min);
break;
case BoundType::RIGHT_OPEN:
_max = unbound(_max);
break;
default:
throw Error("Unknow BoundType");
}
}
LeftBoundedInterval<double>::LeftBoundedInterval(double min, BoundType type) :
_min(std::to_string(min)) {
switch (type) {
case BoundType::OPEN:
_min = unbound(_min);
break;
case BoundType::RIGHT_OPEN:
// Do nothing.
break;
default:
throw Error("Bound type can only be OPEN or RIGHT_OPEN");
}
}
const std::string& LeftBoundedInterval<double>::max() const {
return POSITIVE_INFINITY_NUMERIC;
}
RightBoundedInterval<double>::RightBoundedInterval(double max, BoundType type) :
_max(std::to_string(max)) {
switch (type) {
case BoundType::OPEN:
_max = unbound(_max);
break;
case BoundType::LEFT_OPEN:
// Do nothing.
break;
default:
throw Error("Bound type can only be OPEN or LEFT_OPEN");
}
}
const std::string& RightBoundedInterval<double>::min() const {
return NEGATIVE_INFINITY_NUMERIC;
}
const std::string& UnboundedInterval<std::string>::min() const {
return NEGATIVE_INFINITY_STRING;
}
const std::string& UnboundedInterval<std::string>::max() const {
return POSITIVE_INFINITY_STRING;
}
BoundedInterval<std::string>::BoundedInterval(const std::string &min,
const std::string &max,
BoundType type) {
switch (type) {
case BoundType::CLOSED:
_min = bound(min);
_max = bound(max);
break;
case BoundType::OPEN:
_min = unbound(min);
_max = unbound(max);
break;
case BoundType::LEFT_OPEN:
_min = unbound(min);
_max = bound(max);
break;
case BoundType::RIGHT_OPEN:
_min = bound(min);
_max = unbound(max);
break;
default:
throw Error("Unknow BoundType");
}
}
LeftBoundedInterval<std::string>::LeftBoundedInterval(const std::string &min, BoundType type) {
switch (type) {
case BoundType::OPEN:
_min = unbound(min);
break;
case BoundType::RIGHT_OPEN:
_min = bound(min);
break;
default:
throw Error("Bound type can only be OPEN or RIGHT_OPEN");
}
}
const std::string& LeftBoundedInterval<std::string>::max() const {
return POSITIVE_INFINITY_STRING;
}
RightBoundedInterval<std::string>::RightBoundedInterval(const std::string &max, BoundType type) {
switch (type) {
case BoundType::OPEN:
_max = unbound(max);
break;
case BoundType::LEFT_OPEN:
_max = bound(max);
break;
default:
throw Error("Bound type can only be OPEN or LEFT_OPEN");
}
}
const std::string& RightBoundedInterval<std::string>::min() const {
return NEGATIVE_INFINITY_STRING;
}
}
}
namespace {
std::string unbound(const std::string &bnd) {
return "(" + bnd;
}
std::string bound(const std::string &bnd) {
return "[" + bnd;
}
}

View file

@ -0,0 +1,211 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
#define SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H
#include <string>
#include "utils.h"
namespace sw {
namespace redis {
enum class UpdateType {
EXIST,
NOT_EXIST,
ALWAYS
};
enum class InsertPosition {
BEFORE,
AFTER
};
enum class BoundType {
CLOSED,
OPEN,
LEFT_OPEN,
RIGHT_OPEN
};
// (-inf, +inf)
template <typename T>
class UnboundedInterval;
// [min, max], (min, max), (min, max], [min, max)
template <typename T>
class BoundedInterval;
// [min, +inf), (min, +inf)
template <typename T>
class LeftBoundedInterval;
// (-inf, max], (-inf, max)
template <typename T>
class RightBoundedInterval;
template <>
class UnboundedInterval<double> {
public:
const std::string& min() const;
const std::string& max() const;
};
template <>
class BoundedInterval<double> {
public:
BoundedInterval(double min, double max, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const {
return _max;
}
private:
std::string _min;
std::string _max;
};
template <>
class LeftBoundedInterval<double> {
public:
LeftBoundedInterval(double min, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const;
private:
std::string _min;
};
template <>
class RightBoundedInterval<double> {
public:
RightBoundedInterval(double max, BoundType type);
const std::string& min() const;
const std::string& max() const {
return _max;
}
private:
std::string _max;
};
template <>
class UnboundedInterval<std::string> {
public:
const std::string& min() const;
const std::string& max() const;
};
template <>
class BoundedInterval<std::string> {
public:
BoundedInterval(const std::string &min, const std::string &max, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const {
return _max;
}
private:
std::string _min;
std::string _max;
};
template <>
class LeftBoundedInterval<std::string> {
public:
LeftBoundedInterval(const std::string &min, BoundType type);
const std::string& min() const {
return _min;
}
const std::string& max() const;
private:
std::string _min;
};
template <>
class RightBoundedInterval<std::string> {
public:
RightBoundedInterval(const std::string &max, BoundType type);
const std::string& min() const;
const std::string& max() const {
return _max;
}
private:
std::string _max;
};
struct LimitOptions {
long long offset = 0;
long long count = -1;
};
enum class Aggregation {
SUM,
MIN,
MAX
};
enum class BitOp {
AND,
OR,
XOR,
NOT
};
enum class GeoUnit {
M,
KM,
MI,
FT
};
template <typename T>
struct WithCoord : TupleWithType<std::pair<double, double>, T> {};
template <typename T>
struct WithDist : TupleWithType<double, T> {};
template <typename T>
struct WithHash : TupleWithType<long long, T> {};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_COMMAND_OPTIONS_H

View file

@ -0,0 +1,305 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "connection.h"
#include <cassert>
#include "reply.h"
#include "command.h"
#include "command_args.h"
namespace sw {
namespace redis {
ConnectionOptions::ConnectionOptions(const std::string &uri) :
ConnectionOptions(_parse_options(uri)) {}
ConnectionOptions ConnectionOptions::_parse_options(const std::string &uri) const {
std::string type;
std::string path;
std::tie(type, path) = _split_string(uri, "://");
if (path.empty()) {
throw Error("Invalid URI: no path");
}
if (type == "tcp") {
return _parse_tcp_options(path);
} else if (type == "unix") {
return _parse_unix_options(path);
} else {
throw Error("Invalid URI: invalid type");
}
}
ConnectionOptions ConnectionOptions::_parse_tcp_options(const std::string &path) const {
ConnectionOptions options;
options.type = ConnectionType::TCP;
std::string host;
std::string port;
std::tie(host, port) = _split_string(path, ":");
options.host = host;
try {
if (!port.empty()) {
options.port = std::stoi(port);
} // else use default port, i.e. 6379.
} catch (const std::exception &) {
throw Error("Invalid URL: invalid port");
}
return options;
}
ConnectionOptions ConnectionOptions::_parse_unix_options(const std::string &path) const {
ConnectionOptions options;
options.type = ConnectionType::UNIX;
options.path = path;
return options;
}
auto ConnectionOptions::_split_string(const std::string &str, const std::string &delimiter) const ->
std::pair<std::string, std::string> {
auto pos = str.rfind(delimiter);
if (pos == std::string::npos) {
return {str, ""};
}
return {str.substr(0, pos), str.substr(pos + delimiter.size())};
}
class Connection::Connector {
public:
explicit Connector(const ConnectionOptions &opts);
ContextUPtr connect() const;
private:
ContextUPtr _connect() const;
redisContext* _connect_tcp() const;
redisContext* _connect_unix() const;
void _set_socket_timeout(redisContext &ctx) const;
void _enable_keep_alive(redisContext &ctx) const;
timeval _to_timeval(const std::chrono::milliseconds &dur) const;
const ConnectionOptions &_opts;
};
Connection::Connector::Connector(const ConnectionOptions &opts) : _opts(opts) {}
Connection::ContextUPtr Connection::Connector::connect() const {
auto ctx = _connect();
assert(ctx);
if (ctx->err != REDIS_OK) {
throw_error(*ctx, "Failed to connect to Redis");
}
_set_socket_timeout(*ctx);
_enable_keep_alive(*ctx);
return ctx;
}
Connection::ContextUPtr Connection::Connector::_connect() const {
redisContext *context = nullptr;
switch (_opts.type) {
case ConnectionType::TCP:
context = _connect_tcp();
break;
case ConnectionType::UNIX:
context = _connect_unix();
break;
default:
// Never goes here.
throw Error("Unkonw connection type");
}
if (context == nullptr) {
throw Error("Failed to allocate memory for connection.");
}
return ContextUPtr(context);
}
redisContext* Connection::Connector::_connect_tcp() const {
if (_opts.connect_timeout > std::chrono::milliseconds(0)) {
return redisConnectWithTimeout(_opts.host.c_str(),
_opts.port,
_to_timeval(_opts.connect_timeout));
} else {
return redisConnect(_opts.host.c_str(), _opts.port);
}
}
redisContext* Connection::Connector::_connect_unix() const {
if (_opts.connect_timeout > std::chrono::milliseconds(0)) {
return redisConnectUnixWithTimeout(
_opts.path.c_str(),
_to_timeval(_opts.connect_timeout));
} else {
return redisConnectUnix(_opts.path.c_str());
}
}
void Connection::Connector::_set_socket_timeout(redisContext &ctx) const {
if (_opts.socket_timeout <= std::chrono::milliseconds(0)) {
return;
}
if (redisSetTimeout(&ctx, _to_timeval(_opts.socket_timeout)) != REDIS_OK) {
throw_error(ctx, "Failed to set socket timeout");
}
}
void Connection::Connector::_enable_keep_alive(redisContext &ctx) const {
if (!_opts.keep_alive) {
return;
}
if (redisEnableKeepAlive(&ctx) != REDIS_OK) {
throw_error(ctx, "Failed to enable keep alive option");
}
}
timeval Connection::Connector::_to_timeval(const std::chrono::milliseconds &dur) const {
auto sec = std::chrono::duration_cast<std::chrono::seconds>(dur);
auto msec = std::chrono::duration_cast<std::chrono::microseconds>(dur - sec);
return {
static_cast<std::time_t>(sec.count()),
static_cast<suseconds_t>(msec.count())
};
}
void swap(Connection &lhs, Connection &rhs) noexcept {
std::swap(lhs._ctx, rhs._ctx);
std::swap(lhs._last_active, rhs._last_active);
std::swap(lhs._opts, rhs._opts);
}
Connection::Connection(const ConnectionOptions &opts) :
_ctx(Connector(opts).connect()),
_last_active(std::chrono::steady_clock::now()),
_opts(opts) {
assert(_ctx && !broken());
_set_options();
}
void Connection::reconnect() {
Connection connection(_opts);
swap(*this, connection);
}
void Connection::send(int argc, const char **argv, const std::size_t *argv_len) {
auto ctx = _context();
assert(ctx != nullptr);
if (redisAppendCommandArgv(ctx,
argc,
argv,
argv_len) != REDIS_OK) {
throw_error(*ctx, "Failed to send command");
}
assert(!broken());
}
void Connection::send(CmdArgs &args) {
auto ctx = _context();
assert(ctx != nullptr);
if (redisAppendCommandArgv(ctx,
args.size(),
args.argv(),
args.argv_len()) != REDIS_OK) {
throw_error(*ctx, "Failed to send command");
}
assert(!broken());
}
ReplyUPtr Connection::recv() {
auto *ctx = _context();
assert(ctx != nullptr);
void *r = nullptr;
if (redisGetReply(ctx, &r) != REDIS_OK) {
throw_error(*ctx, "Failed to get reply");
}
assert(!broken() && r != nullptr);
auto reply = ReplyUPtr(static_cast<redisReply*>(r));
if (reply::is_error(*reply)) {
throw_error(*reply);
}
return reply;
}
void Connection::_set_options() {
_auth();
_select_db();
}
void Connection::_auth() {
if (_opts.password.empty()) {
return;
}
cmd::auth(*this, _opts.password);
auto reply = recv();
reply::parse<void>(*reply);
}
void Connection::_select_db() {
if (_opts.db == 0) {
return;
}
cmd::select(*this, _opts.db);
auto reply = recv();
reply::parse<void>(*reply);
}
}
}

View file

@ -0,0 +1,194 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_H
#define SEWENEW_REDISPLUSPLUS_CONNECTION_H
#include <cerrno>
#include <cstring>
#include <memory>
#include <string>
#include <sstream>
#include <chrono>
#include <hiredis/hiredis.h>
#include "errors.h"
#include "reply.h"
#include "utils.h"
namespace sw {
namespace redis {
enum class ConnectionType {
TCP = 0,
UNIX
};
struct ConnectionOptions {
public:
ConnectionOptions() = default;
explicit ConnectionOptions(const std::string &uri);
ConnectionOptions(const ConnectionOptions &) = default;
ConnectionOptions& operator=(const ConnectionOptions &) = default;
ConnectionOptions(ConnectionOptions &&) = default;
ConnectionOptions& operator=(ConnectionOptions &&) = default;
~ConnectionOptions() = default;
ConnectionType type = ConnectionType::TCP;
std::string host;
int port = 6379;
std::string path;
std::string password;
int db = 0;
bool keep_alive = false;
std::chrono::milliseconds connect_timeout{0};
std::chrono::milliseconds socket_timeout{0};
private:
ConnectionOptions _parse_options(const std::string &uri) const;
ConnectionOptions _parse_tcp_options(const std::string &path) const;
ConnectionOptions _parse_unix_options(const std::string &path) const;
auto _split_string(const std::string &str, const std::string &delimiter) const ->
std::pair<std::string, std::string>;
};
class CmdArgs;
class Connection {
public:
explicit Connection(const ConnectionOptions &opts);
Connection(const Connection &) = delete;
Connection& operator=(const Connection &) = delete;
Connection(Connection &&) = default;
Connection& operator=(Connection &&) = default;
~Connection() = default;
// Check if the connection is broken. Client needs to do this check
// before sending some command to the connection. If it's broken,
// client needs to reconnect it.
bool broken() const noexcept {
return _ctx->err != REDIS_OK;
}
void reset() noexcept {
_ctx->err = 0;
}
void reconnect();
auto last_active() const
-> std::chrono::time_point<std::chrono::steady_clock> {
return _last_active;
}
template <typename ...Args>
void send(const char *format, Args &&...args);
void send(int argc, const char **argv, const std::size_t *argv_len);
void send(CmdArgs &args);
ReplyUPtr recv();
const ConnectionOptions& options() const {
return _opts;
}
friend void swap(Connection &lhs, Connection &rhs) noexcept;
private:
class Connector;
struct ContextDeleter {
void operator()(redisContext *context) const {
if (context != nullptr) {
redisFree(context);
}
};
};
using ContextUPtr = std::unique_ptr<redisContext, ContextDeleter>;
void _set_options();
void _auth();
void _select_db();
redisContext* _context();
ContextUPtr _ctx;
// The time that the connection is created or the time that
// the connection is used, i.e. *context()* is called.
std::chrono::time_point<std::chrono::steady_clock> _last_active{};
ConnectionOptions _opts;
};
using ConnectionSPtr = std::shared_ptr<Connection>;
enum class Role {
MASTER,
SLAVE
};
// Inline implementaions.
template <typename ...Args>
inline void Connection::send(const char *format, Args &&...args) {
auto ctx = _context();
assert(ctx != nullptr);
if (redisAppendCommand(ctx,
format,
std::forward<Args>(args)...) != REDIS_OK) {
throw_error(*ctx, "Failed to send command");
}
assert(!broken());
}
inline redisContext* Connection::_context() {
_last_active = std::chrono::steady_clock::now();
return _ctx.get();
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_H

View file

@ -0,0 +1,249 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "connection_pool.h"
#include <cassert>
#include "errors.h"
namespace sw {
namespace redis {
ConnectionPool::ConnectionPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts) :
_opts(connection_opts),
_pool_opts(pool_opts) {
if (_pool_opts.size == 0) {
throw Error("CANNOT create an empty pool");
}
// Lazily create connections.
}
ConnectionPool::ConnectionPool(SimpleSentinel sentinel,
const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts) :
_opts(connection_opts),
_pool_opts(pool_opts),
_sentinel(std::move(sentinel)) {
// In this case, the connection must be of TCP type.
if (_opts.type != ConnectionType::TCP) {
throw Error("Sentinel only supports TCP connection");
}
if (_opts.connect_timeout == std::chrono::milliseconds(0)
|| _opts.socket_timeout == std::chrono::milliseconds(0)) {
throw Error("With sentinel, connection timeout and socket timeout cannot be 0");
}
// Cleanup connection options.
_update_connection_opts("", -1);
assert(_sentinel);
}
ConnectionPool::ConnectionPool(ConnectionPool &&that) {
std::lock_guard<std::mutex> lock(that._mutex);
_move(std::move(that));
}
ConnectionPool& ConnectionPool::operator=(ConnectionPool &&that) {
if (this != &that) {
std::lock(_mutex, that._mutex);
std::lock_guard<std::mutex> lock_this(_mutex, std::adopt_lock);
std::lock_guard<std::mutex> lock_that(that._mutex, std::adopt_lock);
_move(std::move(that));
}
return *this;
}
Connection ConnectionPool::fetch() {
std::unique_lock<std::mutex> lock(_mutex);
if (_pool.empty()) {
if (_used_connections == _pool_opts.size) {
_wait_for_connection(lock);
} else {
// Lazily create a new connection.
auto connection = _create();
++_used_connections;
return connection;
}
}
// _pool is NOT empty.
auto connection = _fetch();
auto connection_lifetime = _pool_opts.connection_lifetime;
if (_sentinel) {
auto opts = _opts;
auto role_changed = _role_changed(connection.options());
auto sentinel = _sentinel;
lock.unlock();
if (role_changed || _need_reconnect(connection, connection_lifetime)) {
try {
connection = _create(sentinel, opts, false);
} catch (const Error &e) {
// Failed to reconnect, return it to the pool, and retry latter.
release(std::move(connection));
throw;
}
}
return connection;
}
lock.unlock();
if (_need_reconnect(connection, connection_lifetime)) {
try {
connection.reconnect();
} catch (const Error &e) {
// Failed to reconnect, return it to the pool, and retry latter.
release(std::move(connection));
throw;
}
}
return connection;
}
ConnectionOptions ConnectionPool::connection_options() {
std::lock_guard<std::mutex> lock(_mutex);
return _opts;
}
void ConnectionPool::release(Connection connection) {
{
std::lock_guard<std::mutex> lock(_mutex);
_pool.push_back(std::move(connection));
}
_cv.notify_one();
}
Connection ConnectionPool::create() {
std::unique_lock<std::mutex> lock(_mutex);
auto opts = _opts;
if (_sentinel) {
auto sentinel = _sentinel;
lock.unlock();
return _create(sentinel, opts, false);
} else {
lock.unlock();
return Connection(opts);
}
}
void ConnectionPool::_move(ConnectionPool &&that) {
_opts = std::move(that._opts);
_pool_opts = std::move(that._pool_opts);
_pool = std::move(that._pool);
_used_connections = that._used_connections;
_sentinel = std::move(that._sentinel);
}
Connection ConnectionPool::_create() {
if (_sentinel) {
// Get Redis host and port info from sentinel.
return _create(_sentinel, _opts, true);
}
return Connection(_opts);
}
Connection ConnectionPool::_create(SimpleSentinel &sentinel,
const ConnectionOptions &opts,
bool locked) {
try {
auto connection = sentinel.create(opts);
std::unique_lock<std::mutex> lock(_mutex, std::defer_lock);
if (!locked) {
lock.lock();
}
const auto &connection_opts = connection.options();
if (_role_changed(connection_opts)) {
// Master/Slave has been changed, reconnect all connections.
_update_connection_opts(connection_opts.host, connection_opts.port);
}
return connection;
} catch (const StopIterError &e) {
throw Error("Failed to create connection with sentinel");
}
}
Connection ConnectionPool::_fetch() {
assert(!_pool.empty());
auto connection = std::move(_pool.front());
_pool.pop_front();
return connection;
}
void ConnectionPool::_wait_for_connection(std::unique_lock<std::mutex> &lock) {
auto timeout = _pool_opts.wait_timeout;
if (timeout > std::chrono::milliseconds(0)) {
// Wait until _pool is no longer empty or timeout.
if (!_cv.wait_for(lock,
timeout,
[this] { return !(this->_pool).empty(); })) {
throw Error("Failed to fetch a connection in "
+ std::to_string(timeout.count()) + " milliseconds");
}
} else {
// Wait forever.
_cv.wait(lock, [this] { return !(this->_pool).empty(); });
}
}
bool ConnectionPool::_need_reconnect(const Connection &connection,
const std::chrono::milliseconds &connection_lifetime) const {
if (connection.broken()) {
return true;
}
if (connection_lifetime > std::chrono::milliseconds(0)) {
auto now = std::chrono::steady_clock::now();
if (now - connection.last_active() > connection_lifetime) {
return true;
}
}
return false;
}
}
}

View file

@ -0,0 +1,115 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
#define SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H
#include <chrono>
#include <mutex>
#include <memory>
#include <condition_variable>
#include <deque>
#include "connection.h"
#include "sentinel.h"
namespace sw {
namespace redis {
struct ConnectionPoolOptions {
// Max number of connections, including both in-use and idle ones.
std::size_t size = 1;
// Max time to wait for a connection. 0ms means client waits forever.
std::chrono::milliseconds wait_timeout{0};
// Max lifetime of a connection. 0ms means we never expire the connection.
std::chrono::milliseconds connection_lifetime{0};
};
class ConnectionPool {
public:
ConnectionPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts);
ConnectionPool(SimpleSentinel sentinel,
const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts);
ConnectionPool() = default;
ConnectionPool(ConnectionPool &&that);
ConnectionPool& operator=(ConnectionPool &&that);
ConnectionPool(const ConnectionPool &) = delete;
ConnectionPool& operator=(const ConnectionPool &) = delete;
~ConnectionPool() = default;
// Fetch a connection from pool.
Connection fetch();
ConnectionOptions connection_options();
void release(Connection connection);
// Create a new connection.
Connection create();
private:
void _move(ConnectionPool &&that);
// NOT thread-safe
Connection _create();
Connection _create(SimpleSentinel &sentinel, const ConnectionOptions &opts, bool locked);
Connection _fetch();
void _wait_for_connection(std::unique_lock<std::mutex> &lock);
bool _need_reconnect(const Connection &connection,
const std::chrono::milliseconds &connection_lifetime) const;
void _update_connection_opts(const std::string &host, int port) {
_opts.host = host;
_opts.port = port;
}
bool _role_changed(const ConnectionOptions &opts) const {
return opts.port != _opts.port || opts.host != _opts.host;
}
ConnectionOptions _opts;
ConnectionPoolOptions _pool_opts;
std::deque<Connection> _pool;
std::size_t _used_connections = 0;
std::mutex _mutex;
std::condition_variable _cv;
SimpleSentinel _sentinel;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_CONNECTION_POOL_H

View file

@ -0,0 +1,96 @@
/*
* 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 <cstdint>
namespace sw {
namespace redis {
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;
}
}
}

View file

@ -0,0 +1,136 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "errors.h"
#include <cassert>
#include <cerrno>
#include <unordered_map>
#include <tuple>
#include "shards.h"
namespace {
using namespace sw::redis;
std::pair<ReplyErrorType, std::string> parse_error(const std::string &msg);
std::unordered_map<std::string, ReplyErrorType> error_map = {
{"MOVED", ReplyErrorType::MOVED},
{"ASK", ReplyErrorType::ASK}
};
}
namespace sw {
namespace redis {
void throw_error(redisContext &context, const std::string &err_info) {
auto err_code = context.err;
const auto *err_str = context.errstr;
if (err_str == nullptr) {
throw Error(err_info + ": null error message: " + std::to_string(err_code));
}
auto err_msg = err_info + ": " + err_str;
switch (err_code) {
case REDIS_ERR_IO:
if (errno == EAGAIN || errno == EINTR) {
throw TimeoutError(err_msg);
} else {
throw IoError(err_msg);
}
break;
case REDIS_ERR_EOF:
throw ClosedError(err_msg);
break;
case REDIS_ERR_PROTOCOL:
throw ProtoError(err_msg);
break;
case REDIS_ERR_OOM:
throw OomError(err_msg);
break;
case REDIS_ERR_OTHER:
throw Error(err_msg);
break;
default:
throw Error(err_info + ": Unknown error code");
}
}
void throw_error(const redisReply &reply) {
assert(reply.type == REDIS_REPLY_ERROR);
if (reply.str == nullptr) {
throw Error("Null error reply");
}
auto err_str = std::string(reply.str, reply.len);
auto err_type = ReplyErrorType::ERR;
std::string err_msg;
std::tie(err_type, err_msg) = parse_error(err_str);
switch (err_type) {
case ReplyErrorType::MOVED:
throw MovedError(err_msg);
break;
case ReplyErrorType::ASK:
throw AskError(err_msg);
break;
default:
throw ReplyError(err_str);
break;
}
}
}
}
namespace {
using namespace sw::redis;
std::pair<ReplyErrorType, std::string> parse_error(const std::string &err) {
// The error contains an Error Prefix, and an optional error message.
auto idx = err.find_first_of(" \n");
if (idx == std::string::npos) {
throw ProtoError("No Error Prefix: " + err);
}
auto err_prefix = err.substr(0, idx);
auto err_type = ReplyErrorType::ERR;
auto iter = error_map.find(err_prefix);
if (iter != error_map.end()) {
// Specific error.
err_type = iter->second;
} // else Generic error.
return {err_type, err.substr(idx + 1)};
}
}

View file

@ -0,0 +1,159 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_ERRORS_H
#define SEWENEW_REDISPLUSPLUS_ERRORS_H
#include <exception>
#include <string>
#include <hiredis/hiredis.h>
namespace sw {
namespace redis {
enum ReplyErrorType {
ERR,
MOVED,
ASK
};
class Error : public std::exception {
public:
explicit Error(const std::string &msg) : _msg(msg) {}
Error(const Error &) = default;
Error& operator=(const Error &) = default;
Error(Error &&) = default;
Error& operator=(Error &&) = default;
virtual ~Error() = default;
virtual const char* what() const noexcept {
return _msg.data();
}
private:
std::string _msg;
};
class IoError : public Error {
public:
explicit IoError(const std::string &msg) : Error(msg) {}
IoError(const IoError &) = default;
IoError& operator=(const IoError &) = default;
IoError(IoError &&) = default;
IoError& operator=(IoError &&) = default;
virtual ~IoError() = default;
};
class TimeoutError : public IoError {
public:
explicit TimeoutError(const std::string &msg) : IoError(msg) {}
TimeoutError(const TimeoutError &) = default;
TimeoutError& operator=(const TimeoutError &) = default;
TimeoutError(TimeoutError &&) = default;
TimeoutError& operator=(TimeoutError &&) = default;
virtual ~TimeoutError() = default;
};
class ClosedError : public Error {
public:
explicit ClosedError(const std::string &msg) : Error(msg) {}
ClosedError(const ClosedError &) = default;
ClosedError& operator=(const ClosedError &) = default;
ClosedError(ClosedError &&) = default;
ClosedError& operator=(ClosedError &&) = default;
virtual ~ClosedError() = default;
};
class ProtoError : public Error {
public:
explicit ProtoError(const std::string &msg) : Error(msg) {}
ProtoError(const ProtoError &) = default;
ProtoError& operator=(const ProtoError &) = default;
ProtoError(ProtoError &&) = default;
ProtoError& operator=(ProtoError &&) = default;
virtual ~ProtoError() = default;
};
class OomError : public Error {
public:
explicit OomError(const std::string &msg) : Error(msg) {}
OomError(const OomError &) = default;
OomError& operator=(const OomError &) = default;
OomError(OomError &&) = default;
OomError& operator=(OomError &&) = default;
virtual ~OomError() = default;
};
class ReplyError : public Error {
public:
explicit ReplyError(const std::string &msg) : Error(msg) {}
ReplyError(const ReplyError &) = default;
ReplyError& operator=(const ReplyError &) = default;
ReplyError(ReplyError &&) = default;
ReplyError& operator=(ReplyError &&) = default;
virtual ~ReplyError() = default;
};
class WatchError : public Error {
public:
explicit WatchError() : Error("Watched key has been modified") {}
WatchError(const WatchError &) = default;
WatchError& operator=(const WatchError &) = default;
WatchError(WatchError &&) = default;
WatchError& operator=(WatchError &&) = default;
virtual ~WatchError() = default;
};
// MovedError and AskError are defined in shards.h
class MovedError;
class AskError;
void throw_error(redisContext &context, const std::string &err_info);
void throw_error(const redisReply &reply);
}
}
#endif // end SEWENEW_REDISPLUSPLUS_ERRORS_H

View file

@ -0,0 +1,35 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "pipeline.h"
namespace sw {
namespace redis {
std::vector<ReplyUPtr> PipelineImpl::exec(Connection &connection, std::size_t cmd_num) {
std::vector<ReplyUPtr> replies;
while (cmd_num > 0) {
replies.push_back(connection.recv());
--cmd_num;
}
return replies;
}
}
}

View file

@ -0,0 +1,49 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_PIPELINE_H
#define SEWENEW_REDISPLUSPLUS_PIPELINE_H
#include <cassert>
#include <vector>
#include "connection.h"
namespace sw {
namespace redis {
class PipelineImpl {
public:
template <typename Cmd, typename ...Args>
void command(Connection &connection, Cmd cmd, Args &&...args) {
assert(!connection.broken());
cmd(connection, std::forward<Args>(args)...);
}
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
void discard(Connection &connection, std::size_t /*cmd_num*/) {
// Reconnect to Redis to discard all commands.
connection.reconnect();
}
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_PIPELINE_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,208 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
#define SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP
namespace sw {
namespace redis {
template <typename Impl>
template <typename ...Args>
QueuedRedis<Impl>::QueuedRedis(const ConnectionSPtr &connection, Args &&...args) :
_connection(connection),
_impl(std::forward<Args>(args)...) {
assert(_connection);
}
template <typename Impl>
Redis QueuedRedis<Impl>::redis() {
return Redis(_connection);
}
template <typename Impl>
template <typename Cmd, typename ...Args>
auto QueuedRedis<Impl>::command(Cmd cmd, Args &&...args)
-> typename std::enable_if<!std::is_convertible<Cmd, StringView>::value,
QueuedRedis<Impl>&>::type {
try {
_sanity_check();
_impl.command(*_connection, cmd, std::forward<Args>(args)...);
++_cmd_num;
} catch (const Error &e) {
_invalidate();
throw;
}
return *this;
}
template <typename Impl>
template <typename ...Args>
QueuedRedis<Impl>& QueuedRedis<Impl>::command(const StringView &cmd_name, Args &&...args) {
auto cmd = [](Connection &connection, const StringView &cmd_name, Args &&...args) {
CmdArgs cmd_args;
cmd_args.append(cmd_name, std::forward<Args>(args)...);
connection.send(cmd_args);
};
return command(cmd, cmd_name, std::forward<Args>(args)...);
}
template <typename Impl>
template <typename Input>
auto QueuedRedis<Impl>::command(Input first, Input last)
-> typename std::enable_if<IsIter<Input>::value, QueuedRedis<Impl>&>::type {
if (first == last) {
throw Error("command: empty range");
}
auto cmd = [](Connection &connection, Input first, Input last) {
CmdArgs cmd_args;
while (first != last) {
cmd_args.append(*first);
++first;
}
connection.send(cmd_args);
};
return command(cmd, first, last);
}
template <typename Impl>
QueuedReplies QueuedRedis<Impl>::exec() {
try {
_sanity_check();
auto replies = _impl.exec(*_connection, _cmd_num);
_rewrite_replies(replies);
_reset();
return QueuedReplies(std::move(replies));
} catch (const Error &e) {
_invalidate();
throw;
}
}
template <typename Impl>
void QueuedRedis<Impl>::discard() {
try {
_sanity_check();
_impl.discard(*_connection, _cmd_num);
_reset();
} catch (const Error &e) {
_invalidate();
throw;
}
}
template <typename Impl>
void QueuedRedis<Impl>::_sanity_check() const {
if (!_valid) {
throw Error("Not in valid state");
}
if (_connection->broken()) {
throw Error("Connection is broken");
}
}
template <typename Impl>
inline void QueuedRedis<Impl>::_reset() {
_cmd_num = 0;
_set_cmd_indexes.clear();
_georadius_cmd_indexes.clear();
}
template <typename Impl>
void QueuedRedis<Impl>::_invalidate() {
_valid = false;
_reset();
}
template <typename Impl>
void QueuedRedis<Impl>::_rewrite_replies(std::vector<ReplyUPtr> &replies) const {
_rewrite_replies(_set_cmd_indexes, reply::rewrite_set_reply, replies);
_rewrite_replies(_georadius_cmd_indexes, reply::rewrite_georadius_reply, replies);
}
template <typename Impl>
template <typename Func>
void QueuedRedis<Impl>::_rewrite_replies(const std::vector<std::size_t> &indexes,
Func rewriter,
std::vector<ReplyUPtr> &replies) const {
for (auto idx : indexes) {
assert(idx < replies.size());
auto &reply = replies[idx];
assert(reply);
rewriter(*reply);
}
}
inline std::size_t QueuedReplies::size() const {
return _replies.size();
}
inline redisReply& QueuedReplies::get(std::size_t idx) {
_index_check(idx);
auto &reply = _replies[idx];
assert(reply);
return *reply;
}
template <typename Result>
inline Result QueuedReplies::get(std::size_t idx) {
auto &reply = get(idx);
return reply::parse<Result>(reply);
}
template <typename Output>
inline void QueuedReplies::get(std::size_t idx, Output output) {
auto &reply = get(idx);
reply::to_array(reply, output);
}
inline void QueuedReplies::_index_check(std::size_t idx) const {
if (idx >= size()) {
throw Error("Out of range");
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_QUEUED_REDIS_HPP

View file

@ -0,0 +1,25 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
#define SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H
#include "redis.h"
#include "redis_cluster.h"
#include "queued_redis.h"
#include "sentinel.h"
#endif // end SEWENEW_REDISPLUSPLUS_REDISPLUSPLUS_H

View file

@ -0,0 +1,882 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "redis.h"
#include <hiredis/hiredis.h>
#include "command.h"
#include "errors.h"
#include "queued_redis.h"
namespace sw {
namespace redis {
Redis::Redis(const std::string &uri) : Redis(ConnectionOptions(uri)) {}
Redis::Redis(const ConnectionSPtr &connection) : _connection(connection) {
assert(_connection);
}
Pipeline Redis::pipeline() {
return Pipeline(std::make_shared<Connection>(_pool.create()));
}
Transaction Redis::transaction(bool piped) {
return Transaction(std::make_shared<Connection>(_pool.create()), piped);
}
Subscriber Redis::subscriber() {
return Subscriber(_pool.create());
}
// CONNECTION commands.
void Redis::auth(const StringView &password) {
auto reply = command(cmd::auth, password);
reply::parse<void>(*reply);
}
std::string Redis::echo(const StringView &msg) {
auto reply = command(cmd::echo, msg);
return reply::parse<std::string>(*reply);
}
std::string Redis::ping() {
auto reply = command<void (*)(Connection &)>(cmd::ping);
return reply::to_status(*reply);
}
std::string Redis::ping(const StringView &msg) {
auto reply = command<void (*)(Connection &, const StringView &)>(cmd::ping, msg);
return reply::parse<std::string>(*reply);
}
void Redis::swapdb(long long idx1, long long idx2) {
auto reply = command(cmd::swapdb, idx1, idx2);
reply::parse<void>(*reply);
}
// SERVER commands.
void Redis::bgrewriteaof() {
auto reply = command(cmd::bgrewriteaof);
reply::parse<void>(*reply);
}
void Redis::bgsave() {
auto reply = command(cmd::bgsave);
reply::parse<void>(*reply);
}
long long Redis::dbsize() {
auto reply = command(cmd::dbsize);
return reply::parse<long long>(*reply);
}
void Redis::flushall(bool async) {
auto reply = command(cmd::flushall, async);
reply::parse<void>(*reply);
}
void Redis::flushdb(bool async) {
auto reply = command(cmd::flushdb, async);
reply::parse<void>(*reply);
}
std::string Redis::info() {
auto reply = command<void (*)(Connection &)>(cmd::info);
return reply::parse<std::string>(*reply);
}
std::string Redis::info(const StringView &section) {
auto reply = command<void (*)(Connection &, const StringView &)>(cmd::info, section);
return reply::parse<std::string>(*reply);
}
long long Redis::lastsave() {
auto reply = command(cmd::lastsave);
return reply::parse<long long>(*reply);
}
void Redis::save() {
auto reply = command(cmd::save);
reply::parse<void>(*reply);
}
// KEY commands.
long long Redis::del(const StringView &key) {
auto reply = command(cmd::del, key);
return reply::parse<long long>(*reply);
}
OptionalString Redis::dump(const StringView &key) {
auto reply = command(cmd::dump, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::exists(const StringView &key) {
auto reply = command(cmd::exists, key);
return reply::parse<long long>(*reply);
}
bool Redis::expire(const StringView &key, long long timeout) {
auto reply = command(cmd::expire, key, timeout);
return reply::parse<bool>(*reply);
}
bool Redis::expireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::expireat, key, timestamp);
return reply::parse<bool>(*reply);
}
bool Redis::move(const StringView &key, long long db) {
auto reply = command(cmd::move, key, db);
return reply::parse<bool>(*reply);
}
bool Redis::persist(const StringView &key) {
auto reply = command(cmd::persist, key);
return reply::parse<bool>(*reply);
}
bool Redis::pexpire(const StringView &key, long long timeout) {
auto reply = command(cmd::pexpire, key, timeout);
return reply::parse<bool>(*reply);
}
bool Redis::pexpireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::pexpireat, key, timestamp);
return reply::parse<bool>(*reply);
}
long long Redis::pttl(const StringView &key) {
auto reply = command(cmd::pttl, key);
return reply::parse<long long>(*reply);
}
OptionalString Redis::randomkey() {
auto reply = command(cmd::randomkey);
return reply::parse<OptionalString>(*reply);
}
void Redis::rename(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::rename, key, newkey);
reply::parse<void>(*reply);
}
bool Redis::renamenx(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::renamenx, key, newkey);
return reply::parse<bool>(*reply);
}
void Redis::restore(const StringView &key,
const StringView &val,
long long ttl,
bool replace) {
auto reply = command(cmd::restore, key, val, ttl, replace);
reply::parse<void>(*reply);
}
long long Redis::touch(const StringView &key) {
auto reply = command(cmd::touch, key);
return reply::parse<long long>(*reply);
}
long long Redis::ttl(const StringView &key) {
auto reply = command(cmd::ttl, key);
return reply::parse<long long>(*reply);
}
std::string Redis::type(const StringView &key) {
auto reply = command(cmd::type, key);
return reply::parse<std::string>(*reply);
}
long long Redis::unlink(const StringView &key) {
auto reply = command(cmd::unlink, key);
return reply::parse<long long>(*reply);
}
long long Redis::wait(long long numslaves, long long timeout) {
auto reply = command(cmd::wait, numslaves, timeout);
return reply::parse<long long>(*reply);
}
// STRING commands.
long long Redis::append(const StringView &key, const StringView &val) {
auto reply = command(cmd::append, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::bitcount(const StringView &key, long long start, long long end) {
auto reply = command(cmd::bitcount, key, start, end);
return reply::parse<long long>(*reply);
}
long long Redis::bitop(BitOp op, const StringView &destination, const StringView &key) {
auto reply = command(cmd::bitop, op, destination, key);
return reply::parse<long long>(*reply);
}
long long Redis::bitpos(const StringView &key,
long long bit,
long long start,
long long end) {
auto reply = command(cmd::bitpos, key, bit, start, end);
return reply::parse<long long>(*reply);
}
long long Redis::decr(const StringView &key) {
auto reply = command(cmd::decr, key);
return reply::parse<long long>(*reply);
}
long long Redis::decrby(const StringView &key, long long decrement) {
auto reply = command(cmd::decrby, key, decrement);
return reply::parse<long long>(*reply);
}
OptionalString Redis::get(const StringView &key) {
auto reply = command(cmd::get, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::getbit(const StringView &key, long long offset) {
auto reply = command(cmd::getbit, key, offset);
return reply::parse<long long>(*reply);
}
std::string Redis::getrange(const StringView &key, long long start, long long end) {
auto reply = command(cmd::getrange, key, start, end);
return reply::parse<std::string>(*reply);
}
OptionalString Redis::getset(const StringView &key, const StringView &val) {
auto reply = command(cmd::getset, key, val);
return reply::parse<OptionalString>(*reply);
}
long long Redis::incr(const StringView &key) {
auto reply = command(cmd::incr, key);
return reply::parse<long long>(*reply);
}
long long Redis::incrby(const StringView &key, long long increment) {
auto reply = command(cmd::incrby, key, increment);
return reply::parse<long long>(*reply);
}
double Redis::incrbyfloat(const StringView &key, double increment) {
auto reply = command(cmd::incrbyfloat, key, increment);
return reply::parse<double>(*reply);
}
void Redis::psetex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::psetex, key, ttl, val);
reply::parse<void>(*reply);
}
bool Redis::set(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type) {
auto reply = command(cmd::set, key, val, ttl.count(), type);
reply::rewrite_set_reply(*reply);
return reply::parse<bool>(*reply);
}
void Redis::setex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::setex, key, ttl, val);
reply::parse<void>(*reply);
}
bool Redis::setnx(const StringView &key, const StringView &val) {
auto reply = command(cmd::setnx, key, val);
return reply::parse<bool>(*reply);
}
long long Redis::setrange(const StringView &key, long long offset, const StringView &val) {
auto reply = command(cmd::setrange, key, offset, val);
return reply::parse<long long>(*reply);
}
long long Redis::strlen(const StringView &key) {
auto reply = command(cmd::strlen, key);
return reply::parse<long long>(*reply);
}
// LIST commands.
OptionalStringPair Redis::blpop(const StringView &key, long long timeout) {
auto reply = command(cmd::blpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair Redis::blpop(const StringView &key, const std::chrono::seconds &timeout) {
return blpop(key, timeout.count());
}
OptionalStringPair Redis::brpop(const StringView &key, long long timeout) {
auto reply = command(cmd::brpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair Redis::brpop(const StringView &key, const std::chrono::seconds &timeout) {
return brpop(key, timeout.count());
}
OptionalString Redis::brpoplpush(const StringView &source,
const StringView &destination,
long long timeout) {
auto reply = command(cmd::brpoplpush, source, destination, timeout);
return reply::parse<OptionalString>(*reply);
}
OptionalString Redis::lindex(const StringView &key, long long index) {
auto reply = command(cmd::lindex, key, index);
return reply::parse<OptionalString>(*reply);
}
long long Redis::linsert(const StringView &key,
InsertPosition position,
const StringView &pivot,
const StringView &val) {
auto reply = command(cmd::linsert, key, position, pivot, val);
return reply::parse<long long>(*reply);
}
long long Redis::llen(const StringView &key) {
auto reply = command(cmd::llen, key);
return reply::parse<long long>(*reply);
}
OptionalString Redis::lpop(const StringView &key) {
auto reply = command(cmd::lpop, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::lpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpush, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::lpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpushx, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::lrem(const StringView &key, long long count, const StringView &val) {
auto reply = command(cmd::lrem, key, count, val);
return reply::parse<long long>(*reply);
}
void Redis::lset(const StringView &key, long long index, const StringView &val) {
auto reply = command(cmd::lset, key, index, val);
reply::parse<void>(*reply);
}
void Redis::ltrim(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::ltrim, key, start, stop);
reply::parse<void>(*reply);
}
OptionalString Redis::rpop(const StringView &key) {
auto reply = command(cmd::rpop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString Redis::rpoplpush(const StringView &source, const StringView &destination) {
auto reply = command(cmd::rpoplpush, source, destination);
return reply::parse<OptionalString>(*reply);
}
long long Redis::rpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpush, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::rpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpushx, key, val);
return reply::parse<long long>(*reply);
}
long long Redis::hdel(const StringView &key, const StringView &field) {
auto reply = command(cmd::hdel, key, field);
return reply::parse<long long>(*reply);
}
bool Redis::hexists(const StringView &key, const StringView &field) {
auto reply = command(cmd::hexists, key, field);
return reply::parse<bool>(*reply);
}
OptionalString Redis::hget(const StringView &key, const StringView &field) {
auto reply = command(cmd::hget, key, field);
return reply::parse<OptionalString>(*reply);
}
long long Redis::hincrby(const StringView &key, const StringView &field, long long increment) {
auto reply = command(cmd::hincrby, key, field, increment);
return reply::parse<long long>(*reply);
}
double Redis::hincrbyfloat(const StringView &key, const StringView &field, double increment) {
auto reply = command(cmd::hincrbyfloat, key, field, increment);
return reply::parse<double>(*reply);
}
long long Redis::hlen(const StringView &key) {
auto reply = command(cmd::hlen, key);
return reply::parse<long long>(*reply);
}
bool Redis::hset(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hset, key, field, val);
return reply::parse<bool>(*reply);
}
bool Redis::hset(const StringView &key, const std::pair<StringView, StringView> &item) {
return hset(key, item.first, item.second);
}
bool Redis::hsetnx(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hsetnx, key, field, val);
return reply::parse<bool>(*reply);
}
bool Redis::hsetnx(const StringView &key, const std::pair<StringView, StringView> &item) {
return hsetnx(key, item.first, item.second);
}
long long Redis::hstrlen(const StringView &key, const StringView &field) {
auto reply = command(cmd::hstrlen, key, field);
return reply::parse<long long>(*reply);
}
// SET commands.
long long Redis::sadd(const StringView &key, const StringView &member) {
auto reply = command(cmd::sadd, key, member);
return reply::parse<long long>(*reply);
}
long long Redis::scard(const StringView &key) {
auto reply = command(cmd::scard, key);
return reply::parse<long long>(*reply);
}
long long Redis::sdiffstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sdiffstore, destination, key);
return reply::parse<long long>(*reply);
}
long long Redis::sinterstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sinterstore, destination, key);
return reply::parse<long long>(*reply);
}
bool Redis::sismember(const StringView &key, const StringView &member) {
auto reply = command(cmd::sismember, key, member);
return reply::parse<bool>(*reply);
}
bool Redis::smove(const StringView &source,
const StringView &destination,
const StringView &member) {
auto reply = command(cmd::smove, source, destination, member);
return reply::parse<bool>(*reply);
}
OptionalString Redis::spop(const StringView &key) {
auto reply = command(cmd::spop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString Redis::srandmember(const StringView &key) {
auto reply = command(cmd::srandmember, key);
return reply::parse<OptionalString>(*reply);
}
long long Redis::srem(const StringView &key, const StringView &member) {
auto reply = command(cmd::srem, key, member);
return reply::parse<long long>(*reply);
}
long long Redis::sunionstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sunionstore, destination, key);
return reply::parse<long long>(*reply);
}
// SORTED SET commands.
auto Redis::bzpopmax(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmax, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
auto Redis::bzpopmin(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmin, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
long long Redis::zadd(const StringView &key,
const StringView &member,
double score,
UpdateType type,
bool changed) {
auto reply = command(cmd::zadd, key, member, score, type, changed);
return reply::parse<long long>(*reply);
}
long long Redis::zcard(const StringView &key) {
auto reply = command(cmd::zcard, key);
return reply::parse<long long>(*reply);
}
double Redis::zincrby(const StringView &key, double increment, const StringView &member) {
auto reply = command(cmd::zincrby, key, increment, member);
return reply::parse<double>(*reply);
}
long long Redis::zinterstore(const StringView &destination, const StringView &key, double weight) {
auto reply = command(cmd::zinterstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
Optional<std::pair<std::string, double>> Redis::zpopmax(const StringView &key) {
auto reply = command(cmd::zpopmax, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
Optional<std::pair<std::string, double>> Redis::zpopmin(const StringView &key) {
auto reply = command(cmd::zpopmin, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
OptionalLongLong Redis::zrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
long long Redis::zrem(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrem, key, member);
return reply::parse<long long>(*reply);
}
long long Redis::zremrangebyrank(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::zremrangebyrank, key, start, stop);
return reply::parse<long long>(*reply);
}
OptionalLongLong Redis::zrevrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrevrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalDouble Redis::zscore(const StringView &key, const StringView &member) {
auto reply = command(cmd::zscore, key, member);
return reply::parse<OptionalDouble>(*reply);
}
long long Redis::zunionstore(const StringView &destination, const StringView &key, double weight) {
auto reply = command(cmd::zunionstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
// HYPERLOGLOG commands.
bool Redis::pfadd(const StringView &key, const StringView &element) {
auto reply = command(cmd::pfadd, key, element);
return reply::parse<bool>(*reply);
}
long long Redis::pfcount(const StringView &key) {
auto reply = command(cmd::pfcount, key);
return reply::parse<long long>(*reply);
}
void Redis::pfmerge(const StringView &destination, const StringView &key) {
auto reply = command(cmd::pfmerge, destination, key);
reply::parse<void>(*reply);
}
// GEO commands.
long long Redis::geoadd(const StringView &key,
const std::tuple<StringView, double, double> &member) {
auto reply = command(cmd::geoadd, key, member);
return reply::parse<long long>(*reply);
}
OptionalDouble Redis::geodist(const StringView &key,
const StringView &member1,
const StringView &member2,
GeoUnit unit) {
auto reply = command(cmd::geodist, key, member1, member2, unit);
return reply::parse<OptionalDouble>(*reply);
}
OptionalLongLong Redis::georadius(const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadius_store,
key,
loc,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalLongLong Redis::georadiusbymember(const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadiusbymember_store,
key,
member,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
// SCRIPTING commands.
void Redis::script_flush() {
auto reply = command(cmd::script_flush);
reply::parse<void>(*reply);
}
void Redis::script_kill() {
auto reply = command(cmd::script_kill);
reply::parse<void>(*reply);
}
std::string Redis::script_load(const StringView &script) {
auto reply = command(cmd::script_load, script);
return reply::parse<std::string>(*reply);
}
// PUBSUB commands.
long long Redis::publish(const StringView &channel, const StringView &message) {
auto reply = command(cmd::publish, channel, message);
return reply::parse<long long>(*reply);
}
// Transaction commands.
void Redis::watch(const StringView &key) {
auto reply = command(cmd::watch, key);
reply::parse<void>(*reply);
}
// Stream commands.
long long Redis::xack(const StringView &key, const StringView &group, const StringView &id) {
auto reply = command(cmd::xack, key, group, id);
return reply::parse<long long>(*reply);
}
long long Redis::xdel(const StringView &key, const StringView &id) {
auto reply = command(cmd::xdel, key, id);
return reply::parse<long long>(*reply);
}
void Redis::xgroup_create(const StringView &key,
const StringView &group,
const StringView &id,
bool mkstream) {
auto reply = command(cmd::xgroup_create, key, group, id, mkstream);
reply::parse<void>(*reply);
}
void Redis::xgroup_setid(const StringView &key, const StringView &group, const StringView &id) {
auto reply = command(cmd::xgroup_setid, key, group, id);
reply::parse<void>(*reply);
}
long long Redis::xgroup_destroy(const StringView &key, const StringView &group) {
auto reply = command(cmd::xgroup_destroy, key, group);
return reply::parse<long long>(*reply);
}
long long Redis::xgroup_delconsumer(const StringView &key,
const StringView &group,
const StringView &consumer) {
auto reply = command(cmd::xgroup_delconsumer, key, group, consumer);
return reply::parse<long long>(*reply);
}
long long Redis::xlen(const StringView &key) {
auto reply = command(cmd::xlen, key);
return reply::parse<long long>(*reply);
}
long long Redis::xtrim(const StringView &key, long long count, bool approx) {
auto reply = command(cmd::xtrim, key, count, approx);
return reply::parse<long long>(*reply);
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,769 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "redis_cluster.h"
#include <hiredis/hiredis.h>
#include "command.h"
#include "errors.h"
#include "queued_redis.h"
namespace sw {
namespace redis {
RedisCluster::RedisCluster(const std::string &uri) : RedisCluster(ConnectionOptions(uri)) {}
Redis RedisCluster::redis(const StringView &hash_tag) {
auto opts = _pool.connection_options(hash_tag);
return Redis(std::make_shared<Connection>(opts));
}
Pipeline RedisCluster::pipeline(const StringView &hash_tag) {
auto opts = _pool.connection_options(hash_tag);
return Pipeline(std::make_shared<Connection>(opts));
}
Transaction RedisCluster::transaction(const StringView &hash_tag, bool piped) {
auto opts = _pool.connection_options(hash_tag);
return Transaction(std::make_shared<Connection>(opts), piped);
}
Subscriber RedisCluster::subscriber() {
auto opts = _pool.connection_options();
return Subscriber(Connection(opts));
}
// KEY commands.
long long RedisCluster::del(const StringView &key) {
auto reply = command(cmd::del, key);
return reply::parse<long long>(*reply);
}
OptionalString RedisCluster::dump(const StringView &key) {
auto reply = command(cmd::dump, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::exists(const StringView &key) {
auto reply = command(cmd::exists, key);
return reply::parse<long long>(*reply);
}
bool RedisCluster::expire(const StringView &key, long long timeout) {
auto reply = command(cmd::expire, key, timeout);
return reply::parse<bool>(*reply);
}
bool RedisCluster::expireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::expireat, key, timestamp);
return reply::parse<bool>(*reply);
}
bool RedisCluster::persist(const StringView &key) {
auto reply = command(cmd::persist, key);
return reply::parse<bool>(*reply);
}
bool RedisCluster::pexpire(const StringView &key, long long timeout) {
auto reply = command(cmd::pexpire, key, timeout);
return reply::parse<bool>(*reply);
}
bool RedisCluster::pexpireat(const StringView &key, long long timestamp) {
auto reply = command(cmd::pexpireat, key, timestamp);
return reply::parse<bool>(*reply);
}
long long RedisCluster::pttl(const StringView &key) {
auto reply = command(cmd::pttl, key);
return reply::parse<long long>(*reply);
}
void RedisCluster::rename(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::rename, key, newkey);
reply::parse<void>(*reply);
}
bool RedisCluster::renamenx(const StringView &key, const StringView &newkey) {
auto reply = command(cmd::renamenx, key, newkey);
return reply::parse<bool>(*reply);
}
void RedisCluster::restore(const StringView &key,
const StringView &val,
long long ttl,
bool replace) {
auto reply = command(cmd::restore, key, val, ttl, replace);
reply::parse<void>(*reply);
}
long long RedisCluster::touch(const StringView &key) {
auto reply = command(cmd::touch, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::ttl(const StringView &key) {
auto reply = command(cmd::ttl, key);
return reply::parse<long long>(*reply);
}
std::string RedisCluster::type(const StringView &key) {
auto reply = command(cmd::type, key);
return reply::parse<std::string>(*reply);
}
long long RedisCluster::unlink(const StringView &key) {
auto reply = command(cmd::unlink, key);
return reply::parse<long long>(*reply);
}
// STRING commands.
long long RedisCluster::append(const StringView &key, const StringView &val) {
auto reply = command(cmd::append, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::bitcount(const StringView &key, long long start, long long end) {
auto reply = command(cmd::bitcount, key, start, end);
return reply::parse<long long>(*reply);
}
long long RedisCluster::bitop(BitOp op, const StringView &destination, const StringView &key) {
auto reply = _command(cmd::bitop, destination, op, destination, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::bitpos(const StringView &key,
long long bit,
long long start,
long long end) {
auto reply = command(cmd::bitpos, key, bit, start, end);
return reply::parse<long long>(*reply);
}
long long RedisCluster::decr(const StringView &key) {
auto reply = command(cmd::decr, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::decrby(const StringView &key, long long decrement) {
auto reply = command(cmd::decrby, key, decrement);
return reply::parse<long long>(*reply);
}
OptionalString RedisCluster::get(const StringView &key) {
auto reply = command(cmd::get, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::getbit(const StringView &key, long long offset) {
auto reply = command(cmd::getbit, key, offset);
return reply::parse<long long>(*reply);
}
std::string RedisCluster::getrange(const StringView &key, long long start, long long end) {
auto reply = command(cmd::getrange, key, start, end);
return reply::parse<std::string>(*reply);
}
OptionalString RedisCluster::getset(const StringView &key, const StringView &val) {
auto reply = command(cmd::getset, key, val);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::incr(const StringView &key) {
auto reply = command(cmd::incr, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::incrby(const StringView &key, long long increment) {
auto reply = command(cmd::incrby, key, increment);
return reply::parse<long long>(*reply);
}
double RedisCluster::incrbyfloat(const StringView &key, double increment) {
auto reply = command(cmd::incrbyfloat, key, increment);
return reply::parse<double>(*reply);
}
void RedisCluster::psetex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::psetex, key, ttl, val);
reply::parse<void>(*reply);
}
bool RedisCluster::set(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl,
UpdateType type) {
auto reply = command(cmd::set, key, val, ttl.count(), type);
reply::rewrite_set_reply(*reply);
return reply::parse<bool>(*reply);
}
void RedisCluster::setex(const StringView &key,
long long ttl,
const StringView &val) {
auto reply = command(cmd::setex, key, ttl, val);
reply::parse<void>(*reply);
}
bool RedisCluster::setnx(const StringView &key, const StringView &val) {
auto reply = command(cmd::setnx, key, val);
return reply::parse<bool>(*reply);
}
long long RedisCluster::setrange(const StringView &key, long long offset, const StringView &val) {
auto reply = command(cmd::setrange, key, offset, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::strlen(const StringView &key) {
auto reply = command(cmd::strlen, key);
return reply::parse<long long>(*reply);
}
// LIST commands.
OptionalStringPair RedisCluster::blpop(const StringView &key, long long timeout) {
auto reply = command(cmd::blpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair RedisCluster::blpop(const StringView &key, const std::chrono::seconds &timeout) {
return blpop(key, timeout.count());
}
OptionalStringPair RedisCluster::brpop(const StringView &key, long long timeout) {
auto reply = command(cmd::brpop, key, timeout);
return reply::parse<OptionalStringPair>(*reply);
}
OptionalStringPair RedisCluster::brpop(const StringView &key, const std::chrono::seconds &timeout) {
return brpop(key, timeout.count());
}
OptionalString RedisCluster::brpoplpush(const StringView &source,
const StringView &destination,
long long timeout) {
auto reply = command(cmd::brpoplpush, source, destination, timeout);
return reply::parse<OptionalString>(*reply);
}
OptionalString RedisCluster::lindex(const StringView &key, long long index) {
auto reply = command(cmd::lindex, key, index);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::linsert(const StringView &key,
InsertPosition position,
const StringView &pivot,
const StringView &val) {
auto reply = command(cmd::linsert, key, position, pivot, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::llen(const StringView &key) {
auto reply = command(cmd::llen, key);
return reply::parse<long long>(*reply);
}
OptionalString RedisCluster::lpop(const StringView &key) {
auto reply = command(cmd::lpop, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::lpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpush, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::lpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::lpushx, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::lrem(const StringView &key, long long count, const StringView &val) {
auto reply = command(cmd::lrem, key, count, val);
return reply::parse<long long>(*reply);
}
void RedisCluster::lset(const StringView &key, long long index, const StringView &val) {
auto reply = command(cmd::lset, key, index, val);
reply::parse<void>(*reply);
}
void RedisCluster::ltrim(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::ltrim, key, start, stop);
reply::parse<void>(*reply);
}
OptionalString RedisCluster::rpop(const StringView &key) {
auto reply = command(cmd::rpop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString RedisCluster::rpoplpush(const StringView &source, const StringView &destination) {
auto reply = command(cmd::rpoplpush, source, destination);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::rpush(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpush, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::rpushx(const StringView &key, const StringView &val) {
auto reply = command(cmd::rpushx, key, val);
return reply::parse<long long>(*reply);
}
long long RedisCluster::hdel(const StringView &key, const StringView &field) {
auto reply = command(cmd::hdel, key, field);
return reply::parse<long long>(*reply);
}
bool RedisCluster::hexists(const StringView &key, const StringView &field) {
auto reply = command(cmd::hexists, key, field);
return reply::parse<bool>(*reply);
}
OptionalString RedisCluster::hget(const StringView &key, const StringView &field) {
auto reply = command(cmd::hget, key, field);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::hincrby(const StringView &key, const StringView &field, long long increment) {
auto reply = command(cmd::hincrby, key, field, increment);
return reply::parse<long long>(*reply);
}
double RedisCluster::hincrbyfloat(const StringView &key, const StringView &field, double increment) {
auto reply = command(cmd::hincrbyfloat, key, field, increment);
return reply::parse<double>(*reply);
}
long long RedisCluster::hlen(const StringView &key) {
auto reply = command(cmd::hlen, key);
return reply::parse<long long>(*reply);
}
bool RedisCluster::hset(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hset, key, field, val);
return reply::parse<bool>(*reply);
}
bool RedisCluster::hset(const StringView &key, const std::pair<StringView, StringView> &item) {
return hset(key, item.first, item.second);
}
bool RedisCluster::hsetnx(const StringView &key, const StringView &field, const StringView &val) {
auto reply = command(cmd::hsetnx, key, field, val);
return reply::parse<bool>(*reply);
}
bool RedisCluster::hsetnx(const StringView &key, const std::pair<StringView, StringView> &item) {
return hsetnx(key, item.first, item.second);
}
long long RedisCluster::hstrlen(const StringView &key, const StringView &field) {
auto reply = command(cmd::hstrlen, key, field);
return reply::parse<long long>(*reply);
}
// SET commands.
long long RedisCluster::sadd(const StringView &key, const StringView &member) {
auto reply = command(cmd::sadd, key, member);
return reply::parse<long long>(*reply);
}
long long RedisCluster::scard(const StringView &key) {
auto reply = command(cmd::scard, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::sdiffstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sdiffstore, destination, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::sinterstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sinterstore, destination, key);
return reply::parse<long long>(*reply);
}
bool RedisCluster::sismember(const StringView &key, const StringView &member) {
auto reply = command(cmd::sismember, key, member);
return reply::parse<bool>(*reply);
}
bool RedisCluster::smove(const StringView &source,
const StringView &destination,
const StringView &member) {
auto reply = command(cmd::smove, source, destination, member);
return reply::parse<bool>(*reply);
}
OptionalString RedisCluster::spop(const StringView &key) {
auto reply = command(cmd::spop, key);
return reply::parse<OptionalString>(*reply);
}
OptionalString RedisCluster::srandmember(const StringView &key) {
auto reply = command(cmd::srandmember, key);
return reply::parse<OptionalString>(*reply);
}
long long RedisCluster::srem(const StringView &key, const StringView &member) {
auto reply = command(cmd::srem, key, member);
return reply::parse<long long>(*reply);
}
long long RedisCluster::sunionstore(const StringView &destination, const StringView &key) {
auto reply = command(cmd::sunionstore, destination, key);
return reply::parse<long long>(*reply);
}
// SORTED SET commands.
auto RedisCluster::bzpopmax(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmax, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
auto RedisCluster::bzpopmin(const StringView &key, long long timeout)
-> Optional<std::tuple<std::string, std::string, double>> {
auto reply = command(cmd::bzpopmin, key, timeout);
return reply::parse<Optional<std::tuple<std::string, std::string, double>>>(*reply);
}
long long RedisCluster::zadd(const StringView &key,
const StringView &member,
double score,
UpdateType type,
bool changed) {
auto reply = command(cmd::zadd, key, member, score, type, changed);
return reply::parse<long long>(*reply);
}
long long RedisCluster::zcard(const StringView &key) {
auto reply = command(cmd::zcard, key);
return reply::parse<long long>(*reply);
}
double RedisCluster::zincrby(const StringView &key, double increment, const StringView &member) {
auto reply = command(cmd::zincrby, key, increment, member);
return reply::parse<double>(*reply);
}
long long RedisCluster::zinterstore(const StringView &destination,
const StringView &key,
double weight) {
auto reply = command(cmd::zinterstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
Optional<std::pair<std::string, double>> RedisCluster::zpopmax(const StringView &key) {
auto reply = command(cmd::zpopmax, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
Optional<std::pair<std::string, double>> RedisCluster::zpopmin(const StringView &key) {
auto reply = command(cmd::zpopmin, key, 1);
return reply::parse<Optional<std::pair<std::string, double>>>(*reply);
}
OptionalLongLong RedisCluster::zrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
long long RedisCluster::zrem(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrem, key, member);
return reply::parse<long long>(*reply);
}
long long RedisCluster::zremrangebyrank(const StringView &key, long long start, long long stop) {
auto reply = command(cmd::zremrangebyrank, key, start, stop);
return reply::parse<long long>(*reply);
}
OptionalLongLong RedisCluster::zrevrank(const StringView &key, const StringView &member) {
auto reply = command(cmd::zrevrank, key, member);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalDouble RedisCluster::zscore(const StringView &key, const StringView &member) {
auto reply = command(cmd::zscore, key, member);
return reply::parse<OptionalDouble>(*reply);
}
long long RedisCluster::zunionstore(const StringView &destination,
const StringView &key,
double weight) {
auto reply = command(cmd::zunionstore, destination, key, weight);
return reply::parse<long long>(*reply);
}
// HYPERLOGLOG commands.
bool RedisCluster::pfadd(const StringView &key, const StringView &element) {
auto reply = command(cmd::pfadd, key, element);
return reply::parse<bool>(*reply);
}
long long RedisCluster::pfcount(const StringView &key) {
auto reply = command(cmd::pfcount, key);
return reply::parse<long long>(*reply);
}
void RedisCluster::pfmerge(const StringView &destination, const StringView &key) {
auto reply = command(cmd::pfmerge, destination, key);
reply::parse<void>(*reply);
}
// GEO commands.
long long RedisCluster::geoadd(const StringView &key,
const std::tuple<StringView, double, double> &member) {
auto reply = command(cmd::geoadd, key, member);
return reply::parse<long long>(*reply);
}
OptionalDouble RedisCluster::geodist(const StringView &key,
const StringView &member1,
const StringView &member2,
GeoUnit unit) {
auto reply = command(cmd::geodist, key, member1, member2, unit);
return reply::parse<OptionalDouble>(*reply);
}
OptionalLongLong RedisCluster::georadius(const StringView &key,
const std::pair<double, double> &loc,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadius_store,
key,
loc,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
OptionalLongLong RedisCluster::georadiusbymember(const StringView &key,
const StringView &member,
double radius,
GeoUnit unit,
const StringView &destination,
bool store_dist,
long long count) {
auto reply = command(cmd::georadiusbymember_store,
key,
member,
radius,
unit,
destination,
store_dist,
count);
reply::rewrite_georadius_reply(*reply);
return reply::parse<OptionalLongLong>(*reply);
}
// PUBSUB commands.
long long RedisCluster::publish(const StringView &channel, const StringView &message) {
auto reply = command(cmd::publish, channel, message);
return reply::parse<long long>(*reply);
}
// Stream commands.
long long RedisCluster::xack(const StringView &key, const StringView &group, const StringView &id) {
auto reply = command(cmd::xack, key, group, id);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xdel(const StringView &key, const StringView &id) {
auto reply = command(cmd::xdel, key, id);
return reply::parse<long long>(*reply);
}
void RedisCluster::xgroup_create(const StringView &key,
const StringView &group,
const StringView &id,
bool mkstream) {
auto reply = command(cmd::xgroup_create, key, group, id, mkstream);
reply::parse<void>(*reply);
}
void RedisCluster::xgroup_setid(const StringView &key,
const StringView &group,
const StringView &id) {
auto reply = command(cmd::xgroup_setid, key, group, id);
reply::parse<void>(*reply);
}
long long RedisCluster::xgroup_destroy(const StringView &key, const StringView &group) {
auto reply = command(cmd::xgroup_destroy, key, group);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xgroup_delconsumer(const StringView &key,
const StringView &group,
const StringView &consumer) {
auto reply = command(cmd::xgroup_delconsumer, key, group, consumer);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xlen(const StringView &key) {
auto reply = command(cmd::xlen, key);
return reply::parse<long long>(*reply);
}
long long RedisCluster::xtrim(const StringView &key, long long count, bool approx) {
auto reply = command(cmd::xtrim, key, count, approx);
return reply::parse<long long>(*reply);
}
void RedisCluster::_asking(Connection &connection) {
// Send ASKING command.
connection.send("ASKING");
auto reply = connection.recv();
assert(reply);
reply::parse<void>(*reply);
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,150 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "reply.h"
namespace sw {
namespace redis {
namespace reply {
std::string to_status(redisReply &reply) {
if (!reply::is_status(reply)) {
throw ProtoError("Expect STATUS reply");
}
if (reply.str == nullptr) {
throw ProtoError("A null status reply");
}
// Old version hiredis' *redisReply::len* is of type int.
// So we CANNOT have something like: *return {reply.str, reply.len}*.
return std::string(reply.str, reply.len);
}
std::string parse(ParseTag<std::string>, redisReply &reply) {
if (!reply::is_string(reply) && !reply::is_status(reply)) {
throw ProtoError("Expect STRING reply");
}
if (reply.str == nullptr) {
throw ProtoError("A null string reply");
}
// Old version hiredis' *redisReply::len* is of type int.
// So we CANNOT have something like: *return {reply.str, reply.len}*.
return std::string(reply.str, reply.len);
}
long long parse(ParseTag<long long>, redisReply &reply) {
if (!reply::is_integer(reply)) {
throw ProtoError("Expect INTEGER reply");
}
return reply.integer;
}
double parse(ParseTag<double>, redisReply &reply) {
return std::stod(parse<std::string>(reply));
}
bool parse(ParseTag<bool>, redisReply &reply) {
auto ret = parse<long long>(reply);
if (ret == 1) {
return true;
} else if (ret == 0) {
return false;
} else {
throw ProtoError("Invalid bool reply: " + std::to_string(ret));
}
}
void parse(ParseTag<void>, redisReply &reply) {
if (!reply::is_status(reply)) {
throw ProtoError("Expect STATUS reply");
}
if (reply.str == nullptr) {
throw ProtoError("A null status reply");
}
static const std::string OK = "OK";
// Old version hiredis' *redisReply::len* is of type int.
// So we have to cast it to an unsigned int.
if (static_cast<std::size_t>(reply.len) != OK.size()
|| OK.compare(0, OK.size(), reply.str, reply.len) != 0) {
throw ProtoError("NOT ok status reply: " + reply::to_status(reply));
}
}
void rewrite_set_reply(redisReply &reply) {
if (is_nil(reply)) {
// Failed to set, and make it a FALSE reply.
reply.type = REDIS_REPLY_INTEGER;
reply.integer = 0;
return;
}
// Check if it's a "OK" status reply.
reply::parse<void>(reply);
assert(is_status(reply) && reply.str != nullptr);
free(reply.str);
// Make it a TRUE reply.
reply.type = REDIS_REPLY_INTEGER;
reply.integer = 1;
}
void rewrite_georadius_reply(redisReply &reply) {
if (is_array(reply) && reply.element == nullptr) {
// Make it a nil reply.
reply.type = REDIS_REPLY_NIL;
}
}
namespace detail {
bool is_flat_array(redisReply &reply) {
assert(reply::is_array(reply));
// Empty array reply.
if (reply.element == nullptr || reply.elements == 0) {
return false;
}
auto *sub_reply = reply.element[0];
// Null element.
if (sub_reply == nullptr) {
return false;
}
return !reply::is_array(*sub_reply);
}
}
}
}
}

View file

@ -0,0 +1,363 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_REPLY_H
#define SEWENEW_REDISPLUSPLUS_REPLY_H
#include <cassert>
#include <string>
#include <memory>
#include <functional>
#include <tuple>
#include <hiredis/hiredis.h>
#include "errors.h"
#include "utils.h"
namespace sw {
namespace redis {
struct ReplyDeleter {
void operator()(redisReply *reply) const {
if (reply != nullptr) {
freeReplyObject(reply);
}
}
};
using ReplyUPtr = std::unique_ptr<redisReply, ReplyDeleter>;
namespace reply {
template <typename T>
struct ParseTag {};
template <typename T>
inline T parse(redisReply &reply) {
return parse(ParseTag<T>(), reply);
}
void parse(ParseTag<void>, redisReply &reply);
std::string parse(ParseTag<std::string>, redisReply &reply);
long long parse(ParseTag<long long>, redisReply &reply);
double parse(ParseTag<double>, redisReply &reply);
bool parse(ParseTag<bool>, redisReply &reply);
template <typename T>
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply);
template <typename T, typename U>
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply);
template <typename ...Args>
std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply);
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type = 0>
T parse(ParseTag<T>, redisReply &reply);
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type = 0>
T parse(ParseTag<T>, redisReply &reply);
template <typename Output>
long long parse_scan_reply(redisReply &reply, Output output);
inline bool is_error(redisReply &reply) {
return reply.type == REDIS_REPLY_ERROR;
}
inline bool is_nil(redisReply &reply) {
return reply.type == REDIS_REPLY_NIL;
}
inline bool is_string(redisReply &reply) {
return reply.type == REDIS_REPLY_STRING;
}
inline bool is_status(redisReply &reply) {
return reply.type == REDIS_REPLY_STATUS;
}
inline bool is_integer(redisReply &reply) {
return reply.type == REDIS_REPLY_INTEGER;
}
inline bool is_array(redisReply &reply) {
return reply.type == REDIS_REPLY_ARRAY;
}
std::string to_status(redisReply &reply);
template <typename Output>
void to_array(redisReply &reply, Output output);
// Rewrite set reply to bool type
void rewrite_set_reply(redisReply &reply);
// Rewrite georadius reply to OptionalLongLong type
void rewrite_georadius_reply(redisReply &reply);
template <typename Output>
auto parse_xpending_reply(redisReply &reply, Output output)
-> std::tuple<long long, OptionalString, OptionalString>;
}
// Inline implementations.
namespace reply {
namespace detail {
template <typename Output>
void to_array(redisReply &reply, Output output) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.element == nullptr) {
// Empty array.
return;
}
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
auto *sub_reply = reply.element[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null array element reply");
}
*output = parse<typename IterType<Output>::type>(*sub_reply);
++output;
}
}
bool is_flat_array(redisReply &reply);
template <typename Output>
void to_flat_array(redisReply &reply, Output output) {
if (reply.element == nullptr) {
// Empty array.
return;
}
if (reply.elements % 2 != 0) {
throw ProtoError("Not string pair array reply");
}
for (std::size_t idx = 0; idx != reply.elements; idx += 2) {
auto *key_reply = reply.element[idx];
auto *val_reply = reply.element[idx + 1];
if (key_reply == nullptr || val_reply == nullptr) {
throw ProtoError("Null string array reply");
}
using Pair = typename IterType<Output>::type;
using FirstType = typename std::decay<typename Pair::first_type>::type;
using SecondType = typename std::decay<typename Pair::second_type>::type;
*output = std::make_pair(parse<FirstType>(*key_reply),
parse<SecondType>(*val_reply));
++output;
}
}
template <typename Output>
void to_array(std::true_type, redisReply &reply, Output output) {
if (is_flat_array(reply)) {
to_flat_array(reply, output);
} else {
to_array(reply, output);
}
}
template <typename Output>
void to_array(std::false_type, redisReply &reply, Output output) {
to_array(reply, output);
}
template <typename T>
std::tuple<T> parse_tuple(redisReply **reply, std::size_t idx) {
assert(reply != nullptr);
auto *sub_reply = reply[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null reply");
}
return std::make_tuple(parse<T>(*sub_reply));
}
template <typename T, typename ...Args>
auto parse_tuple(redisReply **reply, std::size_t idx) ->
typename std::enable_if<sizeof...(Args) != 0, std::tuple<T, Args...>>::type {
assert(reply != nullptr);
return std::tuple_cat(parse_tuple<T>(reply, idx),
parse_tuple<Args...>(reply, idx + 1));
}
}
template <typename T>
Optional<T> parse(ParseTag<Optional<T>>, redisReply &reply) {
if (reply::is_nil(reply)) {
return {};
}
return Optional<T>(parse<T>(reply));
}
template <typename T, typename U>
std::pair<T, U> parse(ParseTag<std::pair<T, U>>, redisReply &reply) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.elements != 2) {
throw ProtoError("NOT key-value PAIR reply");
}
if (reply.element == nullptr) {
throw ProtoError("Null PAIR reply");
}
auto *first = reply.element[0];
auto *second = reply.element[1];
if (first == nullptr || second == nullptr) {
throw ProtoError("Null pair reply");
}
return std::make_pair(parse<typename std::decay<T>::type>(*first),
parse<typename std::decay<U>::type>(*second));
}
template <typename ...Args>
std::tuple<Args...> parse(ParseTag<std::tuple<Args...>>, redisReply &reply) {
constexpr auto size = sizeof...(Args);
static_assert(size > 0, "DO NOT support parsing tuple with 0 element");
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.elements != size) {
throw ProtoError("Expect tuple reply with " + std::to_string(size) + "elements");
}
if (reply.element == nullptr) {
throw ProtoError("Null TUPLE reply");
}
return detail::parse_tuple<Args...>(reply.element, 0);
}
template <typename T, typename std::enable_if<IsSequenceContainer<T>::value, int>::type>
T parse(ParseTag<T>, redisReply &reply) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
T container;
to_array(reply, std::back_inserter(container));
return container;
}
template <typename T, typename std::enable_if<IsAssociativeContainer<T>::value, int>::type>
T parse(ParseTag<T>, redisReply &reply) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
T container;
to_array(reply, std::inserter(container, container.end()));
return container;
}
template <typename Output>
long long parse_scan_reply(redisReply &reply, Output output) {
if (reply.elements != 2 || reply.element == nullptr) {
throw ProtoError("Invalid scan reply");
}
auto *cursor_reply = reply.element[0];
auto *data_reply = reply.element[1];
if (cursor_reply == nullptr || data_reply == nullptr) {
throw ProtoError("Invalid cursor reply or data reply");
}
auto cursor_str = reply::parse<std::string>(*cursor_reply);
auto new_cursor = 0;
try {
new_cursor = std::stoll(cursor_str);
} catch (const std::exception &e) {
throw ProtoError("Invalid cursor reply: " + cursor_str);
}
reply::to_array(*data_reply, output);
return new_cursor;
}
template <typename Output>
void to_array(redisReply &reply, Output output) {
if (!is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
detail::to_array(typename IsKvPairIter<Output>::type(), reply, output);
}
template <typename Output>
auto parse_xpending_reply(redisReply &reply, Output output)
-> std::tuple<long long, OptionalString, OptionalString> {
if (!is_array(reply) || reply.elements != 4) {
throw ProtoError("expect array reply with 4 elements");
}
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
if (reply.element[idx] == nullptr) {
throw ProtoError("null array reply");
}
}
auto num = parse<long long>(*(reply.element[0]));
auto start = parse<OptionalString>(*(reply.element[1]));
auto end = parse<OptionalString>(*(reply.element[2]));
auto &entry_reply = *(reply.element[3]);
if (!is_nil(entry_reply)) {
to_array(entry_reply, output);
}
return std::make_tuple(num, std::move(start), std::move(end));
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_REPLY_H

View file

@ -0,0 +1,361 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "sentinel.h"
#include <cassert>
#include <thread>
#include <random>
#include <algorithm>
#include "redis.h"
#include "errors.h"
namespace sw {
namespace redis {
class Sentinel::Iterator {
public:
Iterator(std::list<Connection> &healthy_sentinels,
std::list<ConnectionOptions> &broken_sentinels) :
_healthy_sentinels(healthy_sentinels),
_broken_sentinels(broken_sentinels) {
reset();
}
Connection& next();
void reset();
private:
std::list<Connection> &_healthy_sentinels;
std::size_t _healthy_size = 0;
std::list<ConnectionOptions> &_broken_sentinels;
std::size_t _broken_size = 0;
};
Connection& Sentinel::Iterator::next() {
while (_healthy_size > 0) {
assert(_healthy_sentinels.size() >= _healthy_size);
--_healthy_size;
auto &connection = _healthy_sentinels.front();
if (connection.broken()) {
_broken_sentinels.push_front(connection.options());
++_broken_size;
_healthy_sentinels.pop_front();
} else {
_healthy_sentinels.splice(_healthy_sentinels.end(),
_healthy_sentinels,
_healthy_sentinels.begin());
return _healthy_sentinels.back();
}
}
while (_broken_size > 0) {
assert(_broken_sentinels.size() >= _broken_size);
--_broken_size;
try {
const auto &opt = _broken_sentinels.front();
Connection connection(opt);
_healthy_sentinels.push_back(std::move(connection));
_broken_sentinels.pop_front();
return _healthy_sentinels.back();
} catch (const Error &e) {
// Failed to connect to sentinel.
_broken_sentinels.splice(_broken_sentinels.end(),
_broken_sentinels,
_broken_sentinels.begin());
}
}
throw StopIterError();
}
void Sentinel::Iterator::reset() {
_healthy_size = _healthy_sentinels.size();
_broken_size = _broken_sentinels.size();
}
Sentinel::Sentinel(const SentinelOptions &sentinel_opts) :
_broken_sentinels(_parse_options(sentinel_opts)),
_sentinel_opts(sentinel_opts) {
if (_sentinel_opts.connect_timeout == std::chrono::milliseconds(0)
|| _sentinel_opts.socket_timeout == std::chrono::milliseconds(0)) {
throw Error("With sentinel, connection timeout and socket timeout cannot be 0");
}
}
Connection Sentinel::master(const std::string &master_name, const ConnectionOptions &opts) {
std::lock_guard<std::mutex> lock(_mutex);
Iterator iter(_healthy_sentinels, _broken_sentinels);
std::size_t retries = 0;
while (true) {
try {
auto &sentinel = iter.next();
auto master = _get_master_addr_by_name(sentinel, master_name);
if (!master) {
// Try the next sentinel.
continue;
}
auto connection = _connect_redis(*master, opts);
if (_get_role(connection) != Role::MASTER) {
// Retry the whole process at most SentinelOptions::max_retry times.
++retries;
if (retries > _sentinel_opts.max_retry) {
throw Error("Failed to get master from sentinel");
}
std::this_thread::sleep_for(_sentinel_opts.retry_interval);
// Restart the iteration.
iter.reset();
continue;
}
return connection;
} catch (const StopIterError &e) {
throw;
} catch (const Error &e) {
continue;
}
}
}
Connection Sentinel::slave(const std::string &master_name, const ConnectionOptions &opts) {
std::lock_guard<std::mutex> lock(_mutex);
Iterator iter(_healthy_sentinels, _broken_sentinels);
std::size_t retries = 0;
while (true) {
try {
auto &sentinel = iter.next();
auto slaves = _get_slave_addr_by_name(sentinel, master_name);
if (slaves.empty()) {
// Try the next sentinel.
continue;
}
// Normally slaves list is NOT very large, so there won't be a performance problem.
auto slave_iter = std::find(slaves.begin(),
slaves.end(),
Node{opts.host, opts.port});
if (slave_iter != slaves.end() && slave_iter != slaves.begin()) {
// The given node is still a valid slave. Try it first.
std::swap(*(slaves.begin()), *slave_iter);
}
for (const auto &slave : slaves) {
try {
auto connection = _connect_redis(slave, opts);
if (_get_role(connection) != Role::SLAVE) {
// Retry the whole process at most SentinelOptions::max_retry times.
++retries;
if (retries > _sentinel_opts.max_retry) {
throw Error("Failed to get slave from sentinel");
}
std::this_thread::sleep_for(_sentinel_opts.retry_interval);
// Restart the iteration.
iter.reset();
break;
}
return connection;
} catch (const Error &e) {
// Try the next slave.
continue;
}
}
} catch (const StopIterError &e) {
throw;
} catch (const Error &e) {
continue;
}
}
}
Optional<Node> Sentinel::_get_master_addr_by_name(Connection &connection, const StringView &name) {
connection.send("SENTINEL GET-MASTER-ADDR-BY-NAME %b", name.data(), name.size());
auto reply = connection.recv();
assert(reply);
auto master = reply::parse<Optional<std::pair<std::string, std::string>>>(*reply);
if (!master) {
return {};
}
int port = 0;
try {
port = std::stoi(master->second);
} catch (const std::exception &) {
throw ProtoError("Master port is invalid: " + master->second);
}
return Optional<Node>{Node{master->first, port}};
}
std::vector<Node> Sentinel::_get_slave_addr_by_name(Connection &connection,
const StringView &name) {
try {
connection.send("SENTINEL SLAVES %b", name.data(), name.size());
auto reply = connection.recv();
assert(reply);
auto slaves = _parse_slave_info(*reply);
// Make slave list random.
std::mt19937 gen(std::random_device{}());
std::shuffle(slaves.begin(), slaves.end(), gen);
return slaves;
} catch (const ReplyError &e) {
// Unknown master name.
return {};
}
}
std::vector<Node> Sentinel::_parse_slave_info(redisReply &reply) const {
using SlaveInfo = std::unordered_map<std::string, std::string>;
auto slaves = reply::parse<std::vector<SlaveInfo>>(reply);
std::vector<Node> nodes;
for (const auto &slave : slaves) {
auto flags_iter = slave.find("flags");
auto ip_iter = slave.find("ip");
auto port_iter = slave.find("port");
if (flags_iter == slave.end() || ip_iter == slave.end() || port_iter == slave.end()) {
throw ProtoError("Invalid slave info");
}
// This slave is down, e.g. 's_down,slave,disconnected'
if (flags_iter->second != "slave") {
continue;
}
int port = 0;
try {
port = std::stoi(port_iter->second);
} catch (const std::exception &) {
throw ProtoError("Slave port is invalid: " + port_iter->second);
}
nodes.push_back(Node{ip_iter->second, port});
}
return nodes;
}
Connection Sentinel::_connect_redis(const Node &node, ConnectionOptions opts) {
opts.host = node.host;
opts.port = node.port;
return Connection(opts);
}
Role Sentinel::_get_role(Connection &connection) {
connection.send("INFO REPLICATION");
auto reply = connection.recv();
assert(reply);
auto info = reply::parse<std::string>(*reply);
auto start = info.find("role:");
if (start == std::string::npos) {
throw ProtoError("Invalid INFO REPLICATION reply");
}
start += 5;
auto stop = info.find("\r\n", start);
if (stop == std::string::npos) {
throw ProtoError("Invalid INFO REPLICATION reply");
}
auto role = info.substr(start, stop - start);
if (role == "master") {
return Role::MASTER;
} else if (role == "slave") {
return Role::SLAVE;
} else {
throw Error("Invalid role: " + role);
}
}
std::list<ConnectionOptions> Sentinel::_parse_options(const SentinelOptions &opts) const {
std::list<ConnectionOptions> options;
for (const auto &node : opts.nodes) {
ConnectionOptions opt;
opt.host = node.first;
opt.port = node.second;
opt.password = opts.password;
opt.keep_alive = opts.keep_alive;
opt.connect_timeout = opts.connect_timeout;
opt.socket_timeout = opts.socket_timeout;
options.push_back(opt);
}
return options;
}
SimpleSentinel::SimpleSentinel(const std::shared_ptr<Sentinel> &sentinel,
const std::string &master_name,
Role role) :
_sentinel(sentinel),
_master_name(master_name),
_role(role) {
if (!_sentinel) {
throw Error("Sentinel cannot be null");
}
if (_role != Role::MASTER && _role != Role::SLAVE) {
throw Error("Role must be Role::MASTER or Role::SLAVE");
}
}
Connection SimpleSentinel::create(const ConnectionOptions &opts) {
assert(_sentinel);
if (_role == Role::MASTER) {
return _sentinel->master(_master_name, opts);
}
assert(_role == Role::SLAVE);
return _sentinel->slave(_master_name, opts);
}
}
}

View file

@ -0,0 +1,138 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SENTINEL_H
#define SEWENEW_REDISPLUSPLUS_SENTINEL_H
#include <string>
#include <list>
#include <vector>
#include <memory>
#include <mutex>
#include "connection.h"
#include "shards.h"
#include "reply.h"
namespace sw {
namespace redis {
struct SentinelOptions {
std::vector<std::pair<std::string, int>> nodes;
std::string password;
bool keep_alive = true;
std::chrono::milliseconds connect_timeout{100};
std::chrono::milliseconds socket_timeout{100};
std::chrono::milliseconds retry_interval{100};
std::size_t max_retry = 2;
};
class Sentinel {
public:
explicit Sentinel(const SentinelOptions &sentinel_opts);
Sentinel(const Sentinel &) = delete;
Sentinel& operator=(const Sentinel &) = delete;
Sentinel(Sentinel &&) = delete;
Sentinel& operator=(Sentinel &&) = delete;
~Sentinel() = default;
private:
Connection master(const std::string &master_name, const ConnectionOptions &opts);
Connection slave(const std::string &master_name, const ConnectionOptions &opts);
class Iterator;
friend class SimpleSentinel;
std::list<ConnectionOptions> _parse_options(const SentinelOptions &opts) const;
Optional<Node> _get_master_addr_by_name(Connection &connection, const StringView &name);
std::vector<Node> _get_slave_addr_by_name(Connection &connection, const StringView &name);
Connection _connect_redis(const Node &node, ConnectionOptions opts);
Role _get_role(Connection &connection);
std::vector<Node> _parse_slave_info(redisReply &reply) const;
std::list<Connection> _healthy_sentinels;
std::list<ConnectionOptions> _broken_sentinels;
SentinelOptions _sentinel_opts;
std::mutex _mutex;
};
class SimpleSentinel {
public:
SimpleSentinel(const std::shared_ptr<Sentinel> &sentinel,
const std::string &master_name,
Role role);
SimpleSentinel() = default;
SimpleSentinel(const SimpleSentinel &) = default;
SimpleSentinel& operator=(const SimpleSentinel &) = default;
SimpleSentinel(SimpleSentinel &&) = default;
SimpleSentinel& operator=(SimpleSentinel &&) = default;
~SimpleSentinel() = default;
explicit operator bool() const {
return bool(_sentinel);
}
Connection create(const ConnectionOptions &opts);
private:
std::shared_ptr<Sentinel> _sentinel;
std::string _master_name;
Role _role = Role::MASTER;
};
class StopIterError : public Error {
public:
StopIterError() : Error("StopIterError") {}
StopIterError(const StopIterError &) = default;
StopIterError& operator=(const StopIterError &) = default;
StopIterError(StopIterError &&) = default;
StopIterError& operator=(StopIterError &&) = default;
virtual ~StopIterError() = default;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SENTINEL_H

View file

@ -0,0 +1,50 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "shards.h"
namespace sw {
namespace redis {
RedirectionError::RedirectionError(const std::string &msg): ReplyError(msg) {
std::tie(_slot, _node) = _parse_error(msg);
}
std::pair<Slot, Node> RedirectionError::_parse_error(const std::string &msg) const {
// "slot ip:port"
auto space_pos = msg.find(" ");
auto colon_pos = msg.find(":");
if (space_pos == std::string::npos
|| colon_pos == std::string::npos
|| colon_pos < space_pos) {
throw ProtoError("Invalid ASK error message: " + msg);
}
try {
auto slot = std::stoull(msg.substr(0, space_pos));
auto host = msg.substr(space_pos + 1, colon_pos - space_pos - 1);
auto port = std::stoi(msg.substr(colon_pos + 1));
return {slot, {host, port}};
} catch (const std::exception &e) {
throw ProtoError("Invalid ASK error message: " + msg);
}
}
}
}

View file

@ -0,0 +1,115 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_H
#define SEWENEW_REDISPLUSPLUS_SHARDS_H
#include <string>
#include <map>
#include "errors.h"
namespace sw {
namespace redis {
using Slot = std::size_t;
struct SlotRange {
Slot min;
Slot max;
};
inline bool operator<(const SlotRange &lhs, const SlotRange &rhs) {
return lhs.max < rhs.max;
}
struct Node {
std::string host;
int port;
};
inline bool operator==(const Node &lhs, const Node &rhs) {
return lhs.host == rhs.host && lhs.port == rhs.port;
}
struct NodeHash {
std::size_t operator()(const Node &node) const noexcept {
auto host_hash = std::hash<std::string>{}(node.host);
auto port_hash = std::hash<int>{}(node.port);
return host_hash ^ (port_hash << 1);
}
};
using Shards = std::map<SlotRange, Node>;
class RedirectionError : public ReplyError {
public:
RedirectionError(const std::string &msg);
RedirectionError(const RedirectionError &) = default;
RedirectionError& operator=(const RedirectionError &) = default;
RedirectionError(RedirectionError &&) = default;
RedirectionError& operator=(RedirectionError &&) = default;
virtual ~RedirectionError() = default;
Slot slot() const {
return _slot;
}
const Node& node() const {
return _node;
}
private:
std::pair<Slot, Node> _parse_error(const std::string &msg) const;
Slot _slot = 0;
Node _node;
};
class MovedError : public RedirectionError {
public:
explicit MovedError(const std::string &msg) : RedirectionError(msg) {}
MovedError(const MovedError &) = default;
MovedError& operator=(const MovedError &) = default;
MovedError(MovedError &&) = default;
MovedError& operator=(MovedError &&) = default;
virtual ~MovedError() = default;
};
class AskError : public RedirectionError {
public:
explicit AskError(const std::string &msg) : RedirectionError(msg) {}
AskError(const AskError &) = default;
AskError& operator=(const AskError &) = default;
AskError(AskError &&) = default;
AskError& operator=(AskError &&) = default;
virtual ~AskError() = default;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_H

View file

@ -0,0 +1,319 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "shards_pool.h"
#include <unordered_set>
#include "errors.h"
namespace sw {
namespace redis {
const std::size_t ShardsPool::SHARDS;
ShardsPool::ShardsPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts) :
_pool_opts(pool_opts),
_connection_opts(connection_opts) {
if (_connection_opts.type != ConnectionType::TCP) {
throw Error("Only support TCP connection for Redis Cluster");
}
Connection connection(_connection_opts);
_shards = _cluster_slots(connection);
_init_pool(_shards);
}
ShardsPool::ShardsPool(ShardsPool &&that) {
std::lock_guard<std::mutex> lock(that._mutex);
_move(std::move(that));
}
ShardsPool& ShardsPool::operator=(ShardsPool &&that) {
if (this != &that) {
std::lock(_mutex, that._mutex);
std::lock_guard<std::mutex> lock_this(_mutex, std::adopt_lock);
std::lock_guard<std::mutex> lock_that(that._mutex, std::adopt_lock);
_move(std::move(that));
}
return *this;
}
GuardedConnection ShardsPool::fetch(const StringView &key) {
auto slot = _slot(key);
return _fetch(slot);
}
GuardedConnection ShardsPool::fetch() {
auto slot = _slot();
return _fetch(slot);
}
GuardedConnection ShardsPool::fetch(const Node &node) {
std::lock_guard<std::mutex> lock(_mutex);
auto iter = _pools.find(node);
if (iter == _pools.end()) {
// Node doesn't exist, and it should be a newly created node.
// So add a new connection pool.
iter = _add_node(node);
}
assert(iter != _pools.end());
return GuardedConnection(iter->second);
}
void ShardsPool::update() {
// My might send command to a removed node.
// Try at most 3 times.
for (auto idx = 0; idx < 3; ++idx) {
try {
// Randomly pick a connection.
auto guarded_connection = fetch();
auto shards = _cluster_slots(guarded_connection.connection());
std::unordered_set<Node, NodeHash> nodes;
for (const auto &shard : shards) {
nodes.insert(shard.second);
}
std::lock_guard<std::mutex> lock(_mutex);
// TODO: If shards is unchanged, no need to update, and return immediately.
_shards = std::move(shards);
// Remove non-existent nodes.
for (auto iter = _pools.begin(); iter != _pools.end(); ) {
if (nodes.find(iter->first) == nodes.end()) {
// Node has been removed.
_pools.erase(iter++);
} else {
++iter;
}
}
// Add connection pool for new nodes.
// In fact, connections will be created lazily.
for (const auto &node : nodes) {
if (_pools.find(node) == _pools.end()) {
_add_node(node);
}
}
// Update successfully.
return;
} catch (const Error &) {
// continue;
}
}
throw Error("Failed to update shards info");
}
ConnectionOptions ShardsPool::connection_options(const StringView &key) {
auto slot = _slot(key);
return _connection_options(slot);
}
ConnectionOptions ShardsPool::connection_options() {
auto slot = _slot();
return _connection_options(slot);
}
void ShardsPool::_move(ShardsPool &&that) {
_pool_opts = that._pool_opts;
_connection_opts = that._connection_opts;
_shards = std::move(that._shards);
_pools = std::move(that._pools);
}
void ShardsPool::_init_pool(const Shards &shards) {
for (const auto &shard : shards) {
_add_node(shard.second);
}
}
Shards ShardsPool::_cluster_slots(Connection &connection) const {
auto reply = _cluster_slots_command(connection);
assert(reply);
return _parse_reply(*reply);
}
ReplyUPtr ShardsPool::_cluster_slots_command(Connection &connection) const {
connection.send("CLUSTER SLOTS");
return connection.recv();
}
Shards ShardsPool::_parse_reply(redisReply &reply) const {
if (!reply::is_array(reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply.element == nullptr || reply.elements == 0) {
throw Error("Empty slots");
}
Shards shards;
for (std::size_t idx = 0; idx != reply.elements; ++idx) {
auto *sub_reply = reply.element[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null slot info");
}
shards.emplace(_parse_slot_info(*sub_reply));
}
return shards;
}
std::pair<SlotRange, Node> ShardsPool::_parse_slot_info(redisReply &reply) const {
if (reply.elements < 3 || reply.element == nullptr) {
throw ProtoError("Invalid slot info");
}
// Min slot id
auto *min_slot_reply = reply.element[0];
if (min_slot_reply == nullptr) {
throw ProtoError("Invalid min slot");
}
std::size_t min_slot = reply::parse<long long>(*min_slot_reply);
// Max slot id
auto *max_slot_reply = reply.element[1];
if (max_slot_reply == nullptr) {
throw ProtoError("Invalid max slot");
}
std::size_t max_slot = reply::parse<long long>(*max_slot_reply);
if (min_slot > max_slot) {
throw ProtoError("Invalid slot range");
}
// Master node info
auto *node_reply = reply.element[2];
if (node_reply == nullptr
|| !reply::is_array(*node_reply)
|| node_reply->element == nullptr
|| node_reply->elements < 2) {
throw ProtoError("Invalid node info");
}
auto master_host = reply::parse<std::string>(*(node_reply->element[0]));
int master_port = reply::parse<long long>(*(node_reply->element[1]));
// By now, we ignore node id and other replicas' info.
return {SlotRange{min_slot, max_slot}, Node{master_host, master_port}};
}
Slot ShardsPool::_slot(const StringView &key) const {
// The following code is copied from: https://redis.io/topics/cluster-spec
// And I did some minor changes.
const auto *k = key.data();
auto keylen = key.size();
// start-end indexes of { and }.
std::size_t s = 0;
std::size_t e = 0;
// Search the first occurrence of '{'.
for (s = 0; s < keylen; s++)
if (k[s] == '{') break;
// No '{' ? Hash the whole key. This is the base case.
if (s == keylen) return crc16(k, keylen) & SHARDS;
// '{' found? Check if we have the corresponding '}'.
for (e = s + 1; e < keylen; e++)
if (k[e] == '}') break;
// No '}' or nothing between {} ? Hash the whole key.
if (e == keylen || e == s + 1) return crc16(k, keylen) & SHARDS;
// If we are here there is both a { and a } on its right. Hash
// what is in the middle between { and }.
return crc16(k + s + 1, e - s - 1) & SHARDS;
}
Slot ShardsPool::_slot() const {
static thread_local std::default_random_engine engine;
std::uniform_int_distribution<std::size_t> uniform_dist(0, SHARDS);
return uniform_dist(engine);
}
ConnectionPoolSPtr& ShardsPool::_get_pool(Slot slot) {
auto shards_iter = _shards.lower_bound(SlotRange{slot, slot});
if (shards_iter == _shards.end() || slot < shards_iter->first.min) {
throw Error("Slot is out of range: " + std::to_string(slot));
}
const auto &node = shards_iter->second;
auto node_iter = _pools.find(node);
if (node_iter == _pools.end()) {
throw Error("Slot is NOT covered: " + std::to_string(slot));
}
return node_iter->second;
}
GuardedConnection ShardsPool::_fetch(Slot slot) {
std::lock_guard<std::mutex> lock(_mutex);
auto &pool = _get_pool(slot);
assert(pool);
return GuardedConnection(pool);
}
ConnectionOptions ShardsPool::_connection_options(Slot slot) {
std::lock_guard<std::mutex> lock(_mutex);
auto &pool = _get_pool(slot);
assert(pool);
return pool->connection_options();
}
auto ShardsPool::_add_node(const Node &node) -> NodeMap::iterator {
auto opts = _connection_opts;
opts.host = node.host;
opts.port = node.port;
return _pools.emplace(node, std::make_shared<ConnectionPool>(_pool_opts, opts)).first;
}
}
}

View file

@ -0,0 +1,137 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
#define SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H
#include <cassert>
#include <unordered_map>
#include <string>
#include <random>
#include <memory>
#include "reply.h"
#include "connection_pool.h"
#include "shards.h"
namespace sw {
namespace redis {
using ConnectionPoolSPtr = std::shared_ptr<ConnectionPool>;
class GuardedConnection {
public:
GuardedConnection(const ConnectionPoolSPtr &pool) : _pool(pool),
_connection(_pool->fetch()) {
assert(!_connection.broken());
}
GuardedConnection(const GuardedConnection &) = delete;
GuardedConnection& operator=(const GuardedConnection &) = delete;
GuardedConnection(GuardedConnection &&) = default;
GuardedConnection& operator=(GuardedConnection &&) = default;
~GuardedConnection() {
_pool->release(std::move(_connection));
}
Connection& connection() {
return _connection;
}
private:
ConnectionPoolSPtr _pool;
Connection _connection;
};
class ShardsPool {
public:
ShardsPool() = default;
ShardsPool(const ShardsPool &that) = delete;
ShardsPool& operator=(const ShardsPool &that) = delete;
ShardsPool(ShardsPool &&that);
ShardsPool& operator=(ShardsPool &&that);
~ShardsPool() = default;
ShardsPool(const ConnectionPoolOptions &pool_opts,
const ConnectionOptions &connection_opts);
// Fetch a connection by key.
GuardedConnection fetch(const StringView &key);
// Randomly pick a connection.
GuardedConnection fetch();
// Fetch a connection by node.
GuardedConnection fetch(const Node &node);
void update();
ConnectionOptions connection_options(const StringView &key);
ConnectionOptions connection_options();
private:
void _move(ShardsPool &&that);
void _init_pool(const Shards &shards);
Shards _cluster_slots(Connection &connection) const;
ReplyUPtr _cluster_slots_command(Connection &connection) const;
Shards _parse_reply(redisReply &reply) const;
std::pair<SlotRange, Node> _parse_slot_info(redisReply &reply) const;
// Get slot by key.
std::size_t _slot(const StringView &key) const;
// Randomly pick a slot.
std::size_t _slot() const;
ConnectionPoolSPtr& _get_pool(Slot slot);
GuardedConnection _fetch(Slot slot);
ConnectionOptions _connection_options(Slot slot);
using NodeMap = std::unordered_map<Node, ConnectionPoolSPtr, NodeHash>;
NodeMap::iterator _add_node(const Node &node);
ConnectionPoolOptions _pool_opts;
ConnectionOptions _connection_opts;
Shards _shards;
NodeMap _pools;
std::mutex _mutex;
static const std::size_t SHARDS = 16383;
};
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SHARDS_POOL_H

View file

@ -0,0 +1,222 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "subscriber.h"
#include <cassert>
namespace sw {
namespace redis {
const Subscriber::TypeIndex Subscriber::_msg_type_index = {
{"message", MsgType::MESSAGE},
{"pmessage", MsgType::PMESSAGE},
{"subscribe", MsgType::SUBSCRIBE},
{"unsubscribe", MsgType::UNSUBSCRIBE},
{"psubscribe", MsgType::PSUBSCRIBE},
{"punsubscribe", MsgType::PUNSUBSCRIBE}
};
Subscriber::Subscriber(Connection connection) : _connection(std::move(connection)) {}
void Subscriber::subscribe(const StringView &channel) {
_check_connection();
// TODO: cmd::subscribe DOES NOT send the subscribe message to Redis.
// In fact, it puts the command to network buffer.
// So we need a queue to record these sub or unsub commands, and
// ensure that before stopping the subscriber, all these commands
// have really been sent to Redis.
cmd::subscribe(_connection, channel);
}
void Subscriber::unsubscribe() {
_check_connection();
cmd::unsubscribe(_connection);
}
void Subscriber::unsubscribe(const StringView &channel) {
_check_connection();
cmd::unsubscribe(_connection, channel);
}
void Subscriber::psubscribe(const StringView &pattern) {
_check_connection();
cmd::psubscribe(_connection, pattern);
}
void Subscriber::punsubscribe() {
_check_connection();
cmd::punsubscribe(_connection);
}
void Subscriber::punsubscribe(const StringView &pattern) {
_check_connection();
cmd::punsubscribe(_connection, pattern);
}
void Subscriber::consume() {
_check_connection();
ReplyUPtr reply;
try {
reply = _connection.recv();
} catch (const TimeoutError &) {
_connection.reset();
throw;
}
assert(reply);
if (!reply::is_array(*reply) || reply->elements < 1 || reply->element == nullptr) {
throw ProtoError("Invalid subscribe message");
}
auto type = _msg_type(reply->element[0]);
switch (type) {
case MsgType::MESSAGE:
_handle_message(*reply);
break;
case MsgType::PMESSAGE:
_handle_pmessage(*reply);
break;
case MsgType::SUBSCRIBE:
case MsgType::UNSUBSCRIBE:
case MsgType::PSUBSCRIBE:
case MsgType::PUNSUBSCRIBE:
_handle_meta(type, *reply);
break;
default:
assert(false);
}
}
Subscriber::MsgType Subscriber::_msg_type(redisReply *reply) const {
if (reply == nullptr) {
throw ProtoError("Null type reply.");
}
auto type = reply::parse<std::string>(*reply);
auto iter = _msg_type_index.find(type);
if (iter == _msg_type_index.end()) {
throw ProtoError("Invalid message type.");
}
return iter->second;
}
void Subscriber::_check_connection() {
if (_connection.broken()) {
throw Error("Connection is broken");
}
}
void Subscriber::_handle_message(redisReply &reply) {
if (_msg_callback == nullptr) {
return;
}
if (reply.elements != 3) {
throw ProtoError("Expect 3 sub replies");
}
assert(reply.element != nullptr);
auto *channel_reply = reply.element[1];
if (channel_reply == nullptr) {
throw ProtoError("Null channel reply");
}
auto channel = reply::parse<std::string>(*channel_reply);
auto *msg_reply = reply.element[2];
if (msg_reply == nullptr) {
throw ProtoError("Null message reply");
}
auto msg = reply::parse<std::string>(*msg_reply);
_msg_callback(std::move(channel), std::move(msg));
}
void Subscriber::_handle_pmessage(redisReply &reply) {
if (_pmsg_callback == nullptr) {
return;
}
if (reply.elements != 4) {
throw ProtoError("Expect 4 sub replies");
}
assert(reply.element != nullptr);
auto *pattern_reply = reply.element[1];
if (pattern_reply == nullptr) {
throw ProtoError("Null pattern reply");
}
auto pattern = reply::parse<std::string>(*pattern_reply);
auto *channel_reply = reply.element[2];
if (channel_reply == nullptr) {
throw ProtoError("Null channel reply");
}
auto channel = reply::parse<std::string>(*channel_reply);
auto *msg_reply = reply.element[3];
if (msg_reply == nullptr) {
throw ProtoError("Null message reply");
}
auto msg = reply::parse<std::string>(*msg_reply);
_pmsg_callback(std::move(pattern), std::move(channel), std::move(msg));
}
void Subscriber::_handle_meta(MsgType type, redisReply &reply) {
if (_meta_callback == nullptr) {
return;
}
if (reply.elements != 3) {
throw ProtoError("Expect 3 sub replies");
}
assert(reply.element != nullptr);
auto *channel_reply = reply.element[1];
if (channel_reply == nullptr) {
throw ProtoError("Null channel reply");
}
auto channel = reply::parse<OptionalString>(*channel_reply);
auto *num_reply = reply.element[2];
if (num_reply == nullptr) {
throw ProtoError("Null num reply");
}
auto num = reply::parse<long long>(*num_reply);
_meta_callback(type, std::move(channel), num);
}
}
}

View file

@ -0,0 +1,231 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
#define SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H
#include <unordered_map>
#include <string>
#include <functional>
#include "connection.h"
#include "reply.h"
#include "command.h"
#include "utils.h"
namespace sw {
namespace redis {
// @NOTE: Subscriber is NOT thread-safe.
// Subscriber uses callbacks to handle messages. There are 6 kinds of messages:
// 1) MESSAGE: message sent to a channel.
// 2) PMESSAGE: message sent to channels of a given pattern.
// 3) SUBSCRIBE: meta message sent when we successfully subscribe to a channel.
// 4) UNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel.
// 5) PSUBSCRIBE: meta message sent when we successfully subscribe to a channel pattern.
// 6) PUNSUBSCRIBE: meta message sent when we successfully unsubscribe to a channel pattern.
//
// Use Subscriber::on_message(MsgCallback) to set the callback function for message of
// *MESSAGE* type, and the callback interface is:
// void (std::string channel, std::string msg)
//
// Use Subscriber::on_pmessage(PatternMsgCallback) to set the callback function for message of
// *PMESSAGE* type, and the callback interface is:
// void (std::string pattern, std::string channel, std::string msg)
//
// Messages of other types are called *META MESSAGE*, they have the same callback interface.
// Use Subscriber::on_meta(MetaCallback) to set the callback function:
// void (Subscriber::MsgType type, OptionalString channel, long long num)
//
// NOTE: If we haven't subscribe/psubscribe to any channel/pattern, and try to
// unsubscribe/punsubscribe without any parameter, i.e. unsubscribe/punsubscribe all
// channels/patterns, *channel* will be null. So the second parameter of meta callback
// is of type *OptionalString*.
//
// All these callback interfaces pass std::string by value, and you can take their ownership
// (i.e. std::move) safely.
//
// If you don't set callback for a specific kind of message, Subscriber::consume() will
// receive the message, and ignore it, i.e. no callback will be called.
class Subscriber {
public:
Subscriber(const Subscriber &) = delete;
Subscriber& operator=(const Subscriber &) = delete;
Subscriber(Subscriber &&) = default;
Subscriber& operator=(Subscriber &&) = default;
~Subscriber() = default;
enum class MsgType {
SUBSCRIBE,
UNSUBSCRIBE,
PSUBSCRIBE,
PUNSUBSCRIBE,
MESSAGE,
PMESSAGE
};
template <typename MsgCb>
void on_message(MsgCb msg_callback);
template <typename PMsgCb>
void on_pmessage(PMsgCb pmsg_callback);
template <typename MetaCb>
void on_meta(MetaCb meta_callback);
void subscribe(const StringView &channel);
template <typename Input>
void subscribe(Input first, Input last);
template <typename T>
void subscribe(std::initializer_list<T> channels) {
subscribe(channels.begin(), channels.end());
}
void unsubscribe();
void unsubscribe(const StringView &channel);
template <typename Input>
void unsubscribe(Input first, Input last);
template <typename T>
void unsubscribe(std::initializer_list<T> channels) {
unsubscribe(channels.begin(), channels.end());
}
void psubscribe(const StringView &pattern);
template <typename Input>
void psubscribe(Input first, Input last);
template <typename T>
void psubscribe(std::initializer_list<T> channels) {
psubscribe(channels.begin(), channels.end());
}
void punsubscribe();
void punsubscribe(const StringView &channel);
template <typename Input>
void punsubscribe(Input first, Input last);
template <typename T>
void punsubscribe(std::initializer_list<T> channels) {
punsubscribe(channels.begin(), channels.end());
}
void consume();
private:
friend class Redis;
friend class RedisCluster;
explicit Subscriber(Connection connection);
MsgType _msg_type(redisReply *reply) const;
void _check_connection();
void _handle_message(redisReply &reply);
void _handle_pmessage(redisReply &reply);
void _handle_meta(MsgType type, redisReply &reply);
using MsgCallback = std::function<void (std::string channel, std::string msg)>;
using PatternMsgCallback = std::function<void (std::string pattern,
std::string channel,
std::string msg)>;
using MetaCallback = std::function<void (MsgType type,
OptionalString channel,
long long num)>;
using TypeIndex = std::unordered_map<std::string, MsgType>;
static const TypeIndex _msg_type_index;
Connection _connection;
MsgCallback _msg_callback = nullptr;
PatternMsgCallback _pmsg_callback = nullptr;
MetaCallback _meta_callback = nullptr;
};
template <typename MsgCb>
void Subscriber::on_message(MsgCb msg_callback) {
_msg_callback = msg_callback;
}
template <typename PMsgCb>
void Subscriber::on_pmessage(PMsgCb pmsg_callback) {
_pmsg_callback = pmsg_callback;
}
template <typename MetaCb>
void Subscriber::on_meta(MetaCb meta_callback) {
_meta_callback = meta_callback;
}
template <typename Input>
void Subscriber::subscribe(Input first, Input last) {
if (first == last) {
return;
}
_check_connection();
cmd::subscribe_range(_connection, first, last);
}
template <typename Input>
void Subscriber::unsubscribe(Input first, Input last) {
_check_connection();
cmd::unsubscribe_range(_connection, first, last);
}
template <typename Input>
void Subscriber::psubscribe(Input first, Input last) {
if (first == last) {
return;
}
_check_connection();
cmd::psubscribe_range(_connection, first, last);
}
template <typename Input>
void Subscriber::punsubscribe(Input first, Input last) {
_check_connection();
cmd::punsubscribe_range(_connection, first, last);
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_SUBSCRIBER_H

View file

@ -0,0 +1,123 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#include "transaction.h"
#include "command.h"
namespace sw {
namespace redis {
std::vector<ReplyUPtr> TransactionImpl::exec(Connection &connection, std::size_t cmd_num) {
_close_transaction();
_get_queued_replies(connection, cmd_num);
return _exec(connection);
}
void TransactionImpl::discard(Connection &connection, std::size_t cmd_num) {
_close_transaction();
_get_queued_replies(connection, cmd_num);
_discard(connection);
}
void TransactionImpl::_open_transaction(Connection &connection) {
assert(!_in_transaction);
cmd::multi(connection);
auto reply = connection.recv();
auto status = reply::to_status(*reply);
if (status != "OK") {
throw Error("Failed to open transaction: " + status);
}
_in_transaction = true;
}
void TransactionImpl::_close_transaction() {
if (!_in_transaction) {
throw Error("No command in transaction");
}
_in_transaction = false;
}
void TransactionImpl::_get_queued_reply(Connection &connection) {
auto reply = connection.recv();
auto status = reply::to_status(*reply);
if (status != "QUEUED") {
throw Error("Invalid QUEUED reply: " + status);
}
}
void TransactionImpl::_get_queued_replies(Connection &connection, std::size_t cmd_num) {
if (_piped) {
// Get all QUEUED reply
while (cmd_num > 0) {
_get_queued_reply(connection);
--cmd_num;
}
}
}
std::vector<ReplyUPtr> TransactionImpl::_exec(Connection &connection) {
cmd::exec(connection);
auto reply = connection.recv();
if (reply::is_nil(*reply)) {
// Execution has been aborted, i.e. watched key has been modified.
throw WatchError();
}
if (!reply::is_array(*reply)) {
throw ProtoError("Expect ARRAY reply");
}
if (reply->element == nullptr || reply->elements == 0) {
// Since we don't allow EXEC without any command, this ARRAY reply
// should NOT be null or empty.
throw ProtoError("Null ARRAY reply");
}
std::vector<ReplyUPtr> replies;
for (std::size_t idx = 0; idx != reply->elements; ++idx) {
auto *sub_reply = reply->element[idx];
if (sub_reply == nullptr) {
throw ProtoError("Null sub reply");
}
auto r = ReplyUPtr(sub_reply);
reply->element[idx] = nullptr;
replies.push_back(std::move(r));
}
return replies;
}
void TransactionImpl::_discard(Connection &connection) {
cmd::discard(connection);
auto reply = connection.recv();
reply::parse<void>(*reply);
}
}
}

View file

@ -0,0 +1,77 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TRANSACTION_H
#define SEWENEW_REDISPLUSPLUS_TRANSACTION_H
#include <cassert>
#include <vector>
#include "connection.h"
#include "errors.h"
namespace sw {
namespace redis {
class TransactionImpl {
public:
explicit TransactionImpl(bool piped) : _piped(piped) {}
template <typename Cmd, typename ...Args>
void command(Connection &connection, Cmd cmd, Args &&...args);
std::vector<ReplyUPtr> exec(Connection &connection, std::size_t cmd_num);
void discard(Connection &connection, std::size_t cmd_num);
private:
void _open_transaction(Connection &connection);
void _close_transaction();
void _get_queued_reply(Connection &connection);
void _get_queued_replies(Connection &connection, std::size_t cmd_num);
std::vector<ReplyUPtr> _exec(Connection &connection);
void _discard(Connection &connection);
bool _in_transaction = false;
bool _piped;
};
template <typename Cmd, typename ...Args>
void TransactionImpl::command(Connection &connection, Cmd cmd, Args &&...args) {
assert(!connection.broken());
if (!_in_transaction) {
_open_transaction(connection);
}
cmd(connection, std::forward<Args>(args)...);
if (!_piped) {
_get_queued_reply(connection);
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TRANSACTION_H

View file

@ -0,0 +1,269 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_UTILS_H
#define SEWENEW_REDISPLUSPLUS_UTILS_H
#include <cstring>
#include <string>
#include <type_traits>
namespace sw {
namespace redis {
// By now, not all compilers support std::string_view,
// so we make our own implementation.
class StringView {
public:
constexpr StringView() noexcept = default;
constexpr StringView(const char *data, std::size_t size) : _data(data), _size(size) {}
StringView(const char *data) : _data(data), _size(std::strlen(data)) {}
StringView(const std::string &str) : _data(str.data()), _size(str.size()) {}
constexpr StringView(const StringView &) noexcept = default;
StringView& operator=(const StringView &) noexcept = default;
constexpr const char* data() const noexcept {
return _data;
}
constexpr std::size_t size() const noexcept {
return _size;
}
private:
const char *_data = nullptr;
std::size_t _size = 0;
};
template <typename T>
class Optional {
public:
Optional() = default;
Optional(const Optional &) = default;
Optional& operator=(const Optional &) = default;
Optional(Optional &&) = default;
Optional& operator=(Optional &&) = default;
~Optional() = default;
template <typename ...Args>
explicit Optional(Args &&...args) : _value(true, T(std::forward<Args>(args)...)) {}
explicit operator bool() const {
return _value.first;
}
T& value() {
return _value.second;
}
const T& value() const {
return _value.second;
}
T* operator->() {
return &(_value.second);
}
const T* operator->() const {
return &(_value.second);
}
T& operator*() {
return _value.second;
}
const T& operator*() const {
return _value.second;
}
private:
std::pair<bool, T> _value;
};
using OptionalString = Optional<std::string>;
using OptionalLongLong = Optional<long long>;
using OptionalDouble = Optional<double>;
using OptionalStringPair = Optional<std::pair<std::string, std::string>>;
template <typename ...>
struct IsKvPair : std::false_type {};
template <typename T, typename U>
struct IsKvPair<std::pair<T, U>> : std::true_type {};
template <typename ...>
using Void = void;
template <typename T, typename U = Void<>>
struct IsInserter : std::false_type {};
template <typename T>
//struct IsInserter<T, Void<typename T::container_type>> : std::true_type {};
struct IsInserter<T,
typename std::enable_if<!std::is_void<typename T::container_type>::value>::type>
: std::true_type {};
template <typename Iter, typename T = Void<>>
struct IterType {
using type = typename std::iterator_traits<Iter>::value_type;
};
template <typename Iter>
//struct IterType<Iter, Void<typename Iter::container_type>> {
struct IterType<Iter,
//typename std::enable_if<std::is_void<typename Iter::value_type>::value>::type> {
typename std::enable_if<IsInserter<Iter>::value>::type> {
using type = typename std::decay<typename Iter::container_type::value_type>::type;
};
template <typename Iter, typename T = Void<>>
struct IsIter : std::false_type {};
template <typename Iter>
struct IsIter<Iter, typename std::enable_if<IsInserter<Iter>::value>::type> : std::true_type {};
template <typename Iter>
//struct IsIter<Iter, Void<typename std::iterator_traits<Iter>::iterator_category>>
struct IsIter<Iter,
typename std::enable_if<!std::is_void<
typename std::iterator_traits<Iter>::value_type>::value>::type>
: std::integral_constant<bool, !std::is_convertible<Iter, StringView>::value> {};
template <typename T>
struct IsKvPairIter : IsKvPair<typename IterType<T>::type> {};
template <typename T, typename Tuple>
struct TupleWithType : std::false_type {};
template <typename T>
struct TupleWithType<T, std::tuple<>> : std::false_type {};
template <typename T, typename U, typename ...Args>
struct TupleWithType<T, std::tuple<U, Args...>> : TupleWithType<T, std::tuple<Args...>> {};
template <typename T, typename ...Args>
struct TupleWithType<T, std::tuple<T, Args...>> : std::true_type {};
template <std::size_t ...Is>
struct IndexSequence {};
template <std::size_t I, std::size_t ...Is>
struct MakeIndexSequence : MakeIndexSequence<I - 1, I - 1, Is...> {};
template <std::size_t ...Is>
struct MakeIndexSequence<0, Is...> : IndexSequence<Is...> {};
// NthType and NthValue are taken from
// https://stackoverflow.com/questions/14261183
template <std::size_t I, typename ...Args>
struct NthType {};
template <typename Arg, typename ...Args>
struct NthType<0, Arg, Args...> {
using type = Arg;
};
template <std::size_t I, typename Arg, typename ...Args>
struct NthType<I, Arg, Args...> {
using type = typename NthType<I - 1, Args...>::type;
};
template <typename ...Args>
struct LastType {
using type = typename NthType<sizeof...(Args) - 1, Args...>::type;
};
struct InvalidLastType {};
template <>
struct LastType<> {
using type = InvalidLastType;
};
template <std::size_t I, typename Arg, typename ...Args>
auto NthValue(Arg &&arg, Args &&...)
-> typename std::enable_if<(I == 0), decltype(std::forward<Arg>(arg))>::type {
return std::forward<Arg>(arg);
}
template <std::size_t I, typename Arg, typename ...Args>
auto NthValue(Arg &&, Args &&...args)
-> typename std::enable_if<(I > 0),
decltype(std::forward<typename NthType<I, Arg, Args...>::type>(
std::declval<typename NthType<I, Arg, Args...>::type>()))>::type {
return std::forward<typename NthType<I, Arg, Args...>::type>(
NthValue<I - 1>(std::forward<Args>(args)...));
}
template <typename ...Args>
auto LastValue(Args &&...args)
-> decltype(std::forward<typename LastType<Args...>::type>(
std::declval<typename LastType<Args...>::type>())) {
return std::forward<typename LastType<Args...>::type>(
NthValue<sizeof...(Args) - 1>(std::forward<Args>(args)...));
}
template <typename T, typename = Void<>>
struct HasPushBack : std::false_type {};
template <typename T>
struct HasPushBack<T,
typename std::enable_if<
std::is_void<decltype(
std::declval<T>().push_back(std::declval<typename T::value_type>())
)>::value>::type> : std::true_type {};
template <typename T, typename = Void<>>
struct HasInsert : std::false_type {};
template <typename T>
struct HasInsert<T,
typename std::enable_if<
std::is_same<
decltype(std::declval<T>().insert(std::declval<typename T::const_iterator>(),
std::declval<typename T::value_type>())),
typename T::iterator>::value>::type> : std::true_type {};
template <typename T>
struct IsSequenceContainer
: std::integral_constant<bool,
HasPushBack<T>::value
&& !std::is_same<typename std::decay<T>::type, std::string>::value> {};
template <typename T>
struct IsAssociativeContainer
: std::integral_constant<bool,
HasInsert<T>::value && !HasPushBack<T>::value> {};
uint16_t crc16(const char *buf, int len);
}
}
#endif // end SEWENEW_REDISPLUSPLUS_UTILS_H

View file

@ -0,0 +1,33 @@
project(test_redis++)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
cmake_minimum_required(VERSION 3.0.0)
else()
cmake_minimum_required(VERSION 2.8.0)
endif()
set(PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR}/src/sw/redis++)
file(GLOB PROJECT_SOURCE_FILES "${PROJECT_SOURCE_DIR}/*.cpp")
add_executable(${PROJECT_NAME} ${PROJECT_SOURCE_FILES})
# hiredis dependency
find_path(HIREDIS_HEADER hiredis)
target_include_directories(${PROJECT_NAME} PUBLIC ${HIREDIS_HEADER})
find_library(HIREDIS_STATIC_LIB libhiredis.a)
target_link_libraries(${PROJECT_NAME} ${HIREDIS_STATIC_LIB})
# redis++ dependency
target_include_directories(${PROJECT_NAME} PUBLIC ../src)
set(REDIS_PLUS_PLUS_LIB ${CMAKE_CURRENT_BINARY_DIR}/../lib/libredis++.a)
## solaris socket dependency
IF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)" )
target_link_libraries(${PROJECT_NAME} -lsocket)
ENDIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)" )
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} ${REDIS_PLUS_PLUS_LIB} ${CMAKE_THREAD_LIBS_INIT})

View file

@ -0,0 +1,83 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
struct BenchmarkOptions {
std::size_t pool_size = 5;
std::size_t thread_num = 10;
std::size_t total_request_num = 100000;
std::size_t key_len = 10;
std::size_t val_len = 10;
};
template <typename RedisInstance>
class BenchmarkTest {
public:
BenchmarkTest(const BenchmarkOptions &opts, RedisInstance &instance);
~BenchmarkTest() {
_cleanup();
}
void run();
private:
template <typename Func>
void _run(const std::string &title, Func &&func);
template <typename Func>
std::size_t _run(Func &&func, std::size_t request_num);
void _test_get();
std::vector<std::string> _gen_keys() const;
std::string _gen_value() const;
void _cleanup();
const std::string& _key(std::size_t idx) const {
return _keys[idx % _keys.size()];
}
BenchmarkOptions _opts;
RedisInstance &_redis;
std::vector<std::string> _keys;
std::string _value;
};
}
}
}
#include "benchmark_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_H

View file

@ -0,0 +1,178 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP
#include <chrono>
#include <random>
#include <future>
#include <algorithm>
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
BenchmarkTest<RedisInstance>::BenchmarkTest(const BenchmarkOptions &opts,
RedisInstance &instance) : _opts(opts), _redis(instance) {
REDIS_ASSERT(_opts.pool_size > 0
&& _opts.thread_num > 0
&& _opts.total_request_num > 0
&& _opts.key_len > 0
&& _opts.val_len > 0,
"Invalid benchmark test options.");
_keys = _gen_keys();
_value = _gen_value();
}
template <typename RedisInstance>
void BenchmarkTest<RedisInstance>::run() {
_cleanup();
_run("SET key value", [this](std::size_t idx) { this->_redis.set(this->_key(idx), _value); });
_run("GET key", [this](std::size_t idx) {
auto res = this->_redis.get(this->_key(idx));
(void)res;
});
_cleanup();
_run("LPUSH key value", [this](std::size_t idx) {
this->_redis.lpush(this->_key(idx), _value);
});
_run("LRANGE key 0 10", [this](std::size_t idx) {
std::vector<std::string> res;
res.reserve(10);
this->_redis.lrange(this->_key(idx), 0, 10, std::back_inserter(res));
});
_run("LPOP key", [this](std::size_t idx) {
auto res = this->_redis.lpop(this->_key(idx));
(void)res;
});
_cleanup();
_run("INCR key", [this](std::size_t idx) {
auto num = this->_redis.incr(this->_key(idx));
(void)num;
});
_cleanup();
_run("SADD key member", [this](std::size_t idx) {
auto num = this->_redis.sadd(this->_key(idx), _value);
(void)num;
});
_run("SPOP key", [this](std::size_t idx) {
auto res = this->_redis.spop(this->_key(idx));
(void)res;
});
_cleanup();
}
template <typename RedisInstance>
template <typename Func>
void BenchmarkTest<RedisInstance>::_run(const std::string &title, Func &&func) {
auto thread_num = _opts.thread_num;
auto requests_per_thread = _opts.total_request_num / thread_num;
auto total_request_num = requests_per_thread * thread_num;
std::vector<std::future<std::size_t>> res;
res.reserve(thread_num);
res.push_back(std::async(std::launch::async,
[this](Func &&func, std::size_t request_num) {
return this->_run(std::forward<Func>(func), request_num);
},
std::forward<Func>(func),
requests_per_thread));
auto total_in_msec = 0;
for (auto &fut : res) {
total_in_msec += fut.get();
}
auto total_in_sec = total_in_msec * 1.0 / 1000;
auto avg = total_in_msec * 1.0 / total_request_num;
auto ops = static_cast<std::size_t>(1000 / avg);
std::cout << "-----" << title << "-----" << std::endl;
std::cout << total_request_num << " requests cost " << total_in_sec << " seconds" << std::endl;
std::cout << ops << " requests per second" << std::endl;
}
template <typename RedisInstance>
template <typename Func>
std::size_t BenchmarkTest<RedisInstance>::_run(Func &&func, std::size_t request_num) {
auto start = std::chrono::steady_clock::now();
for (auto idx = 0U; idx != request_num; ++idx) {
func(idx);
}
auto stop = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count();
}
template <typename RedisInstance>
std::vector<std::string> BenchmarkTest<RedisInstance>::_gen_keys() const {
const auto KEY_NUM = 100;
std::vector<std::string> res;
res.reserve(KEY_NUM);
std::default_random_engine engine(std::random_device{}());
std::uniform_int_distribution<int> uniform_dist(0, 255);
for (auto i = 0; i != KEY_NUM; ++i) {
std::string str;
str.reserve(_opts.key_len);
for (std::size_t j = 0; j != _opts.key_len; ++j) {
str.push_back(static_cast<char>(uniform_dist(engine)));
}
res.push_back(str);
}
return res;
}
template <typename RedisInstance>
std::string BenchmarkTest<RedisInstance>::_gen_value() const {
return std::string(_opts.val_len, 'x');
}
template <typename RedisInstance>
void BenchmarkTest<RedisInstance>::_cleanup() {
for (const auto &key : _keys) {
_redis.del(key);
}
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_BENCHMARK_TEST_HPP

View file

@ -0,0 +1,49 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
class ConnectionCmdTest {
public:
explicit ConnectionCmdTest(RedisInstance &instance) : _redis(instance) {}
void run();
private:
void _run(Redis &redis);
RedisInstance &_redis;
};
}
}
}
#include "connection_cmds_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_H

View file

@ -0,0 +1,50 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
void ConnectionCmdTest<RedisInstance>::run() {
cluster_specializing_test(*this, &ConnectionCmdTest<RedisInstance>::_run, _redis);
}
template <typename RedisInstance>
void ConnectionCmdTest<RedisInstance>::_run(Redis &instance) {
auto message = std::string("hello");
REDIS_ASSERT(instance.echo(message) == message, "failed to test echo");
REDIS_ASSERT(instance.ping() == "PONG", "failed to test ping");
REDIS_ASSERT(instance.ping(message) == message, "failed to test ping");
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_CONNECTION_CMDS_TEST_HPP

View file

@ -0,0 +1,47 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
class GeoCmdTest {
public:
explicit GeoCmdTest(RedisInstance &instance) : _redis(instance) {}
void run();
private:
RedisInstance &_redis;
};
}
}
}
#include "geo_cmds_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_H

View file

@ -0,0 +1,149 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP
#include <vector>
#include <tuple>
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
void GeoCmdTest<RedisInstance>::run() {
auto key = test_key("geo");
auto dest = test_key("dest");
KeyDeleter<RedisInstance> deleter(_redis, {key, dest});
auto members = {
std::make_tuple("m1", 10.0, 11.0),
std::make_tuple("m2", 10.1, 11.1),
std::make_tuple("m3", 10.2, 11.2)
};
REDIS_ASSERT(_redis.geoadd(key, std::make_tuple("m1", 10.0, 11.0)) == 1,
"failed to test geoadd");
REDIS_ASSERT(_redis.geoadd(key, members) == 2, "failed to test geoadd");
auto dist = _redis.geodist(key, "m1", "m4", GeoUnit::KM);
REDIS_ASSERT(!dist, "failed to test geodist with nonexistent member");
std::vector<OptionalString> hashes;
_redis.geohash(key, {"m1", "m4"}, std::back_inserter(hashes));
REDIS_ASSERT(hashes.size() == 2, "failed to test geohash");
REDIS_ASSERT(bool(hashes[0]) && *(hashes[0]) == "s1zned3z8u0" && !(hashes[1]),
"failed to test geohash");
hashes.clear();
_redis.geohash(key, {"m4"}, std::back_inserter(hashes));
REDIS_ASSERT(hashes.size() == 1 && !(hashes[0]), "failed to test geohash");
std::vector<Optional<std::pair<double, double>>> pos;
_redis.geopos(key, {"m4"}, std::back_inserter(pos));
REDIS_ASSERT(pos.size() == 1 && !(pos[0]), "failed to test geopos");
auto num = _redis.georadius(key,
std::make_pair(10.1, 11.1),
100,
GeoUnit::KM,
dest,
false,
10);
REDIS_ASSERT(bool(num) && *num == 3, "failed to test georadius with store option");
std::vector<std::string> mems;
_redis.georadius(key,
std::make_pair(10.1, 11.1),
100,
GeoUnit::KM,
10,
true,
std::back_inserter(mems));
REDIS_ASSERT(mems.size() == 3, "failed to test georadius with no option");
std::vector<std::tuple<std::string, double>> with_dist;
_redis.georadius(key,
std::make_pair(10.1, 11.1),
100,
GeoUnit::KM,
10,
true,
std::back_inserter(with_dist));
REDIS_ASSERT(with_dist.size() == 3, "failed to test georadius with dist");
std::vector<std::tuple<std::string, double, std::pair<double, double>>> with_dist_coord;
_redis.georadius(key,
std::make_pair(10.1, 11.1),
100,
GeoUnit::KM,
10,
true,
std::back_inserter(with_dist_coord));
REDIS_ASSERT(with_dist_coord.size() == 3, "failed to test georadius with dist and coord");
num = _redis.georadiusbymember(key,
"m1",
100,
GeoUnit::KM,
dest,
false,
10);
REDIS_ASSERT(bool(num) && *num == 3, "failed to test georadiusbymember with store option");
mems.clear();
_redis.georadiusbymember(key,
"m1",
100,
GeoUnit::KM,
10,
true,
std::back_inserter(mems));
REDIS_ASSERT(mems.size() == 3, "failed to test georadiusbymember with no option");
with_dist.clear();
_redis.georadiusbymember(key,
"m1",
100,
GeoUnit::KM,
10,
true,
std::back_inserter(with_dist));
REDIS_ASSERT(with_dist.size() == 3, "failed to test georadiusbymember with dist");
with_dist_coord.clear();
_redis.georadiusbymember(key,
"m1",
100,
GeoUnit::KM,
10,
true,
std::back_inserter(with_dist_coord));
REDIS_ASSERT(with_dist_coord.size() == 3,
"failed to test georadiusbymember with dist and coord");
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_GEO_CMDS_TEST_HPP

View file

@ -0,0 +1,55 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
class HashCmdTest {
public:
explicit HashCmdTest(RedisInstance &instance) : _redis(instance) {}
void run();
private:
void _test_hash();
void _test_hash_batch();
void _test_numeric();
void _test_hscan();
RedisInstance &_redis;
};
}
}
}
#include "hash_cmds_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_H

View file

@ -0,0 +1,177 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP
#include <unordered_map>
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
void HashCmdTest<RedisInstance>::run() {
_test_hash();
_test_hash_batch();
_test_numeric();
_test_hscan();
}
template <typename RedisInstance>
void HashCmdTest<RedisInstance>::_test_hash() {
auto key = test_key("hash");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto f1 = std::string("f1");
auto v1 = std::string("v1");
auto f2 = std::string("f2");
auto v2 = std::string("v2");
auto f3 = std::string("f3");
auto v3 = std::string("v3");
REDIS_ASSERT(_redis.hset(key, f1, v1), "failed to test hset");
REDIS_ASSERT(!_redis.hset(key, f1, v2), "failed to test hset with exist field");
auto res = _redis.hget(key, f1);
REDIS_ASSERT(res && *res == v2, "failed to test hget");
REDIS_ASSERT(_redis.hsetnx(key, f2, v1), "failed to test hsetnx");
REDIS_ASSERT(!_redis.hsetnx(key, f2, v2), "failed to test hsetnx with exist field");
res = _redis.hget(key, f2);
REDIS_ASSERT(res && *res == v1, "failed to test hget");
REDIS_ASSERT(!_redis.hexists(key, f3), "failed to test hexists");
REDIS_ASSERT(_redis.hset(key, std::make_pair(f3, v3)), "failed to test hset");
REDIS_ASSERT(_redis.hexists(key, f3), "failed to test hexists");
REDIS_ASSERT(_redis.hlen(key) == 3, "failed to test hlen");
REDIS_ASSERT(_redis.hstrlen(key, f1) == static_cast<long long>(v1.size()),
"failed to test hstrlen");
REDIS_ASSERT(_redis.hdel(key, f1) == 1, "failed to test hdel");
REDIS_ASSERT(_redis.hdel(key, {f1, f2, f3}) == 2, "failed to test hdel range");
}
template <typename RedisInstance>
void HashCmdTest<RedisInstance>::_test_hash_batch() {
auto key = test_key("hash");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto f1 = std::string("f1");
auto v1 = std::string("v1");
auto f2 = std::string("f2");
auto v2 = std::string("v2");
auto f3 = std::string("f3");
_redis.hmset(key, {std::make_pair(f1, v1),
std::make_pair(f2, v2)});
std::vector<std::string> fields;
_redis.hkeys(key, std::back_inserter(fields));
REDIS_ASSERT(fields.size() == 2, "failed to test hkeys");
std::vector<std::string> vals;
_redis.hvals(key, std::back_inserter(vals));
REDIS_ASSERT(vals.size() == 2, "failed to test hvals");
std::unordered_map<std::string, std::string> items;
_redis.hgetall(key, std::inserter(items, items.end()));
REDIS_ASSERT(items.size() == 2 && items[f1] == v1 && items[f2] == v2,
"failed to test hgetall");
std::vector<std::pair<std::string, std::string>> item_vec;
_redis.hgetall(key, std::back_inserter(item_vec));
REDIS_ASSERT(item_vec.size() == 2, "failed to test hgetall");
std::vector<OptionalString> res;
_redis.hmget(key, {f1, f2, f3}, std::back_inserter(res));
REDIS_ASSERT(res.size() == 3
&& bool(res[0]) && *(res[0]) == v1
&& bool(res[1]) && *(res[1]) == v2
&& !res[2],
"failed to test hmget");
}
template <typename RedisInstance>
void HashCmdTest<RedisInstance>::_test_numeric() {
auto key = test_key("numeric");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto field = "field";
REDIS_ASSERT(_redis.hincrby(key, field, 1) == 1, "failed to test hincrby");
REDIS_ASSERT(_redis.hincrby(key, field, -1) == 0, "failed to test hincrby");
REDIS_ASSERT(_redis.hincrbyfloat(key, field, 1.5) == 1.5, "failed to test hincrbyfloat");
}
template <typename RedisInstance>
void HashCmdTest<RedisInstance>::_test_hscan() {
auto key = test_key("hscan");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto items = std::unordered_map<std::string, std::string>{
std::make_pair("f1", "v1"),
std::make_pair("f2", "v2"),
std::make_pair("f3", "v3"),
};
_redis.hmset(key, items.begin(), items.end());
std::unordered_map<std::string, std::string> item_map;
auto cursor = 0;
while (true) {
cursor = _redis.hscan(key, cursor, "f*", 2, std::inserter(item_map, item_map.end()));
if (cursor == 0) {
break;
}
}
REDIS_ASSERT(item_map == items, "failed to test hscan with pattern and count");
std::vector<std::pair<std::string, std::string>> item_vec;
cursor = 0;
while (true) {
cursor = _redis.hscan(key, cursor, std::back_inserter(item_vec));
if (cursor == 0) {
break;
}
}
REDIS_ASSERT(item_vec.size() == items.size(), "failed to test hscan");
for (const auto &ele : item_vec) {
REDIS_ASSERT(items.find(ele.first) != items.end(), "failed to test hscan");
}
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_HASH_CMDS_TEST_HPP

View file

@ -0,0 +1,47 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
class HyperloglogCmdTest {
public:
explicit HyperloglogCmdTest(RedisInstance &instance) : _redis(instance) {}
void run();
private:
RedisInstance &_redis;
};
}
}
}
#include "hyperloglog_cmds_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_H

View file

@ -0,0 +1,67 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
void HyperloglogCmdTest<RedisInstance>::run() {
auto k1 = test_key("k1");
auto k2 = test_key("k2");
auto k3 = test_key("k3");
KeyDeleter<RedisInstance> deleter(_redis, {k1, k2, k3});
_redis.pfadd(k1, "a");
auto members1 = {"b", "c", "d", "e", "f", "g"};
_redis.pfadd(k1, members1);
auto cnt = _redis.pfcount(k1);
auto err = cnt * 1.0 / (1 + members1.size());
REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfadd and pfcount");
auto members2 = {"a", "b", "c", "h", "i", "j", "k"};
_redis.pfadd(k2, members2);
auto total = 1 + members1.size() + members2.size() - 3;
cnt = _redis.pfcount({k1, k2});
err = cnt * 1.0 / total;
REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfcount");
_redis.pfmerge(k3, {k1, k2});
cnt = _redis.pfcount(k3);
err = cnt * 1.0 / total;
REDIS_ASSERT(err < 1.02 && err > 0.98, "failed to test pfcount");
_redis.pfmerge(k3, k1);
REDIS_ASSERT(cnt == _redis.pfcount(k3), "failed to test pfmerge");
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_HYPERLOGLOG_CMDS_TEST_HPP

View file

@ -0,0 +1,55 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
class KeysCmdTest {
public:
explicit KeysCmdTest(RedisInstance &instance) : _redis(instance) {}
void run();
private:
void _test_key();
void _test_randomkey(Redis &instance);
void _test_ttl();
void _test_scan(Redis &instance);
RedisInstance &_redis;
};
}
}
}
#include "keys_cmds_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_H

View file

@ -0,0 +1,166 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP
#include <vector>
#include <unordered_set>
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
void KeysCmdTest<RedisInstance>::run() {
_test_key();
cluster_specializing_test(*this, &KeysCmdTest<RedisInstance>::_test_randomkey, _redis);
_test_ttl();
cluster_specializing_test(*this, &KeysCmdTest<RedisInstance>::_test_scan, _redis);
}
template <typename RedisInstance>
void KeysCmdTest<RedisInstance>::_test_key() {
auto key = test_key("key");
auto dest = test_key("dest");
auto new_key_name = test_key("new-key");
auto not_exist_key = test_key("not-exist");
KeyDeleter<RedisInstance> deleter(_redis, {key, dest, new_key_name});
REDIS_ASSERT(_redis.exists(key) == 0, "failed to test exists");
auto val = std::string("val");
_redis.set(key, val);
REDIS_ASSERT(_redis.exists({key, not_exist_key}) == 1, "failed to test exists");
auto new_val = _redis.dump(key);
REDIS_ASSERT(bool(new_val), "failed to test dump");
_redis.restore(dest, *new_val, std::chrono::seconds(1000));
new_val = _redis.get(dest);
REDIS_ASSERT(bool(new_val) && *new_val == val, "failed to test dump and restore");
_redis.rename(dest, new_key_name);
bool not_exist = false;
try {
_redis.rename(not_exist_key, new_key_name);
} catch (const Error &e) {
not_exist = true;
}
REDIS_ASSERT(not_exist, "failed to test rename with nonexistent key");
REDIS_ASSERT(_redis.renamenx(new_key_name, dest), "failed to test renamenx");
REDIS_ASSERT(_redis.touch(not_exist_key) == 0, "failed to test touch");
REDIS_ASSERT(_redis.touch({key, dest, new_key_name}) == 2, "failed to test touch");
REDIS_ASSERT(_redis.type(key) == "string", "failed to test type");
REDIS_ASSERT(_redis.del({new_key_name, dest}) == 1, "failed to test del");
REDIS_ASSERT(_redis.unlink({new_key_name, key}) == 1, "failed to test unlink");
}
template <typename RedisInstance>
void KeysCmdTest<RedisInstance>::_test_randomkey(Redis &instance) {
auto key = test_key("randomkey");
KeyDeleter<Redis> deleter(instance, key);
instance.set(key, "value");
auto rand_key = instance.randomkey();
REDIS_ASSERT(bool(rand_key), "failed to test randomkey");
}
template <typename RedisInstance>
void KeysCmdTest<RedisInstance>::_test_ttl() {
using namespace std::chrono;
auto key = test_key("ttl");
KeyDeleter<RedisInstance> deleter(_redis, key);
_redis.set(key, "val", seconds(100));
auto ttl = _redis.ttl(key);
REDIS_ASSERT(ttl > 0 && ttl <= 100, "failed to test ttl");
REDIS_ASSERT(_redis.persist(key), "failed to test persist");
ttl = _redis.ttl(key);
REDIS_ASSERT(ttl == -1, "failed to test ttl");
REDIS_ASSERT(_redis.expire(key, seconds(100)),
"failed to test expire");
auto tp = time_point_cast<seconds>(system_clock::now() + seconds(100));
REDIS_ASSERT(_redis.expireat(key, tp), "failed to test expireat");
ttl = _redis.ttl(key);
REDIS_ASSERT(ttl > 0, "failed to test expireat");
REDIS_ASSERT(_redis.pexpire(key, milliseconds(100000)), "failed to test expire");
auto pttl = _redis.pttl(key);
REDIS_ASSERT(pttl > 0 && pttl <= 100000, "failed to test pttl");
auto tp_milli = time_point_cast<milliseconds>(system_clock::now() + milliseconds(100000));
REDIS_ASSERT(_redis.pexpireat(key, tp_milli), "failed to test pexpireat");
pttl = _redis.pttl(key);
REDIS_ASSERT(pttl > 0, "failed to test pexpireat");
}
template <typename RedisInstance>
void KeysCmdTest<RedisInstance>::_test_scan(Redis &instance) {
std::string key_pattern = "!@#$%^&()_+alseufoawhnlkszd";
auto k1 = test_key(key_pattern + "k1");
auto k2 = test_key(key_pattern + "k2");
auto k3 = test_key(key_pattern + "k3");
auto keys = {k1, k2, k3};
KeyDeleter<Redis> deleter(instance, keys);
instance.set(k1, "v");
instance.set(k2, "v");
instance.set(k3, "v");
auto cursor = 0;
std::unordered_set<std::string> res;
while (true) {
cursor = instance.scan(cursor, "*" + key_pattern + "*", 2, std::inserter(res, res.end()));
if (cursor == 0) {
break;
}
}
REDIS_ASSERT(res == std::unordered_set<std::string>(keys),
"failed to test scan");
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_KEYS_CMDS_TEST_HPP

View file

@ -0,0 +1,55 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
class ListCmdTest {
public:
explicit ListCmdTest(RedisInstance &instance) : _redis(instance) {}
void run();
private:
void _test_lpoppush();
void _test_rpoppush();
void _test_list();
void _test_blocking();
RedisInstance &_redis;
};
}
}
}
#include "list_cmds_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_H

View file

@ -0,0 +1,154 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
void ListCmdTest<RedisInstance>::run() {
_test_lpoppush();
_test_rpoppush();
_test_list();
_test_blocking();
}
template <typename RedisInstance>
void ListCmdTest<RedisInstance>::_test_lpoppush() {
auto key = test_key("lpoppush");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto item = _redis.lpop(key);
REDIS_ASSERT(!item, "failed to test lpop");
REDIS_ASSERT(_redis.lpushx(key, "1") == 0, "failed to test lpushx");
REDIS_ASSERT(_redis.lpush(key, "1") == 1, "failed to test lpush");
REDIS_ASSERT(_redis.lpushx(key, "2") == 2, "failed to test lpushx");
REDIS_ASSERT(_redis.lpush(key, {"3", "4", "5"}) == 5, "failed to test lpush");
item = _redis.lpop(key);
REDIS_ASSERT(item && *item == "5", "failed to test lpop");
}
template <typename RedisInstance>
void ListCmdTest<RedisInstance>::_test_rpoppush() {
auto key = test_key("rpoppush");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto item = _redis.rpop(key);
REDIS_ASSERT(!item, "failed to test rpop");
REDIS_ASSERT(_redis.rpushx(key, "1") == 0, "failed to test rpushx");
REDIS_ASSERT(_redis.rpush(key, "1") == 1, "failed to test rpush");
REDIS_ASSERT(_redis.rpushx(key, "2") == 2, "failed to test rpushx");
REDIS_ASSERT(_redis.rpush(key, {"3", "4", "5"}) == 5, "failed to test rpush");
item = _redis.rpop(key);
REDIS_ASSERT(item && *item == "5", "failed to test rpop");
}
template <typename RedisInstance>
void ListCmdTest<RedisInstance>::_test_list() {
auto key = test_key("list");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto item = _redis.lindex(key, 0);
REDIS_ASSERT(!item, "failed to test lindex");
_redis.lpush(key, {"1", "2", "3", "4", "5"});
REDIS_ASSERT(_redis.lrem(key, 0, "3") == 1, "failed to test lrem");
REDIS_ASSERT(_redis.linsert(key, InsertPosition::BEFORE, "2", "3") == 5,
"failed to test lindex");
REDIS_ASSERT(_redis.llen(key) == 5, "failed to test llen");
_redis.lset(key, 0, "6");
item = _redis.lindex(key, 0);
REDIS_ASSERT(item && *item == "6", "failed to test lindex");
_redis.ltrim(key, 0, 2);
std::vector<std::string> res;
_redis.lrange(key, 0, -1, std::back_inserter(res));
REDIS_ASSERT(res == std::vector<std::string>({"6", "4", "3"}), "failed to test ltrim");
}
template <typename RedisInstance>
void ListCmdTest<RedisInstance>::_test_blocking() {
auto k1 = test_key("k1");
auto k2 = test_key("k2");
auto k3 = test_key("k3");
auto keys = {k1, k2, k3};
KeyDeleter<RedisInstance> deleter(_redis, keys);
std::string val("value");
_redis.lpush(k1, val);
auto res = _redis.blpop(keys.begin(), keys.end());
REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test blpop");
res = _redis.brpop(keys, std::chrono::seconds(1));
REDIS_ASSERT(!res, "failed to test brpop with timeout");
_redis.lpush(k1, val);
res = _redis.blpop(k1);
REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test blpop");
res = _redis.blpop(k1, std::chrono::seconds(1));
REDIS_ASSERT(!res, "failed to test blpop with timeout");
_redis.lpush(k1, val);
res = _redis.brpop(k1);
REDIS_ASSERT(res && *res == std::make_pair(k1, val), "failed to test brpop");
res = _redis.brpop(k1, std::chrono::seconds(1));
REDIS_ASSERT(!res, "failed to test brpop with timeout");
auto str = _redis.brpoplpush(k2, k3, std::chrono::seconds(1));
REDIS_ASSERT(!str, "failed to test brpoplpush with timeout");
_redis.lpush(k2, val);
str = _redis.brpoplpush(k2, k3);
REDIS_ASSERT(str && *str == val, "failed to test brpoplpush");
str = _redis.rpoplpush(k3, k2);
REDIS_ASSERT(str && *str == val, "failed to test rpoplpush");
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_LIST_CMDS_TEST_HPP

View file

@ -0,0 +1,57 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H
#define SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H
#include <sw/redis++/redis++.h>
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
class PipelineTransactionTest {
public:
explicit PipelineTransactionTest(RedisInstance &instance) : _redis(instance) {}
void run();
private:
Pipeline _pipeline(const StringView &key);
Transaction _transaction(const StringView &key, bool piped);
void _test_pipeline(const StringView &key, Pipeline &pipe);
void _test_transaction(const StringView &key, Transaction &tx);
void _test_watch();
RedisInstance &_redis;
};
}
}
}
#include "pipeline_transaction_test.hpp"
#endif // end SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_H

View file

@ -0,0 +1,184 @@
/**************************************************************************
Copyright (c) 2017 sewenew
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
#ifndef SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP
#define SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP
#include <string>
#include "utils.h"
namespace sw {
namespace redis {
namespace test {
template <typename RedisInstance>
void PipelineTransactionTest<RedisInstance>::run() {
{
auto key = test_key("pipeline");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto pipe = _pipeline(key);
_test_pipeline(key, pipe);
}
{
auto key = test_key("transaction");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto tx = _transaction(key, true);
_test_transaction(key, tx);
}
{
auto key = test_key("transaction");
KeyDeleter<RedisInstance> deleter(_redis, key);
auto tx = _transaction(key, false);
_test_transaction(key, tx);
}
_test_watch();
}
template <typename RedisInstance>
Pipeline PipelineTransactionTest<RedisInstance>::_pipeline(const StringView &) {
return _redis.pipeline();
}
template <>
inline Pipeline PipelineTransactionTest<RedisCluster>::_pipeline(const StringView &key) {
return _redis.pipeline(key);
}
template <typename RedisInstance>
Transaction PipelineTransactionTest<RedisInstance>::_transaction(const StringView &, bool piped) {
return _redis.transaction(piped);
}
template <>
inline Transaction PipelineTransactionTest<RedisCluster>::_transaction(const StringView &key,
bool piped) {
return _redis.transaction(key, piped);
}
template <typename RedisInstance>
void PipelineTransactionTest<RedisInstance>::_test_pipeline(const StringView &key,
Pipeline &pipe) {
std::string val("value");
auto replies = pipe.set(key, val)
.get(key)
.strlen(key)
.exec();
REDIS_ASSERT(replies.get<bool>(0), "failed to test pipeline with set operation");
auto new_val = replies.get<OptionalString>(1);
std::size_t len = replies.get<long long>(2);
REDIS_ASSERT(bool(new_val) && *new_val == val && len == val.size(),
"failed to test pipeline with string operations");
REDIS_ASSERT(reply::parse<bool>(replies.get(0)), "failed to test pipeline with set operation");
new_val = reply::parse<OptionalString>(replies.get(1));
len = reply::parse<long long>(replies.get(2));
REDIS_ASSERT(bool(new_val) && *new_val == val && len == val.size(),
"failed to test pipeline with string operations");
}
template <typename RedisInstance>
void PipelineTransactionTest<RedisInstance>::_test_transaction(const StringView &key,
Transaction &tx) {
std::unordered_map<std::string, std::string> m = {
std::make_pair("f1", "v1"),
std::make_pair("f2", "v2"),
std::make_pair("f3", "v3")
};
auto replies = tx.hmset(key, m.begin(), m.end())
.hgetall(key)
.hdel(key, "f1")
.exec();
replies.get<void>(0);
decltype(m) mm;
replies.get(1, std::inserter(mm, mm.end()));
REDIS_ASSERT(mm == m, "failed to test transaction");
REDIS_ASSERT(replies.get<long long>(2) == 1, "failed to test transaction");
tx.set(key, "value")
.get(key)
.incr(key);
tx.discard();
replies = tx.del(key)
.set(key, "value")
.exec();
REDIS_ASSERT(replies.get<long long>(0) == 1, "failed to test transaction");
REDIS_ASSERT(replies.get<bool>(1), "failed to test transaction");
}
template <typename RedisInstance>
void PipelineTransactionTest<RedisInstance>::_test_watch() {
auto key = test_key("watch");
KeyDeleter<RedisInstance> deleter(_redis, key);
{
auto tx = _transaction(key, false);
auto redis = tx.redis();
redis.watch(key);
auto replies = tx.set(key, "1").get(key).exec();
REDIS_ASSERT(replies.size() == 2
&& replies.template get<bool>(0) == true, "failed to test watch");
auto val = replies.template get<sw::redis::OptionalString>(1);
REDIS_ASSERT(val && *val == "1", "failed to test watch");
}
try {
auto tx = _transaction(key, false);
auto redis = tx.redis();
redis.watch(key);
// Key has been modified by other client.
_redis.set(key, "val");
// Transaction should fail, and throw WatchError
tx.set(key, "1").exec();
REDIS_ASSERT(false, "failed to test watch");
} catch (const sw::redis::WatchError &err) {
// Catch the error.
}
}
}
}
}
#endif // end SEWENEW_REDISPLUSPLUS_TEST_PIPELINE_TRANSACTION_TEST_HPP

Some files were not shown because too many files have changed in this diff Show more