Unverified Commit 3f14bfd8 authored by Loris Cro's avatar Loris Cro Committed by GitHub
Browse files

Merge pull request #1 from antirez/unstable

update to latest unstable
parents 8ea4bdd9 720d1fd3
name: CI
on: [push, pull_request]
jobs:
build-ubuntu:
strategy:
matrix:
platform: [ubuntu-latest, ubuntu-16.04]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- name: make
run: make
- name: test
run: |
sudo apt-get install tcl8.5
make test
build-macos-latest:
strategy:
matrix:
platform: [macos-latest, macOS-10.14]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v1
- name: make
run: make
TLS Support -- Work In Progress
===============================
This is a brief note to capture current thoughts/ideas and track pending action
items.
Getting Started
---------------
### Building
To build with TLS support you'll need OpenSSL development libraries (e.g.
libssl-dev on Debian/Ubuntu).
Run `make BUILD_TLS=yes`.
### Tests
To run Redis test suite with TLS, you'll need TLS support for TCL (i.e.
`tcl-tls` package on Debian/Ubuntu).
1. Run `./utils/gen-test-certs.sh` to generate a root CA and a server
certificate.
2. Run `./runtest --tls` or `./runtest-cluster --tls` to run Redis and Redis
Cluster tests in TLS mode.
### Running manually
To manually run a Redis server with TLS mode (assuming `gen-test-certs.sh` was
invoked so sample certificates/keys are available):
./src/redis-server --tls-port 6379 --port 0 \
--tls-cert-file ./tests/tls/redis.crt \
--tls-key-file ./tests/tls/redis.key \
--tls-ca-cert-file ./tests/tls/ca.crt
To connect to this Redis server with `redis-cli`:
./src/redis-cli --tls \
--cert ./tests/tls/redis.crt \
--key ./tests/tls/redis.key \
--cacert ./tests/tls/ca.crt
This will disable TCP and enable TLS on port 6379. It's also possible to have
both TCP and TLS available, but you'll need to assign different ports.
To make a Replica connect to the master using TLS, use `--tls-replication yes`,
and to make Redis Cluster use TLS across nodes use `--tls-cluster yes`.
Connections
-----------
All socket operations now go through a connection abstraction layer that hides
I/O and read/write event handling from the caller.
**Multi-threading I/O is not currently supported for TLS**, as a TLS connection
needs to do its own manipulation of AE events which is not thread safe. The
solution is probably to manage independent AE loops for I/O threads and longer
term association of connections with threads. This may potentially improve
overall performance as well.
Sync IO for TLS is currently implemented in a hackish way, i.e. making the
socket blocking and configuring socket-level timeout. This means the timeout
value may not be so accurate, and there would be a lot of syscall overhead.
However I believe that getting rid of syncio completely in favor of pure async
work is probably a better move than trying to fix that. For replication it would
probably not be so hard. For cluster keys migration it might be more difficult,
but there are probably other good reasons to improve that part anyway.
To-Do List
==========
Additional TLS Features
-----------------------
1. Add metrics to INFO?
2. Add session caching support. Check if/how it's handled by clients to assess
how useful/important it is.
redis-benchmark
---------------
The current implementation is a mix of using hiredis for parsing and basic
networking (establishing connections), but directly manipulating sockets for
most actions.
This will need to be cleaned up for proper TLS support. The best approach is
probably to migrate to hiredis async mode.
redis-cli
---------
1. Add support for TLS in --slave and --rdb modes.
Others
------
Consider the implications of allowing TLS to be configured on a separate port,
making Redis listening on multiple ports.
This impacts many things, like
1. Startup banner port notification
2. Proctitle
3. How slaves announce themselves
4. Cluster bus port calculation
...@@ -41,9 +41,13 @@ distclean: ...@@ -41,9 +41,13 @@ distclean:
.PHONY: distclean .PHONY: distclean
ifeq ($(BUILD_TLS),yes)
HIREDIS_MAKE_FLAGS = USE_SSL=1
endif
hiredis: .make-prerequisites hiredis: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd hiredis && $(MAKE) static cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS)
.PHONY: hiredis .PHONY: hiredis
......
...@@ -5,3 +5,4 @@ ...@@ -5,3 +5,4 @@
/*.dylib /*.dylib
/*.a /*.a
/*.pc /*.pc
*.dSYM
...@@ -26,20 +26,72 @@ addons: ...@@ -26,20 +26,72 @@ addons:
- libc6-dev-i386 - libc6-dev-i386
- libc6-dbg:i386 - libc6-dbg:i386
- gcc-multilib - gcc-multilib
- g++-multilib
- valgrind - valgrind
env: env:
- CFLAGS="-Werror" - BITS="32"
- PRE="valgrind --track-origins=yes --leak-check=full" - BITS="64"
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
- TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
matrix: script:
exclude: - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
- os: osx if [ "$TRAVIS_OS_NAME" == "osx" ]; then
env: PRE="valgrind --track-origins=yes --leak-check=full" if [ "$BITS" == "32" ]; then
CFLAGS="-m32 -Werror";
CXXFLAGS="-m32 -Werror";
LDFLAGS="-m32";
EXTRA_CMAKE_OPTS=;
else
CFLAGS="-Werror";
CXXFLAGS="-Werror";
fi;
else
TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
if [ "$BITS" == "32" ]; then
CFLAGS="-m32 -Werror";
CXXFLAGS="-m32 -Werror";
LDFLAGS="-m32";
EXTRA_CMAKE_OPTS=;
else
CFLAGS="-Werror";
CXXFLAGS="-Werror";
fi;
fi;
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
- mkdir build/ && cd build/
- cmake .. ${EXTRA_CMAKE_OPTS}
- make VERBOSE=1
- ctest -V
- os: osx matrix:
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full" include:
# Windows MinGW cross compile on Linux
- os: linux
dist: xenial
compiler: mingw
addons:
apt:
packages:
- ninja-build
- gcc-mingw-w64-x86-64
- g++-mingw-w64-x86-64
script:
- mkdir build && cd build
- CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
- ninja -v
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example # Windows MSVC 2017
- os: windows
compiler: msvc
env:
- MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
before_install:
- eval "${MATRIX_EVAL}"
install:
- choco install ninja
script:
- mkdir build && cd build
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release &&
ninja -v'
- ctest -V
...@@ -12,6 +12,16 @@ ...@@ -12,6 +12,16 @@
compare to other values, casting might be necessary or can be removed, if compare to other values, casting might be necessary or can be removed, if
casting was applied before. casting was applied before.
### 0.x.x (unreleased)
**BREAKING CHANGES**:
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
User code should compare this to `size_t` values as well.
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
### 0.14.0 (2018-09-25) ### 0.14.0 (2018-09-25)
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) * Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
...@@ -50,8 +60,9 @@ ...@@ -50,8 +60,9 @@
* 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
* Make hiredis compile in Cygwin on Windows, now CI-tested * Make hiredis compile in Cygwin on Windows, now CI-tested
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
**BREAKING CHANGES**: protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
* Remove backwards compatibility macro's * Remove backwards compatibility macro's
......
CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
INCLUDE(GNUInstallDirs)
PROJECT(hiredis)
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
MACRO(getVersionBit name)
SET(VERSION_REGEX "^#define ${name} (.+)$")
FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h"
VERSION_BIT REGEX ${VERSION_REGEX})
STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
ENDMACRO(getVersionBit)
getVersionBit(HIREDIS_MAJOR)
getVersionBit(HIREDIS_MINOR)
getVersionBit(HIREDIS_PATCH)
getVersionBit(HIREDIS_SONAME)
SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
MESSAGE("Detected version: ${VERSION}")
PROJECT(hiredis VERSION "${VERSION}")
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
ADD_LIBRARY(hiredis SHARED
async.c
dict.c
hiredis.c
net.c
read.c
sds.c
sockcompat.c)
SET_TARGET_PROPERTIES(hiredis
PROPERTIES
VERSION "${HIREDIS_SONAME}")
IF(WIN32 OR MINGW)
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
ENDIF()
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
INSTALL(TARGETS hiredis
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
INSTALL(FILES hiredis.h read.h sds.h async.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(DIRECTORY adapters
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
IF(ENABLE_SSL)
IF (NOT OPENSSL_ROOT_DIR)
IF (APPLE)
SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
ENDIF()
ENDIF()
FIND_PACKAGE(OpenSSL REQUIRED)
ADD_LIBRARY(hiredis_ssl SHARED
ssl.c)
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
INSTALL(TARGETS hiredis_ssl
DESTINATION "${CMAKE_INSTALL_LIBDIR}")
INSTALL(FILES hiredis_ssl.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
ENDIF()
IF(NOT (WIN32 OR MINGW))
ENABLE_TESTING()
ADD_EXECUTABLE(hiredis-test test.c)
TARGET_LINK_LIBRARIES(hiredis-test hiredis)
ADD_TEST(NAME hiredis-test
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
ENDIF()
# Add examples
IF(ENABLE_EXAMPLES)
ADD_SUBDIRECTORY(examples)
ENDIF(ENABLE_EXAMPLES)
...@@ -3,11 +3,17 @@ ...@@ -3,11 +3,17 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis 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 # This file is released under the BSD license, see the COPYING file
OBJ=net.o hiredis.o sds.o async.o read.o OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
SSL_OBJ=ssl.o
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
ifeq ($(USE_SSL),1)
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
endif
TESTS=hiredis-test TESTS=hiredis-test
LIBNAME=libhiredis LIBNAME=libhiredis
SSL_LIBNAME=libhiredis_ssl
PKGCONFNAME=hiredis.pc PKGCONFNAME=hiredis.pc
SSL_PKGCONFNAME=hiredis_ssl.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}') HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
...@@ -39,7 +45,7 @@ export REDIS_TEST_CONFIG ...@@ -39,7 +45,7 @@ export REDIS_TEST_CONFIG
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 -Wno-missing-field-initializers
DEBUG_FLAGS?= -g -ggdb DEBUG_FLAGS?= -g -ggdb
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
REAL_LDFLAGS=$(LDFLAGS) REAL_LDFLAGS=$(LDFLAGS)
...@@ -49,12 +55,30 @@ STLIBSUFFIX=a ...@@ -49,12 +55,30 @@ STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=ar rcs $(STLIBNAME) SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=$(AR) rcs
# Platform-specific overrides # Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
USE_SSL?=0
# This is required for test.c only
ifeq ($(USE_SSL),1)
CFLAGS+=-DHIREDIS_TEST_SSL
endif
ifeq ($(uname_S),Linux)
SSL_LDFLAGS=-lssl -lcrypto
else
OPENSSL_PREFIX?=/usr/local/opt/openssl
CFLAGS+=-I$(OPENSSL_PREFIX)/include
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
endif
ifeq ($(uname_S),SunOS) 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)
...@@ -66,40 +90,61 @@ ifeq ($(uname_S),Darwin) ...@@ -66,40 +90,61 @@ ifeq ($(uname_S),Darwin)
endif endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
ifeq ($(USE_SSL),1)
all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
endif
# Deps (use make dep to generate this) # Deps (use make dep to generate this)
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
dict.o: dict.c fmacros.h dict.h dict.o: dict.c fmacros.h dict.h
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
read.o: read.c fmacros.h read.h sds.h read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c sds.h sds.o: sds.c sds.h
sockcompat.o: sockcompat.c sockcompat.h
ssl.o: ssl.c hiredis.h
test.o: test.c fmacros.h hiredis.h read.h sds.h test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ) $(DYLIBNAME): $(OBJ)
$(DYLIB_MAKE_CMD) $(OBJ) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
$(STLIBNAME): $(OBJ) $(STLIBNAME): $(OBJ)
$(STLIB_MAKE_CMD) $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
$(SSL_DYLIBNAME): $(SSL_OBJ)
$(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
$(SSL_STLIBNAME): $(SSL_OBJ)
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
dynamic: $(DYLIBNAME) dynamic: $(DYLIBNAME)
static: $(STLIBNAME) static: $(STLIBNAME)
ifeq ($(USE_SSL),1)
dynamic: $(SSL_DYLIBNAME)
static: $(SSL_STLIBNAME)
endif
# Binaries: # Binaries:
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME) hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME) hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME) 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) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME) 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) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME) hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
ifndef AE_DIR ifndef AE_DIR
hiredis-example-ae: hiredis-example-ae:
...@@ -116,7 +161,7 @@ hiredis-example-libuv: ...@@ -116,7 +161,7 @@ hiredis-example-libuv:
@false @false
else else
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME) 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) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
endif endif
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),) ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
...@@ -133,32 +178,33 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME) ...@@ -133,32 +178,33 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
endif endif
hiredis-example: examples/example.c $(STLIBNAME) hiredis-example: examples/example.c $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
examples: $(EXAMPLES) examples: $(EXAMPLES)
hiredis-test: test.o $(STLIBNAME) TEST_LIBS = $(STLIBNAME)
ifeq ($(USE_SSL),1)
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
endif
hiredis-test: test.o $(TEST_LIBS)
hiredis-%: %.o $(STLIBNAME) hiredis-%: %.o $(STLIBNAME)
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME) $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
test: hiredis-test test: hiredis-test
./hiredis-test ./hiredis-test
check: hiredis-test check: hiredis-test
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) - TEST_SSL=$(USE_SSL) ./test.sh
$(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: .c.o:
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $< $(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
clean: clean:
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep: dep:
$(CC) -MM *.c $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
INSTALL?= cp -pPR INSTALL?= cp -pPR
...@@ -175,6 +221,20 @@ $(PKGCONFNAME): hiredis.h ...@@ -175,6 +221,20 @@ $(PKGCONFNAME): hiredis.h
@echo Libs: -L\$${libdir} -lhiredis >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
$(SSL_PKGCONFNAME): hiredis.h
@echo "Generating $@ for pkgconfig..."
@echo prefix=$(PREFIX) > $@
@echo exec_prefix=\$${prefix} >> $@
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
@echo >> $@
@echo Name: hiredis_ssl >> $@
@echo Description: SSL Support for hiredis. >> $@
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
@echo Requires: hiredis >> $@
@echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
@echo Libs.private: -lssl -lcrypto >> $@
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH) $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
......
...@@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin ...@@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
```c ```c
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
``` ```
`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
### Sending commands and their callbacks ### Sending commands and their callbacks
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop. In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
...@@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory. ...@@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory.
## AUTHORS ## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. 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 Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
Jan-Erik Rediger (janerik at fnordig dot com) Jan-Erik Rediger (janerik at fnordig dot com)
...@@ -34,48 +34,113 @@ ...@@ -34,48 +34,113 @@
#include "../hiredis.h" #include "../hiredis.h"
#include "../async.h" #include "../async.h"
#define REDIS_LIBEVENT_DELETED 0x01
#define REDIS_LIBEVENT_ENTERED 0x02
typedef struct redisLibeventEvents { typedef struct redisLibeventEvents {
redisAsyncContext *context; redisAsyncContext *context;
struct event *rev, *wev; struct event *ev;
struct event_base *base;
struct timeval tv;
short flags;
short state;
} redisLibeventEvents; } redisLibeventEvents;
static void redisLibeventReadEvent(int fd, short event, void *arg) { static void redisLibeventDestroy(redisLibeventEvents *e) {
((void)fd); ((void)event); free(e);
redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleRead(e->context);
} }
static void redisLibeventWriteEvent(int fd, short event, void *arg) { static void redisLibeventHandler(int fd, short event, void *arg) {
((void)fd); ((void)event); ((void)fd);
redisLibeventEvents *e = (redisLibeventEvents*)arg; redisLibeventEvents *e = (redisLibeventEvents*)arg;
redisAsyncHandleWrite(e->context); e->state |= REDIS_LIBEVENT_ENTERED;
#define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
redisLibeventDestroy(e);\
return; \
}
if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
redisAsyncHandleTimeout(e->context);
CHECK_DELETED();
}
if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
redisAsyncHandleRead(e->context);
CHECK_DELETED();
}
if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
redisAsyncHandleWrite(e->context);
CHECK_DELETED();
}
e->state &= ~REDIS_LIBEVENT_ENTERED;
#undef CHECK_DELETED
}
static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;
if (isRemove) {
if ((e->flags & flag) == 0) {
return;
} else {
e->flags &= ~flag;
}
} else {
if (e->flags & flag) {
return;
} else {
e->flags |= flag;
}
}
event_del(e->ev);
event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
redisLibeventHandler, privdata);
event_add(e->ev, tv);
} }
static void redisLibeventAddRead(void *privdata) { static void redisLibeventAddRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_READ, 0);
event_add(e->rev,NULL);
} }
static void redisLibeventDelRead(void *privdata) { static void redisLibeventDelRead(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_READ, 1);
event_del(e->rev);
} }
static void redisLibeventAddWrite(void *privdata) { static void redisLibeventAddWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_WRITE, 0);
event_add(e->wev,NULL);
} }
static void redisLibeventDelWrite(void *privdata) { static void redisLibeventDelWrite(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventUpdate(privdata, EV_WRITE, 1);
event_del(e->wev);
} }
static void redisLibeventCleanup(void *privdata) { static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata; redisLibeventEvents *e = (redisLibeventEvents*)privdata;
event_free(e->rev); if (!e) {
event_free(e->wev); return;
free(e); }
event_del(e->ev);
event_free(e->ev);
e->ev = NULL;
if (e->state & REDIS_LIBEVENT_ENTERED) {
e->state |= REDIS_LIBEVENT_DELETED;
} else {
redisLibeventDestroy(e);
}
}
static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
redisLibeventEvents *e = (redisLibeventEvents *)privdata;
short flags = e->flags;
e->flags = 0;
e->tv = tv;
redisLibeventUpdate(e, flags, 0);
} }
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
...@@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { ...@@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisLibeventEvents*)malloc(sizeof(*e)); e = (redisLibeventEvents*)calloc(1, sizeof(*e));
e->context = ac; e->context = ac;
/* Register functions to start/stop listening for events */ /* Register functions to start/stop listening for events */
...@@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) { ...@@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
ac->ev.addWrite = redisLibeventAddWrite; ac->ev.addWrite = redisLibeventAddWrite;
ac->ev.delWrite = redisLibeventDelWrite; ac->ev.delWrite = redisLibeventDelWrite;
ac->ev.cleanup = redisLibeventCleanup; ac->ev.cleanup = redisLibeventCleanup;
ac->ev.scheduleTimer = redisLibeventSetTimeout;
ac->ev.data = e; ac->ev.data = e;
/* Initialize and install read/write events */ /* Initialize and install read/write events */
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e); e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e); e->base = base;
event_add(e->rev, NULL);
event_add(e->wev, NULL);
return REDIS_OK; return REDIS_OK;
} }
#endif #endif
...@@ -5,8 +5,9 @@ environment: ...@@ -5,8 +5,9 @@ environment:
CC: gcc CC: gcc
- CYG_BASH: C:\cygwin\bin\bash - CYG_BASH: C:\cygwin\bin\bash
CC: gcc CC: gcc
TARGET: 32bit CFLAGS: -m32
TARGET_VARS: 32bit-vars CXXFLAGS: -m32
LDFLAGS: -m32
clone_depth: 1 clone_depth: 1
...@@ -20,4 +21,4 @@ install: ...@@ -20,4 +21,4 @@ install:
build_script: build_script:
- 'echo building...' - '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"' - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
...@@ -32,7 +32,9 @@ ...@@ -32,7 +32,9 @@
#include "fmacros.h" #include "fmacros.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifndef _MSC_VER
#include <strings.h> #include <strings.h>
#endif
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
...@@ -40,22 +42,9 @@ ...@@ -40,22 +42,9 @@
#include "net.h" #include "net.h"
#include "dict.c" #include "dict.c"
#include "sds.h" #include "sds.h"
#include "win32.h"
#define _EL_ADD_READ(ctx) do { \ #include "async_private.h"
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 */ /* Forward declaration of function in hiredis.c */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
...@@ -126,6 +115,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) { ...@@ -126,6 +115,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->ev.addWrite = NULL; ac->ev.addWrite = NULL;
ac->ev.delWrite = NULL; ac->ev.delWrite = NULL;
ac->ev.cleanup = NULL; ac->ev.cleanup = NULL;
ac->ev.scheduleTimer = NULL;
ac->onConnect = NULL; ac->onConnect = NULL;
ac->onDisconnect = NULL; ac->onDisconnect = NULL;
...@@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) { ...@@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
ac->errstr = c->errstr; ac->errstr = c->errstr;
} }
redisAsyncContext *redisAsyncConnect(const char *ip, int port) { redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
redisOptions myOptions = *options;
redisContext *c; redisContext *c;
redisAsyncContext *ac; redisAsyncContext *ac;
c = redisConnectNonBlock(ip,port); myOptions.options |= REDIS_OPT_NONBLOCK;
if (c == NULL) c = redisConnectWithOptions(&myOptions);
if (c == NULL) {
return NULL; return NULL;
}
ac = redisAsyncInitialize(c); ac = redisAsyncInitialize(c);
if (ac == NULL) { if (ac == NULL) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
__redisAsyncCopyError(ac); __redisAsyncCopyError(ac);
return ac; return ac;
} }
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); redisOptions options = {0};
redisAsyncContext *ac = redisAsyncInitialize(c); REDIS_OPTIONS_SET_TCP(&options, ip, port);
__redisAsyncCopyError(ac); options.endpoint.tcp.source_addr = source_addr;
return ac; return redisAsyncConnectWithOptions(&options);
} }
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr) { const char *source_addr) {
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr); redisOptions options = {0};
redisAsyncContext *ac = redisAsyncInitialize(c); REDIS_OPTIONS_SET_TCP(&options, ip, port);
__redisAsyncCopyError(ac); options.options |= REDIS_OPT_REUSEADDR;
return ac; options.endpoint.tcp.source_addr = source_addr;
return redisAsyncConnectWithOptions(&options);
} }
redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisAsyncContext *redisAsyncConnectUnix(const char *path) {
redisContext *c; redisOptions options = {0};
redisAsyncContext *ac; REDIS_OPTIONS_SET_UNIX(&options, path);
return redisAsyncConnectWithOptions(&options);
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) { int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
...@@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) { ...@@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
} }
/* Helper function to make the disconnect happen and clean up. */ /* Helper function to make the disconnect happen and clean up. */
static void __redisAsyncDisconnect(redisAsyncContext *ac) { void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */ /* Make sure error is accessible if there is any */
...@@ -344,9 +330,15 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -344,9 +330,15 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
c->flags |= REDIS_DISCONNECTING; c->flags |= REDIS_DISCONNECTING;
} }
/* cleanup event library on disconnect.
* this is safe to call multiple times */
_EL_CLEANUP(ac);
/* For non-clean disconnects, __redisAsyncFree() will execute pending /* For non-clean disconnects, __redisAsyncFree() will execute pending
* callbacks with a NULL-reply. */ * callbacks with a NULL-reply. */
__redisAsyncFree(ac); if (!(c->flags & REDIS_NO_AUTO_FREE)) {
__redisAsyncFree(ac);
}
} }
/* Tries to do a clean disconnect from Redis, meaning it stops new commands /* Tries to do a clean disconnect from Redis, meaning it stops new commands
...@@ -358,6 +350,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -358,6 +350,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
void redisAsyncDisconnect(redisAsyncContext *ac) { void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
c->flags |= REDIS_DISCONNECTING; c->flags |= REDIS_DISCONNECTING;
/** unset the auto-free flag here, because disconnect undoes this */
c->flags &= ~REDIS_NO_AUTO_FREE;
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL) if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} }
...@@ -408,7 +403,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, ...@@ -408,7 +403,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
assert(reply->element[2]->type == REDIS_REPLY_INTEGER); assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
/* Unset subscribed flag only when no pipelined pending subscribe. */ /* Unset subscribed flag only when no pipelined pending subscribe. */
if (reply->element[2]->integer == 0 if (reply->element[2]->integer == 0
&& dictSize(ac->sub.channels) == 0 && dictSize(ac->sub.channels) == 0
&& dictSize(ac->sub.patterns) == 0) && dictSize(ac->sub.patterns) == 0)
c->flags &= ~REDIS_SUBSCRIBED; c->flags &= ~REDIS_SUBSCRIBED;
...@@ -524,6 +519,18 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) { ...@@ -524,6 +519,18 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
} }
} }
void redisAsyncRead(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
if (redisBufferRead(c) == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
/* Always re-schedule reads */
_EL_ADD_READ(ac);
redisProcessCallbacks(ac);
}
}
/* This function should be called when the socket is readable. /* This function should be called when the socket is readable.
* It processes all replies that can be read and executes their callbacks. * It processes all replies that can be read and executes their callbacks.
*/ */
...@@ -539,18 +546,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) { ...@@ -539,18 +546,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
return; return;
} }
if (redisBufferRead(c) == REDIS_ERR) { c->funcs->async_read(ac);
}
void redisAsyncWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
int done = 0;
if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} else { } else {
/* Always re-schedule reads */ /* Continue writing when not done, stop writing otherwise */
if (!done)
_EL_ADD_WRITE(ac);
else
_EL_DEL_WRITE(ac);
/* Always schedule reads after writes */
_EL_ADD_READ(ac); _EL_ADD_READ(ac);
redisProcessCallbacks(ac);
} }
} }
void redisAsyncHandleWrite(redisAsyncContext *ac) { void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
int done = 0;
if (!(c->flags & REDIS_CONNECTED)) { if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */ /* Abort connect was not successful. */
...@@ -561,18 +579,37 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) { ...@@ -561,18 +579,37 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
return; return;
} }
if (redisBufferWrite(c,&done) == REDIS_ERR) { c->funcs->async_write(ac);
__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 */ void __redisSetError(redisContext *c, int type, const char *str);
_EL_ADD_READ(ac);
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redisCallback cb;
if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
/* Nothing to do - just an idle timeout */
return;
}
if (!c->err) {
__redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
}
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
ac->onConnect(ac, REDIS_ERR);
} }
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
__redisRunCallback(ac, &cb, NULL);
}
/**
* TODO: Don't automatically sever the connection,
* rather, allow to ignore <x> responses before the queue is clear
*/
__redisAsyncDisconnect(ac);
} }
/* Sets a pointer to the first argument and its length starting at p. Returns /* Sets a pointer to the first argument and its length starting at p. Returns
...@@ -714,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void ...@@ -714,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len); int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status; return status;
} }
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
if (!ac->c.timeout) {
ac->c.timeout = calloc(1, sizeof(tv));
}
if (tv.tv_sec == ac->c.timeout->tv_sec &&
tv.tv_usec == ac->c.timeout->tv_usec) {
return;
}
*ac->c.timeout = tv;
}
...@@ -57,6 +57,7 @@ typedef struct redisCallbackList { ...@@ -57,6 +57,7 @@ typedef struct redisCallbackList {
/* Connection callback prototypes */ /* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status); typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
typedef void(redisTimerCallback)(void *timer, void *privdata);
/* Context for an async connection to Redis */ /* Context for an async connection to Redis */
typedef struct redisAsyncContext { typedef struct redisAsyncContext {
...@@ -81,6 +82,7 @@ typedef struct redisAsyncContext { ...@@ -81,6 +82,7 @@ typedef struct redisAsyncContext {
void (*addWrite)(void *privdata); void (*addWrite)(void *privdata);
void (*delWrite)(void *privdata); void (*delWrite)(void *privdata);
void (*cleanup)(void *privdata); void (*cleanup)(void *privdata);
void (*scheduleTimer)(void *privdata, struct timeval tv);
} ev; } ev;
/* Called when either the connection is terminated due to an error or per /* Called when either the connection is terminated due to an error or per
...@@ -106,6 +108,7 @@ typedef struct redisAsyncContext { ...@@ -106,6 +108,7 @@ typedef struct redisAsyncContext {
} redisAsyncContext; } redisAsyncContext;
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
redisAsyncContext *redisAsyncConnect(const char *ip, int port); redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr); redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
...@@ -113,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port, ...@@ -113,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
redisAsyncContext *redisAsyncConnectUnix(const char *path); redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac);
/* Handle read/write events */ /* Handle read/write events */
void redisAsyncHandleRead(redisAsyncContext *ac); void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac); void redisAsyncHandleWrite(redisAsyncContext *ac);
void redisAsyncHandleTimeout(redisAsyncContext *ac);
void redisAsyncRead(redisAsyncContext *ac);
void redisAsyncWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the /* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */ * output buffer and register the provided callback. */
......
/*
* 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_PRIVATE_H
#define __HIREDIS_ASYNC_PRIVATE_H
#define _EL_ADD_READ(ctx) \
do { \
refreshTimeout(ctx); \
if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
} while (0)
#define _EL_DEL_READ(ctx) do { \
if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
} while(0)
#define _EL_ADD_WRITE(ctx) \
do { \
refreshTimeout(ctx); \
if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
} while (0)
#define _EL_DEL_WRITE(ctx) do { \
if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
} while(0)
#define _EL_CLEANUP(ctx) do { \
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
ctx->ev.cleanup = NULL; \
} while(0);
static inline void refreshTimeout(redisAsyncContext *ctx) {
if (ctx->c.timeout && ctx->ev.scheduleTimer &&
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
// } else {
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
// if (ctx->c.timeout){
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
// ctx->c.timeout->tv_usec);
// }
}
}
void __redisAsyncDisconnect(redisAsyncContext *ac);
void redisProcessCallbacks(redisAsyncContext *ac);
#endif /* __HIREDIS_ASYNC_PRIVATE_H */
INCLUDE(FindPkgConfig)
# Check for GLib
PKG_CHECK_MODULES(GLIB2 glib-2.0)
if (GLIB2_FOUND)
INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
ADD_EXECUTABLE(example-glib example-glib.c)
TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
ENDIF(GLIB2_FOUND)
FIND_PATH(LIBEV ev.h
HINTS /usr/local /usr/opt/local
ENV LIBEV_INCLUDE_DIR)
if (LIBEV)
# Just compile and link with libev
ADD_EXECUTABLE(example-libev example-libev.c)
TARGET_LINK_LIBRARIES(example-libev hiredis ev)
ENDIF()
FIND_PATH(LIBEVENT event.h)
if (LIBEVENT)
ADD_EXECUTABLE(example-libevent example-libevent)
TARGET_LINK_LIBRARIES(example-libevent hiredis event)
ENDIF()
FIND_PATH(LIBUV uv.h)
IF (LIBUV)
ADD_EXECUTABLE(example-libuv example-libuv.c)
TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
ENDIF()
IF (APPLE)
FIND_LIBRARY(CF CoreFoundation)
ADD_EXECUTABLE(example-macosx example-macosx.c)
TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
ENDIF()
IF (ENABLE_SSL)
ADD_EXECUTABLE(example-ssl example-ssl.c)
TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
ENDIF()
ADD_EXECUTABLE(example example.c)
TARGET_LINK_LIBRARIES(example hiredis)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <hiredis.h>
#include <hiredis_ssl.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();
if (argc < 5) {
fprintf(stderr,
"Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
exit(1);
}
const char *value = argv[1];
size_t nvalue = strlen(value);
const char *hostname = argv[2];
int port = atoi(argv[3]);
const char *cert = argv[4];
const char *certKey = argv[5];
const char *caCert = argc > 5 ? argv[6] : NULL;
redisAsyncContext *c = redisAsyncConnect(hostname, port);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
return 1;
}
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
printf("SSL Error!\n");
exit(1);
}
redisLibeventAttach(c,base);
redisAsyncSetConnectCallback(c,connectCallback);
redisAsyncSetDisconnectCallback(c,disconnectCallback);
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base);
return 0;
}
...@@ -9,7 +9,12 @@ ...@@ -9,7 +9,12 @@
void getCallback(redisAsyncContext *c, void *r, void *privdata) { void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r; redisReply *reply = r;
if (reply == NULL) return; if (reply == NULL) {
if (c->errstr) {
printf("errstr: %s\n", c->errstr);
}
return;
}
printf("argv[%s]: %s\n", (char*)privdata, reply->str); printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */ /* Disconnect after receiving the reply to GET */
...@@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) { ...@@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
int main (int argc, char **argv) { int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new(); struct event_base *base = event_base_new();
redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
struct timeval tv = {0};
tv.tv_sec = 1;
options.timeout = &tv;
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
if (c->err) { if (c->err) {
/* Let *c leak for now... */ /* Let *c leak for now... */
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
......
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis.h>
#include <hiredis_ssl.h>
int main(int argc, char **argv) {
unsigned int j;
redisContext *c;
redisReply *reply;
if (argc < 4) {
printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]);
exit(1);
}
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
int port = atoi(argv[2]);
const char *cert = argv[3];
const char *key = argv[4];
const char *ca = argc > 4 ? argv[5] : NULL;
struct timeval tv = { 1, 500000 }; // 1.5 seconds
redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, hostname, port);
options.timeout = &tv;
c = redisConnectWithOptions(&options);
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis context\n");
}
exit(1);
}
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
printf("Couldn't initialize SSL!\n");
printf("Error: %s\n", c->errstr);
redisFree(c);
exit(1);
}
/* PING server */
reply = redisCommand(c,"PING");
printf("PING: %s\n", reply->str);
freeReplyObject(reply);
/* Set a key */
reply = redisCommand(c,"SET %s %s", "foo", "hello world");
printf("SET: %s\n", reply->str);
freeReplyObject(reply);
/* Set a key using binary safe API */
reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
printf("SET (binary API): %s\n", reply->str);
freeReplyObject(reply);
/* Try a GET and two INCR */
reply = redisCommand(c,"GET foo");
printf("GET foo: %s\n", reply->str);
freeReplyObject(reply);
reply = redisCommand(c,"INCR counter");
printf("INCR counter: %lld\n", reply->integer);
freeReplyObject(reply);
/* again ... */
reply = redisCommand(c,"INCR counter");
printf("INCR counter: %lld\n", reply->integer);
freeReplyObject(reply);
/* Create a list of numbers, from 0 to 9 */
reply = redisCommand(c,"DEL mylist");
freeReplyObject(reply);
for (j = 0; j < 10; j++) {
char buf[64];
snprintf(buf,64,"%u",j);
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
freeReplyObject(reply);
}
/* Let's check what we have inside the list */
reply = redisCommand(c,"LRANGE mylist 0 -1");
if (reply->type == REDIS_REPLY_ARRAY) {
for (j = 0; j < reply->elements; j++) {
printf("%u) %s\n", j, reply->element[j]->str);
}
}
freeReplyObject(reply);
/* Disconnects and frees the context */
redisFree(c);
return 0;
}
...@@ -5,14 +5,27 @@ ...@@ -5,14 +5,27 @@
#include <hiredis.h> #include <hiredis.h>
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j; unsigned int j, isunix = 0;
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1"; const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
if (argc > 2) {
if (*argv[2] == 'u' || *argv[2] == 'U') {
isunix = 1;
/* in this case, host is the path to the unix socket */
printf("Will connect to unix socket @%s\n", hostname);
}
}
int port = (argc > 2) ? atoi(argv[2]) : 6379; int port = (argc > 2) ? atoi(argv[2]) : 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout(hostname, port, timeout); if (isunix) {
c = redisConnectUnixWithTimeout(hostname, timeout);
} else {
c = redisConnectWithTimeout(hostname, port, timeout);
}
if (c == NULL || c->err) { if (c == NULL || c->err) {
if (c) { if (c) {
printf("Connection error: %s\n", c->errstr); printf("Connection error: %s\n", c->errstr);
......
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
#include "fmacros.h" #include "fmacros.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <ctype.h> #include <ctype.h>
...@@ -42,10 +41,20 @@ ...@@ -42,10 +41,20 @@
#include "hiredis.h" #include "hiredis.h"
#include "net.h" #include "net.h"
#include "sds.h" #include "sds.h"
#include "async.h"
#include "win32.h"
static redisContextFuncs redisContextDefaultFuncs = {
.free_privdata = NULL,
.async_read = redisAsyncRead,
.async_write = redisAsyncWrite,
.read = redisNetRead,
.write = redisNetWrite
};
static redisReply *createReplyObject(int type); static 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, size_t 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 *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task); static void *createNilObject(const redisReadTask *task);
...@@ -112,21 +121,34 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len ...@@ -112,21 +121,34 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
if (r == NULL) if (r == NULL)
return NULL; return NULL;
buf = malloc(len+1);
if (buf == NULL) {
freeReplyObject(r);
return NULL;
}
assert(task->type == REDIS_REPLY_ERROR || assert(task->type == REDIS_REPLY_ERROR ||
task->type == REDIS_REPLY_STATUS || task->type == REDIS_REPLY_STATUS ||
task->type == REDIS_REPLY_STRING); task->type == REDIS_REPLY_STRING ||
task->type == REDIS_REPLY_VERB);
/* Copy string value */ /* Copy string value */
memcpy(buf,str,len); if (task->type == REDIS_REPLY_VERB) {
buf[len] = '\0'; buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
if (buf == NULL) {
freeReplyObject(r);
return NULL;
}
memcpy(r->vtype,str,3);
r->vtype[3] = '\0';
memcpy(buf,str+4,len-4);
buf[len-4] = '\0';
r->len = len-4;
} else {
buf = malloc(len+1);
if (buf == NULL) {
freeReplyObject(r);
return NULL;
}
memcpy(buf,str,len);
buf[len] = '\0';
r->len = len;
}
r->str = buf; r->str = buf;
r->len = len;
if (task->parent) { if (task->parent) {
parent = task->parent->obj; parent = task->parent->obj;
...@@ -138,7 +160,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len ...@@ -138,7 +160,7 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
return r; return r;
} }
static void *createArrayObject(const redisReadTask *task, int elements) { static void *createArrayObject(const redisReadTask *task, size_t elements) {
redisReply *r, *parent; redisReply *r, *parent;
r = createReplyObject(task->type); r = createReplyObject(task->type);
...@@ -649,29 +671,30 @@ redisReader *redisReaderCreate(void) { ...@@ -649,29 +671,30 @@ redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions); return redisReaderCreateWithFunctions(&defaultFunctions);
} }
static redisContext *redisContextInit(void) { static redisContext *redisContextInit(const redisOptions *options) {
redisContext *c; redisContext *c;
c = calloc(1,sizeof(redisContext)); c = calloc(1, sizeof(*c));
if (c == NULL) if (c == NULL)
return NULL; return NULL;
c->funcs = &redisContextDefaultFuncs;
c->obuf = sdsempty(); c->obuf = sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
c->fd = REDIS_INVALID_FD;
if (c->obuf == NULL || c->reader == NULL) { if (c->obuf == NULL || c->reader == NULL) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
(void)options; /* options are used in other functions */
return c; return c;
} }
void redisFree(redisContext *c) { void redisFree(redisContext *c) {
if (c == NULL) if (c == NULL)
return; return;
if (c->fd > 0) redisNetClose(c);
close(c->fd);
sdsfree(c->obuf); sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
...@@ -680,12 +703,16 @@ void redisFree(redisContext *c) { ...@@ -680,12 +703,16 @@ void redisFree(redisContext *c) {
free(c->unix_sock.path); free(c->unix_sock.path);
free(c->timeout); free(c->timeout);
free(c->saddr); free(c->saddr);
if (c->funcs->free_privdata) {
c->funcs->free_privdata(c->privdata);
}
memset(c, 0xff, sizeof(*c));
free(c); free(c);
} }
int redisFreeKeepFd(redisContext *c) { redisFD redisFreeKeepFd(redisContext *c) {
int fd = c->fd; redisFD fd = c->fd;
c->fd = -1; c->fd = REDIS_INVALID_FD;
redisFree(c); redisFree(c);
return fd; return fd;
} }
...@@ -694,10 +721,13 @@ int redisReconnect(redisContext *c) { ...@@ -694,10 +721,13 @@ int redisReconnect(redisContext *c) {
c->err = 0; c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr)); memset(c->errstr, '\0', strlen(c->errstr));
if (c->fd > 0) { if (c->privdata && c->funcs->free_privdata) {
close(c->fd); c->funcs->free_privdata(c->privdata);
c->privdata = NULL;
} }
redisNetClose(c);
sdsfree(c->obuf); sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
...@@ -718,112 +748,107 @@ int redisReconnect(redisContext *c) { ...@@ -718,112 +748,107 @@ int redisReconnect(redisContext *c) {
return REDIS_ERR; return REDIS_ERR;
} }
redisContext *redisConnectWithOptions(const redisOptions *options) {
redisContext *c = redisContextInit(options);
if (c == NULL) {
return NULL;
}
if (!(options->options & REDIS_OPT_NONBLOCK)) {
c->flags |= REDIS_BLOCK;
}
if (options->options & REDIS_OPT_REUSEADDR) {
c->flags |= REDIS_REUSEADDR;
}
if (options->options & REDIS_OPT_NOAUTOFREE) {
c->flags |= REDIS_NO_AUTO_FREE;
}
if (options->type == REDIS_CONN_TCP) {
redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
options->endpoint.tcp.port, options->timeout,
options->endpoint.tcp.source_addr);
} else if (options->type == REDIS_CONN_UNIX) {
redisContextConnectUnix(c, options->endpoint.unix_socket,
options->timeout);
} else if (options->type == REDIS_CONN_USERFD) {
c->fd = options->endpoint.fd;
c->flags |= REDIS_CONNECTED;
} else {
// Unknown type - FIXME - FREE
return NULL;
}
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
redisContextSetTimeout(c, *options->timeout);
}
return c;
}
/* Connect to a Redis instance. On error the field error in the returned /* Connect to a Redis instance. On error the field error in the returned
* context will be set to the return value of the error function. * context will be set to the return value of the error function.
* When no set of reply functions is given, the default set will be used. */ * When no set of reply functions is given, the default set will be used. */
redisContext *redisConnect(const char *ip, int port) { redisContext *redisConnect(const char *ip, int port) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
c = redisContextInit(); return redisConnectWithOptions(&options);
if (c == NULL)
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectTcp(c,ip,port,NULL);
return c;
} }
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
c = redisContextInit(); options.timeout = &tv;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectTcp(c,ip,port,&tv);
return c;
} }
redisContext *redisConnectNonBlock(const char *ip, int port) { redisContext *redisConnectNonBlock(const char *ip, int port) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port);
c = redisContextInit(); options.options |= REDIS_OPT_NONBLOCK;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags &= ~REDIS_BLOCK;
redisContextConnectTcp(c,ip,port,NULL);
return c;
} }
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(); redisOptions options = {0};
if (c == NULL) REDIS_OPTIONS_SET_TCP(&options, ip, port);
return NULL; options.endpoint.tcp.source_addr = source_addr;
c->flags &= ~REDIS_BLOCK; options.options |= REDIS_OPT_NONBLOCK;
redisContextConnectBindTcp(c,ip,port,NULL,source_addr); return redisConnectWithOptions(&options);
return c;
} }
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(); redisOptions options = {0};
if (c == NULL) REDIS_OPTIONS_SET_TCP(&options, ip, port);
return NULL; options.endpoint.tcp.source_addr = source_addr;
c->flags &= ~REDIS_BLOCK; options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
c->flags |= REDIS_REUSEADDR; return redisConnectWithOptions(&options);
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
return c;
} }
redisContext *redisConnectUnix(const char *path) { redisContext *redisConnectUnix(const char *path) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path);
c = redisContextInit(); return redisConnectWithOptions(&options);
if (c == NULL)
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectUnix(c,path,NULL);
return c;
} }
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path);
c = redisContextInit(); options.timeout = &tv;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags |= REDIS_BLOCK;
redisContextConnectUnix(c,path,&tv);
return c;
} }
redisContext *redisConnectUnixNonBlock(const char *path) { redisContext *redisConnectUnixNonBlock(const char *path) {
redisContext *c; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path);
c = redisContextInit(); options.options |= REDIS_OPT_NONBLOCK;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->flags &= ~REDIS_BLOCK;
redisContextConnectUnix(c,path,NULL);
return c;
} }
redisContext *redisConnectFd(int fd) { redisContext *redisConnectFd(redisFD fd) {
redisContext *c; redisOptions options = {0};
options.type = REDIS_CONN_USERFD;
c = redisContextInit(); options.endpoint.fd = fd;
if (c == NULL) return redisConnectWithOptions(&options);
return NULL;
c->fd = fd;
c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
return c;
} }
/* Set read/write timeout on a blocking socket. */ /* Set read/write timeout on a blocking socket. */
...@@ -853,22 +878,15 @@ int redisBufferRead(redisContext *c) { ...@@ -853,22 +878,15 @@ int redisBufferRead(redisContext *c) {
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
nread = read(c->fd,buf,sizeof(buf)); nread = c->funcs->read(c, buf, sizeof(buf));
if (nread == -1) { if (nread > 0) {
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
/* Try again later */ __redisSetError(c, c->reader->err, c->reader->errstr);
} else {
__redisSetError(c,REDIS_ERR_IO,NULL);
return REDIS_ERR; return REDIS_ERR;
} else {
} }
} else if (nread == 0) { } else if (nread < 0) {
__redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
return REDIS_ERR; return REDIS_ERR;
} else {
if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
__redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR;
}
} }
return REDIS_OK; return REDIS_OK;
} }
...@@ -883,21 +901,15 @@ int redisBufferRead(redisContext *c) { ...@@ -883,21 +901,15 @@ int redisBufferRead(redisContext *c) {
* c->errstr to hold the appropriate error string. * c->errstr to hold the appropriate error string.
*/ */
int redisBufferWrite(redisContext *c, int *done) { int redisBufferWrite(redisContext *c, int *done) {
int nwritten;
/* Return early when the context has seen an error. */ /* Return early when the context has seen an error. */
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
if (sdslen(c->obuf) > 0) { if (sdslen(c->obuf) > 0) {
nwritten = write(c->fd,c->obuf,sdslen(c->obuf)); int nwritten = c->funcs->write(c);
if (nwritten == -1) { if (nwritten < 0) {
if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { return REDIS_ERR;
/* Try again later */
} else {
__redisSetError(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
} else if (nwritten > 0) { } else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) { if (nwritten == (signed)sdslen(c->obuf)) {
sdsfree(c->obuf); sdsfree(c->obuf);
......
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