Unverified Commit 49816941 authored by chendianqiang's avatar chendianqiang Committed by GitHub
Browse files

Merge pull request #2 from antirez/unstable

merge from redis
parents 68ceb466 f311a529
...@@ -119,7 +119,7 @@ parameter (the path of the configuration file): ...@@ -119,7 +119,7 @@ parameter (the path of the configuration file):
It is possible to alter the Redis configuration by passing parameters directly It is possible to alter the Redis configuration by passing parameters directly
as options using the command line. Examples: as options using the command line. Examples:
% ./redis-server --port 9999 --slaveof 127.0.0.1 6379 % ./redis-server --port 9999 --replicaof 127.0.0.1 6379
% ./redis-server /etc/redis/6379.conf --loglevel debug % ./redis-server /etc/redis/6379.conf --loglevel debug
All the options in redis.conf are also supported as options using the command All the options in redis.conf are also supported as options using the command
...@@ -216,7 +216,7 @@ Inside the root are the following important directories: ...@@ -216,7 +216,7 @@ Inside the root are the following important directories:
* `src`: contains the Redis implementation, written in C. * `src`: contains the Redis implementation, written in C.
* `tests`: contains the unit tests, implemented in Tcl. * `tests`: contains the unit tests, implemented in Tcl.
* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `anitrez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository. * `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`.
There are a few more directories but they are not very important for our goals There are a few more directories but they are not very important for our goals
here. We'll focus mostly on `src`, where the Redis implementation is contained, here. We'll focus mostly on `src`, where the Redis implementation is contained,
...@@ -227,7 +227,7 @@ of complexity incrementally. ...@@ -227,7 +227,7 @@ of complexity incrementally.
Note: lately Redis was refactored quite a bit. Function names and file Note: lately Redis was refactored quite a bit. Function names and file
names have been changed, so you may find that this documentation reflects the names have been changed, so you may find that this documentation reflects the
`unstable` branch more closely. For instance in Redis 3.0 the `server.c` `unstable` branch more closely. For instance in Redis 3.0 the `server.c`
and `server.h` files were named to `redis.c` and `redis.h`. However the overall and `server.h` files were named `redis.c` and `redis.h`. However the overall
structure is the same. Keep in mind that all the new developments and pull structure is the same. Keep in mind that all the new developments and pull
requests should be performed against the `unstable` branch. requests should be performed against the `unstable` branch.
...@@ -245,7 +245,7 @@ A few important fields in this structure are: ...@@ -245,7 +245,7 @@ A few important fields in this structure are:
* `server.db` is an array of Redis databases, where data is stored. * `server.db` is an array of Redis databases, where data is stored.
* `server.commands` is the command table. * `server.commands` is the command table.
* `server.clients` is a linked list of clients connected to the server. * `server.clients` is a linked list of clients connected to the server.
* `server.master` is a special client, the master, if the instance is a slave. * `server.master` is a special client, the master, if the instance is a replica.
There are tons of other fields. Most fields are commented directly inside There are tons of other fields. Most fields are commented directly inside
the structure definition. the structure definition.
...@@ -323,7 +323,7 @@ Inside server.c you can find code that handles other vital things of the Redis s ...@@ -323,7 +323,7 @@ Inside server.c you can find code that handles other vital things of the Redis s
networking.c networking.c
--- ---
This file defines all the I/O functions with clients, masters and slaves This file defines all the I/O functions with clients, masters and replicas
(which in Redis are just special clients): (which in Redis are just special clients):
* `createClient()` allocates and initializes a new client. * `createClient()` allocates and initializes a new client.
...@@ -390,16 +390,16 @@ replication.c ...@@ -390,16 +390,16 @@ replication.c
This is one of the most complex files inside Redis, it is recommended to This is one of the most complex files inside Redis, it is recommended to
approach it only after getting a bit familiar with the rest of the code base. approach it only after getting a bit familiar with the rest of the code base.
In this file there is the implementation of both the master and slave role In this file there is the implementation of both the master and replica role
of Redis. of Redis.
One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing slave instances connected One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing replica instances connected
to our master, so that the slaves can get the writes performed by the clients: to our master, so that the replicas can get the writes performed by the clients:
this way their data set will remain synchronized with the one in the master. this way their data set will remain synchronized with the one in the master.
This file also implements both the `SYNC` and `PSYNC` commands that are This file also implements both the `SYNC` and `PSYNC` commands that are
used in order to perform the first synchronization between masters and used in order to perform the first synchronization between masters and
slaves, or to continue the replication after a disconnection. replicas, or to continue the replication after a disconnection.
Other C files Other C files
--- ---
......
...@@ -2,7 +2,6 @@ This directory contains all Redis dependencies, except for the libc that ...@@ -2,7 +2,6 @@ This directory contains all Redis dependencies, except for the libc that
should be provided by the operating system. should be provided by the operating system.
* **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time. * **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time.
* **geohash-int** is inside the dependencies directory but is actually part of the Redis project, since it is our private fork (heavily modified) of a library initially developed for Ardb, which is in turn a fork of Redis.
* **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed. * **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed.
* **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed. * **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed.
* **lua** is Lua 5.1 with minor changes for security and additional libraries. * **lua** is Lua 5.1 with minor changes for security and additional libraries.
...@@ -42,11 +41,6 @@ the following additional steps: ...@@ -42,11 +41,6 @@ the following additional steps:
changed, otherwise you could just copy the old implementation if you are changed, otherwise you could just copy the old implementation if you are
upgrading just to a similar version of Jemalloc. upgrading just to a similar version of Jemalloc.
Geohash
---
This is never upgraded since it's part of the Redis project. If there are changes to merge from Ardb there is the need to manually check differences, but at this point the source code is pretty different.
Hiredis Hiredis
--- ---
......
...@@ -8,6 +8,12 @@ os: ...@@ -8,6 +8,12 @@ os:
- linux - linux
- osx - osx
branches:
only:
- staging
- trying
- master
before_script: before_script:
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi - if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
......
### 1.0.0 (unreleased) ### 1.0.0 (unreleased)
**Fixes**: **BREAKING CHANGES**:
* 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.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 * 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 * Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
* Fix warnings, when compiled with -Wshadow * Fix warnings, when compiled with -Wshadow
...@@ -9,11 +53,6 @@ ...@@ -9,11 +53,6 @@
**BREAKING CHANGES**: **BREAKING CHANGES**:
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
User code should compare this to `size_t` values as well.
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
* Remove backwards compatibility macro's * Remove backwards compatibility macro's
This removes the following old function aliases, use the new name now: This removes the following old function aliases, use the new name now:
...@@ -94,7 +133,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows ...@@ -94,7 +133,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
* Add IPv6 support * Add IPv6 support
* Remove possiblity of multiple close on same fd * Remove possibility of multiple close on same fd
* Add ability to bind source address on connect * Add ability to bind source address on connect
......
...@@ -36,13 +36,13 @@ endef ...@@ -36,13 +36,13 @@ endef
export REDIS_TEST_CONFIG export REDIS_TEST_CONFIG
# Fallback to gcc when $CC is not in $PATH. # Fallback to gcc when $CC is not in $PATH.
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc') 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++') CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
OPTIMIZATION?=-O3 OPTIMIZATION?=-O3
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
DEBUG_FLAGS?= -g -ggdb DEBUG_FLAGS?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(ARCH) REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
REAL_LDFLAGS=$(LDFLAGS) $(ARCH) REAL_LDFLAGS=$(LDFLAGS)
DYLIBSUFFIX=so DYLIBSUFFIX=so
STLIBSUFFIX=a STLIBSUFFIX=a
...@@ -58,12 +58,11 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') ...@@ -58,12 +58,11 @@ uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
REAL_LDFLAGS+= -ldl -lnsl -lsocket REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
INSTALL= cp -r
endif endif
ifeq ($(uname_S),Darwin) ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
endif endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
...@@ -94,7 +93,7 @@ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) ...@@ -94,7 +93,7 @@ hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME) $(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) hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
...@@ -161,11 +160,7 @@ clean: ...@@ -161,11 +160,7 @@ clean:
dep: dep:
$(CC) -MM *.c $(CC) -MM *.c
ifeq ($(uname_S),SunOS) INSTALL?= cp -pPR
INSTALL?= cp -r
endif
INSTALL?= cp -a
$(PKGCONFNAME): hiredis.h $(PKGCONFNAME): hiredis.h
@echo "Generating $@ for pkgconfig..." @echo "Generating $@ for pkgconfig..."
...@@ -181,8 +176,9 @@ $(PKGCONFNAME): hiredis.h ...@@ -181,8 +176,9 @@ $(PKGCONFNAME): hiredis.h
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH) $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH) $(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
......
...@@ -73,8 +73,8 @@ static void redisLibeventDelWrite(void *privdata) { ...@@ -73,8 +73,8 @@ static void redisLibeventDelWrite(void *privdata) {
static void redisLibeventCleanup(void *privdata) { static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_del(e->rev); event_free(e->rev);
event_del(e->wev); event_free(e->wev);
free(e); free(e);
} }
......
...@@ -15,15 +15,12 @@ typedef struct redisLibuvEvents { ...@@ -15,15 +15,12 @@ typedef struct redisLibuvEvents {
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) { static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
redisLibuvEvents* p = (redisLibuvEvents*)handle->data; redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
int ev = (status ? p->events : events);
if (status != 0) { if (p->context != NULL && (ev & UV_READABLE)) {
return;
}
if (p->context != NULL && (events & UV_READABLE)) {
redisAsyncHandleRead(p->context); redisAsyncHandleRead(p->context);
} }
if (p->context != NULL && (events & UV_WRITABLE)) { if (p->context != NULL && (ev & UV_WRITABLE)) {
redisAsyncHandleWrite(p->context); redisAsyncHandleWrite(p->context);
} }
} }
......
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin) # Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
environment: environment:
matrix: matrix:
- CYG_ROOT: C:\cygwin64 - CYG_BASH: C:\cygwin64\bin\bash
CYG_SETUP: setup-x86_64.exe
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin64\var\cache\setup
CYG_BASH: C:\cygwin64\bin\bash
CC: gcc CC: gcc
- CYG_ROOT: C:\cygwin - CYG_BASH: C:\cygwin\bin\bash
CYG_SETUP: setup-x86.exe
CYG_MIRROR: http://cygwin.mirror.constant.com
CYG_CACHE: C:\cygwin\var\cache\setup
CYG_BASH: C:\cygwin\bin\bash
CC: gcc CC: gcc
TARGET: 32bit TARGET: 32bit
TARGET_VARS: 32bit-vars TARGET_VARS: 32bit-vars
# Cache Cygwin files to speed up build
cache:
- '%CYG_CACHE%'
clone_depth: 1 clone_depth: 1
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail # Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
...@@ -27,8 +16,6 @@ init: ...@@ -27,8 +16,6 @@ init:
# Install needed build dependencies # Install needed build dependencies
install: install:
- ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
- '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
- '%CYG_BASH% -lc "cygcheck -dc cygwin"' - '%CYG_BASH% -lc "cygcheck -dc cygwin"'
build_script: build_script:
......
...@@ -336,7 +336,8 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -336,7 +336,8 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
if (ac->err == 0) { if (ac->err == 0) {
/* For clean disconnects, there should be no pending callbacks. */ /* For clean disconnects, there should be no pending callbacks. */
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR); int ret = __redisShiftCallback(&ac->replies,NULL);
assert(ret == REDIS_ERR);
} else { } else {
/* Disconnection is caused by an error, make sure that pending /* Disconnection is caused by an error, make sure that pending
* callbacks cannot call new commands. */ * callbacks cannot call new commands. */
...@@ -364,6 +365,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -364,6 +365,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) { static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
dict *callbacks; dict *callbacks;
redisCallback *cb;
dictEntry *de; dictEntry *de;
int pvariant; int pvariant;
char *stype; char *stype;
...@@ -387,16 +389,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -387,16 +389,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
de = dictFind(callbacks,sname); de = dictFind(callbacks,sname);
if (de != NULL) { if (de != NULL) {
memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb)); cb = dictGetEntryVal(de);
/* If this is an subscribe reply decrease pending counter. */
if (strcasecmp(stype+pvariant,"subscribe") == 0) {
cb->pending_subs -= 1;
}
memcpy(dstcb,cb,sizeof(*dstcb));
/* If this is an unsubscribe message, remove it. */ /* If this is an unsubscribe message, remove it. */
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) { if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
dictDelete(callbacks,sname); if (cb->pending_subs == 0)
dictDelete(callbacks,sname);
/* If this was the last unsubscribe message, revert to /* If this was the last unsubscribe message, revert to
* non-subscribe mode. */ * non-subscribe mode. */
assert(reply->element[2]->type == REDIS_REPLY_INTEGER); assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
if (reply->element[2]->integer == 0)
/* Unset subscribed flag only when no pipelined pending subscribe. */
if (reply->element[2]->integer == 0
&& dictSize(ac->sub.channels) == 0
&& dictSize(ac->sub.patterns) == 0)
c->flags &= ~REDIS_SUBSCRIBED; c->flags &= ~REDIS_SUBSCRIBED;
} }
} }
...@@ -410,7 +424,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -410,7 +424,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
void redisProcessCallbacks(redisAsyncContext *ac) { void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb = {NULL, NULL, NULL}; redisCallback cb = {NULL, NULL, 0, NULL};
void *reply = NULL; void *reply = NULL;
int status; int status;
...@@ -492,22 +506,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) { ...@@ -492,22 +506,22 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
* write event fires. When connecting was not successful, the connect callback * write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */ * is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
int completed = 0;
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
if (redisCheckSocketError(c) == REDIS_ERR) { /* Error! */
/* Try again later when connect(2) is still in progress. */ redisCheckSocketError(c);
if (errno == EINPROGRESS) if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
return REDIS_OK;
if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
return REDIS_ERR; return REDIS_ERR;
} else if (completed == 1) {
/* connected! */
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
} else {
return REDIS_OK;
} }
/* Mark context as connected. */
c->flags |= REDIS_CONNECTED;
if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
return REDIS_OK;
} }
/* This function should be called when the socket is readable. /* This function should be called when the socket is readable.
...@@ -583,6 +597,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len ...@@ -583,6 +597,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) { static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
struct dict *cbdict;
dictEntry *de;
redisCallback *existcb;
int pvariant, hasnext; int pvariant, hasnext;
const char *cstr, *astr; const char *cstr, *astr;
size_t clen, alen; size_t clen, alen;
...@@ -596,6 +613,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -596,6 +613,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
/* Setup callback */ /* Setup callback */
cb.fn = fn; cb.fn = fn;
cb.privdata = privdata; cb.privdata = privdata;
cb.pending_subs = 1;
/* Find out which command will be appended. */ /* Find out which command will be appended. */
p = nextArgument(cmd,&cstr,&clen); p = nextArgument(cmd,&cstr,&clen);
...@@ -612,9 +630,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -612,9 +630,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
while ((p = nextArgument(p,&astr,&alen)) != NULL) { while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen); sname = sdsnewlen(astr,alen);
if (pvariant) if (pvariant)
ret = dictReplace(ac->sub.patterns,sname,&cb); cbdict = ac->sub.patterns;
else else
ret = dictReplace(ac->sub.channels,sname,&cb); cbdict = ac->sub.channels;
de = dictFind(cbdict,sname);
if (de != NULL) {
existcb = dictGetEntryVal(de);
cb.pending_subs = existcb->pending_subs + 1;
}
ret = dictReplace(cbdict,sname,&cb);
if (ret == 0) sdsfree(sname); if (ret == 0) sdsfree(sname);
} }
...@@ -676,6 +703,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv ...@@ -676,6 +703,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv
int len; int len;
int status; int status;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len < 0)
return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
sdsfree(cmd); sdsfree(cmd);
return status; return status;
......
...@@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*); ...@@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
typedef struct redisCallback { typedef struct redisCallback {
struct redisCallback *next; /* simple singly linked list */ struct redisCallback *next; /* simple singly linked list */
redisCallbackFn *fn; redisCallbackFn *fn;
int pending_subs;
void *privdata; void *privdata;
} redisCallback; } redisCallback;
...@@ -92,6 +93,10 @@ typedef struct redisAsyncContext { ...@@ -92,6 +93,10 @@ typedef struct redisAsyncContext {
/* Regular command callbacks */ /* Regular command callbacks */
redisCallbackList replies; redisCallbackList replies;
/* Address used for connect() */
struct sockaddr *saddr;
size_t addrlen;
/* Subscription callbacks */ /* Subscription callbacks */
struct { struct {
redisCallbackList invalid; redisCallbackList invalid;
......
#ifndef __HIREDIS_FMACRO_H #ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H #define __HIREDIS_FMACRO_H
#if defined(__linux__)
#define _BSD_SOURCE
#define _DEFAULT_SOURCE
#endif
#if defined(__CYGWIN__)
#include <sys/cdefs.h>
#endif
#if defined(__sun__)
#define _POSIX_C_SOURCE 200112L
#else
#if !(defined(__APPLE__) && defined(__MACH__)) && !(defined(__FreeBSD__))
#define _XOPEN_SOURCE 600 #define _XOPEN_SOURCE 600
#endif #define _POSIX_C_SOURCE 200112L
#endif
#if defined(__APPLE__) && defined(__MACH__) #if defined(__APPLE__) && defined(__MACH__)
#define _OSX /* Enable TCP_KEEPALIVE */
#define _DARWIN_C_SOURCE
#endif #endif
#endif #endif
...@@ -47,7 +47,9 @@ static redisReply *createReplyObject(int type); ...@@ -47,7 +47,9 @@ static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len); static void *createStringObject(const redisReadTask *task, char *str, size_t len);
static void *createArrayObject(const redisReadTask *task, int elements); static void *createArrayObject(const redisReadTask *task, int elements);
static void *createIntegerObject(const redisReadTask *task, long long value); static void *createIntegerObject(const redisReadTask *task, long long value);
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task); static void *createNilObject(const redisReadTask *task);
static void *createBoolObject(const redisReadTask *task, int bval);
/* Default set of functions to build the reply. Keep in mind that such a /* Default set of functions to build the reply. Keep in mind that such a
* function returning NULL is interpreted as OOM. */ * function returning NULL is interpreted as OOM. */
...@@ -55,7 +57,9 @@ static redisReplyObjectFunctions defaultFunctions = { ...@@ -55,7 +57,9 @@ static redisReplyObjectFunctions defaultFunctions = {
createStringObject, createStringObject,
createArrayObject, createArrayObject,
createIntegerObject, createIntegerObject,
createDoubleObject,
createNilObject, createNilObject,
createBoolObject,
freeReplyObject freeReplyObject
}; };
...@@ -82,18 +86,19 @@ void freeReplyObject(void *reply) { ...@@ -82,18 +86,19 @@ void freeReplyObject(void *reply) {
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
break; /* Nothing to free */ break; /* Nothing to free */
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP:
case REDIS_REPLY_SET:
if (r->element != NULL) { if (r->element != NULL) {
for (j = 0; j < r->elements; j++) for (j = 0; j < r->elements; j++)
if (r->element[j] != NULL) freeReplyObject(r->element[j]);
freeReplyObject(r->element[j]);
free(r->element); free(r->element);
} }
break; break;
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
if (r->str != NULL) case REDIS_REPLY_DOUBLE:
free(r->str); free(r->str);
break; break;
} }
free(r); free(r);
...@@ -125,7 +130,9 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len ...@@ -125,7 +130,9 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -134,7 +141,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len ...@@ -134,7 +141,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
static void *createArrayObject(const redisReadTask *task, int elements) { static void *createArrayObject(const redisReadTask *task, int elements) {
redisReply *r, *parent; redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_ARRAY); r = createReplyObject(task->type);
if (r == NULL) if (r == NULL)
return NULL; return NULL;
...@@ -150,7 +157,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) { ...@@ -150,7 +157,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -167,7 +176,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) { ...@@ -167,7 +176,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
}
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_DOUBLE);
if (r == NULL)
return NULL;
r->dval = value;
r->str = malloc(len+1);
if (r->str == NULL) {
freeReplyObject(r);
return NULL;
}
/* The double reply also has the original protocol string representing a
* double as a null terminated string. This way the caller does not need
* to format back for string conversion, especially since Redis does efforts
* to make the string more human readable avoiding the calssical double
* decimal string conversion artifacts. */
memcpy(r->str, str, len);
r->str[len] = '\0';
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -182,7 +225,28 @@ static void *createNilObject(const redisReadTask *task) { ...@@ -182,7 +225,28 @@ static void *createNilObject(const redisReadTask *task) {
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY); assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
}
static void *createBoolObject(const redisReadTask *task, int bval) {
redisReply *r, *parent;
r = createReplyObject(REDIS_REPLY_BOOL);
if (r == NULL)
return NULL;
r->integer = bval != 0;
if (task->parent) {
parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
...@@ -432,11 +496,7 @@ cleanup: ...@@ -432,11 +496,7 @@ cleanup:
} }
sdsfree(curarg); sdsfree(curarg);
free(cmd);
/* No need to check cmd since it is the last statement that can fail,
* but do it anyway to be as defensive as possible. */
if (cmd != NULL)
free(cmd);
return error_type; return error_type;
} }
...@@ -581,7 +641,7 @@ void __redisSetError(redisContext *c, int type, const char *str) { ...@@ -581,7 +641,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
} else { } else {
/* Only REDIS_ERR_IO may lack a description! */ /* Only REDIS_ERR_IO may lack a description! */
assert(type == REDIS_ERR_IO); assert(type == REDIS_ERR_IO);
__redis_strerror_r(errno, c->errstr, sizeof(c->errstr)); strerror_r(errno, c->errstr, sizeof(c->errstr));
} }
} }
...@@ -596,14 +656,8 @@ static redisContext *redisContextInit(void) { ...@@ -596,14 +656,8 @@ static redisContext *redisContextInit(void) {
if (c == NULL) if (c == NULL)
return NULL; return NULL;
c->err = 0;
c->errstr[0] = '\0';
c->obuf = sdsempty(); c->obuf = sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
c->tcp.host = NULL;
c->tcp.source_addr = NULL;
c->unix_sock.path = NULL;
c->timeout = NULL;
if (c->obuf == NULL || c->reader == NULL) { if (c->obuf == NULL || c->reader == NULL) {
redisFree(c); redisFree(c);
...@@ -618,18 +672,14 @@ void redisFree(redisContext *c) { ...@@ -618,18 +672,14 @@ void redisFree(redisContext *c) {
return; return;
if (c->fd > 0) if (c->fd > 0)
close(c->fd); close(c->fd);
if (c->obuf != NULL)
sdsfree(c->obuf); sdsfree(c->obuf);
if (c->reader != NULL) redisReaderFree(c->reader);
redisReaderFree(c->reader); free(c->tcp.host);
if (c->tcp.host) free(c->tcp.source_addr);
free(c->tcp.host); free(c->unix_sock.path);
if (c->tcp.source_addr) free(c->timeout);
free(c->tcp.source_addr); free(c->saddr);
if (c->unix_sock.path)
free(c->unix_sock.path);
if (c->timeout)
free(c->timeout);
free(c); free(c);
} }
...@@ -710,6 +760,8 @@ redisContext *redisConnectNonBlock(const char *ip, int port) { ...@@ -710,6 +760,8 @@ redisContext *redisConnectNonBlock(const char *ip, int port) {
redisContext *redisConnectBindNonBlock(const char *ip, int port, redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisContextInit(); redisContext *c = redisContextInit();
if (c == NULL)
return NULL;
c->flags &= ~REDIS_BLOCK; c->flags &= ~REDIS_BLOCK;
redisContextConnectBindTcp(c,ip,port,NULL,source_addr); redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
return c; return c;
...@@ -718,6 +770,8 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port, ...@@ -718,6 +770,8 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port,
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port, redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisContextInit(); redisContext *c = redisContextInit();
if (c == NULL)
return NULL;
c->flags &= ~REDIS_BLOCK; c->flags &= ~REDIS_BLOCK;
c->flags |= REDIS_REUSEADDR; c->flags |= REDIS_REUSEADDR;
redisContextConnectBindTcp(c,ip,port,NULL,source_addr); redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
...@@ -789,7 +843,7 @@ int redisEnableKeepAlive(redisContext *c) { ...@@ -789,7 +843,7 @@ int redisEnableKeepAlive(redisContext *c) {
/* Use this function to handle a read event on the descriptor. It will try /* Use this function to handle a read event on the descriptor. It will try
* and read some bytes from the socket and feed them to the reply parser. * and read some bytes from the socket and feed them to the reply parser.
* *
* After this function is called, you may use redisContextReadReply to * After this function is called, you may use redisGetReplyFromReader to
* see if there is a reply available. */ * see if there is a reply available. */
int redisBufferRead(redisContext *c) { int redisBufferRead(redisContext *c) {
char buf[1024*16]; char buf[1024*16];
...@@ -1007,9 +1061,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) { ...@@ -1007,9 +1061,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
void *redisCommand(redisContext *c, const char *format, ...) { void *redisCommand(redisContext *c, const char *format, ...) {
va_list ap; va_list ap;
void *reply = NULL;
va_start(ap,format); va_start(ap,format);
reply = redisvCommand(c,format,ap); void *reply = redisvCommand(c,format,ap);
va_end(ap); va_end(ap);
return reply; return reply;
} }
......
...@@ -40,9 +40,9 @@ ...@@ -40,9 +40,9 @@
#include "sds.h" /* for sds */ #include "sds.h" /* for sds */
#define HIREDIS_MAJOR 0 #define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 13 #define HIREDIS_MINOR 14
#define HIREDIS_PATCH 3 #define HIREDIS_PATCH 0
#define HIREDIS_SONAME 0.13 #define HIREDIS_SONAME 0.14
/* Connection type can be blocking or non-blocking and is set in the /* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */ * least significant bit of the flags field in redisContext. */
...@@ -80,30 +80,6 @@ ...@@ -80,30 +80,6 @@
* SO_REUSEADDR is being used. */ * SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10 #define REDIS_CONNECT_RETRIES 10
/* strerror_r has two completely different prototypes and behaviors
* depending on system issues, so we need to operate on the error buffer
* differently depending on which strerror_r we're using. */
#ifndef _GNU_SOURCE
/* "regular" POSIX strerror_r that does the right thing. */
#define __redis_strerror_r(errno, buf, len) \
do { \
strerror_r((errno), (buf), (len)); \
} while (0)
#else
/* "bad" GNU strerror_r we need to clean up after. */
#define __redis_strerror_r(errno, buf, len) \
do { \
char *err_str = strerror_r((errno), (buf), (len)); \
/* If return value _isn't_ the start of the buffer we passed in, \
* then GNU strerror_r returned an internal static buffer and we \
* need to copy the result into our private buffer. */ \
if (err_str != (buf)) { \
strncpy((buf), err_str, ((len) - 1)); \
buf[(len)-1] = '\0'; \
} \
} while (0)
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
...@@ -112,8 +88,10 @@ extern "C" { ...@@ -112,8 +88,10 @@ extern "C" {
typedef struct redisReply { typedef struct redisReply {
int type; /* REDIS_REPLY_* */ int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
double dval; /* The double when type is REDIS_REPLY_DOUBLE */
size_t len; /* Length of string */ size_t len; /* Length of string */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
and REDIS_REPLY_DOUBLE (in additionl to dval). */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply; } redisReply;
...@@ -158,6 +136,9 @@ typedef struct redisContext { ...@@ -158,6 +136,9 @@ typedef struct redisContext {
char *path; char *path;
} unix_sock; } unix_sock;
/* For non-blocking connect */
struct sockadr *saddr;
size_t addrlen;
} redisContext; } redisContext;
redisContext *redisConnect(const char *ip, int port); redisContext *redisConnect(const char *ip, int port);
......
...@@ -65,12 +65,13 @@ static void redisContextCloseFd(redisContext *c) { ...@@ -65,12 +65,13 @@ static void redisContextCloseFd(redisContext *c) {
} }
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 }; char buf[128] = { 0 };
size_t len = 0; size_t len = 0;
if (prefix != NULL) if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix); len = snprintf(buf,sizeof(buf),"%s: ",prefix);
__redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len); strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
__redisSetError(c,type,buf); __redisSetError(c,type,buf);
} }
...@@ -135,14 +136,13 @@ int redisKeepAlive(redisContext *c, int interval) { ...@@ -135,14 +136,13 @@ int redisKeepAlive(redisContext *c, int interval) {
val = interval; val = interval;
#ifdef _OSX #if defined(__APPLE__) && defined(__MACH__)
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR; return REDIS_ERR;
} }
#else #else
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR; return REDIS_ERR;
...@@ -221,8 +221,10 @@ static int redisContextWaitReady(redisContext *c, long msec) { ...@@ -221,8 +221,10 @@ static int redisContextWaitReady(redisContext *c, long msec) {
return REDIS_ERR; return REDIS_ERR;
} }
if (redisCheckSocketError(c) != REDIS_OK) if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
redisCheckSocketError(c);
return REDIS_ERR; return REDIS_ERR;
}
return REDIS_OK; return REDIS_OK;
} }
...@@ -232,8 +234,28 @@ static int redisContextWaitReady(redisContext *c, long msec) { ...@@ -232,8 +234,28 @@ static int redisContextWaitReady(redisContext *c, long msec) {
return REDIS_ERR; return REDIS_ERR;
} }
int redisCheckConnectDone(redisContext *c, int *completed) {
int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
if (rc == 0) {
*completed = 1;
return REDIS_OK;
}
switch (errno) {
case EISCONN:
*completed = 1;
return REDIS_OK;
case EALREADY:
case EINPROGRESS:
case EWOULDBLOCK:
*completed = 0;
return REDIS_OK;
default:
return REDIS_ERR;
}
}
int redisCheckSocketError(redisContext *c) { int redisCheckSocketError(redisContext *c) {
int err = 0; int err = 0, errno_saved = errno;
socklen_t errlen = sizeof(err); socklen_t errlen = sizeof(err);
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
...@@ -241,6 +263,10 @@ int redisCheckSocketError(redisContext *c) { ...@@ -241,6 +263,10 @@ int redisCheckSocketError(redisContext *c) {
return REDIS_ERR; return REDIS_ERR;
} }
if (err == 0) {
err = errno_saved;
}
if (err) { if (err) {
errno = err; errno = err;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
...@@ -285,8 +311,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -285,8 +311,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
* This is a bit ugly, but atleast it works and doesn't leak memory. * This is a bit ugly, but atleast it works and doesn't leak memory.
**/ **/
if (c->tcp.host != addr) { if (c->tcp.host != addr) {
if (c->tcp.host) free(c->tcp.host);
free(c->tcp.host);
c->tcp.host = strdup(addr); c->tcp.host = strdup(addr);
} }
...@@ -299,8 +324,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, ...@@ -299,8 +324,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
memcpy(c->timeout, timeout, sizeof(struct timeval)); memcpy(c->timeout, timeout, sizeof(struct timeval));
} }
} else { } else {
if (c->timeout) free(c->timeout);
free(c->timeout);
c->timeout = NULL; c->timeout = NULL;
} }
...@@ -356,6 +380,7 @@ addrretry: ...@@ -356,6 +380,7 @@ addrretry:
n = 1; n = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
sizeof(n)) < 0) { sizeof(n)) < 0) {
freeaddrinfo(bservinfo);
goto error; goto error;
} }
} }
...@@ -374,12 +399,27 @@ addrretry: ...@@ -374,12 +399,27 @@ addrretry:
goto error; goto error;
} }
} }
/* For repeat connection */
if (c->saddr) {
free(c->saddr);
}
c->saddr = malloc(p->ai_addrlen);
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
c->addrlen = p->ai_addrlen;
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) { if (errno == EHOSTUNREACH) {
redisContextCloseFd(c); redisContextCloseFd(c);
continue; continue;
} else if (errno == EINPROGRESS && !blocking) { } else if (errno == EINPROGRESS) {
/* This is ok. */ if (blocking) {
goto wait_for_ready;
}
/* This is ok.
* Note that even when it's in blocking mode, we unset blocking
* for `connect()`
*/
} else if (errno == EADDRNOTAVAIL && reuseaddr) { } else if (errno == EADDRNOTAVAIL && reuseaddr) {
if (++reuses >= REDIS_CONNECT_RETRIES) { if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error; goto error;
...@@ -388,6 +428,7 @@ addrretry: ...@@ -388,6 +428,7 @@ addrretry:
goto addrretry; goto addrretry;
} }
} else { } else {
wait_for_ready:
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error; goto error;
} }
...@@ -411,7 +452,10 @@ addrretry: ...@@ -411,7 +452,10 @@ addrretry:
error: error:
rv = REDIS_ERR; rv = REDIS_ERR;
end: end:
freeaddrinfo(servinfo); if(servinfo) {
freeaddrinfo(servinfo);
}
return rv; // Need to return REDIS_OK if alright return rv; // Need to return REDIS_OK if alright
} }
...@@ -431,7 +475,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time ...@@ -431,7 +475,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
struct sockaddr_un sa; struct sockaddr_un sa;
long timeout_msec = -1; long timeout_msec = -1;
if (redisCreateSocket(c,AF_LOCAL) < 0) if (redisCreateSocket(c,AF_UNIX) < 0)
return REDIS_ERR; return REDIS_ERR;
if (redisSetBlocking(c,0) != REDIS_OK) if (redisSetBlocking(c,0) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
...@@ -448,15 +492,14 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time ...@@ -448,15 +492,14 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
memcpy(c->timeout, timeout, sizeof(struct timeval)); memcpy(c->timeout, timeout, sizeof(struct timeval));
} }
} else { } else {
if (c->timeout) free(c->timeout);
free(c->timeout);
c->timeout = NULL; c->timeout = NULL;
} }
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
sa.sun_family = AF_LOCAL; sa.sun_family = AF_UNIX;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1); strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS && !blocking) { if (errno == EINPROGRESS && !blocking) {
......
...@@ -37,10 +37,6 @@ ...@@ -37,10 +37,6 @@
#include "hiredis.h" #include "hiredis.h"
#if defined(__sun)
#define AF_LOCAL AF_UNIX
#endif
int redisCheckSocketError(redisContext *c); int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextSetTimeout(redisContext *c, const struct timeval tv);
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
...@@ -49,5 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, ...@@ -49,5 +45,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
const char *source_addr); const char *source_addr);
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
int redisKeepAlive(redisContext *c, int interval); int redisKeepAlive(redisContext *c, int interval);
int redisCheckConnectDone(redisContext *c, int *completed);
#endif #endif
...@@ -29,7 +29,6 @@ ...@@ -29,7 +29,6 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "fmacros.h" #include "fmacros.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
...@@ -39,6 +38,8 @@ ...@@ -39,6 +38,8 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h>
#include <math.h>
#include "read.h" #include "read.h"
#include "sds.h" #include "sds.h"
...@@ -52,11 +53,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) { ...@@ -52,11 +53,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
} }
/* Clear input buffer on errors. */ /* Clear input buffer on errors. */
if (r->buf != NULL) { sdsfree(r->buf);
sdsfree(r->buf); r->buf = NULL;
r->buf = NULL; r->pos = r->len = 0;
r->pos = r->len = 0;
}
/* Reset task stack. */ /* Reset task stack. */
r->ridx = -1; r->ridx = -1;
...@@ -143,33 +142,79 @@ static char *seekNewline(char *s, size_t len) { ...@@ -143,33 +142,79 @@ static char *seekNewline(char *s, size_t len) {
return NULL; return NULL;
} }
/* Read a long long value starting at *s, under the assumption that it will be /* Convert a string into a long long. Returns REDIS_OK if the string could be
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */ * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
static long long readLongLong(char *s) { * will be set to the parsed value when appropriate.
long long v = 0; *
int dec, mult = 1; * Note that this function demands that the string strictly represents
char c; * 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
if (*s == '-') { * for the string "0" representing the zero number.
mult = -1; *
s++; * Because of its strictness, it is safe to use this function to check if
} else if (*s == '+') { * you can convert a string into a long long, and obtain back the string
mult = 1; * from the number without any loss in the string representation. */
s++; static int string2ll(const char *s, size_t slen, long long *value) {
const char *p = s;
size_t plen = 0;
int negative = 0;
unsigned long long v;
if (plen == slen)
return REDIS_ERR;
/* Special case: first and only digit is 0. */
if (slen == 1 && p[0] == '0') {
if (value != NULL) *value = 0;
return REDIS_OK;
} }
while ((c = *(s++)) != '\r') { if (p[0] == '-') {
dec = c - '0'; negative = 1;
if (dec >= 0 && dec < 10) { p++; plen++;
v *= 10;
v += dec; /* Abort on only a negative sign. */
} else { if (plen == slen)
/* Should not happen... */ return REDIS_ERR;
return -1; }
}
/* First digit should be 1-9, otherwise the string should just be 0. */
if (p[0] >= '1' && p[0] <= '9') {
v = p[0]-'0';
p++; plen++;
} else if (p[0] == '0' && slen == 1) {
*value = 0;
return REDIS_OK;
} else {
return REDIS_ERR;
} }
return mult*v; while (plen < slen && p[0] >= '0' && p[0] <= '9') {
if (v > (ULLONG_MAX / 10)) /* Overflow. */
return REDIS_ERR;
v *= 10;
if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
return REDIS_ERR;
v += p[0]-'0';
p++; plen++;
}
/* Return if not all bytes were used. */
if (plen < slen)
return REDIS_ERR;
if (negative) {
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
return REDIS_ERR;
if (value != NULL) *value = -v;
} else {
if (v > LLONG_MAX) /* Overflow. */
return REDIS_ERR;
if (value != NULL) *value = v;
}
return REDIS_OK;
} }
static char *readLine(redisReader *r, int *_len) { static char *readLine(redisReader *r, int *_len) {
...@@ -198,7 +243,9 @@ static void moveToNextTask(redisReader *r) { ...@@ -198,7 +243,9 @@ static void moveToNextTask(redisReader *r) {
cur = &(r->rstack[r->ridx]); cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]); prv = &(r->rstack[r->ridx-1]);
assert(prv->type == REDIS_REPLY_ARRAY); assert(prv->type == REDIS_REPLY_ARRAY ||
prv->type == REDIS_REPLY_MAP ||
prv->type == REDIS_REPLY_SET);
if (cur->idx == prv->elements-1) { if (cur->idx == prv->elements-1) {
r->ridx--; r->ridx--;
} else { } else {
...@@ -220,10 +267,58 @@ static int processLineItem(redisReader *r) { ...@@ -220,10 +267,58 @@ static int processLineItem(redisReader *r) {
if ((p = readLine(r,&len)) != NULL) { if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) { if (cur->type == REDIS_REPLY_INTEGER) {
if (r->fn && r->fn->createInteger) if (r->fn && r->fn->createInteger) {
obj = r->fn->createInteger(cur,readLongLong(p)); long long v;
else 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; obj = (void*)REDIS_REPLY_INTEGER;
}
} else if (cur->type == REDIS_REPLY_DOUBLE) {
if (r->fn && r->fn->createDouble) {
char buf[326], *eptr;
double d;
if ((size_t)len >= sizeof(buf)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Double value is too large");
return REDIS_ERR;
}
memcpy(buf,p,len);
buf[len] = '\0';
if (strcasecmp(buf,",inf") == 0) {
d = 1.0/0.0; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) {
d = -1.0/0.0; /* Nevative infinite. */
} else {
d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad double value");
return REDIS_ERR;
}
}
obj = r->fn->createDouble(cur,d,buf,len);
} else {
obj = (void*)REDIS_REPLY_DOUBLE;
}
} else if (cur->type == REDIS_REPLY_NIL) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
else
obj = (void*)REDIS_REPLY_NIL;
} else if (cur->type == REDIS_REPLY_BOOL) {
int bval = p[0] == 't' || p[0] == 'T';
if (r->fn && r->fn->createBool)
obj = r->fn->createBool(cur,bval);
else
obj = (void*)REDIS_REPLY_BOOL;
} else { } else {
/* Type will be error or status. */ /* Type will be error or status. */
if (r->fn && r->fn->createString) if (r->fn && r->fn->createString)
...@@ -250,7 +345,7 @@ static int processBulkItem(redisReader *r) { ...@@ -250,7 +345,7 @@ static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj = NULL; void *obj = NULL;
char *p, *s; char *p, *s;
long len; long long len;
unsigned long bytelen; unsigned long bytelen;
int success = 0; int success = 0;
...@@ -259,9 +354,20 @@ static int processBulkItem(redisReader *r) { ...@@ -259,9 +354,20 @@ static int processBulkItem(redisReader *r) {
if (s != NULL) { if (s != NULL) {
p = r->buf+r->pos; p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */ bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
len = readLongLong(p);
if (len < 0) { if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad bulk string length");
return REDIS_ERR;
}
if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bulk string length out of range");
return REDIS_ERR;
}
if (len == -1) {
/* The nil object can always be created. */ /* The nil object can always be created. */
if (r->fn && r->fn->createNil) if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur); obj = r->fn->createNil(cur);
...@@ -299,12 +405,13 @@ static int processBulkItem(redisReader *r) { ...@@ -299,12 +405,13 @@ static int processBulkItem(redisReader *r) {
return REDIS_ERR; return REDIS_ERR;
} }
static int processMultiBulkItem(redisReader *r) { /* Process the array, map and set types. */
static int processAggregateItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj; void *obj;
char *p; char *p;
long elements; long long elements;
int root = 0; int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */ /* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) { if (r->ridx == 8) {
...@@ -313,10 +420,21 @@ static int processMultiBulkItem(redisReader *r) { ...@@ -313,10 +420,21 @@ static int processMultiBulkItem(redisReader *r) {
return REDIS_ERR; return REDIS_ERR;
} }
if ((p = readLine(r,NULL)) != NULL) { if ((p = readLine(r,&len)) != NULL) {
elements = readLongLong(p); if (string2ll(p, len, &elements) == REDIS_ERR) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Bad multi-bulk length");
return REDIS_ERR;
}
root = (r->ridx == 0); 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 (elements == -1) {
if (r->fn && r->fn->createNil) if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur); obj = r->fn->createNil(cur);
...@@ -330,10 +448,12 @@ static int processMultiBulkItem(redisReader *r) { ...@@ -330,10 +448,12 @@ static int processMultiBulkItem(redisReader *r) {
moveToNextTask(r); moveToNextTask(r);
} else { } else {
if (cur->type == REDIS_REPLY_MAP) elements *= 2;
if (r->fn && r->fn->createArray) if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements); obj = r->fn->createArray(cur,elements);
else else
obj = (void*)REDIS_REPLY_ARRAY; obj = (void*)(long)cur->type;
if (obj == NULL) { if (obj == NULL) {
__redisReaderSetErrorOOM(r); __redisReaderSetErrorOOM(r);
...@@ -381,12 +501,27 @@ static int processItem(redisReader *r) { ...@@ -381,12 +501,27 @@ static int processItem(redisReader *r) {
case ':': case ':':
cur->type = REDIS_REPLY_INTEGER; cur->type = REDIS_REPLY_INTEGER;
break; break;
case ',':
cur->type = REDIS_REPLY_DOUBLE;
break;
case '_':
cur->type = REDIS_REPLY_NIL;
break;
case '$': case '$':
cur->type = REDIS_REPLY_STRING; cur->type = REDIS_REPLY_STRING;
break; break;
case '*': case '*':
cur->type = REDIS_REPLY_ARRAY; cur->type = REDIS_REPLY_ARRAY;
break; break;
case '%':
cur->type = REDIS_REPLY_MAP;
break;
case '~':
cur->type = REDIS_REPLY_SET;
break;
case '#':
cur->type = REDIS_REPLY_BOOL;
break;
default: default:
__redisReaderSetErrorProtocolByte(r,*p); __redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR; return REDIS_ERR;
...@@ -402,11 +537,16 @@ static int processItem(redisReader *r) { ...@@ -402,11 +537,16 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER: case REDIS_REPLY_INTEGER:
case REDIS_REPLY_DOUBLE:
case REDIS_REPLY_NIL:
case REDIS_REPLY_BOOL:
return processLineItem(r); return processLineItem(r);
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
return processBulkItem(r); return processBulkItem(r);
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
return processMultiBulkItem(r); case REDIS_REPLY_MAP:
case REDIS_REPLY_SET:
return processAggregateItem(r);
default: default:
assert(NULL); assert(NULL);
return REDIS_ERR; /* Avoid warning. */ return REDIS_ERR; /* Avoid warning. */
...@@ -416,12 +556,10 @@ static int processItem(redisReader *r) { ...@@ -416,12 +556,10 @@ static int processItem(redisReader *r) {
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
redisReader *r; redisReader *r;
r = calloc(sizeof(redisReader),1); r = calloc(1,sizeof(redisReader));
if (r == NULL) if (r == NULL)
return NULL; return NULL;
r->err = 0;
r->errstr[0] = '\0';
r->fn = fn; r->fn = fn;
r->buf = sdsempty(); r->buf = sdsempty();
r->maxbuf = REDIS_READER_MAX_BUF; r->maxbuf = REDIS_READER_MAX_BUF;
...@@ -435,10 +573,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { ...@@ -435,10 +573,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
} }
void redisReaderFree(redisReader *r) { void redisReaderFree(redisReader *r) {
if (r == NULL)
return;
if (r->reply != NULL && r->fn && r->fn->freeObject) if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply); r->fn->freeObject(r->reply);
if (r->buf != NULL) sdsfree(r->buf);
sdsfree(r->buf);
free(r); free(r);
} }
......
...@@ -53,6 +53,14 @@ ...@@ -53,6 +53,14 @@
#define REDIS_REPLY_NIL 4 #define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5 #define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6 #define REDIS_REPLY_ERROR 6
#define REDIS_REPLY_DOUBLE 7
#define REDIS_REPLY_BOOL 8
#define REDIS_REPLY_VERB 9
#define REDIS_REPLY_MAP 9
#define REDIS_REPLY_SET 10
#define REDIS_REPLY_ATTR 11
#define REDIS_REPLY_PUSH 12
#define REDIS_REPLY_BIGNUM 13
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ #define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
...@@ -73,7 +81,9 @@ typedef struct redisReplyObjectFunctions { ...@@ -73,7 +81,9 @@ typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t); void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int); void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long); void *(*createInteger)(const redisReadTask*, long long);
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*); void *(*createNil)(const redisReadTask*);
void *(*createBool)(const redisReadTask*, int);
void (*freeObject)(void*); void (*freeObject)(void*);
} redisReplyObjectFunctions; } redisReplyObjectFunctions;
......
...@@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { ...@@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
hdrlen = sdsHdrSize(type); hdrlen = sdsHdrSize(type);
if (oldtype==type) { if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1); newsh = s_realloc(sh, hdrlen+newlen+1);
if (newsh == NULL) return NULL; if (newsh == NULL) {
s_free(sh);
return NULL;
}
s = (char*)newsh+hdrlen; s = (char*)newsh+hdrlen;
} else { } else {
/* Since the header size changes, need to move the string forward, /* Since the header size changes, need to move the string forward,
...@@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
/* Make sure there is always space for at least 1 char. */ /* Make sure there is always space for at least 1 char. */
if (sdsavail(s)==0) { if (sdsavail(s)==0) {
s = sdsMakeRoomFor(s,1); s = sdsMakeRoomFor(s,1);
if (s == NULL) goto fmt_error;
} }
switch(*f) { switch(*f) {
...@@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = (next == 's') ? strlen(str) : sdslen(str); l = (next == 's') ? strlen(str) : sdslen(str);
if (sdsavail(s) < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
if (s == NULL) goto fmt_error;
} }
memcpy(s+i,str,l); memcpy(s+i,str,l);
sdsinclen(s,l); sdsinclen(s,l);
...@@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = sdsll2str(buf,num); l = sdsll2str(buf,num);
if (sdsavail(s) < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
if (s == NULL) goto fmt_error;
} }
memcpy(s+i,buf,l); memcpy(s+i,buf,l);
sdsinclen(s,l); sdsinclen(s,l);
...@@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = sdsull2str(buf,unum); l = sdsull2str(buf,unum);
if (sdsavail(s) < l) { if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l); s = sdsMakeRoomFor(s,l);
if (s == NULL) goto fmt_error;
} }
memcpy(s+i,buf,l); memcpy(s+i,buf,l);
sdsinclen(s,l); sdsinclen(s,l);
...@@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
/* Add null-term */ /* Add null-term */
s[i] = '\0'; s[i] = '\0';
return s; return s;
fmt_error:
va_end(ap);
return NULL;
} }
/* Remove the part of the string from left and from right composed just of /* Remove the part of the string from left and from right composed just of
...@@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) { ...@@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) {
if (*p) p++; if (*p) p++;
} }
/* add the token to the vector */ /* add the token to the vector */
vector = s_realloc(vector,((*argc)+1)*sizeof(char*)); {
vector[*argc] = current; char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
(*argc)++; if (new_vector == NULL) {
current = NULL; s_free(vector);
return NULL;
}
vector = new_vector;
vector[*argc] = current;
(*argc)++;
current = NULL;
}
} else { } else {
/* Even on empty input string return something not NULL. */ /* Even on empty input string return something not NULL. */
if (vector == NULL) vector = s_malloc(sizeof(void*)); if (vector == NULL) vector = s_malloc(sizeof(void*));
......
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <sys/socket.h>
#include <sys/time.h> #include <sys/time.h>
#include <netdb.h>
#include <assert.h> #include <assert.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
...@@ -91,7 +93,7 @@ static int disconnect(redisContext *c, int keep_fd) { ...@@ -91,7 +93,7 @@ static int disconnect(redisContext *c, int keep_fd) {
return -1; return -1;
} }
static redisContext *connect(struct config config) { static redisContext *do_connect(struct config config) {
redisContext *c = NULL; redisContext *c = NULL;
if (config.type == CONN_TCP) { if (config.type == CONN_TCP) {
...@@ -248,7 +250,7 @@ static void test_append_formatted_commands(struct config config) { ...@@ -248,7 +250,7 @@ static void test_append_formatted_commands(struct config config) {
char *cmd; char *cmd;
int len; int len;
c = connect(config); c = do_connect(config);
test("Append format command: "); test("Append format command: ");
...@@ -302,6 +304,82 @@ static void test_reply_reader(void) { ...@@ -302,6 +304,82 @@ static void test_reply_reader(void) {
strncasecmp(reader->errstr,"No support for",14) == 0); strncasecmp(reader->errstr,"No support for",14) == 0);
redisReaderFree(reader); 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: "); test("Works with NULL functions for reply: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
reader->fn = NULL; reader->fn = NULL;
...@@ -358,18 +436,32 @@ static void test_free_null(void) { ...@@ -358,18 +436,32 @@ static void test_free_null(void) {
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c; redisContext *c;
struct addrinfo hints = {.ai_family = AF_INET};
test("Returns error when host cannot be resolved: "); struct addrinfo *ai_tmp = NULL;
c = redisConnect((char*)"idontexist.test", 6379); const char *bad_domain = "idontexist.com";
test_cond(c->err == REDIS_ERR_OTHER &&
(strcmp(c->errstr,"Name or service not known") == 0 || int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp);
strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 || if (rv != 0) {
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 || // Address does *not* exist
strcmp(c->errstr,"No address associated with hostname") == 0 || test("Returns error when host cannot be resolved: ");
strcmp(c->errstr,"Temporary failure in name resolution") == 0 || // First see if this domain name *actually* resolves to NXDOMAIN
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 || c = redisConnect("dontexist.com", 6379);
strcmp(c->errstr,"no address associated with name") == 0)); test_cond(
redisFree(c); c->err == REDIS_ERR_OTHER &&
(strcmp(c->errstr, "Name or service not known") == 0 ||
strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 ||
strcmp(c->errstr,
"nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
strcmp(c->errstr,
"hostname nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "no address associated with name") == 0));
redisFree(c);
} else {
printf("Skipping NXDOMAIN test. Found evil ISP!\n");
freeaddrinfo(ai_tmp);
}
test("Returns error when the port is not open: "); test("Returns error when the port is not open: ");
c = redisConnect((char*)"localhost", 1); c = redisConnect((char*)"localhost", 1);
...@@ -387,7 +479,7 @@ static void test_blocking_connection(struct config config) { ...@@ -387,7 +479,7 @@ static void test_blocking_connection(struct config config) {
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
c = connect(config); c = do_connect(config);
test("Is able to deliver commands: "); test("Is able to deliver commands: ");
reply = redisCommand(c,"PING"); reply = redisCommand(c,"PING");
...@@ -468,7 +560,7 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -468,7 +560,7 @@ static void test_blocking_connection_timeouts(struct config config) {
const char *cmd = "DEBUG SLEEP 3\r\n"; const char *cmd = "DEBUG SLEEP 3\r\n";
struct timeval tv; struct timeval tv;
c = connect(config); c = do_connect(config);
test("Successfully completes a command when the timeout is not exceeded: "); test("Successfully completes a command when the timeout is not exceeded: ");
reply = redisCommand(c,"SET foo fast"); reply = redisCommand(c,"SET foo fast");
freeReplyObject(reply); freeReplyObject(reply);
...@@ -480,7 +572,7 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -480,7 +572,7 @@ static void test_blocking_connection_timeouts(struct config config) {
freeReplyObject(reply); freeReplyObject(reply);
disconnect(c, 0); disconnect(c, 0);
c = connect(config); c = do_connect(config);
test("Does not return a reply when the command times out: "); test("Does not return a reply when the command times out: ");
s = write(c->fd, cmd, strlen(cmd)); s = write(c->fd, cmd, strlen(cmd));
tv.tv_sec = 0; tv.tv_sec = 0;
...@@ -514,7 +606,7 @@ static void test_blocking_io_errors(struct config config) { ...@@ -514,7 +606,7 @@ static void test_blocking_io_errors(struct config config) {
int major, minor; int major, minor;
/* Connect to target given by config. */ /* Connect to target given by config. */
c = connect(config); c = do_connect(config);
{ {
/* Find out Redis version to determine the path for the next test */ /* Find out Redis version to determine the path for the next test */
const char *field = "redis_version:"; const char *field = "redis_version:";
...@@ -549,7 +641,7 @@ static void test_blocking_io_errors(struct config config) { ...@@ -549,7 +641,7 @@ static void test_blocking_io_errors(struct config config) {
strcmp(c->errstr,"Server closed the connection") == 0); strcmp(c->errstr,"Server closed the connection") == 0);
redisFree(c); redisFree(c);
c = connect(config); c = do_connect(config);
test("Returns I/O error on socket timeout: "); test("Returns I/O error on socket timeout: ");
struct timeval tv = { 0, 1000 }; struct timeval tv = { 0, 1000 };
assert(redisSetTimeout(c,tv) == REDIS_OK); assert(redisSetTimeout(c,tv) == REDIS_OK);
...@@ -583,7 +675,7 @@ static void test_invalid_timeout_errors(struct config config) { ...@@ -583,7 +675,7 @@ static void test_invalid_timeout_errors(struct config config) {
} }
static void test_throughput(struct config config) { static void test_throughput(struct config config) {
redisContext *c = connect(config); redisContext *c = do_connect(config);
redisReply **replies; redisReply **replies;
int i, num; int i, num;
long long t1, t2; long long t1, t2;
...@@ -616,6 +708,17 @@ static void test_throughput(struct config config) { ...@@ -616,6 +708,17 @@ static void test_throughput(struct config config) {
free(replies); free(replies);
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); 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; num = 10000;
replies = malloc(sizeof(redisReply*)*num); replies = malloc(sizeof(redisReply*)*num);
for (i = 0; i < num; i++) for (i = 0; i < num; i++)
...@@ -644,6 +747,19 @@ static void test_throughput(struct config config) { ...@@ -644,6 +747,19 @@ static void test_throughput(struct config config) {
free(replies); free(replies);
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); 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); disconnect(c, 0);
} }
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment