commit
3f14bfd809
28
.github/workflows/ci.yml
vendored
Normal file
28
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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
|
106
TLS.md
Normal file
106
TLS.md
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
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
|
6
deps/Makefile
vendored
6
deps/Makefile
vendored
@ -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
|
||||||
|
|
||||||
|
1
deps/hiredis/.gitignore
vendored
1
deps/hiredis/.gitignore
vendored
@ -5,3 +5,4 @@
|
|||||||
/*.dylib
|
/*.dylib
|
||||||
/*.a
|
/*.a
|
||||||
/*.pc
|
/*.pc
|
||||||
|
*.dSYM
|
||||||
|
74
deps/hiredis/.travis.yml
vendored
74
deps/hiredis/.travis.yml
vendored
@ -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"
|
script:
|
||||||
|
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
|
||||||
|
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||||
|
if [ "$BITS" == "32" ]; then
|
||||||
|
CFLAGS="-m32 -Werror";
|
||||||
|
CXXFLAGS="-m32 -Werror";
|
||||||
|
LDFLAGS="-m32";
|
||||||
|
EXTRA_CMAKE_OPTS=;
|
||||||
|
else
|
||||||
|
CFLAGS="-Werror";
|
||||||
|
CXXFLAGS="-Werror";
|
||||||
|
fi;
|
||||||
|
else
|
||||||
|
TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
|
||||||
|
if [ "$BITS" == "32" ]; then
|
||||||
|
CFLAGS="-m32 -Werror";
|
||||||
|
CXXFLAGS="-m32 -Werror";
|
||||||
|
LDFLAGS="-m32";
|
||||||
|
EXTRA_CMAKE_OPTS=;
|
||||||
|
else
|
||||||
|
CFLAGS="-Werror";
|
||||||
|
CXXFLAGS="-Werror";
|
||||||
|
fi;
|
||||||
|
fi;
|
||||||
|
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
|
||||||
|
- mkdir build/ && cd build/
|
||||||
|
- cmake .. ${EXTRA_CMAKE_OPTS}
|
||||||
|
- make VERBOSE=1
|
||||||
|
- ctest -V
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
exclude:
|
include:
|
||||||
- os: osx
|
# Windows MinGW cross compile on Linux
|
||||||
env: PRE="valgrind --track-origins=yes --leak-check=full"
|
- 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
|
||||||
|
|
||||||
- os: osx
|
# Windows MSVC 2017
|
||||||
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
- os: windows
|
||||||
|
compiler: msvc
|
||||||
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example
|
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
|
||||||
|
15
deps/hiredis/CHANGELOG.md
vendored
15
deps/hiredis/CHANGELOG.md
vendored
@ -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 @@
|
|||||||
* 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
|
||||||
|
|
||||||
|
90
deps/hiredis/CMakeLists.txt
vendored
Normal file
90
deps/hiredis/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
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)
|
106
deps/hiredis/Makefile
vendored
106
deps/hiredis/Makefile
vendored
@ -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
|
|||||||
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
|
|||||||
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)
|
|||||||
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:
|
|||||||
@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)
|
|||||||
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
|
|||||||
@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)
|
||||||
|
3
deps/hiredis/README.md
vendored
3
deps/hiredis/README.md
vendored
@ -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.
|
|||||||
## 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)
|
||||||
|
112
deps/hiredis/adapters/libevent.h
vendored
112
deps/hiredis/adapters/libevent.h
vendored
@ -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) {
|
|||||||
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) {
|
|||||||
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
|
||||||
|
7
deps/hiredis/appveyor.yml
vendored
7
deps/hiredis/appveyor.yml
vendored
@ -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:
|
|||||||
|
|
||||||
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"'
|
||||||
|
176
deps/hiredis/async.c
vendored
176
deps/hiredis/async.c
vendored
@ -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 @@
|
|||||||
#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) {
|
|||||||
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) {
|
|||||||
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) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 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) {
|
|||||||
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) {
|
|||||||
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,
|
|||||||
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) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,28 +546,13 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redisBufferRead(c) == REDIS_ERR) {
|
c->funcs->async_read(ac);
|
||||||
__redisAsyncDisconnect(ac);
|
|
||||||
} else {
|
|
||||||
/* Always re-schedule reads */
|
|
||||||
_EL_ADD_READ(ac);
|
|
||||||
redisProcessCallbacks(ac);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
void redisAsyncWrite(redisAsyncContext *ac) {
|
||||||
redisContext *c = &(ac->c);
|
redisContext *c = &(ac->c);
|
||||||
int done = 0;
|
int done = 0;
|
||||||
|
|
||||||
if (!(c->flags & REDIS_CONNECTED)) {
|
|
||||||
/* Abort connect was not successful. */
|
|
||||||
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
|
|
||||||
return;
|
|
||||||
/* Try again later when the context is still not connected. */
|
|
||||||
if (!(c->flags & REDIS_CONNECTED))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
||||||
__redisAsyncDisconnect(ac);
|
__redisAsyncDisconnect(ac);
|
||||||
} else {
|
} else {
|
||||||
@ -575,6 +567,51 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
|
||||||
|
if (!(c->flags & REDIS_CONNECTED)) {
|
||||||
|
/* Abort connect was not successful. */
|
||||||
|
if (__redisAsyncHandleConnect(ac) != REDIS_OK)
|
||||||
|
return;
|
||||||
|
/* Try again later when the context is still not connected. */
|
||||||
|
if (!(c->flags & REDIS_CONNECTED))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->funcs->async_write(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __redisSetError(redisContext *c, int type, const char *str);
|
||||||
|
|
||||||
|
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
||||||
|
redisContext *c = &(ac->c);
|
||||||
|
redisCallback cb;
|
||||||
|
|
||||||
|
if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
|
||||||
|
/* Nothing to do - just an idle timeout */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c->err) {
|
||||||
|
__redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
|
||||||
|
ac->onConnect(ac, REDIS_ERR);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
|
||||||
|
__redisRunCallback(ac, &cb, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Don't automatically sever the connection,
|
||||||
|
* rather, allow to ignore <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
|
||||||
* the number of bytes to skip to get to the following argument. */
|
* the number of bytes to skip to get to the following argument. */
|
||||||
static const char *nextArgument(const char *start, const char **str, size_t *len) {
|
static const char *nextArgument(const char *start, const char **str, size_t *len) {
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
8
deps/hiredis/async.h
vendored
8
deps/hiredis/async.h
vendored
@ -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 {
|
|||||||
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 {
|
|||||||
} 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,
|
|||||||
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. */
|
||||||
|
72
deps/hiredis/async_private.h
vendored
Normal file
72
deps/hiredis/async_private.h
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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 */
|
46
deps/hiredis/examples/CMakeLists.txt
vendored
Normal file
46
deps/hiredis/examples/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
INCLUDE(FindPkgConfig)
|
||||||
|
# Check for GLib
|
||||||
|
|
||||||
|
PKG_CHECK_MODULES(GLIB2 glib-2.0)
|
||||||
|
if (GLIB2_FOUND)
|
||||||
|
INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
|
||||||
|
LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
|
||||||
|
ADD_EXECUTABLE(example-glib example-glib.c)
|
||||||
|
TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
|
||||||
|
ENDIF(GLIB2_FOUND)
|
||||||
|
|
||||||
|
FIND_PATH(LIBEV ev.h
|
||||||
|
HINTS /usr/local /usr/opt/local
|
||||||
|
ENV LIBEV_INCLUDE_DIR)
|
||||||
|
|
||||||
|
if (LIBEV)
|
||||||
|
# Just compile and link with libev
|
||||||
|
ADD_EXECUTABLE(example-libev example-libev.c)
|
||||||
|
TARGET_LINK_LIBRARIES(example-libev hiredis ev)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
FIND_PATH(LIBEVENT event.h)
|
||||||
|
if (LIBEVENT)
|
||||||
|
ADD_EXECUTABLE(example-libevent example-libevent)
|
||||||
|
TARGET_LINK_LIBRARIES(example-libevent hiredis event)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
FIND_PATH(LIBUV uv.h)
|
||||||
|
IF (LIBUV)
|
||||||
|
ADD_EXECUTABLE(example-libuv example-libuv.c)
|
||||||
|
TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
IF (APPLE)
|
||||||
|
FIND_LIBRARY(CF CoreFoundation)
|
||||||
|
ADD_EXECUTABLE(example-macosx example-macosx.c)
|
||||||
|
TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
IF (ENABLE_SSL)
|
||||||
|
ADD_EXECUTABLE(example-ssl example-ssl.c)
|
||||||
|
TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
ADD_EXECUTABLE(example example.c)
|
||||||
|
TARGET_LINK_LIBRARIES(example hiredis)
|
73
deps/hiredis/examples/example-libevent-ssl.c
vendored
Normal file
73
deps/hiredis/examples/example-libevent-ssl.c
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#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;
|
||||||
|
}
|
15
deps/hiredis/examples/example-libevent.c
vendored
15
deps/hiredis/examples/example-libevent.c
vendored
@ -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) {
|
|||||||
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);
|
||||||
|
97
deps/hiredis/examples/example-ssl.c
vendored
Normal file
97
deps/hiredis/examples/example-ssl.c
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
#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;
|
||||||
|
}
|
17
deps/hiredis/examples/example.c
vendored
17
deps/hiredis/examples/example.c
vendored
@ -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);
|
||||||
|
256
deps/hiredis/hiredis.c
vendored
256
deps/hiredis/hiredis.c
vendored
@ -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 @@
|
|||||||
#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
|
|||||||
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
|
|||||||
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) {
|
|||||||
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) {
|
|||||||
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) {
|
|||||||
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) {
|
|||||||
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) {
|
|||||||
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);
|
||||||
|
return REDIS_ERR;
|
||||||
} else {
|
} else {
|
||||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
|
||||||
return REDIS_ERR;
|
|
||||||
}
|
}
|
||||||
} 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) {
|
|||||||
* 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);
|
||||||
|
103
deps/hiredis/hiredis.h
vendored
103
deps/hiredis/hiredis.h
vendored
@ -35,7 +35,11 @@
|
|||||||
#define __HIREDIS_H
|
#define __HIREDIS_H
|
||||||
#include "read.h"
|
#include "read.h"
|
||||||
#include <stdarg.h> /* for va_list */
|
#include <stdarg.h> /* for va_list */
|
||||||
|
#ifndef _MSC_VER
|
||||||
#include <sys/time.h> /* for struct timeval */
|
#include <sys/time.h> /* for struct timeval */
|
||||||
|
#else
|
||||||
|
struct timeval; /* forward declaration */
|
||||||
|
#endif
|
||||||
#include <stdint.h> /* uintXX_t, etc */
|
#include <stdint.h> /* uintXX_t, etc */
|
||||||
#include "sds.h" /* for sds */
|
#include "sds.h" /* for sds */
|
||||||
|
|
||||||
@ -74,6 +78,12 @@
|
|||||||
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
||||||
#define REDIS_REUSEADDR 0x80
|
#define REDIS_REUSEADDR 0x80
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag that indicates the user does not want the context to
|
||||||
|
* be automatically freed upon error
|
||||||
|
*/
|
||||||
|
#define REDIS_NO_AUTO_FREE 0x200
|
||||||
|
|
||||||
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||||
|
|
||||||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
||||||
@ -92,6 +102,8 @@ typedef struct redisReply {
|
|||||||
size_t len; /* Length of string */
|
size_t len; /* Length of string */
|
||||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||||
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
||||||
|
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
|
||||||
|
terminated 3 character content type, such as "txt". */
|
||||||
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;
|
||||||
@ -111,14 +123,93 @@ void redisFreeSdsCommand(sds cmd);
|
|||||||
|
|
||||||
enum redisConnectionType {
|
enum redisConnectionType {
|
||||||
REDIS_CONN_TCP,
|
REDIS_CONN_TCP,
|
||||||
REDIS_CONN_UNIX
|
REDIS_CONN_UNIX,
|
||||||
|
REDIS_CONN_USERFD
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct redisSsl;
|
||||||
|
|
||||||
|
#define REDIS_OPT_NONBLOCK 0x01
|
||||||
|
#define REDIS_OPT_REUSEADDR 0x02
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't automatically free the async object on a connection failure,
|
||||||
|
* or other implicit conditions. Only free on an explicit call to disconnect() or free()
|
||||||
|
*/
|
||||||
|
#define REDIS_OPT_NOAUTOFREE 0x04
|
||||||
|
|
||||||
|
/* In Unix systems a file descriptor is a regular signed int, with -1
|
||||||
|
* representing an invalid descriptor. In Windows it is a SOCKET
|
||||||
|
* (32- or 64-bit unsigned integer depending on the architecture), where
|
||||||
|
* all bits set (~0) is INVALID_SOCKET. */
|
||||||
|
#ifndef _WIN32
|
||||||
|
typedef int redisFD;
|
||||||
|
#define REDIS_INVALID_FD -1
|
||||||
|
#else
|
||||||
|
#ifdef _WIN64
|
||||||
|
typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */
|
||||||
|
#else
|
||||||
|
typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */
|
||||||
|
#endif
|
||||||
|
#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
/*
|
||||||
|
* the type of connection to use. This also indicates which
|
||||||
|
* `endpoint` member field to use
|
||||||
|
*/
|
||||||
|
int type;
|
||||||
|
/* bit field of REDIS_OPT_xxx */
|
||||||
|
int options;
|
||||||
|
/* timeout value. if NULL, no timeout is used */
|
||||||
|
const struct timeval *timeout;
|
||||||
|
union {
|
||||||
|
/** use this field for tcp/ip connections */
|
||||||
|
struct {
|
||||||
|
const char *source_addr;
|
||||||
|
const char *ip;
|
||||||
|
int port;
|
||||||
|
} tcp;
|
||||||
|
/** use this field for unix domain sockets */
|
||||||
|
const char *unix_socket;
|
||||||
|
/**
|
||||||
|
* use this field to have hiredis operate an already-open
|
||||||
|
* file descriptor */
|
||||||
|
redisFD fd;
|
||||||
|
} endpoint;
|
||||||
|
} redisOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper macros to initialize options to their specified fields.
|
||||||
|
*/
|
||||||
|
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \
|
||||||
|
(opts)->type = REDIS_CONN_TCP; \
|
||||||
|
(opts)->endpoint.tcp.ip = ip_; \
|
||||||
|
(opts)->endpoint.tcp.port = port_;
|
||||||
|
|
||||||
|
#define REDIS_OPTIONS_SET_UNIX(opts, path) \
|
||||||
|
(opts)->type = REDIS_CONN_UNIX; \
|
||||||
|
(opts)->endpoint.unix_socket = path;
|
||||||
|
|
||||||
|
struct redisAsyncContext;
|
||||||
|
struct redisContext;
|
||||||
|
|
||||||
|
typedef struct redisContextFuncs {
|
||||||
|
void (*free_privdata)(void *);
|
||||||
|
void (*async_read)(struct redisAsyncContext *);
|
||||||
|
void (*async_write)(struct redisAsyncContext *);
|
||||||
|
int (*read)(struct redisContext *, char *, size_t);
|
||||||
|
int (*write)(struct redisContext *);
|
||||||
|
} redisContextFuncs;
|
||||||
|
|
||||||
/* Context for a connection to Redis */
|
/* Context for a connection to Redis */
|
||||||
typedef struct redisContext {
|
typedef struct redisContext {
|
||||||
|
const redisContextFuncs *funcs; /* Function table */
|
||||||
|
|
||||||
int err; /* Error flags, 0 when there is no error */
|
int err; /* Error flags, 0 when there is no error */
|
||||||
char errstr[128]; /* String representation of error when applicable */
|
char errstr[128]; /* String representation of error when applicable */
|
||||||
int fd;
|
redisFD fd;
|
||||||
int flags;
|
int flags;
|
||||||
char *obuf; /* Write buffer */
|
char *obuf; /* Write buffer */
|
||||||
redisReader *reader; /* Protocol reader */
|
redisReader *reader; /* Protocol reader */
|
||||||
@ -139,8 +230,12 @@ typedef struct redisContext {
|
|||||||
/* For non-blocking connect */
|
/* For non-blocking connect */
|
||||||
struct sockadr *saddr;
|
struct sockadr *saddr;
|
||||||
size_t addrlen;
|
size_t addrlen;
|
||||||
|
|
||||||
|
/* Additional private data for hiredis addons such as SSL */
|
||||||
|
void *privdata;
|
||||||
} redisContext;
|
} redisContext;
|
||||||
|
|
||||||
|
redisContext *redisConnectWithOptions(const redisOptions *options);
|
||||||
redisContext *redisConnect(const char *ip, int port);
|
redisContext *redisConnect(const char *ip, int port);
|
||||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
||||||
redisContext *redisConnectNonBlock(const char *ip, int port);
|
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||||
@ -151,7 +246,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
|||||||
redisContext *redisConnectUnix(const char *path);
|
redisContext *redisConnectUnix(const char *path);
|
||||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
||||||
redisContext *redisConnectUnixNonBlock(const char *path);
|
redisContext *redisConnectUnixNonBlock(const char *path);
|
||||||
redisContext *redisConnectFd(int fd);
|
redisContext *redisConnectFd(redisFD fd);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconnect the given context using the saved information.
|
* Reconnect the given context using the saved information.
|
||||||
@ -167,7 +262,7 @@ int redisReconnect(redisContext *c);
|
|||||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||||
int redisEnableKeepAlive(redisContext *c);
|
int redisEnableKeepAlive(redisContext *c);
|
||||||
void redisFree(redisContext *c);
|
void redisFree(redisContext *c);
|
||||||
int redisFreeKeepFd(redisContext *c);
|
redisFD redisFreeKeepFd(redisContext *c);
|
||||||
int redisBufferRead(redisContext *c);
|
int redisBufferRead(redisContext *c);
|
||||||
int redisBufferWrite(redisContext *c, int *done);
|
int redisBufferWrite(redisContext *c, int *done);
|
||||||
|
|
||||||
|
11
deps/hiredis/hiredis.pc.in
vendored
Normal file
11
deps/hiredis/hiredis.pc.in
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
exec_prefix=${prefix}
|
||||||
|
libdir=${exec_prefix}/lib
|
||||||
|
includedir=${prefix}/include
|
||||||
|
pkgincludedir=${includedir}/hiredis
|
||||||
|
|
||||||
|
Name: hiredis
|
||||||
|
Description: Minimalistic C client library for Redis.
|
||||||
|
Version: @PROJECT_VERSION@
|
||||||
|
Libs: -L${libdir} -lhiredis
|
||||||
|
Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
|
53
deps/hiredis/hiredis_ssl.h
vendored
Normal file
53
deps/hiredis/hiredis_ssl.h
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HIREDIS_SSL_H
|
||||||
|
#define __HIREDIS_SSL_H
|
||||||
|
|
||||||
|
/* This is the underlying struct for SSL in ssl.h, which is not included to
|
||||||
|
* keep build dependencies short here.
|
||||||
|
*/
|
||||||
|
struct ssl_st;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secure the connection using SSL. This should be done before any command is
|
||||||
|
* executed on the connection.
|
||||||
|
*/
|
||||||
|
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
|
||||||
|
const char *keypath, const char *servername);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiate SSL/TLS negotiation on a provided context.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
|
||||||
|
|
||||||
|
#endif /* __HIREDIS_SSL_H */
|
12
deps/hiredis/hiredis_ssl.pc.in
vendored
Normal file
12
deps/hiredis/hiredis_ssl.pc.in
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
exec_prefix=${prefix}
|
||||||
|
libdir=${exec_prefix}/lib
|
||||||
|
includedir=${prefix}/include
|
||||||
|
pkgincludedir=${includedir}/hiredis
|
||||||
|
|
||||||
|
Name: hiredis_ssl
|
||||||
|
Description: SSL Support for hiredis.
|
||||||
|
Version: @PROJECT_VERSION@
|
||||||
|
Requires: hiredis
|
||||||
|
Libs: -L${libdir} -lhiredis_ssl
|
||||||
|
Libs.private: -lssl -lcrypto
|
122
deps/hiredis/net.c
vendored
122
deps/hiredis/net.c
vendored
@ -34,36 +34,64 @@
|
|||||||
|
|
||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <netdb.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <poll.h>
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
#include "sds.h"
|
#include "sds.h"
|
||||||
|
#include "sockcompat.h"
|
||||||
|
#include "win32.h"
|
||||||
|
|
||||||
/* Defined in hiredis.c */
|
/* Defined in hiredis.c */
|
||||||
void __redisSetError(redisContext *c, int type, const char *str);
|
void __redisSetError(redisContext *c, int type, const char *str);
|
||||||
|
|
||||||
static void redisContextCloseFd(redisContext *c) {
|
void redisNetClose(redisContext *c) {
|
||||||
if (c && c->fd >= 0) {
|
if (c && c->fd != REDIS_INVALID_FD) {
|
||||||
close(c->fd);
|
close(c->fd);
|
||||||
c->fd = -1;
|
c->fd = REDIS_INVALID_FD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
|
int nread = recv(c->fd, buf, bufcap, 0);
|
||||||
|
if (nread == -1) {
|
||||||
|
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||||
|
/* Try again later */
|
||||||
|
return 0;
|
||||||
|
} else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
|
||||||
|
/* especially in windows */
|
||||||
|
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (nread == 0) {
|
||||||
|
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisNetWrite(redisContext *c) {
|
||||||
|
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
|
||||||
|
if (nwritten < 0) {
|
||||||
|
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||||
|
/* Try again later */
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nwritten;
|
||||||
|
}
|
||||||
|
|
||||||
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
|
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
|
||||||
int errorno = errno; /* snprintf() may change errno */
|
int errorno = errno; /* snprintf() may change errno */
|
||||||
char buf[128] = { 0 };
|
char buf[128] = { 0 };
|
||||||
@ -79,15 +107,15 @@ static int redisSetReuseAddr(redisContext *c) {
|
|||||||
int on = 1;
|
int on = 1;
|
||||||
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int redisCreateSocket(redisContext *c, int type) {
|
static int redisCreateSocket(redisContext *c, int type) {
|
||||||
int s;
|
redisFD s;
|
||||||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
|
if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
@ -101,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int redisSetBlocking(redisContext *c, int blocking) {
|
static int redisSetBlocking(redisContext *c, int blocking) {
|
||||||
|
#ifndef _WIN32
|
||||||
int flags;
|
int flags;
|
||||||
|
|
||||||
/* Set the socket nonblocking.
|
/* Set the socket nonblocking.
|
||||||
@ -108,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
|||||||
* interrupted by a signal. */
|
* interrupted by a signal. */
|
||||||
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
|
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) {
|
|||||||
|
|
||||||
if (fcntl(c->fd, F_SETFL, flags) == -1) {
|
if (fcntl(c->fd, F_SETFL, flags) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
u_long mode = blocking ? 0 : 1;
|
||||||
|
if (ioctl(c->fd, FIONBIO, &mode) == -1) {
|
||||||
|
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
|
||||||
|
redisNetClose(c);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
#endif /* _WIN32 */
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int redisKeepAlive(redisContext *c, int interval) {
|
int redisKeepAlive(redisContext *c, int interval) {
|
||||||
int val = 1;
|
int val = 1;
|
||||||
int fd = c->fd;
|
redisFD fd = c->fd;
|
||||||
|
|
||||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
||||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||||
@ -170,7 +207,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
|
|||||||
int yes = 1;
|
int yes = 1;
|
||||||
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
@ -212,12 +249,12 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
|||||||
|
|
||||||
if ((res = poll(wfd, 1, msec)) == -1) {
|
if ((res = poll(wfd, 1, msec)) == -1) {
|
||||||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
|
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
} else if (res == 0) {
|
} else if (res == 0) {
|
||||||
errno = ETIMEDOUT;
|
errno = ETIMEDOUT;
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +267,7 @@ static int redisContextWaitReady(redisContext *c, long msec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,11 +314,18 @@ int redisCheckSocketError(redisContext *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
|
const void *to_ptr = &tv;
|
||||||
|
size_t to_sz = sizeof(tv);
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||||
|
to_ptr = &timeout_msec;
|
||||||
|
to_sz = sizeof(timeout_msec);
|
||||||
|
#endif
|
||||||
|
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
|
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
|
||||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
|
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
@ -291,7 +335,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
|||||||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||||
const struct timeval *timeout,
|
const struct timeval *timeout,
|
||||||
const char *source_addr) {
|
const char *source_addr) {
|
||||||
int s, rv, n;
|
redisFD s;
|
||||||
|
int rv, n;
|
||||||
char _port[6]; /* strlen("65535"); */
|
char _port[6]; /* strlen("65535"); */
|
||||||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||||
int blocking = (c->flags & REDIS_BLOCK);
|
int blocking = (c->flags & REDIS_BLOCK);
|
||||||
@ -360,7 +405,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
|||||||
}
|
}
|
||||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||||
addrretry:
|
addrretry:
|
||||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
c->fd = s;
|
c->fd = s;
|
||||||
@ -401,16 +446,14 @@ addrretry:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* For repeat connection */
|
/* For repeat connection */
|
||||||
if (c->saddr) {
|
free(c->saddr);
|
||||||
free(c->saddr);
|
|
||||||
}
|
|
||||||
c->saddr = malloc(p->ai_addrlen);
|
c->saddr = malloc(p->ai_addrlen);
|
||||||
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
|
||||||
c->addrlen = 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);
|
redisNetClose(c);
|
||||||
continue;
|
continue;
|
||||||
} else if (errno == EINPROGRESS) {
|
} else if (errno == EINPROGRESS) {
|
||||||
if (blocking) {
|
if (blocking) {
|
||||||
@ -424,7 +467,7 @@ addrretry:
|
|||||||
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
||||||
goto error;
|
goto error;
|
||||||
} else {
|
} else {
|
||||||
redisContextCloseFd(c);
|
redisNetClose(c);
|
||||||
goto addrretry;
|
goto addrretry;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -471,8 +514,9 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
|||||||
}
|
}
|
||||||
|
|
||||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
||||||
|
#ifndef _WIN32
|
||||||
int blocking = (c->flags & REDIS_BLOCK);
|
int blocking = (c->flags & REDIS_BLOCK);
|
||||||
struct sockaddr_un sa;
|
struct sockaddr_un *sa;
|
||||||
long timeout_msec = -1;
|
long timeout_msec = -1;
|
||||||
|
|
||||||
if (redisCreateSocket(c,AF_UNIX) < 0)
|
if (redisCreateSocket(c,AF_UNIX) < 0)
|
||||||
@ -499,9 +543,11 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
|||||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
|
||||||
sa.sun_family = AF_UNIX;
|
sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
|
||||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
c->addrlen = sizeof(struct sockaddr_un);
|
||||||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
sa->sun_family = AF_UNIX;
|
||||||
|
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
|
||||||
|
if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
|
||||||
if (errno == EINPROGRESS && !blocking) {
|
if (errno == EINPROGRESS && !blocking) {
|
||||||
/* This is ok. */
|
/* This is ok. */
|
||||||
} else {
|
} else {
|
||||||
@ -516,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
|
|||||||
|
|
||||||
c->flags |= REDIS_CONNECTED;
|
c->flags |= REDIS_CONNECTED;
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
|
#else
|
||||||
|
/* We currently do not support Unix sockets for Windows. */
|
||||||
|
/* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
|
||||||
|
errno = EPROTONOSUPPORT;
|
||||||
|
return REDIS_ERR;
|
||||||
|
#endif /* _WIN32 */
|
||||||
}
|
}
|
||||||
|
4
deps/hiredis/net.h
vendored
4
deps/hiredis/net.h
vendored
@ -37,6 +37,10 @@
|
|||||||
|
|
||||||
#include "hiredis.h"
|
#include "hiredis.h"
|
||||||
|
|
||||||
|
void redisNetClose(redisContext *c);
|
||||||
|
int redisNetRead(redisContext *c, char *buf, size_t bufcap);
|
||||||
|
int redisNetWrite(redisContext *c);
|
||||||
|
|
||||||
int redisCheckSocketError(redisContext *c);
|
int 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);
|
||||||
|
28
deps/hiredis/read.c
vendored
28
deps/hiredis/read.c
vendored
@ -31,10 +31,10 @@
|
|||||||
|
|
||||||
#include "fmacros.h"
|
#include "fmacros.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <strings.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <strings.h>
|
||||||
#endif
|
#endif
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
#include "read.h"
|
#include "read.h"
|
||||||
#include "sds.h"
|
#include "sds.h"
|
||||||
|
#include "win32.h"
|
||||||
|
|
||||||
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||||
size_t len;
|
size_t len;
|
||||||
@ -294,9 +295,9 @@ static int processLineItem(redisReader *r) {
|
|||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
|
|
||||||
if (strcasecmp(buf,",inf") == 0) {
|
if (strcasecmp(buf,",inf") == 0) {
|
||||||
d = 1.0/0.0; /* Positive infinite. */
|
d = INFINITY; /* Positive infinite. */
|
||||||
} else if (strcasecmp(buf,",-inf") == 0) {
|
} else if (strcasecmp(buf,",-inf") == 0) {
|
||||||
d = -1.0/0.0; /* Nevative infinite. */
|
d = -INFINITY; /* Nevative infinite. */
|
||||||
} else {
|
} else {
|
||||||
d = strtod((char*)buf,&eptr);
|
d = strtod((char*)buf,&eptr);
|
||||||
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
|
||||||
@ -379,10 +380,18 @@ static int processBulkItem(redisReader *r) {
|
|||||||
/* Only continue when the buffer contains the entire bulk item. */
|
/* Only continue when the buffer contains the entire bulk item. */
|
||||||
bytelen += len+2; /* include \r\n */
|
bytelen += len+2; /* include \r\n */
|
||||||
if (r->pos+bytelen <= r->len) {
|
if (r->pos+bytelen <= r->len) {
|
||||||
|
if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
|
||||||
|
(cur->type == REDIS_REPLY_VERB && s[5] != ':'))
|
||||||
|
{
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Verbatim string 4 bytes of content type are "
|
||||||
|
"missing or incorrectly encoded.");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
if (r->fn && r->fn->createString)
|
if (r->fn && r->fn->createString)
|
||||||
obj = r->fn->createString(cur,s+2,len);
|
obj = r->fn->createString(cur,s+2,len);
|
||||||
else
|
else
|
||||||
obj = (void*)REDIS_REPLY_STRING;
|
obj = (void*)(long)cur->type;
|
||||||
success = 1;
|
success = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -430,7 +439,7 @@ static int processAggregateItem(redisReader *r) {
|
|||||||
|
|
||||||
root = (r->ridx == 0);
|
root = (r->ridx == 0);
|
||||||
|
|
||||||
if (elements < -1 || elements > INT_MAX) {
|
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) {
|
||||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
"Multi-bulk length out of range");
|
"Multi-bulk length out of range");
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -523,6 +532,9 @@ static int processItem(redisReader *r) {
|
|||||||
case '#':
|
case '#':
|
||||||
cur->type = REDIS_REPLY_BOOL;
|
cur->type = REDIS_REPLY_BOOL;
|
||||||
break;
|
break;
|
||||||
|
case '=':
|
||||||
|
cur->type = REDIS_REPLY_VERB;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
__redisReaderSetErrorProtocolByte(r,*p);
|
__redisReaderSetErrorProtocolByte(r,*p);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -543,6 +555,7 @@ static int processItem(redisReader *r) {
|
|||||||
case REDIS_REPLY_BOOL:
|
case REDIS_REPLY_BOOL:
|
||||||
return processLineItem(r);
|
return processLineItem(r);
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
return processBulkItem(r);
|
return processBulkItem(r);
|
||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
case REDIS_REPLY_MAP:
|
case REDIS_REPLY_MAP:
|
||||||
@ -657,8 +670,11 @@ int redisReaderGetReply(redisReader *r, void **reply) {
|
|||||||
|
|
||||||
/* Emit a reply when there is one. */
|
/* Emit a reply when there is one. */
|
||||||
if (r->ridx == -1) {
|
if (r->ridx == -1) {
|
||||||
if (reply != NULL)
|
if (reply != NULL) {
|
||||||
*reply = r->reply;
|
*reply = r->reply;
|
||||||
|
} else if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||||
|
r->fn->freeObject(r->reply);
|
||||||
|
}
|
||||||
r->reply = NULL;
|
r->reply = NULL;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
|
5
deps/hiredis/read.h
vendored
5
deps/hiredis/read.h
vendored
@ -45,6 +45,7 @@
|
|||||||
#define REDIS_ERR_EOF 3 /* End of file */
|
#define REDIS_ERR_EOF 3 /* End of file */
|
||||||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
||||||
#define REDIS_ERR_OOM 5 /* Out of memory */
|
#define REDIS_ERR_OOM 5 /* Out of memory */
|
||||||
|
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
|
||||||
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
||||||
|
|
||||||
#define REDIS_REPLY_STRING 1
|
#define REDIS_REPLY_STRING 1
|
||||||
@ -55,12 +56,12 @@
|
|||||||
#define REDIS_REPLY_ERROR 6
|
#define REDIS_REPLY_ERROR 6
|
||||||
#define REDIS_REPLY_DOUBLE 7
|
#define REDIS_REPLY_DOUBLE 7
|
||||||
#define REDIS_REPLY_BOOL 8
|
#define REDIS_REPLY_BOOL 8
|
||||||
#define REDIS_REPLY_VERB 9
|
|
||||||
#define REDIS_REPLY_MAP 9
|
#define REDIS_REPLY_MAP 9
|
||||||
#define REDIS_REPLY_SET 10
|
#define REDIS_REPLY_SET 10
|
||||||
#define REDIS_REPLY_ATTR 11
|
#define REDIS_REPLY_ATTR 11
|
||||||
#define REDIS_REPLY_PUSH 12
|
#define REDIS_REPLY_PUSH 12
|
||||||
#define REDIS_REPLY_BIGNUM 13
|
#define REDIS_REPLY_BIGNUM 13
|
||||||
|
#define REDIS_REPLY_VERB 14
|
||||||
|
|
||||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||||
|
|
||||||
@ -79,7 +80,7 @@ typedef struct redisReadTask {
|
|||||||
|
|
||||||
typedef struct redisReplyObjectFunctions {
|
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*, size_t);
|
||||||
void *(*createInteger)(const redisReadTask*, long long);
|
void *(*createInteger)(const redisReadTask*, long long);
|
||||||
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
|
void *(*createDouble)(const redisReadTask*, double, char*, size_t);
|
||||||
void *(*createNil)(const redisReadTask*);
|
void *(*createNil)(const redisReadTask*);
|
||||||
|
2
deps/hiredis/sds.c
vendored
2
deps/hiredis/sds.c
vendored
@ -1035,7 +1035,7 @@ sds *sdssplitargs(const char *line, int *argc) {
|
|||||||
s_free(vector);
|
s_free(vector);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector = new_vector;
|
vector = new_vector;
|
||||||
vector[*argc] = current;
|
vector[*argc] = current;
|
||||||
(*argc)++;
|
(*argc)++;
|
||||||
|
31
deps/hiredis/sds.h
vendored
31
deps/hiredis/sds.h
vendored
@ -34,6 +34,9 @@
|
|||||||
#define __SDS_H
|
#define __SDS_H
|
||||||
|
|
||||||
#define SDS_MAX_PREALLOC (1024*1024)
|
#define SDS_MAX_PREALLOC (1024*1024)
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define __attribute__(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
@ -132,20 +135,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
|
|||||||
case SDS_TYPE_5:
|
case SDS_TYPE_5:
|
||||||
{
|
{
|
||||||
unsigned char *fp = ((unsigned char*)s)-1;
|
unsigned char *fp = ((unsigned char*)s)-1;
|
||||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_8:
|
case SDS_TYPE_8:
|
||||||
SDS_HDR(8,s)->len = newlen;
|
SDS_HDR(8,s)->len = (uint8_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_16:
|
case SDS_TYPE_16:
|
||||||
SDS_HDR(16,s)->len = newlen;
|
SDS_HDR(16,s)->len = (uint16_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_32:
|
case SDS_TYPE_32:
|
||||||
SDS_HDR(32,s)->len = newlen;
|
SDS_HDR(32,s)->len = (uint32_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_64:
|
case SDS_TYPE_64:
|
||||||
SDS_HDR(64,s)->len = newlen;
|
SDS_HDR(64,s)->len = (uint64_t)newlen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,21 +159,21 @@ static inline void sdsinclen(sds s, size_t inc) {
|
|||||||
case SDS_TYPE_5:
|
case SDS_TYPE_5:
|
||||||
{
|
{
|
||||||
unsigned char *fp = ((unsigned char*)s)-1;
|
unsigned char *fp = ((unsigned char*)s)-1;
|
||||||
unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
|
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
|
||||||
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_8:
|
case SDS_TYPE_8:
|
||||||
SDS_HDR(8,s)->len += inc;
|
SDS_HDR(8,s)->len += (uint8_t)inc;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_16:
|
case SDS_TYPE_16:
|
||||||
SDS_HDR(16,s)->len += inc;
|
SDS_HDR(16,s)->len += (uint16_t)inc;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_32:
|
case SDS_TYPE_32:
|
||||||
SDS_HDR(32,s)->len += inc;
|
SDS_HDR(32,s)->len += (uint32_t)inc;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_64:
|
case SDS_TYPE_64:
|
||||||
SDS_HDR(64,s)->len += inc;
|
SDS_HDR(64,s)->len += (uint64_t)inc;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -200,16 +203,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
|
|||||||
/* Nothing to do, this type has no total allocation info. */
|
/* Nothing to do, this type has no total allocation info. */
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_8:
|
case SDS_TYPE_8:
|
||||||
SDS_HDR(8,s)->alloc = newlen;
|
SDS_HDR(8,s)->alloc = (uint8_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_16:
|
case SDS_TYPE_16:
|
||||||
SDS_HDR(16,s)->alloc = newlen;
|
SDS_HDR(16,s)->alloc = (uint16_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_32:
|
case SDS_TYPE_32:
|
||||||
SDS_HDR(32,s)->alloc = newlen;
|
SDS_HDR(32,s)->alloc = (uint32_t)newlen;
|
||||||
break;
|
break;
|
||||||
case SDS_TYPE_64:
|
case SDS_TYPE_64:
|
||||||
SDS_HDR(64,s)->alloc = newlen;
|
SDS_HDR(64,s)->alloc = (uint64_t)newlen;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
248
deps/hiredis/sockcompat.c
vendored
Normal file
248
deps/hiredis/sockcompat.c
vendored
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define REDIS_SOCKCOMPAT_IMPLEMENTATION
|
||||||
|
#include "sockcompat.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static int _wsaErrorToErrno(int err) {
|
||||||
|
switch (err) {
|
||||||
|
case WSAEWOULDBLOCK:
|
||||||
|
return EWOULDBLOCK;
|
||||||
|
case WSAEINPROGRESS:
|
||||||
|
return EINPROGRESS;
|
||||||
|
case WSAEALREADY:
|
||||||
|
return EALREADY;
|
||||||
|
case WSAENOTSOCK:
|
||||||
|
return ENOTSOCK;
|
||||||
|
case WSAEDESTADDRREQ:
|
||||||
|
return EDESTADDRREQ;
|
||||||
|
case WSAEMSGSIZE:
|
||||||
|
return EMSGSIZE;
|
||||||
|
case WSAEPROTOTYPE:
|
||||||
|
return EPROTOTYPE;
|
||||||
|
case WSAENOPROTOOPT:
|
||||||
|
return ENOPROTOOPT;
|
||||||
|
case WSAEPROTONOSUPPORT:
|
||||||
|
return EPROTONOSUPPORT;
|
||||||
|
case WSAEOPNOTSUPP:
|
||||||
|
return EOPNOTSUPP;
|
||||||
|
case WSAEAFNOSUPPORT:
|
||||||
|
return EAFNOSUPPORT;
|
||||||
|
case WSAEADDRINUSE:
|
||||||
|
return EADDRINUSE;
|
||||||
|
case WSAEADDRNOTAVAIL:
|
||||||
|
return EADDRNOTAVAIL;
|
||||||
|
case WSAENETDOWN:
|
||||||
|
return ENETDOWN;
|
||||||
|
case WSAENETUNREACH:
|
||||||
|
return ENETUNREACH;
|
||||||
|
case WSAENETRESET:
|
||||||
|
return ENETRESET;
|
||||||
|
case WSAECONNABORTED:
|
||||||
|
return ECONNABORTED;
|
||||||
|
case WSAECONNRESET:
|
||||||
|
return ECONNRESET;
|
||||||
|
case WSAENOBUFS:
|
||||||
|
return ENOBUFS;
|
||||||
|
case WSAEISCONN:
|
||||||
|
return EISCONN;
|
||||||
|
case WSAENOTCONN:
|
||||||
|
return ENOTCONN;
|
||||||
|
case WSAETIMEDOUT:
|
||||||
|
return ETIMEDOUT;
|
||||||
|
case WSAECONNREFUSED:
|
||||||
|
return ECONNREFUSED;
|
||||||
|
case WSAELOOP:
|
||||||
|
return ELOOP;
|
||||||
|
case WSAENAMETOOLONG:
|
||||||
|
return ENAMETOOLONG;
|
||||||
|
case WSAEHOSTUNREACH:
|
||||||
|
return EHOSTUNREACH;
|
||||||
|
case WSAENOTEMPTY:
|
||||||
|
return ENOTEMPTY;
|
||||||
|
default:
|
||||||
|
/* We just return a generic I/O error if we could not find a relevant error. */
|
||||||
|
return EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _updateErrno(int success) {
|
||||||
|
errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _initWinsock() {
|
||||||
|
static int s_initialized = 0;
|
||||||
|
if (!s_initialized) {
|
||||||
|
static WSADATA wsadata;
|
||||||
|
int err = WSAStartup(MAKEWORD(2,2), &wsadata);
|
||||||
|
if (err != 0) {
|
||||||
|
errno = _wsaErrorToErrno(err);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
s_initialized = 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
|
||||||
|
/* Note: This function is likely to be called before other functions, so run init here. */
|
||||||
|
if (!_initWinsock()) {
|
||||||
|
return EAI_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (getaddrinfo(node, service, hints, res)) {
|
||||||
|
case 0: return 0;
|
||||||
|
case WSATRY_AGAIN: return EAI_AGAIN;
|
||||||
|
case WSAEINVAL: return EAI_BADFLAGS;
|
||||||
|
case WSAEAFNOSUPPORT: return EAI_FAMILY;
|
||||||
|
case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
|
||||||
|
case WSAHOST_NOT_FOUND: return EAI_NONAME;
|
||||||
|
case WSATYPE_NOT_FOUND: return EAI_SERVICE;
|
||||||
|
case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
|
||||||
|
default: return EAI_FAIL; /* Including WSANO_RECOVERY */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *win32_gai_strerror(int errcode) {
|
||||||
|
switch (errcode) {
|
||||||
|
case 0: errcode = 0; break;
|
||||||
|
case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
|
||||||
|
case EAI_BADFLAGS: errcode = WSAEINVAL; break;
|
||||||
|
case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
|
||||||
|
case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
|
||||||
|
case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
|
||||||
|
case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
|
||||||
|
case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
|
||||||
|
default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
|
||||||
|
}
|
||||||
|
return gai_strerror(errcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void win32_freeaddrinfo(struct addrinfo *res) {
|
||||||
|
freeaddrinfo(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKET win32_socket(int domain, int type, int protocol) {
|
||||||
|
SOCKET s;
|
||||||
|
|
||||||
|
/* Note: This function is likely to be called before other functions, so run init here. */
|
||||||
|
if (!_initWinsock()) {
|
||||||
|
return INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
|
||||||
|
int ret = ioctlsocket(fd, (long)request, argp);
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||||
|
int ret = bind(sockfd, addr, addrlen);
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
|
||||||
|
int ret = connect(sockfd, addr, addrlen);
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
|
||||||
|
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
|
||||||
|
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
|
||||||
|
* logic consistent. */
|
||||||
|
if (errno == EWOULDBLOCK) {
|
||||||
|
errno = EINPROGRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
|
||||||
|
int ret = 0;
|
||||||
|
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||||
|
if (*optlen >= sizeof (struct timeval)) {
|
||||||
|
struct timeval *tv = optval;
|
||||||
|
DWORD timeout = 0;
|
||||||
|
socklen_t dwlen = 0;
|
||||||
|
ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
|
||||||
|
tv->tv_sec = timeout / 1000;
|
||||||
|
tv->tv_usec = (timeout * 1000) % 1000000;
|
||||||
|
} else {
|
||||||
|
ret = WSAEFAULT;
|
||||||
|
}
|
||||||
|
*optlen = sizeof (struct timeval);
|
||||||
|
} else {
|
||||||
|
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
|
||||||
|
}
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
|
||||||
|
int ret = 0;
|
||||||
|
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
|
||||||
|
struct timeval *tv = optval;
|
||||||
|
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
|
||||||
|
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
|
||||||
|
} else {
|
||||||
|
ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
|
||||||
|
}
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_close(SOCKET fd) {
|
||||||
|
int ret = closesocket(fd);
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
|
||||||
|
int ret = recv(sockfd, (char*)buf, (int)len, flags);
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
|
||||||
|
int ret = send(sockfd, (const char*)buf, (int)len, flags);
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||||
|
int ret = WSAPoll(fds, nfds, timeout);
|
||||||
|
_updateErrno(ret != SOCKET_ERROR);
|
||||||
|
return ret != SOCKET_ERROR ? ret : -1;
|
||||||
|
}
|
||||||
|
#endif /* _WIN32 */
|
91
deps/hiredis/sockcompat.h
vendored
Normal file
91
deps/hiredis/sockcompat.h
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __SOCKCOMPAT_H
|
||||||
|
#define __SOCKCOMPAT_H
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
/* For POSIX systems we use the standard BSD socket API. */
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#else
|
||||||
|
/* For Windows we use winsock. */
|
||||||
|
#undef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
typedef signed long ssize_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
|
||||||
|
int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
|
||||||
|
const char *win32_gai_strerror(int errcode);
|
||||||
|
void win32_freeaddrinfo(struct addrinfo *res);
|
||||||
|
SOCKET win32_socket(int domain, int type, int protocol);
|
||||||
|
int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
|
||||||
|
int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||||
|
int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||||
|
int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
|
||||||
|
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
|
||||||
|
int win32_close(SOCKET fd);
|
||||||
|
ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
|
||||||
|
ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
|
||||||
|
typedef ULONG nfds_t;
|
||||||
|
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||||
|
|
||||||
|
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
|
||||||
|
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
|
||||||
|
#undef gai_strerror
|
||||||
|
#define gai_strerror(errcode) win32_gai_strerror(errcode)
|
||||||
|
#define freeaddrinfo(res) win32_freeaddrinfo(res)
|
||||||
|
#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
|
||||||
|
#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
|
||||||
|
#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
|
||||||
|
#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
|
||||||
|
#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
|
||||||
|
#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
|
||||||
|
#define close(fd) win32_close(fd)
|
||||||
|
#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
|
||||||
|
#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
|
||||||
|
#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
|
||||||
|
#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
#endif /* __SOCKCOMPAT_H */
|
448
deps/hiredis/ssl.c
vendored
Normal file
448
deps/hiredis/ssl.c
vendored
Normal file
@ -0,0 +1,448 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "hiredis.h"
|
||||||
|
#include "async.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
|
#include "async_private.h"
|
||||||
|
|
||||||
|
void __redisSetError(redisContext *c, int type, const char *str);
|
||||||
|
|
||||||
|
/* The SSL context is attached to SSL/TLS connections as a privdata. */
|
||||||
|
typedef struct redisSSLContext {
|
||||||
|
/**
|
||||||
|
* OpenSSL SSL_CTX; It is optional and will not be set when using
|
||||||
|
* user-supplied SSL.
|
||||||
|
*/
|
||||||
|
SSL_CTX *ssl_ctx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenSSL SSL object.
|
||||||
|
*/
|
||||||
|
SSL *ssl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSL_write() requires to be called again with the same arguments it was
|
||||||
|
* previously called with in the event of an SSL_read/SSL_write situation
|
||||||
|
*/
|
||||||
|
size_t lastLen;
|
||||||
|
|
||||||
|
/** Whether the SSL layer requires read (possibly before a write) */
|
||||||
|
int wantRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a write was requested prior to a read. If set, the write()
|
||||||
|
* should resume whenever a read takes place, if possible
|
||||||
|
*/
|
||||||
|
int pendingWrite;
|
||||||
|
} redisSSLContext;
|
||||||
|
|
||||||
|
/* Forward declaration */
|
||||||
|
redisContextFuncs redisContextSSLFuncs;
|
||||||
|
|
||||||
|
#ifdef HIREDIS_SSL_TRACE
|
||||||
|
/**
|
||||||
|
* Callback used for debugging
|
||||||
|
*/
|
||||||
|
static void sslLogCallback(const SSL *ssl, int where, int ret) {
|
||||||
|
const char *retstr = "";
|
||||||
|
int should_log = 1;
|
||||||
|
/* Ignore low-level SSL stuff */
|
||||||
|
|
||||||
|
if (where & SSL_CB_ALERT) {
|
||||||
|
should_log = 1;
|
||||||
|
}
|
||||||
|
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
|
||||||
|
should_log = 1;
|
||||||
|
}
|
||||||
|
if ((where & SSL_CB_EXIT) && ret == 0) {
|
||||||
|
should_log = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!should_log) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
retstr = SSL_alert_type_string(ret);
|
||||||
|
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
|
||||||
|
|
||||||
|
if (where == SSL_CB_HANDSHAKE_DONE) {
|
||||||
|
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenSSL global initialization and locking handling callbacks.
|
||||||
|
* Note that this is only required for OpenSSL < 1.1.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
|
#define HIREDIS_USE_CRYPTO_LOCKS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||||
|
typedef pthread_mutex_t sslLockType;
|
||||||
|
static void sslLockInit(sslLockType *l) {
|
||||||
|
pthread_mutex_init(l, NULL);
|
||||||
|
}
|
||||||
|
static void sslLockAcquire(sslLockType *l) {
|
||||||
|
pthread_mutex_lock(l);
|
||||||
|
}
|
||||||
|
static void sslLockRelease(sslLockType *l) {
|
||||||
|
pthread_mutex_unlock(l);
|
||||||
|
}
|
||||||
|
static pthread_mutex_t *ossl_locks;
|
||||||
|
|
||||||
|
static void opensslDoLock(int mode, int lkid, const char *f, int line) {
|
||||||
|
sslLockType *l = ossl_locks + lkid;
|
||||||
|
|
||||||
|
if (mode & CRYPTO_LOCK) {
|
||||||
|
sslLockAcquire(l);
|
||||||
|
} else {
|
||||||
|
sslLockRelease(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)f;
|
||||||
|
(void)line;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initOpensslLocks(void) {
|
||||||
|
unsigned ii, nlocks;
|
||||||
|
if (CRYPTO_get_locking_callback() != NULL) {
|
||||||
|
/* Someone already set the callback before us. Don't destroy it! */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nlocks = CRYPTO_num_locks();
|
||||||
|
ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
|
||||||
|
for (ii = 0; ii < nlocks; ii++) {
|
||||||
|
sslLockInit(ossl_locks + ii);
|
||||||
|
}
|
||||||
|
CRYPTO_set_locking_callback(opensslDoLock);
|
||||||
|
}
|
||||||
|
#endif /* HIREDIS_USE_CRYPTO_LOCKS */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSL Connection initialization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
|
||||||
|
if (c->privdata) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
c->privdata = calloc(1, sizeof(redisSSLContext));
|
||||||
|
|
||||||
|
c->funcs = &redisContextSSLFuncs;
|
||||||
|
redisSSLContext *rssl = c->privdata;
|
||||||
|
|
||||||
|
rssl->ssl_ctx = ssl_ctx;
|
||||||
|
rssl->ssl = ssl;
|
||||||
|
|
||||||
|
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||||
|
SSL_set_fd(rssl->ssl, c->fd);
|
||||||
|
SSL_set_connect_state(rssl->ssl);
|
||||||
|
|
||||||
|
ERR_clear_error();
|
||||||
|
int rv = SSL_connect(rssl->ssl);
|
||||||
|
if (rv == 1) {
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = SSL_get_error(rssl->ssl, rv);
|
||||||
|
if (((c->flags & REDIS_BLOCK) == 0) &&
|
||||||
|
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
|
||||||
|
return REDIS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c->err == 0) {
|
||||||
|
char err[512];
|
||||||
|
if (rv == SSL_ERROR_SYSCALL)
|
||||||
|
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
|
||||||
|
else {
|
||||||
|
unsigned long e = ERR_peek_last_error();
|
||||||
|
snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
|
||||||
|
ERR_reason_error_string(e));
|
||||||
|
}
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, err);
|
||||||
|
}
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisInitiateSSL(redisContext *c, SSL *ssl) {
|
||||||
|
return redisSSLConnect(c, NULL, ssl);
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisSecureConnection(redisContext *c, const char *capath,
|
||||||
|
const char *certpath, const char *keypath, const char *servername) {
|
||||||
|
|
||||||
|
SSL_CTX *ssl_ctx = NULL;
|
||||||
|
SSL *ssl = NULL;
|
||||||
|
|
||||||
|
/* Initialize global OpenSSL stuff */
|
||||||
|
static int isInit = 0;
|
||||||
|
if (!isInit) {
|
||||||
|
isInit = 1;
|
||||||
|
SSL_library_init();
|
||||||
|
#ifdef HIREDIS_USE_CRYPTO_LOCKS
|
||||||
|
initOpensslLocks();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HIREDIS_SSL_TRACE
|
||||||
|
SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
|
||||||
|
#endif
|
||||||
|
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||||
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||||
|
if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capath) {
|
||||||
|
if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (certpath) {
|
||||||
|
if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ssl = SSL_new(ssl_ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
if (servername) {
|
||||||
|
if (!SSL_set_tlsext_host_name(ssl, servername)) {
|
||||||
|
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return redisSSLConnect(c, ssl_ctx, ssl);
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (ssl) SSL_free(ssl);
|
||||||
|
if (ssl_ctx) SSL_CTX_free(ssl_ctx);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int maybeCheckWant(redisSSLContext *rssl, int rv) {
|
||||||
|
/**
|
||||||
|
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
|
||||||
|
* and true is returned. False is returned otherwise
|
||||||
|
*/
|
||||||
|
if (rv == SSL_ERROR_WANT_READ) {
|
||||||
|
rssl->wantRead = 1;
|
||||||
|
return 1;
|
||||||
|
} else if (rv == SSL_ERROR_WANT_WRITE) {
|
||||||
|
rssl->pendingWrite = 1;
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of redisContextFuncs for SSL connections.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void redisSSLFreeContext(void *privdata){
|
||||||
|
redisSSLContext *rsc = privdata;
|
||||||
|
|
||||||
|
if (!rsc) return;
|
||||||
|
if (rsc->ssl) {
|
||||||
|
SSL_free(rsc->ssl);
|
||||||
|
rsc->ssl = NULL;
|
||||||
|
}
|
||||||
|
if (rsc->ssl_ctx) {
|
||||||
|
SSL_CTX_free(rsc->ssl_ctx);
|
||||||
|
rsc->ssl_ctx = NULL;
|
||||||
|
}
|
||||||
|
free(rsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
|
||||||
|
redisSSLContext *rssl = c->privdata;
|
||||||
|
|
||||||
|
int nread = SSL_read(rssl->ssl, buf, bufcap);
|
||||||
|
if (nread > 0) {
|
||||||
|
return nread;
|
||||||
|
} else if (nread == 0) {
|
||||||
|
__redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
int err = SSL_get_error(rssl->ssl, nread);
|
||||||
|
if (c->flags & REDIS_BLOCK) {
|
||||||
|
/**
|
||||||
|
* In blocking mode, we should never end up in a situation where
|
||||||
|
* we get an error without it being an actual error, except
|
||||||
|
* in the case of EINTR, which can be spuriously received from
|
||||||
|
* debuggers or whatever.
|
||||||
|
*/
|
||||||
|
if (errno == EINTR) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
const char *msg = NULL;
|
||||||
|
if (errno == EAGAIN) {
|
||||||
|
msg = "Resource temporarily unavailable";
|
||||||
|
}
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, msg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We can very well get an EWOULDBLOCK/EAGAIN, however
|
||||||
|
*/
|
||||||
|
if (maybeCheckWant(rssl, err)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redisSSLWrite(redisContext *c) {
|
||||||
|
redisSSLContext *rssl = c->privdata;
|
||||||
|
|
||||||
|
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
|
||||||
|
int rv = SSL_write(rssl->ssl, c->obuf, len);
|
||||||
|
|
||||||
|
if (rv > 0) {
|
||||||
|
rssl->lastLen = 0;
|
||||||
|
} else if (rv < 0) {
|
||||||
|
rssl->lastLen = len;
|
||||||
|
|
||||||
|
int err = SSL_get_error(rssl->ssl, rv);
|
||||||
|
if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisSSLAsyncRead(redisAsyncContext *ac) {
|
||||||
|
int rv;
|
||||||
|
redisSSLContext *rssl = ac->c.privdata;
|
||||||
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
|
rssl->wantRead = 0;
|
||||||
|
|
||||||
|
if (rssl->pendingWrite) {
|
||||||
|
int done;
|
||||||
|
|
||||||
|
/* This is probably just a write event */
|
||||||
|
rssl->pendingWrite = 0;
|
||||||
|
rv = redisBufferWrite(c, &done);
|
||||||
|
if (rv == REDIS_ERR) {
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
return;
|
||||||
|
} else if (!done) {
|
||||||
|
_EL_ADD_WRITE(ac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = redisBufferRead(c);
|
||||||
|
if (rv == REDIS_ERR) {
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
} else {
|
||||||
|
_EL_ADD_READ(ac);
|
||||||
|
redisProcessCallbacks(ac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||||
|
int rv, done = 0;
|
||||||
|
redisSSLContext *rssl = ac->c.privdata;
|
||||||
|
redisContext *c = &ac->c;
|
||||||
|
|
||||||
|
rssl->pendingWrite = 0;
|
||||||
|
rv = redisBufferWrite(c, &done);
|
||||||
|
if (rv == REDIS_ERR) {
|
||||||
|
__redisAsyncDisconnect(ac);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
if (rssl->wantRead) {
|
||||||
|
/* Need to read-before-write */
|
||||||
|
rssl->pendingWrite = 1;
|
||||||
|
_EL_DEL_WRITE(ac);
|
||||||
|
} else {
|
||||||
|
/* No extra reads needed, just need to write more */
|
||||||
|
_EL_ADD_WRITE(ac);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Already done! */
|
||||||
|
_EL_DEL_WRITE(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Always reschedule a read */
|
||||||
|
_EL_ADD_READ(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
redisContextFuncs redisContextSSLFuncs = {
|
||||||
|
.free_privdata = redisSSLFreeContext,
|
||||||
|
.async_read = redisSSLAsyncRead,
|
||||||
|
.async_write = redisSSLAsyncWrite,
|
||||||
|
.read = redisSSLRead,
|
||||||
|
.write = redisSSLWrite
|
||||||
|
};
|
||||||
|
|
93
deps/hiredis/test.c
vendored
93
deps/hiredis/test.c
vendored
@ -13,12 +13,16 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#include "hiredis.h"
|
#include "hiredis.h"
|
||||||
|
#ifdef HIREDIS_TEST_SSL
|
||||||
|
#include "hiredis_ssl.h"
|
||||||
|
#endif
|
||||||
#include "net.h"
|
#include "net.h"
|
||||||
|
|
||||||
enum connection_type {
|
enum connection_type {
|
||||||
CONN_TCP,
|
CONN_TCP,
|
||||||
CONN_UNIX,
|
CONN_UNIX,
|
||||||
CONN_FD
|
CONN_FD,
|
||||||
|
CONN_SSL
|
||||||
};
|
};
|
||||||
|
|
||||||
struct config {
|
struct config {
|
||||||
@ -33,6 +37,14 @@ struct config {
|
|||||||
struct {
|
struct {
|
||||||
const char *path;
|
const char *path;
|
||||||
} unix_sock;
|
} unix_sock;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
const char *host;
|
||||||
|
int port;
|
||||||
|
const char *ca_cert;
|
||||||
|
const char *cert;
|
||||||
|
const char *key;
|
||||||
|
} ssl;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The following lines make up our testing "framework" :) */
|
/* The following lines make up our testing "framework" :) */
|
||||||
@ -93,11 +105,27 @@ static int disconnect(redisContext *c, int keep_fd) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void do_ssl_handshake(redisContext *c, struct config config) {
|
||||||
|
#ifdef HIREDIS_TEST_SSL
|
||||||
|
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
|
||||||
|
if (c->err) {
|
||||||
|
printf("SSL error: %s\n", c->errstr);
|
||||||
|
redisFree(c);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void) c;
|
||||||
|
(void) config;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
static redisContext *do_connect(struct config config) {
|
static redisContext *do_connect(struct config config) {
|
||||||
redisContext *c = NULL;
|
redisContext *c = NULL;
|
||||||
|
|
||||||
if (config.type == CONN_TCP) {
|
if (config.type == CONN_TCP) {
|
||||||
c = redisConnect(config.tcp.host, config.tcp.port);
|
c = redisConnect(config.tcp.host, config.tcp.port);
|
||||||
|
} else if (config.type == CONN_SSL) {
|
||||||
|
c = redisConnect(config.ssl.host, config.ssl.port);
|
||||||
} else if (config.type == CONN_UNIX) {
|
} else if (config.type == CONN_UNIX) {
|
||||||
c = redisConnectUnix(config.unix_sock.path);
|
c = redisConnectUnix(config.unix_sock.path);
|
||||||
} else if (config.type == CONN_FD) {
|
} else if (config.type == CONN_FD) {
|
||||||
@ -121,9 +149,21 @@ static redisContext *do_connect(struct config config) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.type == CONN_SSL) {
|
||||||
|
do_ssl_handshake(c, config);
|
||||||
|
}
|
||||||
|
|
||||||
return select_database(c);
|
return select_database(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void do_reconnect(redisContext *c, struct config config) {
|
||||||
|
redisReconnect(c);
|
||||||
|
|
||||||
|
if (config.type == CONN_SSL) {
|
||||||
|
do_ssl_handshake(c, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void test_format_commands(void) {
|
static void test_format_commands(void) {
|
||||||
char *cmd;
|
char *cmd;
|
||||||
int len;
|
int len;
|
||||||
@ -360,7 +400,8 @@ static void test_reply_reader(void) {
|
|||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
redisReaderFree(reader);
|
redisReaderFree(reader);
|
||||||
|
|
||||||
test("Set error when array > INT_MAX: ");
|
#if LLONG_MAX > SIZE_MAX
|
||||||
|
test("Set error when array > SIZE_MAX: ");
|
||||||
reader = redisReaderCreate();
|
reader = redisReaderCreate();
|
||||||
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
|
redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
|
||||||
ret = redisReaderGetReply(reader,&reply);
|
ret = redisReaderGetReply(reader,&reply);
|
||||||
@ -369,7 +410,6 @@ static void test_reply_reader(void) {
|
|||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
redisReaderFree(reader);
|
redisReaderFree(reader);
|
||||||
|
|
||||||
#if LLONG_MAX > SIZE_MAX
|
|
||||||
test("Set error when bulk > SIZE_MAX: ");
|
test("Set error when bulk > SIZE_MAX: ");
|
||||||
reader = redisReaderCreate();
|
reader = redisReaderCreate();
|
||||||
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
|
redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
|
||||||
@ -434,22 +474,23 @@ static void test_free_null(void) {
|
|||||||
test_cond(reply == NULL);
|
test_cond(reply == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
|
||||||
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};
|
struct addrinfo hints = {.ai_family = AF_INET};
|
||||||
struct addrinfo *ai_tmp = NULL;
|
struct addrinfo *ai_tmp = NULL;
|
||||||
const char *bad_domain = "idontexist.com";
|
|
||||||
|
|
||||||
int rv = getaddrinfo(bad_domain, "6379", &hints, &ai_tmp);
|
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
|
||||||
if (rv != 0) {
|
if (rv != 0) {
|
||||||
// Address does *not* exist
|
// Address does *not* exist
|
||||||
test("Returns error when host cannot be resolved: ");
|
test("Returns error when host cannot be resolved: ");
|
||||||
// First see if this domain name *actually* resolves to NXDOMAIN
|
// First see if this domain name *actually* resolves to NXDOMAIN
|
||||||
c = redisConnect("dontexist.com", 6379);
|
c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
|
||||||
test_cond(
|
test_cond(
|
||||||
c->err == REDIS_ERR_OTHER &&
|
c->err == REDIS_ERR_OTHER &&
|
||||||
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
(strcmp(c->errstr, "Name or service not known") == 0 ||
|
||||||
strcmp(c->errstr, "Can't resolve: sadkfjaskfjsa.com") == 0 ||
|
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
||||||
|
strcmp(c->errstr, "Name does not resolve") == 0 ||
|
||||||
strcmp(c->errstr,
|
strcmp(c->errstr,
|
||||||
"nodename nor servname provided, or not known") == 0 ||
|
"nodename nor servname provided, or not known") == 0 ||
|
||||||
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
strcmp(c->errstr, "No address associated with hostname") == 0 ||
|
||||||
@ -574,7 +615,8 @@ static void test_blocking_connection_timeouts(struct config config) {
|
|||||||
|
|
||||||
c = do_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));
|
redisAppendFormattedCommand(c, cmd, strlen(cmd));
|
||||||
|
s = c->funcs->write(c);
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = 0;
|
||||||
tv.tv_usec = 10000;
|
tv.tv_usec = 10000;
|
||||||
redisSetTimeout(c, tv);
|
redisSetTimeout(c, tv);
|
||||||
@ -583,7 +625,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
|||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
|
|
||||||
test("Reconnect properly reconnects after a timeout: ");
|
test("Reconnect properly reconnects after a timeout: ");
|
||||||
redisReconnect(c);
|
do_reconnect(c, config);
|
||||||
reply = redisCommand(c, "PING");
|
reply = redisCommand(c, "PING");
|
||||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
@ -591,7 +633,7 @@ static void test_blocking_connection_timeouts(struct config config) {
|
|||||||
test("Reconnect properly uses owned parameters: ");
|
test("Reconnect properly uses owned parameters: ");
|
||||||
config.tcp.host = "foo";
|
config.tcp.host = "foo";
|
||||||
config.unix_sock.path = "foo";
|
config.unix_sock.path = "foo";
|
||||||
redisReconnect(c);
|
do_reconnect(c, config);
|
||||||
reply = redisCommand(c, "PING");
|
reply = redisCommand(c, "PING");
|
||||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
@ -894,6 +936,23 @@ int main(int argc, char **argv) {
|
|||||||
throughput = 0;
|
throughput = 0;
|
||||||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
||||||
test_inherit_fd = 0;
|
test_inherit_fd = 0;
|
||||||
|
#ifdef HIREDIS_TEST_SSL
|
||||||
|
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
|
||||||
|
argv++; argc--;
|
||||||
|
cfg.ssl.port = atoi(argv[0]);
|
||||||
|
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
|
||||||
|
argv++; argc--;
|
||||||
|
cfg.ssl.host = argv[0];
|
||||||
|
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
|
||||||
|
argv++; argc--;
|
||||||
|
cfg.ssl.ca_cert = argv[0];
|
||||||
|
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
|
||||||
|
argv++; argc--;
|
||||||
|
cfg.ssl.cert = argv[0];
|
||||||
|
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
|
||||||
|
argv++; argc--;
|
||||||
|
cfg.ssl.key = argv[0];
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
|
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
|
||||||
exit(1);
|
exit(1);
|
||||||
@ -922,6 +981,20 @@ int main(int argc, char **argv) {
|
|||||||
test_blocking_io_errors(cfg);
|
test_blocking_io_errors(cfg);
|
||||||
if (throughput) test_throughput(cfg);
|
if (throughput) test_throughput(cfg);
|
||||||
|
|
||||||
|
#ifdef HIREDIS_TEST_SSL
|
||||||
|
if (cfg.ssl.port && cfg.ssl.host) {
|
||||||
|
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
|
||||||
|
cfg.type = CONN_SSL;
|
||||||
|
|
||||||
|
test_blocking_connection(cfg);
|
||||||
|
test_blocking_connection_timeouts(cfg);
|
||||||
|
test_blocking_io_errors(cfg);
|
||||||
|
test_invalid_timeout_errors(cfg);
|
||||||
|
test_append_formatted_commands(cfg);
|
||||||
|
if (throughput) test_throughput(cfg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (test_inherit_fd) {
|
if (test_inherit_fd) {
|
||||||
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
||||||
cfg.type = CONN_FD;
|
cfg.type = CONN_FD;
|
||||||
|
70
deps/hiredis/test.sh
vendored
Executable file
70
deps/hiredis/test.sh
vendored
Executable file
@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/sh -ue
|
||||||
|
|
||||||
|
REDIS_SERVER=${REDIS_SERVER:-redis-server}
|
||||||
|
REDIS_PORT=${REDIS_PORT:-56379}
|
||||||
|
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
||||||
|
TEST_SSL=${TEST_SSL:-0}
|
||||||
|
SSL_TEST_ARGS=
|
||||||
|
|
||||||
|
tmpdir=$(mktemp -d)
|
||||||
|
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
||||||
|
SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
|
||||||
|
|
||||||
|
if [ "$TEST_SSL" = "1" ]; then
|
||||||
|
SSL_CA_CERT=${tmpdir}/ca.crt
|
||||||
|
SSL_CA_KEY=${tmpdir}/ca.key
|
||||||
|
SSL_CERT=${tmpdir}/redis.crt
|
||||||
|
SSL_KEY=${tmpdir}/redis.key
|
||||||
|
|
||||||
|
openssl genrsa -out ${tmpdir}/ca.key 4096
|
||||||
|
openssl req \
|
||||||
|
-x509 -new -nodes -sha256 \
|
||||||
|
-key ${SSL_CA_KEY} \
|
||||||
|
-days 3650 \
|
||||||
|
-subj '/CN=Hiredis Test CA' \
|
||||||
|
-out ${SSL_CA_CERT}
|
||||||
|
openssl genrsa -out ${SSL_KEY} 2048
|
||||||
|
openssl req \
|
||||||
|
-new -sha256 \
|
||||||
|
-key ${SSL_KEY} \
|
||||||
|
-subj '/CN=Hiredis Test Cert' | \
|
||||||
|
openssl x509 \
|
||||||
|
-req -sha256 \
|
||||||
|
-CA ${SSL_CA_CERT} \
|
||||||
|
-CAkey ${SSL_CA_KEY} \
|
||||||
|
-CAserial ${tmpdir}/ca.txt \
|
||||||
|
-CAcreateserial \
|
||||||
|
-days 365 \
|
||||||
|
-out ${SSL_CERT}
|
||||||
|
|
||||||
|
SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
set +e
|
||||||
|
kill $(cat ${PID_FILE})
|
||||||
|
rm -rf ${tmpdir}
|
||||||
|
}
|
||||||
|
trap cleanup INT TERM EXIT
|
||||||
|
|
||||||
|
cat > ${tmpdir}/redis.conf <<EOF
|
||||||
|
daemonize yes
|
||||||
|
pidfile ${PID_FILE}
|
||||||
|
port ${REDIS_PORT}
|
||||||
|
bind 127.0.0.1
|
||||||
|
unixsocket ${SOCK_FILE}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$TEST_SSL" = "1" ]; then
|
||||||
|
cat >> ${tmpdir}/redis.conf <<EOF
|
||||||
|
tls-port ${REDIS_SSL_PORT}
|
||||||
|
tls-ca-cert-file ${SSL_CA_CERT}
|
||||||
|
tls-cert-file ${SSL_CERT}
|
||||||
|
tls-key-file ${SSL_KEY}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat ${tmpdir}/redis.conf
|
||||||
|
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||||
|
|
||||||
|
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS}
|
18
deps/hiredis/win32.h
vendored
18
deps/hiredis/win32.h
vendored
@ -2,10 +2,20 @@
|
|||||||
#define _WIN32_HELPER_INCLUDE
|
#define _WIN32_HELPER_INCLUDE
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
|
||||||
|
#include <winsock2.h> /* for struct timeval */
|
||||||
|
|
||||||
#ifndef inline
|
#ifndef inline
|
||||||
#define inline __inline
|
#define inline __inline
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef strcasecmp
|
||||||
|
#define strcasecmp stricmp
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef strncasecmp
|
||||||
|
#define strncasecmp strnicmp
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef va_copy
|
#ifndef va_copy
|
||||||
#define va_copy(d,s) ((d) = (s))
|
#define va_copy(d,s) ((d) = (s))
|
||||||
#endif
|
#endif
|
||||||
@ -37,6 +47,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif /* _MSC_VER */
|
||||||
|
|
||||||
#endif
|
#ifdef _WIN32
|
||||||
#endif
|
#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
#endif /* _WIN32_HELPER_INCLUDE */
|
||||||
|
8
deps/jemalloc/src/background_thread.c
vendored
8
deps/jemalloc/src/background_thread.c
vendored
@ -787,7 +787,13 @@ background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) {
|
|||||||
nstime_init(&stats->run_interval, 0);
|
nstime_init(&stats->run_interval, 0);
|
||||||
for (unsigned i = 0; i < max_background_threads; i++) {
|
for (unsigned i = 0; i < max_background_threads; i++) {
|
||||||
background_thread_info_t *info = &background_thread_info[i];
|
background_thread_info_t *info = &background_thread_info[i];
|
||||||
malloc_mutex_lock(tsdn, &info->mtx);
|
if (malloc_mutex_trylock(tsdn, &info->mtx)) {
|
||||||
|
/*
|
||||||
|
* Each background thread run may take a long time;
|
||||||
|
* avoid waiting on the stats if the thread is active.
|
||||||
|
*/
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (info->state != background_thread_stopped) {
|
if (info->state != background_thread_stopped) {
|
||||||
num_runs += info->tot_n_runs;
|
num_runs += info->tot_n_runs;
|
||||||
nstime_add(&stats->run_interval, &info->tot_sleep_time);
|
nstime_add(&stats->run_interval, &info->tot_sleep_time);
|
||||||
|
75
redis.conf
75
redis.conf
@ -129,6 +129,76 @@ timeout 0
|
|||||||
# Redis default starting with Redis 3.2.1.
|
# Redis default starting with Redis 3.2.1.
|
||||||
tcp-keepalive 300
|
tcp-keepalive 300
|
||||||
|
|
||||||
|
################################# TLS/SSL #####################################
|
||||||
|
|
||||||
|
# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration
|
||||||
|
# directive can be used to define TLS-listening ports. To enable TLS on the
|
||||||
|
# default port, use:
|
||||||
|
#
|
||||||
|
# port 0
|
||||||
|
# tls-port 6379
|
||||||
|
|
||||||
|
# Configure a X.509 certificate and private key to use for authenticating the
|
||||||
|
# server to connected clients, masters or cluster peers. These files should be
|
||||||
|
# PEM formatted.
|
||||||
|
#
|
||||||
|
# tls-cert-file redis.crt tls-key-file redis.key
|
||||||
|
|
||||||
|
# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
|
||||||
|
#
|
||||||
|
# tls-dh-params-file redis.dh
|
||||||
|
|
||||||
|
# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL
|
||||||
|
# clients and peers. Redis requires an explicit configuration of at least one
|
||||||
|
# of these, and will not implicitly use the system wide configuration.
|
||||||
|
#
|
||||||
|
# tls-ca-cert-file ca.crt
|
||||||
|
# tls-ca-cert-dir /etc/ssl/certs
|
||||||
|
|
||||||
|
# If TLS/SSL clients are required to authenticate using a client side
|
||||||
|
# certificate, use this directive.
|
||||||
|
#
|
||||||
|
# Note: this applies to all incoming clients, including replicas.
|
||||||
|
#
|
||||||
|
# tls-auth-clients yes
|
||||||
|
|
||||||
|
# If TLS/SSL should be used when connecting as a replica to a master, enable
|
||||||
|
# this configuration directive:
|
||||||
|
#
|
||||||
|
# tls-replication yes
|
||||||
|
|
||||||
|
# If TLS/SSL should be used for the Redis Cluster bus, enable this configuration
|
||||||
|
# directive.
|
||||||
|
#
|
||||||
|
# NOTE: If TLS/SSL is enabled for Cluster Bus, mutual authentication is always
|
||||||
|
# enforced.
|
||||||
|
#
|
||||||
|
# tls-cluster yes
|
||||||
|
|
||||||
|
# Explicitly specify TLS versions to support. Allowed values are case insensitive
|
||||||
|
# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
|
||||||
|
# "default" which is currently >= TLSv1.1.
|
||||||
|
#
|
||||||
|
# tls-protocols TLSv1.2
|
||||||
|
|
||||||
|
# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information
|
||||||
|
# about the syntax of this string.
|
||||||
|
#
|
||||||
|
# Note: this configuration applies only to <= TLSv1.2.
|
||||||
|
#
|
||||||
|
# tls-ciphers DEFAULT:!MEDIUM
|
||||||
|
|
||||||
|
# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more
|
||||||
|
# information about the syntax of this string, and specifically for TLSv1.3
|
||||||
|
# ciphersuites.
|
||||||
|
#
|
||||||
|
# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256
|
||||||
|
|
||||||
|
# When choosing a cipher, use the server's preference instead of the client
|
||||||
|
# preference. By default, the server follows the client's preference.
|
||||||
|
#
|
||||||
|
# tls-prefer-server-cipher yes
|
||||||
|
|
||||||
################################# GENERAL #####################################
|
################################# GENERAL #####################################
|
||||||
|
|
||||||
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
# By default Redis does not run as a daemon. Use 'yes' if you need it.
|
||||||
@ -1239,7 +1309,7 @@ notify-keyspace-events ""
|
|||||||
# Redis contains an implementation of the Gopher protocol, as specified in
|
# Redis contains an implementation of the Gopher protocol, as specified in
|
||||||
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
|
# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
|
||||||
#
|
#
|
||||||
# The Gopher protocol was very popular in the late '90s. It is an alternative
|
# The Gopher protocol was very popular in the late '90s. It is an alternative
|
||||||
# to the web, and the implementation both server and client side is so simple
|
# to the web, and the implementation both server and client side is so simple
|
||||||
# that the Redis server has just 100 lines of code in order to implement this
|
# that the Redis server has just 100 lines of code in order to implement this
|
||||||
# support.
|
# support.
|
||||||
@ -1277,7 +1347,7 @@ notify-keyspace-events ""
|
|||||||
# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
|
# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
|
||||||
# Once a password is set:
|
# Once a password is set:
|
||||||
#
|
#
|
||||||
# 1. The Gopher server (when enabled, not by default) will kill serve
|
# 1. The Gopher server (when enabled, not by default) will still serve
|
||||||
# content via Gopher.
|
# content via Gopher.
|
||||||
# 2. However other commands cannot be called before the client will
|
# 2. However other commands cannot be called before the client will
|
||||||
# authenticate.
|
# authenticate.
|
||||||
@ -1599,4 +1669,3 @@ rdb-save-incremental-fsync yes
|
|||||||
# Maximum number of set/hash/zset/list fields that will be processed from
|
# Maximum number of set/hash/zset/list fields that will be processed from
|
||||||
# the main dictionary scan
|
# the main dictionary scan
|
||||||
# active-defrag-max-scan-fields 1000
|
# active-defrag-max-scan-fields 1000
|
||||||
|
|
||||||
|
@ -13,4 +13,13 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
make -C tests/modules && \
|
make -C tests/modules && \
|
||||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
$TCLSH tests/test_helper.tcl \
|
||||||
|
--single unit/moduleapi/commandfilter \
|
||||||
|
--single unit/moduleapi/fork \
|
||||||
|
--single unit/moduleapi/testrdb \
|
||||||
|
--single unit/moduleapi/infotest \
|
||||||
|
--single unit/moduleapi/propagate \
|
||||||
|
--single unit/moduleapi/hooks \
|
||||||
|
--single unit/moduleapi/misc \
|
||||||
|
--single unit/moduleapi/blockonkeys \
|
||||||
|
"${@}"
|
||||||
|
10
src/Makefile
10
src/Makefile
@ -93,6 +93,8 @@ else
|
|||||||
ifeq ($(uname_S),Darwin)
|
ifeq ($(uname_S),Darwin)
|
||||||
# Darwin
|
# Darwin
|
||||||
FINAL_LIBS+= -ldl
|
FINAL_LIBS+= -ldl
|
||||||
|
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
|
||||||
|
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
|
||||||
else
|
else
|
||||||
ifeq ($(uname_S),AIX)
|
ifeq ($(uname_S),AIX)
|
||||||
# AIX
|
# AIX
|
||||||
@ -145,6 +147,12 @@ ifeq ($(MALLOC),jemalloc)
|
|||||||
FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
|
FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BUILD_TLS),yes)
|
||||||
|
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
|
||||||
|
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
|
||||||
|
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
|
||||||
|
endif
|
||||||
|
|
||||||
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
|
||||||
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
|
REDIS_LD=$(QUIET_LINK)$(CC) $(FINAL_LDFLAGS)
|
||||||
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
|
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
|
||||||
@ -164,7 +172,7 @@ endif
|
|||||||
|
|
||||||
REDIS_SERVER_NAME=redis-server
|
REDIS_SERVER_NAME=redis-server
|
||||||
REDIS_SENTINEL_NAME=redis-sentinel
|
REDIS_SENTINEL_NAME=redis-sentinel
|
||||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o gopher.o tracking.o sha256.o
|
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o
|
||||||
REDIS_CLI_NAME=redis-cli
|
REDIS_CLI_NAME=redis-cli
|
||||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||||
|
59
src/acl.c
59
src/acl.c
@ -94,6 +94,9 @@ void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
|||||||
void ACLResetSubcommands(user *u);
|
void ACLResetSubcommands(user *u);
|
||||||
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
||||||
|
|
||||||
|
/* The length of the string representation of a hashed password. */
|
||||||
|
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
* Helper functions for the rest of the ACL implementation
|
* Helper functions for the rest of the ACL implementation
|
||||||
* ==========================================================================*/
|
* ==========================================================================*/
|
||||||
@ -145,7 +148,7 @@ int time_independent_strcmp(char *a, char *b) {
|
|||||||
sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||||
SHA256_CTX ctx;
|
SHA256_CTX ctx;
|
||||||
unsigned char hash[SHA256_BLOCK_SIZE];
|
unsigned char hash[SHA256_BLOCK_SIZE];
|
||||||
char hex[SHA256_BLOCK_SIZE*2];
|
char hex[HASH_PASSWORD_LEN];
|
||||||
char *cset = "0123456789abcdef";
|
char *cset = "0123456789abcdef";
|
||||||
|
|
||||||
sha256_init(&ctx);
|
sha256_init(&ctx);
|
||||||
@ -156,7 +159,7 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
|||||||
hex[j*2] = cset[((hash[j]&0xF0)>>4)];
|
hex[j*2] = cset[((hash[j]&0xF0)>>4)];
|
||||||
hex[j*2+1] = cset[(hash[j]&0xF)];
|
hex[j*2+1] = cset[(hash[j]&0xF)];
|
||||||
}
|
}
|
||||||
return sdsnewlen(hex,SHA256_BLOCK_SIZE*2);
|
return sdsnewlen(hex,HASH_PASSWORD_LEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
@ -522,7 +525,7 @@ sds ACLDescribeUser(user *u) {
|
|||||||
listRewind(u->passwords,&li);
|
listRewind(u->passwords,&li);
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
sds thispass = listNodeValue(ln);
|
sds thispass = listNodeValue(ln);
|
||||||
res = sdscatlen(res,">",1);
|
res = sdscatlen(res,"#",1);
|
||||||
res = sdscatsds(res,thispass);
|
res = sdscatsds(res,thispass);
|
||||||
res = sdscatlen(res," ",1);
|
res = sdscatlen(res," ",1);
|
||||||
}
|
}
|
||||||
@ -649,7 +652,14 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
|||||||
* ><password> Add this password to the list of valid password for the user.
|
* ><password> Add this password to the list of valid password for the user.
|
||||||
* For example >mypass will add "mypass" to the list.
|
* For example >mypass will add "mypass" to the list.
|
||||||
* This directive clears the "nopass" flag (see later).
|
* This directive clears the "nopass" flag (see later).
|
||||||
|
* #<hash> Add this password hash to the list of valid hashes for
|
||||||
|
* the user. This is useful if you have previously computed
|
||||||
|
* the hash, and don't want to store it in plaintext.
|
||||||
|
* This directive clears the "nopass" flag (see later).
|
||||||
* <<password> Remove this password from the list of valid passwords.
|
* <<password> Remove this password from the list of valid passwords.
|
||||||
|
* !<hash> Remove this hashed password from the list of valid passwords.
|
||||||
|
* This is useful when you want to remove a password just by
|
||||||
|
* hash without knowing its plaintext version at all.
|
||||||
* nopass All the set passwords of the user are removed, and the user
|
* nopass All the set passwords of the user are removed, and the user
|
||||||
* is flagged as requiring no password: it means that every
|
* is flagged as requiring no password: it means that every
|
||||||
* password will work against this user. If this directive is
|
* password will work against this user. If this directive is
|
||||||
@ -685,6 +695,7 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
|||||||
* EEXIST: You are adding a key pattern after "*" was already added. This is
|
* EEXIST: You are adding a key pattern after "*" was already added. This is
|
||||||
* almost surely an error on the user side.
|
* almost surely an error on the user side.
|
||||||
* ENODEV: The password you are trying to remove from the user does not exist.
|
* ENODEV: The password you are trying to remove from the user does not exist.
|
||||||
|
* EBADMSG: The hash you are trying to add is not a valid hash.
|
||||||
*/
|
*/
|
||||||
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||||
if (oplen == -1) oplen = strlen(op);
|
if (oplen == -1) oplen = strlen(op);
|
||||||
@ -720,8 +731,30 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
} else if (!strcasecmp(op,"resetpass")) {
|
} else if (!strcasecmp(op,"resetpass")) {
|
||||||
u->flags &= ~USER_FLAG_NOPASS;
|
u->flags &= ~USER_FLAG_NOPASS;
|
||||||
listEmpty(u->passwords);
|
listEmpty(u->passwords);
|
||||||
} else if (op[0] == '>') {
|
} else if (op[0] == '>' || op[0] == '#') {
|
||||||
sds newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
sds newpass;
|
||||||
|
if (op[0] == '>') {
|
||||||
|
newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||||
|
} else {
|
||||||
|
if (oplen != HASH_PASSWORD_LEN + 1) {
|
||||||
|
errno = EBADMSG;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Password hashes can only be characters that represent
|
||||||
|
* hexadecimal values, which are numbers and lowercase
|
||||||
|
* characters 'a' through 'f'.
|
||||||
|
*/
|
||||||
|
for(int i = 1; i < HASH_PASSWORD_LEN + 1; i++) {
|
||||||
|
char c = op[i];
|
||||||
|
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
|
||||||
|
errno = EBADMSG;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newpass = sdsnewlen(op+1,oplen-1);
|
||||||
|
}
|
||||||
|
|
||||||
listNode *ln = listSearchKey(u->passwords,newpass);
|
listNode *ln = listSearchKey(u->passwords,newpass);
|
||||||
/* Avoid re-adding the same password multiple times. */
|
/* Avoid re-adding the same password multiple times. */
|
||||||
if (ln == NULL)
|
if (ln == NULL)
|
||||||
@ -729,8 +762,17 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
else
|
else
|
||||||
sdsfree(newpass);
|
sdsfree(newpass);
|
||||||
u->flags &= ~USER_FLAG_NOPASS;
|
u->flags &= ~USER_FLAG_NOPASS;
|
||||||
} else if (op[0] == '<') {
|
} else if (op[0] == '<' || op[0] == '!') {
|
||||||
sds delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
sds delpass;
|
||||||
|
if (op[0] == '<') {
|
||||||
|
delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||||
|
} else {
|
||||||
|
if (oplen != HASH_PASSWORD_LEN + 1) {
|
||||||
|
errno = EBADMSG;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
delpass = sdsnewlen(op+1,oplen-1);
|
||||||
|
}
|
||||||
listNode *ln = listSearchKey(u->passwords,delpass);
|
listNode *ln = listSearchKey(u->passwords,delpass);
|
||||||
sdsfree(delpass);
|
sdsfree(delpass);
|
||||||
if (ln) {
|
if (ln) {
|
||||||
@ -848,6 +890,9 @@ char *ACLSetUserStringError(void) {
|
|||||||
else if (errno == ENODEV)
|
else if (errno == ENODEV)
|
||||||
errmsg = "The password you are trying to remove from the user does "
|
errmsg = "The password you are trying to remove from the user does "
|
||||||
"not exist";
|
"not exist";
|
||||||
|
else if (errno == EBADMSG)
|
||||||
|
errmsg = "The password hash must be exactly 64 characters and contain "
|
||||||
|
"only lowercase hexadecimal characters";
|
||||||
return errmsg;
|
return errmsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
src/ae.c
14
src/ae.c
@ -76,6 +76,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
|||||||
eventLoop->maxfd = -1;
|
eventLoop->maxfd = -1;
|
||||||
eventLoop->beforesleep = NULL;
|
eventLoop->beforesleep = NULL;
|
||||||
eventLoop->aftersleep = NULL;
|
eventLoop->aftersleep = NULL;
|
||||||
|
eventLoop->flags = 0;
|
||||||
if (aeApiCreate(eventLoop) == -1) goto err;
|
if (aeApiCreate(eventLoop) == -1) goto err;
|
||||||
/* Events with mask == AE_NONE are not set. So let's initialize the
|
/* Events with mask == AE_NONE are not set. So let's initialize the
|
||||||
* vector with it. */
|
* vector with it. */
|
||||||
@ -97,6 +98,14 @@ int aeGetSetSize(aeEventLoop *eventLoop) {
|
|||||||
return eventLoop->setsize;
|
return eventLoop->setsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tells the next iteration/s of the event processing to set timeout of 0. */
|
||||||
|
void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
|
||||||
|
if (noWait)
|
||||||
|
eventLoop->flags |= AE_DONT_WAIT;
|
||||||
|
else
|
||||||
|
eventLoop->flags &= ~AE_DONT_WAIT;
|
||||||
|
}
|
||||||
|
|
||||||
/* Resize the maximum set size of the event loop.
|
/* Resize the maximum set size of the event loop.
|
||||||
* If the requested set size is smaller than the current set size, but
|
* If the requested set size is smaller than the current set size, but
|
||||||
* there is already a file descriptor in use that is >= the requested
|
* there is already a file descriptor in use that is >= the requested
|
||||||
@ -406,6 +415,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventLoop->flags & AE_DONT_WAIT) {
|
||||||
|
tv.tv_sec = tv.tv_usec = 0;
|
||||||
|
tvp = &tv;
|
||||||
|
}
|
||||||
|
|
||||||
/* Call the multiplexing API, will return only on timeout or when
|
/* Call the multiplexing API, will return only on timeout or when
|
||||||
* some event fires. */
|
* some event fires. */
|
||||||
numevents = aeApiPoll(eventLoop, tvp);
|
numevents = aeApiPoll(eventLoop, tvp);
|
||||||
|
2
src/ae.h
2
src/ae.h
@ -106,6 +106,7 @@ typedef struct aeEventLoop {
|
|||||||
void *apidata; /* This is used for polling API specific data */
|
void *apidata; /* This is used for polling API specific data */
|
||||||
aeBeforeSleepProc *beforesleep;
|
aeBeforeSleepProc *beforesleep;
|
||||||
aeBeforeSleepProc *aftersleep;
|
aeBeforeSleepProc *aftersleep;
|
||||||
|
int flags;
|
||||||
} aeEventLoop;
|
} aeEventLoop;
|
||||||
|
|
||||||
/* Prototypes */
|
/* Prototypes */
|
||||||
@ -128,5 +129,6 @@ void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep
|
|||||||
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
|
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
|
||||||
int aeGetSetSize(aeEventLoop *eventLoop);
|
int aeGetSetSize(aeEventLoop *eventLoop);
|
||||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
|
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
|
||||||
|
void aeSetDontWait(aeEventLoop *eventLoop, int noWait);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -121,8 +121,8 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
|||||||
|
|
||||||
if (e->events & EPOLLIN) mask |= AE_READABLE;
|
if (e->events & EPOLLIN) mask |= AE_READABLE;
|
||||||
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
|
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
|
||||||
if (e->events & EPOLLERR) mask |= AE_WRITABLE;
|
if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
|
||||||
if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
|
if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
|
||||||
eventLoop->fired[j].fd = e->data.fd;
|
eventLoop->fired[j].fd = e->data.fd;
|
||||||
eventLoop->fired[j].mask = mask;
|
eventLoop->fired[j].mask = mask;
|
||||||
}
|
}
|
||||||
|
22
src/anet.c
22
src/anet.c
@ -279,8 +279,8 @@ static int anetCreateSocket(char *err, int domain) {
|
|||||||
#define ANET_CONNECT_NONE 0
|
#define ANET_CONNECT_NONE 0
|
||||||
#define ANET_CONNECT_NONBLOCK 1
|
#define ANET_CONNECT_NONBLOCK 1
|
||||||
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
|
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
|
||||||
static int anetTcpGenericConnect(char *err, char *addr, int port,
|
static int anetTcpGenericConnect(char *err, const char *addr, int port,
|
||||||
char *source_addr, int flags)
|
const char *source_addr, int flags)
|
||||||
{
|
{
|
||||||
int s = ANET_ERR, rv;
|
int s = ANET_ERR, rv;
|
||||||
char portstr[6]; /* strlen("65535") + 1; */
|
char portstr[6]; /* strlen("65535") + 1; */
|
||||||
@ -359,31 +359,31 @@ end:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetTcpConnect(char *err, char *addr, int port)
|
int anetTcpConnect(char *err, const char *addr, int port)
|
||||||
{
|
{
|
||||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
|
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetTcpNonBlockConnect(char *err, char *addr, int port)
|
int anetTcpNonBlockConnect(char *err, const char *addr, int port)
|
||||||
{
|
{
|
||||||
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port,
|
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
|
||||||
char *source_addr)
|
const char *source_addr)
|
||||||
{
|
{
|
||||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||||
ANET_CONNECT_NONBLOCK);
|
ANET_CONNECT_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port,
|
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
|
||||||
char *source_addr)
|
const char *source_addr)
|
||||||
{
|
{
|
||||||
return anetTcpGenericConnect(err,addr,port,source_addr,
|
return anetTcpGenericConnect(err,addr,port,source_addr,
|
||||||
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
|
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetUnixGenericConnect(char *err, char *path, int flags)
|
int anetUnixGenericConnect(char *err, const char *path, int flags)
|
||||||
{
|
{
|
||||||
int s;
|
int s;
|
||||||
struct sockaddr_un sa;
|
struct sockaddr_un sa;
|
||||||
@ -411,12 +411,12 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetUnixConnect(char *err, char *path)
|
int anetUnixConnect(char *err, const char *path)
|
||||||
{
|
{
|
||||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
|
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int anetUnixNonBlockConnect(char *err, char *path)
|
int anetUnixNonBlockConnect(char *err, const char *path)
|
||||||
{
|
{
|
||||||
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
|
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
12
src/anet.h
12
src/anet.h
@ -49,12 +49,12 @@
|
|||||||
#undef ip_len
|
#undef ip_len
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int anetTcpConnect(char *err, char *addr, int port);
|
int anetTcpConnect(char *err, const char *addr, int port);
|
||||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
int anetTcpNonBlockConnect(char *err, const char *addr, int port);
|
||||||
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
|
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||||
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr);
|
int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
|
||||||
int anetUnixConnect(char *err, char *path);
|
int anetUnixConnect(char *err, const char *path);
|
||||||
int anetUnixNonBlockConnect(char *err, char *path);
|
int anetUnixNonBlockConnect(char *err, const char *path);
|
||||||
int anetRead(int fd, char *buf, int count);
|
int anetRead(int fd, char *buf, int count);
|
||||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||||
|
65
src/aof.c
65
src/aof.c
@ -264,9 +264,9 @@ int startAppendOnly(void) {
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
if (server.rdb_child_pid != -1) {
|
if (hasActiveChildProcess() && server.aof_child_pid == -1) {
|
||||||
server.aof_rewrite_scheduled = 1;
|
server.aof_rewrite_scheduled = 1;
|
||||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
||||||
} else {
|
} else {
|
||||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||||
* start a new one: the old one cannot be reused because it is not
|
* start a new one: the old one cannot be reused because it is not
|
||||||
@ -385,6 +385,10 @@ void flushAppendOnlyFile(int force) {
|
|||||||
* there is much to do about the whole server stopping for power problems
|
* there is much to do about the whole server stopping for power problems
|
||||||
* or alike */
|
* or alike */
|
||||||
|
|
||||||
|
if (server.aof_flush_sleep && sdslen(server.aof_buf)) {
|
||||||
|
usleep(server.aof_flush_sleep);
|
||||||
|
}
|
||||||
|
|
||||||
latencyStartMonitor(latency);
|
latencyStartMonitor(latency);
|
||||||
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||||
latencyEndMonitor(latency);
|
latencyEndMonitor(latency);
|
||||||
@ -395,7 +399,7 @@ void flushAppendOnlyFile(int force) {
|
|||||||
* useful for graphing / monitoring purposes. */
|
* useful for graphing / monitoring purposes. */
|
||||||
if (sync_in_progress) {
|
if (sync_in_progress) {
|
||||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||||
} else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
|
} else if (hasActiveChildProcess()) {
|
||||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||||
} else {
|
} else {
|
||||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||||
@ -491,9 +495,8 @@ void flushAppendOnlyFile(int force) {
|
|||||||
try_fsync:
|
try_fsync:
|
||||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||||
* children doing I/O in the background. */
|
* children doing I/O in the background. */
|
||||||
if (server.aof_no_fsync_on_rewrite &&
|
if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
|
||||||
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
|
return;
|
||||||
return;
|
|
||||||
|
|
||||||
/* Perform the fsync if needed. */
|
/* Perform the fsync if needed. */
|
||||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||||
@ -649,11 +652,12 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
|||||||
|
|
||||||
/* In Redis commands are always executed in the context of a client, so in
|
/* In Redis commands are always executed in the context of a client, so in
|
||||||
* order to load the append only file we need to create a fake client. */
|
* order to load the append only file we need to create a fake client. */
|
||||||
struct client *createFakeClient(void) {
|
struct client *createAOFClient(void) {
|
||||||
struct client *c = zmalloc(sizeof(*c));
|
struct client *c = zmalloc(sizeof(*c));
|
||||||
|
|
||||||
selectDb(c,0);
|
selectDb(c,0);
|
||||||
c->fd = -1;
|
c->id = CLIENT_ID_AOF; /* So modules can identify it's the AOF client. */
|
||||||
|
c->conn = NULL;
|
||||||
c->name = NULL;
|
c->name = NULL;
|
||||||
c->querybuf = sdsempty();
|
c->querybuf = sdsempty();
|
||||||
c->querybuf_peak = 0;
|
c->querybuf_peak = 0;
|
||||||
@ -726,8 +730,8 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
* to the same file we're about to read. */
|
* to the same file we're about to read. */
|
||||||
server.aof_state = AOF_OFF;
|
server.aof_state = AOF_OFF;
|
||||||
|
|
||||||
fakeClient = createFakeClient();
|
fakeClient = createAOFClient();
|
||||||
startLoadingFile(fp, filename);
|
startLoadingFile(fp, filename, RDBFLAGS_AOF_PREAMBLE);
|
||||||
|
|
||||||
/* Check if this AOF file has an RDB preamble. In that case we need to
|
/* Check if this AOF file has an RDB preamble. In that case we need to
|
||||||
* load the RDB file and later continue loading the AOF tail. */
|
* load the RDB file and later continue loading the AOF tail. */
|
||||||
@ -742,7 +746,7 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
|
||||||
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
|
||||||
rioInitWithFile(&rdb,fp);
|
rioInitWithFile(&rdb,fp);
|
||||||
if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
|
if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) {
|
||||||
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
|
||||||
goto readerr;
|
goto readerr;
|
||||||
} else {
|
} else {
|
||||||
@ -763,6 +767,7 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
if (!(loops++ % 1000)) {
|
if (!(loops++ % 1000)) {
|
||||||
loadingProgress(ftello(fp));
|
loadingProgress(ftello(fp));
|
||||||
processEventsWhileBlocked();
|
processEventsWhileBlocked();
|
||||||
|
processModuleLoadingProgressEvent(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||||
@ -836,6 +841,8 @@ int loadAppendOnlyFile(char *filename) {
|
|||||||
freeFakeClientArgv(fakeClient);
|
freeFakeClientArgv(fakeClient);
|
||||||
fakeClient->cmd = NULL;
|
fakeClient->cmd = NULL;
|
||||||
if (server.aof_load_truncated) valid_up_to = ftello(fp);
|
if (server.aof_load_truncated) valid_up_to = ftello(fp);
|
||||||
|
if (server.key_load_delay)
|
||||||
|
usleep(server.key_load_delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This point can only be reached when EOF is reached without errors.
|
/* This point can only be reached when EOF is reached without errors.
|
||||||
@ -853,7 +860,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
|
|||||||
fclose(fp);
|
fclose(fp);
|
||||||
freeFakeClient(fakeClient);
|
freeFakeClient(fakeClient);
|
||||||
server.aof_state = old_aof_state;
|
server.aof_state = old_aof_state;
|
||||||
stopLoading();
|
stopLoading(1);
|
||||||
aofUpdateCurrentSize();
|
aofUpdateCurrentSize();
|
||||||
server.aof_rewrite_base_size = server.aof_current_size;
|
server.aof_rewrite_base_size = server.aof_current_size;
|
||||||
server.aof_fsync_offset = server.aof_current_size;
|
server.aof_fsync_offset = server.aof_current_size;
|
||||||
@ -1394,9 +1401,11 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
if (server.aof_rewrite_incremental_fsync)
|
if (server.aof_rewrite_incremental_fsync)
|
||||||
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
|
||||||
|
|
||||||
|
startSaving(RDBFLAGS_AOF_PREAMBLE);
|
||||||
|
|
||||||
if (server.aof_use_rdb_preamble) {
|
if (server.aof_use_rdb_preamble) {
|
||||||
int error;
|
int error;
|
||||||
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||||
errno = error;
|
errno = error;
|
||||||
goto werr;
|
goto werr;
|
||||||
}
|
}
|
||||||
@ -1459,15 +1468,18 @@ int rewriteAppendOnlyFile(char *filename) {
|
|||||||
if (rename(tmpfile,filename) == -1) {
|
if (rename(tmpfile,filename) == -1) {
|
||||||
serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
|
serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
|
||||||
|
stopSaving(1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
|
|
||||||
werr:
|
werr:
|
||||||
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1563,39 +1575,24 @@ void aofClosePipes(void) {
|
|||||||
*/
|
*/
|
||||||
int rewriteAppendOnlyFileBackground(void) {
|
int rewriteAppendOnlyFileBackground(void) {
|
||||||
pid_t childpid;
|
pid_t childpid;
|
||||||
long long start;
|
|
||||||
|
|
||||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||||
openChildInfoPipe();
|
openChildInfoPipe();
|
||||||
start = ustime();
|
if ((childpid = redisFork()) == 0) {
|
||||||
if ((childpid = fork()) == 0) {
|
|
||||||
char tmpfile[256];
|
char tmpfile[256];
|
||||||
|
|
||||||
/* Child */
|
/* Child */
|
||||||
closeListeningSockets(0);
|
|
||||||
redisSetProcTitle("redis-aof-rewrite");
|
redisSetProcTitle("redis-aof-rewrite");
|
||||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
|
||||||
|
|
||||||
if (private_dirty) {
|
|
||||||
serverLog(LL_NOTICE,
|
|
||||||
"AOF rewrite: %zu MB of memory used by copy-on-write",
|
|
||||||
private_dirty/(1024*1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
server.child_info_data.cow_size = private_dirty;
|
|
||||||
sendChildInfo(CHILD_INFO_TYPE_AOF);
|
|
||||||
exitFromChild(0);
|
exitFromChild(0);
|
||||||
} else {
|
} else {
|
||||||
exitFromChild(1);
|
exitFromChild(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
server.stat_fork_time = ustime()-start;
|
|
||||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
|
||||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
@ -1609,7 +1606,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
server.aof_rewrite_scheduled = 0;
|
server.aof_rewrite_scheduled = 0;
|
||||||
server.aof_rewrite_time_start = time(NULL);
|
server.aof_rewrite_time_start = time(NULL);
|
||||||
server.aof_child_pid = childpid;
|
server.aof_child_pid = childpid;
|
||||||
updateDictResizePolicy();
|
|
||||||
/* We set appendseldb to -1 in order to force the next call to the
|
/* We set appendseldb to -1 in order to force the next call to the
|
||||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||||
@ -1624,13 +1620,14 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
void bgrewriteaofCommand(client *c) {
|
void bgrewriteaofCommand(client *c) {
|
||||||
if (server.aof_child_pid != -1) {
|
if (server.aof_child_pid != -1) {
|
||||||
addReplyError(c,"Background append only file rewriting already in progress");
|
addReplyError(c,"Background append only file rewriting already in progress");
|
||||||
} else if (server.rdb_child_pid != -1) {
|
} else if (hasActiveChildProcess()) {
|
||||||
server.aof_rewrite_scheduled = 1;
|
server.aof_rewrite_scheduled = 1;
|
||||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||||
addReplyStatus(c,"Background append only file rewriting started");
|
addReplyStatus(c,"Background append only file rewriting started");
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.err);
|
addReplyError(c,"Can't execute an AOF background rewriting. "
|
||||||
|
"Please check the server logs for more information.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +174,7 @@ void unblockClient(client *c) {
|
|||||||
} else if (c->btype == BLOCKED_WAIT) {
|
} else if (c->btype == BLOCKED_WAIT) {
|
||||||
unblockClientWaitingReplicas(c);
|
unblockClientWaitingReplicas(c);
|
||||||
} else if (c->btype == BLOCKED_MODULE) {
|
} else if (c->btype == BLOCKED_MODULE) {
|
||||||
|
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
|
||||||
unblockClientFromModule(c);
|
unblockClientFromModule(c);
|
||||||
} else {
|
} else {
|
||||||
serverPanic("Unknown btype in unblockClient().");
|
serverPanic("Unknown btype in unblockClient().");
|
||||||
@ -430,6 +431,49 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Helper function for handleClientsBlockedOnKeys(). This function is called
|
||||||
|
* in order to check if we can serve clients blocked by modules using
|
||||||
|
* RM_BlockClientOnKeys(), when the corresponding key was signaled as ready:
|
||||||
|
* our goal here is to call the RedisModuleBlockedClient reply() callback to
|
||||||
|
* see if the key is really able to serve the client, and in that case,
|
||||||
|
* unblock it. */
|
||||||
|
void serveClientsBlockedOnKeyByModule(readyList *rl) {
|
||||||
|
dictEntry *de;
|
||||||
|
|
||||||
|
/* We serve clients in the same order they blocked for
|
||||||
|
* this key, from the first blocked to the last. */
|
||||||
|
de = dictFind(rl->db->blocking_keys,rl->key);
|
||||||
|
if (de) {
|
||||||
|
list *clients = dictGetVal(de);
|
||||||
|
int numclients = listLength(clients);
|
||||||
|
|
||||||
|
while(numclients--) {
|
||||||
|
listNode *clientnode = listFirst(clients);
|
||||||
|
client *receiver = clientnode->value;
|
||||||
|
|
||||||
|
/* Put at the tail, so that at the next call
|
||||||
|
* we'll not run into it again: clients here may not be
|
||||||
|
* ready to be served, so they'll remain in the list
|
||||||
|
* sometimes. We want also be able to skip clients that are
|
||||||
|
* not blocked for the MODULE type safely. */
|
||||||
|
listDelNode(clients,clientnode);
|
||||||
|
listAddNodeTail(clients,receiver);
|
||||||
|
|
||||||
|
if (receiver->btype != BLOCKED_MODULE) continue;
|
||||||
|
|
||||||
|
/* Note that if *this* client cannot be served by this key,
|
||||||
|
* it does not mean that another client that is next into the
|
||||||
|
* list cannot be served as well: they may be blocked by
|
||||||
|
* different modules with different triggers to consider if a key
|
||||||
|
* is ready or not. This means we can't exit the loop but need
|
||||||
|
* to continue after the first failure. */
|
||||||
|
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
|
||||||
|
|
||||||
|
moduleUnblockClient(receiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* This function should be called by Redis every time a single command,
|
/* This function should be called by Redis every time a single command,
|
||||||
* a MULTI/EXEC block, or a Lua script, terminated its execution after
|
* a MULTI/EXEC block, or a Lua script, terminated its execution after
|
||||||
* being called by a client. It handles serving clients blocked in
|
* being called by a client. It handles serving clients blocked in
|
||||||
@ -480,6 +524,10 @@ void handleClientsBlockedOnKeys(void) {
|
|||||||
serveClientsBlockedOnSortedSetKey(o,rl);
|
serveClientsBlockedOnSortedSetKey(o,rl);
|
||||||
else if (o->type == OBJ_STREAM)
|
else if (o->type == OBJ_STREAM)
|
||||||
serveClientsBlockedOnStreamKey(o,rl);
|
serveClientsBlockedOnStreamKey(o,rl);
|
||||||
|
/* We want to serve clients blocked on module keys
|
||||||
|
* regardless of the object type: we don't know what the
|
||||||
|
* module is trying to accomplish right now. */
|
||||||
|
serveClientsBlockedOnKeyByModule(rl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free this item. */
|
/* Free this item. */
|
||||||
|
@ -80,6 +80,8 @@ void receiveChildInfo(void) {
|
|||||||
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
||||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||||
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
||||||
|
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
|
||||||
|
server.stat_module_cow_bytes = server.child_info_data.cow_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
277
src/cluster.c
277
src/cluster.c
@ -49,7 +49,7 @@ clusterNode *myself = NULL;
|
|||||||
clusterNode *createClusterNode(char *nodename, int flags);
|
clusterNode *createClusterNode(char *nodename, int flags);
|
||||||
int clusterAddNode(clusterNode *node);
|
int clusterAddNode(clusterNode *node);
|
||||||
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||||
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
void clusterReadHandler(connection *conn);
|
||||||
void clusterSendPing(clusterLink *link, int type);
|
void clusterSendPing(clusterLink *link, int type);
|
||||||
void clusterSendFail(char *nodename);
|
void clusterSendFail(char *nodename);
|
||||||
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
|
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
|
||||||
@ -477,7 +477,8 @@ void clusterInit(void) {
|
|||||||
/* Port sanity check II
|
/* Port sanity check II
|
||||||
* The other handshake port check is triggered too late to stop
|
* The other handshake port check is triggered too late to stop
|
||||||
* us from trying to use a too-high cluster port number. */
|
* us from trying to use a too-high cluster port number. */
|
||||||
if (server.port > (65535-CLUSTER_PORT_INCR)) {
|
int port = server.tls_cluster ? server.tls_port : server.port;
|
||||||
|
if (port > (65535-CLUSTER_PORT_INCR)) {
|
||||||
serverLog(LL_WARNING, "Redis port number too high. "
|
serverLog(LL_WARNING, "Redis port number too high. "
|
||||||
"Cluster communication port is 10,000 port "
|
"Cluster communication port is 10,000 port "
|
||||||
"numbers higher than your Redis port. "
|
"numbers higher than your Redis port. "
|
||||||
@ -485,8 +486,7 @@ void clusterInit(void) {
|
|||||||
"lower than 55535.");
|
"lower than 55535.");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
if (listenToPort(port+CLUSTER_PORT_INCR,
|
||||||
if (listenToPort(server.port+CLUSTER_PORT_INCR,
|
|
||||||
server.cfd,&server.cfd_count) == C_ERR)
|
server.cfd,&server.cfd_count) == C_ERR)
|
||||||
{
|
{
|
||||||
exit(1);
|
exit(1);
|
||||||
@ -508,8 +508,8 @@ void clusterInit(void) {
|
|||||||
|
|
||||||
/* Set myself->port / cport to my listening ports, we'll just need to
|
/* Set myself->port / cport to my listening ports, we'll just need to
|
||||||
* discover the IP address via MEET messages. */
|
* discover the IP address via MEET messages. */
|
||||||
myself->port = server.port;
|
myself->port = port;
|
||||||
myself->cport = server.port+CLUSTER_PORT_INCR;
|
myself->cport = port+CLUSTER_PORT_INCR;
|
||||||
if (server.cluster_announce_port)
|
if (server.cluster_announce_port)
|
||||||
myself->port = server.cluster_announce_port;
|
myself->port = server.cluster_announce_port;
|
||||||
if (server.cluster_announce_bus_port)
|
if (server.cluster_announce_bus_port)
|
||||||
@ -593,7 +593,7 @@ clusterLink *createClusterLink(clusterNode *node) {
|
|||||||
link->sndbuf = sdsempty();
|
link->sndbuf = sdsempty();
|
||||||
link->rcvbuf = sdsempty();
|
link->rcvbuf = sdsempty();
|
||||||
link->node = node;
|
link->node = node;
|
||||||
link->fd = -1;
|
link->conn = NULL;
|
||||||
return link;
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,23 +601,45 @@ clusterLink *createClusterLink(clusterNode *node) {
|
|||||||
* This function will just make sure that the original node associated
|
* This function will just make sure that the original node associated
|
||||||
* with this link will have the 'link' field set to NULL. */
|
* with this link will have the 'link' field set to NULL. */
|
||||||
void freeClusterLink(clusterLink *link) {
|
void freeClusterLink(clusterLink *link) {
|
||||||
if (link->fd != -1) {
|
if (link->conn) {
|
||||||
aeDeleteFileEvent(server.el, link->fd, AE_READABLE|AE_WRITABLE);
|
connClose(link->conn);
|
||||||
|
link->conn = NULL;
|
||||||
}
|
}
|
||||||
sdsfree(link->sndbuf);
|
sdsfree(link->sndbuf);
|
||||||
sdsfree(link->rcvbuf);
|
sdsfree(link->rcvbuf);
|
||||||
if (link->node)
|
if (link->node)
|
||||||
link->node->link = NULL;
|
link->node->link = NULL;
|
||||||
close(link->fd);
|
|
||||||
zfree(link);
|
zfree(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clusterConnAcceptHandler(connection *conn) {
|
||||||
|
clusterLink *link;
|
||||||
|
|
||||||
|
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||||
|
serverLog(LL_VERBOSE,
|
||||||
|
"Error accepting cluster node connection: %s", connGetLastError(conn));
|
||||||
|
connClose(conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a link object we use to handle the connection.
|
||||||
|
* It gets passed to the readable handler when data is available.
|
||||||
|
* Initiallly the link->node pointer is set to NULL as we don't know
|
||||||
|
* which node is, but the right node is references once we know the
|
||||||
|
* node identity. */
|
||||||
|
link = createClusterLink(NULL);
|
||||||
|
link->conn = conn;
|
||||||
|
connSetPrivateData(conn, link);
|
||||||
|
|
||||||
|
/* Register read handler */
|
||||||
|
connSetReadHandler(conn, clusterReadHandler);
|
||||||
|
}
|
||||||
|
|
||||||
#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
|
#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
|
||||||
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||||
int cport, cfd;
|
int cport, cfd;
|
||||||
int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
|
int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
|
||||||
char cip[NET_IP_STR_LEN];
|
char cip[NET_IP_STR_LEN];
|
||||||
clusterLink *link;
|
|
||||||
UNUSED(el);
|
UNUSED(el);
|
||||||
UNUSED(mask);
|
UNUSED(mask);
|
||||||
UNUSED(privdata);
|
UNUSED(privdata);
|
||||||
@ -634,19 +656,24 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
"Error accepting cluster node: %s", server.neterr);
|
"Error accepting cluster node: %s", server.neterr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
anetNonBlock(NULL,cfd);
|
|
||||||
anetEnableTcpNoDelay(NULL,cfd);
|
connection *conn = server.tls_cluster ? connCreateAcceptedTLS(cfd,1) : connCreateAcceptedSocket(cfd);
|
||||||
|
connNonBlock(conn);
|
||||||
|
connEnableTcpNoDelay(conn);
|
||||||
|
|
||||||
/* Use non-blocking I/O for cluster messages. */
|
/* Use non-blocking I/O for cluster messages. */
|
||||||
serverLog(LL_VERBOSE,"Accepted cluster node %s:%d", cip, cport);
|
serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
|
||||||
/* Create a link object we use to handle the connection.
|
|
||||||
* It gets passed to the readable handler when data is available.
|
/* Accept the connection now. connAccept() may call our handler directly
|
||||||
* Initiallly the link->node pointer is set to NULL as we don't know
|
* or schedule it for later depending on connection implementation.
|
||||||
* which node is, but the right node is references once we know the
|
*/
|
||||||
* node identity. */
|
if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
|
||||||
link = createClusterLink(NULL);
|
serverLog(LL_VERBOSE,
|
||||||
link->fd = cfd;
|
"Error accepting cluster node connection: %s",
|
||||||
aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link);
|
connGetLastError(conn));
|
||||||
|
connClose(conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1447,7 +1474,7 @@ void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
|
|||||||
memcpy(buf,announced_ip,NET_IP_STR_LEN);
|
memcpy(buf,announced_ip,NET_IP_STR_LEN);
|
||||||
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
|
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
|
||||||
} else {
|
} else {
|
||||||
anetPeerToString(link->fd, buf, NET_IP_STR_LEN, NULL);
|
connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1751,7 +1778,7 @@ int clusterProcessPacket(clusterLink *link) {
|
|||||||
{
|
{
|
||||||
char ip[NET_IP_STR_LEN];
|
char ip[NET_IP_STR_LEN];
|
||||||
|
|
||||||
if (anetSockName(link->fd,ip,sizeof(ip),NULL) != -1 &&
|
if (connSockName(link->conn,ip,sizeof(ip),NULL) != -1 &&
|
||||||
strcmp(ip,myself->ip))
|
strcmp(ip,myself->ip))
|
||||||
{
|
{
|
||||||
memcpy(myself->ip,ip,NET_IP_STR_LEN);
|
memcpy(myself->ip,ip,NET_IP_STR_LEN);
|
||||||
@ -2118,35 +2145,76 @@ void handleLinkIOError(clusterLink *link) {
|
|||||||
/* Send data. This is handled using a trivial send buffer that gets
|
/* Send data. This is handled using a trivial send buffer that gets
|
||||||
* consumed by write(). We don't try to optimize this for speed too much
|
* consumed by write(). We don't try to optimize this for speed too much
|
||||||
* as this is a very low traffic channel. */
|
* as this is a very low traffic channel. */
|
||||||
void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void clusterWriteHandler(connection *conn) {
|
||||||
clusterLink *link = (clusterLink*) privdata;
|
clusterLink *link = connGetPrivateData(conn);
|
||||||
ssize_t nwritten;
|
ssize_t nwritten;
|
||||||
UNUSED(el);
|
|
||||||
UNUSED(mask);
|
|
||||||
|
|
||||||
nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf));
|
nwritten = connWrite(conn, link->sndbuf, sdslen(link->sndbuf));
|
||||||
if (nwritten <= 0) {
|
if (nwritten <= 0) {
|
||||||
serverLog(LL_DEBUG,"I/O error writing to node link: %s",
|
serverLog(LL_DEBUG,"I/O error writing to node link: %s",
|
||||||
(nwritten == -1) ? strerror(errno) : "short write");
|
(nwritten == -1) ? connGetLastError(conn) : "short write");
|
||||||
handleLinkIOError(link);
|
handleLinkIOError(link);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sdsrange(link->sndbuf,nwritten,-1);
|
sdsrange(link->sndbuf,nwritten,-1);
|
||||||
if (sdslen(link->sndbuf) == 0)
|
if (sdslen(link->sndbuf) == 0)
|
||||||
aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
|
connSetWriteHandler(link->conn, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A connect handler that gets called when a connection to another node
|
||||||
|
* gets established.
|
||||||
|
*/
|
||||||
|
void clusterLinkConnectHandler(connection *conn) {
|
||||||
|
clusterLink *link = connGetPrivateData(conn);
|
||||||
|
clusterNode *node = link->node;
|
||||||
|
|
||||||
|
/* Check if connection succeeded */
|
||||||
|
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||||
|
serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s",
|
||||||
|
node->name, node->ip, node->cport,
|
||||||
|
connGetLastError(conn));
|
||||||
|
freeClusterLink(link);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register a read handler from now on */
|
||||||
|
connSetReadHandler(conn, clusterReadHandler);
|
||||||
|
|
||||||
|
/* Queue a PING in the new connection ASAP: this is crucial
|
||||||
|
* to avoid false positives in failure detection.
|
||||||
|
*
|
||||||
|
* If the node is flagged as MEET, we send a MEET message instead
|
||||||
|
* of a PING one, to force the receiver to add us in its node
|
||||||
|
* table. */
|
||||||
|
mstime_t old_ping_sent = node->ping_sent;
|
||||||
|
clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
|
||||||
|
CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
|
||||||
|
if (old_ping_sent) {
|
||||||
|
/* If there was an active ping before the link was
|
||||||
|
* disconnected, we want to restore the ping time, otherwise
|
||||||
|
* replaced by the clusterSendPing() call. */
|
||||||
|
node->ping_sent = old_ping_sent;
|
||||||
|
}
|
||||||
|
/* We can clear the flag after the first packet is sent.
|
||||||
|
* If we'll never receive a PONG, we'll never send new packets
|
||||||
|
* to this node. Instead after the PONG is received and we
|
||||||
|
* are no longer in meet/handshake status, we want to send
|
||||||
|
* normal PING packets. */
|
||||||
|
node->flags &= ~CLUSTER_NODE_MEET;
|
||||||
|
|
||||||
|
serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
|
||||||
|
node->name, node->ip, node->cport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read data. Try to read the first field of the header first to check the
|
/* Read data. Try to read the first field of the header first to check the
|
||||||
* full length of the packet. When a whole packet is in memory this function
|
* full length of the packet. When a whole packet is in memory this function
|
||||||
* will call the function to process the packet. And so forth. */
|
* will call the function to process the packet. And so forth. */
|
||||||
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void clusterReadHandler(connection *conn) {
|
||||||
char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
clusterMsg *hdr;
|
clusterMsg *hdr;
|
||||||
clusterLink *link = (clusterLink*) privdata;
|
clusterLink *link = connGetPrivateData(conn);
|
||||||
unsigned int readlen, rcvbuflen;
|
unsigned int readlen, rcvbuflen;
|
||||||
UNUSED(el);
|
|
||||||
UNUSED(mask);
|
|
||||||
|
|
||||||
while(1) { /* Read as long as there is data to read. */
|
while(1) { /* Read as long as there is data to read. */
|
||||||
rcvbuflen = sdslen(link->rcvbuf);
|
rcvbuflen = sdslen(link->rcvbuf);
|
||||||
@ -2174,13 +2242,13 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
if (readlen > sizeof(buf)) readlen = sizeof(buf);
|
if (readlen > sizeof(buf)) readlen = sizeof(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
nread = read(fd,buf,readlen);
|
nread = connRead(conn,buf,readlen);
|
||||||
if (nread == -1 && errno == EAGAIN) return; /* No more data ready. */
|
if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */
|
||||||
|
|
||||||
if (nread <= 0) {
|
if (nread <= 0) {
|
||||||
/* I/O error... */
|
/* I/O error... */
|
||||||
serverLog(LL_DEBUG,"I/O error reading from node link: %s",
|
serverLog(LL_DEBUG,"I/O error reading from node link: %s",
|
||||||
(nread == 0) ? "connection closed" : strerror(errno));
|
(nread == 0) ? "connection closed" : connGetLastError(conn));
|
||||||
handleLinkIOError(link);
|
handleLinkIOError(link);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@ -2209,8 +2277,7 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
* from event handlers that will do stuff with the same link later. */
|
* from event handlers that will do stuff with the same link later. */
|
||||||
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
|
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
|
||||||
if (sdslen(link->sndbuf) == 0 && msglen != 0)
|
if (sdslen(link->sndbuf) == 0 && msglen != 0)
|
||||||
aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER,
|
connSetWriteHandlerWithBarrier(link->conn, clusterWriteHandler, 1);
|
||||||
clusterWriteHandler,link);
|
|
||||||
|
|
||||||
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
|
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
|
||||||
|
|
||||||
@ -2276,11 +2343,12 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Handle cluster-announce-port as well. */
|
/* Handle cluster-announce-port as well. */
|
||||||
|
int port = server.tls_cluster ? server.tls_port : server.port;
|
||||||
int announced_port = server.cluster_announce_port ?
|
int announced_port = server.cluster_announce_port ?
|
||||||
server.cluster_announce_port : server.port;
|
server.cluster_announce_port : port;
|
||||||
int announced_cport = server.cluster_announce_bus_port ?
|
int announced_cport = server.cluster_announce_bus_port ?
|
||||||
server.cluster_announce_bus_port :
|
server.cluster_announce_bus_port :
|
||||||
(server.port + CLUSTER_PORT_INCR);
|
(port + CLUSTER_PORT_INCR);
|
||||||
|
|
||||||
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
|
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
|
||||||
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
|
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
|
||||||
@ -2517,7 +2585,8 @@ void clusterBroadcastPong(int target) {
|
|||||||
*
|
*
|
||||||
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
||||||
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||||
unsigned char buf[sizeof(clusterMsg)], *payload;
|
unsigned char *payload;
|
||||||
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
uint32_t channel_len, message_len;
|
uint32_t channel_len, message_len;
|
||||||
@ -2537,7 +2606,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
|||||||
|
|
||||||
/* Try to use the local buffer if possible */
|
/* Try to use the local buffer if possible */
|
||||||
if (totlen < sizeof(buf)) {
|
if (totlen < sizeof(buf)) {
|
||||||
payload = buf;
|
payload = (unsigned char*)buf;
|
||||||
} else {
|
} else {
|
||||||
payload = zmalloc(totlen);
|
payload = zmalloc(totlen);
|
||||||
memcpy(payload,hdr,sizeof(*hdr));
|
memcpy(payload,hdr,sizeof(*hdr));
|
||||||
@ -2554,7 +2623,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
|||||||
|
|
||||||
decrRefCount(channel);
|
decrRefCount(channel);
|
||||||
decrRefCount(message);
|
decrRefCount(message);
|
||||||
if (payload != buf) zfree(payload);
|
if (payload != (unsigned char*)buf) zfree(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send a FAIL message to all the nodes we are able to contact.
|
/* Send a FAIL message to all the nodes we are able to contact.
|
||||||
@ -2563,7 +2632,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
|||||||
* we switch the node state to CLUSTER_NODE_FAIL and ask all the other
|
* we switch the node state to CLUSTER_NODE_FAIL and ask all the other
|
||||||
* nodes to do the same ASAP. */
|
* nodes to do the same ASAP. */
|
||||||
void clusterSendFail(char *nodename) {
|
void clusterSendFail(char *nodename) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
|
|
||||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
|
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
|
||||||
@ -2575,7 +2644,7 @@ void clusterSendFail(char *nodename) {
|
|||||||
* slots configuration. The node name, slots bitmap, and configEpoch info
|
* slots configuration. The node name, slots bitmap, and configEpoch info
|
||||||
* are included. */
|
* are included. */
|
||||||
void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
|
|
||||||
if (link == NULL) return;
|
if (link == NULL) return;
|
||||||
@ -2583,7 +2652,7 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
|||||||
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
|
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
|
||||||
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
|
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
|
||||||
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
|
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
|
||||||
clusterSendMessage(link,buf,ntohl(hdr->totlen));
|
clusterSendMessage(link,(unsigned char*)buf,ntohl(hdr->totlen));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send a MODULE message.
|
/* Send a MODULE message.
|
||||||
@ -2591,7 +2660,8 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
|||||||
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
||||||
void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
||||||
unsigned char *payload, uint32_t len) {
|
unsigned char *payload, uint32_t len) {
|
||||||
unsigned char buf[sizeof(clusterMsg)], *heapbuf;
|
unsigned char *heapbuf;
|
||||||
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2606,7 +2676,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
|||||||
|
|
||||||
/* Try to use the local buffer if possible */
|
/* Try to use the local buffer if possible */
|
||||||
if (totlen < sizeof(buf)) {
|
if (totlen < sizeof(buf)) {
|
||||||
heapbuf = buf;
|
heapbuf = (unsigned char*)buf;
|
||||||
} else {
|
} else {
|
||||||
heapbuf = zmalloc(totlen);
|
heapbuf = zmalloc(totlen);
|
||||||
memcpy(heapbuf,hdr,sizeof(*hdr));
|
memcpy(heapbuf,hdr,sizeof(*hdr));
|
||||||
@ -2619,7 +2689,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
|||||||
else
|
else
|
||||||
clusterBroadcastMessage(heapbuf,totlen);
|
clusterBroadcastMessage(heapbuf,totlen);
|
||||||
|
|
||||||
if (heapbuf != buf) zfree(heapbuf);
|
if (heapbuf != (unsigned char*)buf) zfree(heapbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function gets a cluster node ID string as target, the same way the nodes
|
/* This function gets a cluster node ID string as target, the same way the nodes
|
||||||
@ -2663,7 +2733,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
|
|||||||
* Note that we send the failover request to everybody, master and slave nodes,
|
* Note that we send the failover request to everybody, master and slave nodes,
|
||||||
* but only the masters are supposed to reply to our query. */
|
* but only the masters are supposed to reply to our query. */
|
||||||
void clusterRequestFailoverAuth(void) {
|
void clusterRequestFailoverAuth(void) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2679,7 +2749,7 @@ void clusterRequestFailoverAuth(void) {
|
|||||||
|
|
||||||
/* Send a FAILOVER_AUTH_ACK message to the specified node. */
|
/* Send a FAILOVER_AUTH_ACK message to the specified node. */
|
||||||
void clusterSendFailoverAuth(clusterNode *node) {
|
void clusterSendFailoverAuth(clusterNode *node) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2687,12 +2757,12 @@ void clusterSendFailoverAuth(clusterNode *node) {
|
|||||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
|
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
|
||||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||||
hdr->totlen = htonl(totlen);
|
hdr->totlen = htonl(totlen);
|
||||||
clusterSendMessage(node->link,buf,totlen);
|
clusterSendMessage(node->link,(unsigned char*)buf,totlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send a MFSTART message to the specified node. */
|
/* Send a MFSTART message to the specified node. */
|
||||||
void clusterSendMFStart(clusterNode *node) {
|
void clusterSendMFStart(clusterNode *node) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2700,7 +2770,7 @@ void clusterSendMFStart(clusterNode *node) {
|
|||||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
|
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
|
||||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||||
hdr->totlen = htonl(totlen);
|
hdr->totlen = htonl(totlen);
|
||||||
clusterSendMessage(node->link,buf,totlen);
|
clusterSendMessage(node->link,(unsigned char*)buf,totlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Vote for the node asking for our vote if there are the conditions. */
|
/* Vote for the node asking for our vote if there are the conditions. */
|
||||||
@ -3383,13 +3453,11 @@ void clusterCron(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node->link == NULL) {
|
if (node->link == NULL) {
|
||||||
int fd;
|
clusterLink *link = createClusterLink(node);
|
||||||
mstime_t old_ping_sent;
|
link->conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||||
clusterLink *link;
|
connSetPrivateData(link->conn, link);
|
||||||
|
if (connConnect(link->conn, node->ip, node->cport, NET_FIRST_BIND_ADDR,
|
||||||
fd = anetTcpNonBlockBindConnect(server.neterr, node->ip,
|
clusterLinkConnectHandler) == -1) {
|
||||||
node->cport, NET_FIRST_BIND_ADDR);
|
|
||||||
if (fd == -1) {
|
|
||||||
/* We got a synchronous error from connect before
|
/* We got a synchronous error from connect before
|
||||||
* clusterSendPing() had a chance to be called.
|
* clusterSendPing() had a chance to be called.
|
||||||
* If node->ping_sent is zero, failure detection can't work,
|
* If node->ping_sent is zero, failure detection can't work,
|
||||||
@ -3399,37 +3467,11 @@ void clusterCron(void) {
|
|||||||
serverLog(LL_DEBUG, "Unable to connect to "
|
serverLog(LL_DEBUG, "Unable to connect to "
|
||||||
"Cluster Node [%s]:%d -> %s", node->ip,
|
"Cluster Node [%s]:%d -> %s", node->ip,
|
||||||
node->cport, server.neterr);
|
node->cport, server.neterr);
|
||||||
|
|
||||||
|
freeClusterLink(link);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
link = createClusterLink(node);
|
|
||||||
link->fd = fd;
|
|
||||||
node->link = link;
|
node->link = link;
|
||||||
aeCreateFileEvent(server.el,link->fd,AE_READABLE,
|
|
||||||
clusterReadHandler,link);
|
|
||||||
/* Queue a PING in the new connection ASAP: this is crucial
|
|
||||||
* to avoid false positives in failure detection.
|
|
||||||
*
|
|
||||||
* If the node is flagged as MEET, we send a MEET message instead
|
|
||||||
* of a PING one, to force the receiver to add us in its node
|
|
||||||
* table. */
|
|
||||||
old_ping_sent = node->ping_sent;
|
|
||||||
clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
|
|
||||||
CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
|
|
||||||
if (old_ping_sent) {
|
|
||||||
/* If there was an active ping before the link was
|
|
||||||
* disconnected, we want to restore the ping time, otherwise
|
|
||||||
* replaced by the clusterSendPing() call. */
|
|
||||||
node->ping_sent = old_ping_sent;
|
|
||||||
}
|
|
||||||
/* We can clear the flag after the first packet is sent.
|
|
||||||
* If we'll never receive a PONG, we'll never send new packets
|
|
||||||
* to this node. Instead after the PONG is received and we
|
|
||||||
* are no longer in meet/handshake status, we want to send
|
|
||||||
* normal PING packets. */
|
|
||||||
node->flags &= ~CLUSTER_NODE_MEET;
|
|
||||||
|
|
||||||
serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
|
|
||||||
node->name, node->ip, node->cport);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
@ -4252,7 +4294,9 @@ NULL
|
|||||||
}
|
}
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
||||||
/* CLUSTER NODES */
|
/* CLUSTER NODES */
|
||||||
addReplyBulkSds(c,clusterGenNodesDescription(0));
|
sds nodes = clusterGenNodesDescription(0);
|
||||||
|
addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
|
||||||
|
sdsfree(nodes);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
|
||||||
/* CLUSTER MYID */
|
/* CLUSTER MYID */
|
||||||
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
|
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
|
||||||
@ -4494,10 +4538,8 @@ NULL
|
|||||||
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
|
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
|
||||||
|
|
||||||
/* Produce the reply protocol. */
|
/* Produce the reply protocol. */
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",
|
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||||
(unsigned long)sdslen(info)));
|
sdsfree(info);
|
||||||
addReplySds(c,info);
|
|
||||||
addReply(c,shared.crlf);
|
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
|
||||||
int retval = clusterSaveConfig(1);
|
int retval = clusterSaveConfig(1);
|
||||||
|
|
||||||
@ -4940,7 +4982,7 @@ void restoreCommand(client *c) {
|
|||||||
#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
|
#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
|
||||||
|
|
||||||
typedef struct migrateCachedSocket {
|
typedef struct migrateCachedSocket {
|
||||||
int fd;
|
connection *conn;
|
||||||
long last_dbid;
|
long last_dbid;
|
||||||
time_t last_use_time;
|
time_t last_use_time;
|
||||||
} migrateCachedSocket;
|
} migrateCachedSocket;
|
||||||
@ -4957,7 +4999,7 @@ typedef struct migrateCachedSocket {
|
|||||||
* should be called so that the connection will be created from scratch
|
* should be called so that the connection will be created from scratch
|
||||||
* the next time. */
|
* the next time. */
|
||||||
migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) {
|
migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) {
|
||||||
int fd;
|
connection *conn;
|
||||||
sds name = sdsempty();
|
sds name = sdsempty();
|
||||||
migrateCachedSocket *cs;
|
migrateCachedSocket *cs;
|
||||||
|
|
||||||
@ -4977,34 +5019,27 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
|
|||||||
/* Too many items, drop one at random. */
|
/* Too many items, drop one at random. */
|
||||||
dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
|
dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
|
||||||
cs = dictGetVal(de);
|
cs = dictGetVal(de);
|
||||||
close(cs->fd);
|
connClose(cs->conn);
|
||||||
zfree(cs);
|
zfree(cs);
|
||||||
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
|
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create the socket */
|
/* Create the socket */
|
||||||
fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr,
|
conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
|
||||||
atoi(c->argv[2]->ptr));
|
if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
|
||||||
if (fd == -1) {
|
!= C_OK) {
|
||||||
sdsfree(name);
|
|
||||||
addReplyErrorFormat(c,"Can't connect to target node: %s",
|
|
||||||
server.neterr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
anetEnableTcpNoDelay(server.neterr,fd);
|
|
||||||
|
|
||||||
/* Check if it connects within the specified timeout. */
|
|
||||||
if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
|
|
||||||
sdsfree(name);
|
|
||||||
addReplySds(c,
|
addReplySds(c,
|
||||||
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
|
||||||
close(fd);
|
connClose(conn);
|
||||||
|
sdsfree(name);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
connEnableTcpNoDelay(conn);
|
||||||
|
|
||||||
/* Add to the cache and return it to the caller. */
|
/* Add to the cache and return it to the caller. */
|
||||||
cs = zmalloc(sizeof(*cs));
|
cs = zmalloc(sizeof(*cs));
|
||||||
cs->fd = fd;
|
cs->conn = conn;
|
||||||
|
|
||||||
cs->last_dbid = -1;
|
cs->last_dbid = -1;
|
||||||
cs->last_use_time = server.unixtime;
|
cs->last_use_time = server.unixtime;
|
||||||
dictAdd(server.migrate_cached_sockets,name,cs);
|
dictAdd(server.migrate_cached_sockets,name,cs);
|
||||||
@ -5025,7 +5060,7 @@ void migrateCloseSocket(robj *host, robj *port) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(cs->fd);
|
connClose(cs->conn);
|
||||||
zfree(cs);
|
zfree(cs);
|
||||||
dictDelete(server.migrate_cached_sockets,name);
|
dictDelete(server.migrate_cached_sockets,name);
|
||||||
sdsfree(name);
|
sdsfree(name);
|
||||||
@ -5039,7 +5074,7 @@ void migrateCloseTimedoutSockets(void) {
|
|||||||
migrateCachedSocket *cs = dictGetVal(de);
|
migrateCachedSocket *cs = dictGetVal(de);
|
||||||
|
|
||||||
if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
|
if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
|
||||||
close(cs->fd);
|
connClose(cs->conn);
|
||||||
zfree(cs);
|
zfree(cs);
|
||||||
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
|
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
|
||||||
}
|
}
|
||||||
@ -5221,7 +5256,7 @@ try_again:
|
|||||||
|
|
||||||
while ((towrite = sdslen(buf)-pos) > 0) {
|
while ((towrite = sdslen(buf)-pos) > 0) {
|
||||||
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
|
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
|
||||||
nwritten = syncWrite(cs->fd,buf+pos,towrite,timeout);
|
nwritten = connSyncWrite(cs->conn,buf+pos,towrite,timeout);
|
||||||
if (nwritten != (signed)towrite) {
|
if (nwritten != (signed)towrite) {
|
||||||
write_error = 1;
|
write_error = 1;
|
||||||
goto socket_err;
|
goto socket_err;
|
||||||
@ -5235,11 +5270,11 @@ try_again:
|
|||||||
char buf2[1024]; /* Restore reply. */
|
char buf2[1024]; /* Restore reply. */
|
||||||
|
|
||||||
/* Read the AUTH reply if needed. */
|
/* Read the AUTH reply if needed. */
|
||||||
if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0)
|
if (password && connSyncReadLine(cs->conn, buf0, sizeof(buf0), timeout) <= 0)
|
||||||
goto socket_err;
|
goto socket_err;
|
||||||
|
|
||||||
/* Read the SELECT reply if needed. */
|
/* Read the SELECT reply if needed. */
|
||||||
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
|
if (select && connSyncReadLine(cs->conn, buf1, sizeof(buf1), timeout) <= 0)
|
||||||
goto socket_err;
|
goto socket_err;
|
||||||
|
|
||||||
/* Read the RESTORE replies. */
|
/* Read the RESTORE replies. */
|
||||||
@ -5254,7 +5289,7 @@ try_again:
|
|||||||
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
|
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
|
||||||
|
|
||||||
for (j = 0; j < num_keys; j++) {
|
for (j = 0; j < num_keys; j++) {
|
||||||
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
|
if (connSyncReadLine(cs->conn, buf2, sizeof(buf2), timeout) <= 0) {
|
||||||
socket_error = 1;
|
socket_error = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ struct clusterNode;
|
|||||||
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
/* clusterLink encapsulates everything needed to talk with a remote node. */
|
||||||
typedef struct clusterLink {
|
typedef struct clusterLink {
|
||||||
mstime_t ctime; /* Link creation time */
|
mstime_t ctime; /* Link creation time */
|
||||||
int fd; /* TCP socket file descriptor */
|
connection *conn; /* Connection to remote node */
|
||||||
sds sndbuf; /* Packet send buffer */
|
sds sndbuf; /* Packet send buffer */
|
||||||
sds rcvbuf; /* Packet reception buffer */
|
sds rcvbuf; /* Packet reception buffer */
|
||||||
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
struct clusterNode *node; /* Node related to this link if any, or NULL */
|
||||||
|
186
src/config.c
186
src/config.c
@ -144,6 +144,7 @@ configYesNo configs_yesno[] = {
|
|||||||
{"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
|
{"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
|
||||||
{"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
|
{"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
|
||||||
{"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
|
{"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
|
||||||
|
{"jemalloc-bg-thread",NULL,&server.jemalloc_bg_thread,1,1},
|
||||||
{NULL, NULL, 0, 0}
|
{NULL, NULL, 0, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -219,7 +220,7 @@ void queueLoadModule(sds path, sds *argv, int argc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loadServerConfigFromString(char *config) {
|
void loadServerConfigFromString(char *config) {
|
||||||
char *err = NULL;
|
const char *err = NULL;
|
||||||
int linenum = 0, totlines, i;
|
int linenum = 0, totlines, i;
|
||||||
int slaveof_linenum = 0;
|
int slaveof_linenum = 0;
|
||||||
sds *lines;
|
sds *lines;
|
||||||
@ -438,6 +439,7 @@ void loadServerConfigFromString(char *config) {
|
|||||||
server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
|
server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
|
||||||
if (server.repl_diskless_load == INT_MIN) {
|
if (server.repl_diskless_load == INT_MIN) {
|
||||||
err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
|
err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
|
||||||
|
goto loaderr;
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
|
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
|
||||||
server.repl_diskless_sync_delay = atoi(argv[1]);
|
server.repl_diskless_sync_delay = atoi(argv[1]);
|
||||||
@ -513,6 +515,12 @@ void loadServerConfigFromString(char *config) {
|
|||||||
err = "rdb-key-save-delay can't be negative";
|
err = "rdb-key-save-delay can't be negative";
|
||||||
goto loaderr;
|
goto loaderr;
|
||||||
}
|
}
|
||||||
|
} else if (!strcasecmp(argv[0],"key-load-delay") && argc==2) {
|
||||||
|
server.key_load_delay = atoi(argv[1]);
|
||||||
|
if (server.key_load_delay < 0) {
|
||||||
|
err = "key-load-delay can't be negative";
|
||||||
|
goto loaderr;
|
||||||
|
}
|
||||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||||
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
|
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
|
||||||
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
|
||||||
@ -672,6 +680,10 @@ void loadServerConfigFromString(char *config) {
|
|||||||
server.lua_time_limit = strtoll(argv[1],NULL,10);
|
server.lua_time_limit = strtoll(argv[1],NULL,10);
|
||||||
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
|
||||||
server.lua_always_replicate_commands = yesnotoi(argv[1]);
|
server.lua_always_replicate_commands = yesnotoi(argv[1]);
|
||||||
|
if (server.lua_always_replicate_commands == -1) {
|
||||||
|
err = "argument must be 'yes' or 'no'";
|
||||||
|
goto loaderr;
|
||||||
|
}
|
||||||
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
|
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
|
||||||
argc == 2)
|
argc == 2)
|
||||||
{
|
{
|
||||||
@ -791,6 +803,45 @@ void loadServerConfigFromString(char *config) {
|
|||||||
err = sentinelHandleConfiguration(argv+1,argc-1);
|
err = sentinelHandleConfiguration(argv+1,argc-1);
|
||||||
if (err) goto loaderr;
|
if (err) goto loaderr;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-port") && argc == 2) {
|
||||||
|
server.tls_port = atoi(argv[1]);
|
||||||
|
if (server.port < 0 || server.port > 65535) {
|
||||||
|
err = "Invalid tls-port"; goto loaderr;
|
||||||
|
}
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-cluster") && argc == 2) {
|
||||||
|
server.tls_cluster = yesnotoi(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-replication") && argc == 2) {
|
||||||
|
server.tls_replication = yesnotoi(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-auth-clients") && argc == 2) {
|
||||||
|
server.tls_auth_clients = yesnotoi(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-cert-file") && argc == 2) {
|
||||||
|
zfree(server.tls_ctx_config.cert_file);
|
||||||
|
server.tls_ctx_config.cert_file = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-key-file") && argc == 2) {
|
||||||
|
zfree(server.tls_ctx_config.key_file);
|
||||||
|
server.tls_ctx_config.key_file = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-dh-params-file") && argc == 2) {
|
||||||
|
zfree(server.tls_ctx_config.dh_params_file);
|
||||||
|
server.tls_ctx_config.dh_params_file = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-ca-cert-file") && argc == 2) {
|
||||||
|
zfree(server.tls_ctx_config.ca_cert_file);
|
||||||
|
server.tls_ctx_config.ca_cert_file = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-ca-cert-dir") && argc == 2) {
|
||||||
|
zfree(server.tls_ctx_config.ca_cert_dir);
|
||||||
|
server.tls_ctx_config.ca_cert_dir = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-protocols") && argc >= 2) {
|
||||||
|
zfree(server.tls_ctx_config.protocols);
|
||||||
|
server.tls_ctx_config.protocols = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-ciphers") && argc == 2) {
|
||||||
|
zfree(server.tls_ctx_config.ciphers);
|
||||||
|
server.tls_ctx_config.ciphers = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-ciphersuites") && argc == 2) {
|
||||||
|
zfree(server.tls_ctx_config.ciphersuites);
|
||||||
|
server.tls_ctx_config.ciphersuites = zstrdup(argv[1]);
|
||||||
|
} else if (!strcasecmp(argv[0],"tls-prefer-server-ciphers") && argc == 2) {
|
||||||
|
server.tls_ctx_config.prefer_server_ciphers = yesnotoi(argv[1]);
|
||||||
|
#endif /* USE_OPENSSL */
|
||||||
} else {
|
} else {
|
||||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||||
}
|
}
|
||||||
@ -1164,6 +1215,8 @@ void configSetCommand(client *c) {
|
|||||||
"replica-priority",server.slave_priority,0,INT_MAX) {
|
"replica-priority",server.slave_priority,0,INT_MAX) {
|
||||||
} config_set_numerical_field(
|
} config_set_numerical_field(
|
||||||
"rdb-key-save-delay",server.rdb_key_save_delay,0,LLONG_MAX) {
|
"rdb-key-save-delay",server.rdb_key_save_delay,0,LLONG_MAX) {
|
||||||
|
} config_set_numerical_field(
|
||||||
|
"key-load-delay",server.key_load_delay,0,LLONG_MAX) {
|
||||||
} config_set_numerical_field(
|
} config_set_numerical_field(
|
||||||
"slave-announce-port",server.slave_announce_port,0,65535) {
|
"slave-announce-port",server.slave_announce_port,0,65535) {
|
||||||
} config_set_numerical_field(
|
} config_set_numerical_field(
|
||||||
@ -1233,7 +1286,100 @@ void configSetCommand(client *c) {
|
|||||||
"appendfsync",server.aof_fsync,aof_fsync_enum) {
|
"appendfsync",server.aof_fsync,aof_fsync_enum) {
|
||||||
} config_set_enum_field(
|
} config_set_enum_field(
|
||||||
"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) {
|
"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) {
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
/* TLS fields. */
|
||||||
|
} config_set_special_field("tls-cert-file") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.cert_file = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-cert-file. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.cert_file);
|
||||||
|
server.tls_ctx_config.cert_file = zstrdup(o->ptr);
|
||||||
|
} config_set_special_field("tls-key-file") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.key_file = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-key-file. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.key_file);
|
||||||
|
server.tls_ctx_config.key_file = zstrdup(o->ptr);
|
||||||
|
} config_set_special_field("tls-dh-params-file") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.dh_params_file = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-dh-params-file. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.dh_params_file);
|
||||||
|
server.tls_ctx_config.dh_params_file = zstrdup(o->ptr);
|
||||||
|
} config_set_special_field("tls-ca-cert-file") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.ca_cert_file = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-ca-cert-file. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.ca_cert_file);
|
||||||
|
server.tls_ctx_config.ca_cert_file = zstrdup(o->ptr);
|
||||||
|
} config_set_special_field("tls-ca-cert-dir") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.ca_cert_dir = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-ca-cert-dir. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.ca_cert_dir);
|
||||||
|
server.tls_ctx_config.ca_cert_dir = zstrdup(o->ptr);
|
||||||
|
} config_set_bool_field("tls-auth-clients", server.tls_auth_clients) {
|
||||||
|
} config_set_bool_field("tls-replication", server.tls_replication) {
|
||||||
|
} config_set_bool_field("tls-cluster", server.tls_cluster) {
|
||||||
|
} config_set_special_field("tls-protocols") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.protocols = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-protocols. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.protocols);
|
||||||
|
server.tls_ctx_config.protocols = zstrdup(o->ptr);
|
||||||
|
} config_set_special_field("tls-ciphers") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.ciphers = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-ciphers. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.ciphers);
|
||||||
|
server.tls_ctx_config.ciphers = zstrdup(o->ptr);
|
||||||
|
} config_set_special_field("tls-ciphersuites") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.ciphersuites = (char *) o->ptr;
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c,
|
||||||
|
"Unable to configure tls-ciphersuites. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zfree(server.tls_ctx_config.ciphersuites);
|
||||||
|
server.tls_ctx_config.ciphersuites = zstrdup(o->ptr);
|
||||||
|
} config_set_special_field("tls-prefer-server-ciphers") {
|
||||||
|
redisTLSContextConfig tmpctx = server.tls_ctx_config;
|
||||||
|
tmpctx.prefer_server_ciphers = yesnotoi(o->ptr);
|
||||||
|
if (tlsConfigure(&tmpctx) == C_ERR) {
|
||||||
|
addReplyError(c, "Unable to reconfigure TLS. Check server logs.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.tls_ctx_config.prefer_server_ciphers = tmpctx.prefer_server_ciphers;
|
||||||
|
#endif /* USE_OPENSSL */
|
||||||
/* Everyhing else is an error... */
|
/* Everyhing else is an error... */
|
||||||
} config_set_else {
|
} config_set_else {
|
||||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||||
@ -1307,6 +1453,16 @@ void configGetCommand(client *c) {
|
|||||||
config_get_string_field("pidfile",server.pidfile);
|
config_get_string_field("pidfile",server.pidfile);
|
||||||
config_get_string_field("slave-announce-ip",server.slave_announce_ip);
|
config_get_string_field("slave-announce-ip",server.slave_announce_ip);
|
||||||
config_get_string_field("replica-announce-ip",server.slave_announce_ip);
|
config_get_string_field("replica-announce-ip",server.slave_announce_ip);
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
config_get_string_field("tls-cert-file",server.tls_ctx_config.cert_file);
|
||||||
|
config_get_string_field("tls-key-file",server.tls_ctx_config.key_file);
|
||||||
|
config_get_string_field("tls-dh-params-file",server.tls_ctx_config.dh_params_file);
|
||||||
|
config_get_string_field("tls-ca-cert-file",server.tls_ctx_config.ca_cert_file);
|
||||||
|
config_get_string_field("tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir);
|
||||||
|
config_get_string_field("tls-protocols",server.tls_ctx_config.protocols);
|
||||||
|
config_get_string_field("tls-ciphers",server.tls_ctx_config.ciphers);
|
||||||
|
config_get_string_field("tls-ciphersuites",server.tls_ctx_config.ciphersuites);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Numerical values */
|
/* Numerical values */
|
||||||
config_get_numerical_field("maxmemory",server.maxmemory);
|
config_get_numerical_field("maxmemory",server.maxmemory);
|
||||||
@ -1354,6 +1510,7 @@ void configGetCommand(client *c) {
|
|||||||
config_get_numerical_field("slowlog-max-len", server.slowlog_max_len);
|
config_get_numerical_field("slowlog-max-len", server.slowlog_max_len);
|
||||||
config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill);
|
config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill);
|
||||||
config_get_numerical_field("port",server.port);
|
config_get_numerical_field("port",server.port);
|
||||||
|
config_get_numerical_field("tls-port",server.tls_port);
|
||||||
config_get_numerical_field("cluster-announce-port",server.cluster_announce_port);
|
config_get_numerical_field("cluster-announce-port",server.cluster_announce_port);
|
||||||
config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
|
config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
|
||||||
config_get_numerical_field("tcp-backlog",server.tcp_backlog);
|
config_get_numerical_field("tcp-backlog",server.tcp_backlog);
|
||||||
@ -1381,6 +1538,7 @@ void configGetCommand(client *c) {
|
|||||||
config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor);
|
config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor);
|
||||||
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
|
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
|
||||||
config_get_numerical_field("rdb-key-save-delay",server.rdb_key_save_delay);
|
config_get_numerical_field("rdb-key-save-delay",server.rdb_key_save_delay);
|
||||||
|
config_get_numerical_field("key-load-delay",server.key_load_delay);
|
||||||
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
|
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
|
||||||
|
|
||||||
/* Bool (yes/no) values */
|
/* Bool (yes/no) values */
|
||||||
@ -1393,7 +1551,11 @@ void configGetCommand(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
config_get_bool_field("activedefrag", server.active_defrag_enabled);
|
config_get_bool_field("activedefrag", server.active_defrag_enabled);
|
||||||
|
config_get_bool_field("tls-cluster",server.tls_cluster);
|
||||||
|
config_get_bool_field("tls-replication",server.tls_replication);
|
||||||
|
config_get_bool_field("tls-auth-clients",server.tls_auth_clients);
|
||||||
|
config_get_bool_field("tls-prefer-server-ciphers",
|
||||||
|
server.tls_ctx_config.prefer_server_ciphers);
|
||||||
/* Enum values */
|
/* Enum values */
|
||||||
config_get_enum_field("maxmemory-policy",
|
config_get_enum_field("maxmemory-policy",
|
||||||
server.maxmemory_policy,maxmemory_policy_enum);
|
server.maxmemory_policy,maxmemory_policy_enum);
|
||||||
@ -1507,6 +1669,7 @@ void configGetCommand(client *c) {
|
|||||||
}
|
}
|
||||||
matches++;
|
matches++;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDeferredMapLen(c,replylen,matches);
|
setDeferredMapLen(c,replylen,matches);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2113,7 +2276,7 @@ int rewriteConfig(char *path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE);
|
rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE);
|
||||||
rewriteConfigNumericalOption(state,"port",server.port,CONFIG_DEFAULT_SERVER_PORT);
|
rewriteConfigNumericalOption(state,"tls-port",server.tls_port,CONFIG_DEFAULT_SERVER_TLS_PORT);
|
||||||
rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
|
rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
|
||||||
rewriteConfigNumericalOption(state,"cluster-announce-bus-port",server.cluster_announce_bus_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT);
|
rewriteConfigNumericalOption(state,"cluster-announce-bus-port",server.cluster_announce_bus_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT);
|
||||||
rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,CONFIG_DEFAULT_TCP_BACKLOG);
|
rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,CONFIG_DEFAULT_TCP_BACKLOG);
|
||||||
@ -2195,6 +2358,21 @@ int rewriteConfig(char *path) {
|
|||||||
rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
|
rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
|
||||||
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
|
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
|
||||||
rewriteConfigNumericalOption(state,"rdb-key-save-delay",server.rdb_key_save_delay,CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY);
|
rewriteConfigNumericalOption(state,"rdb-key-save-delay",server.rdb_key_save_delay,CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY);
|
||||||
|
rewriteConfigNumericalOption(state,"key-load-delay",server.key_load_delay,CONFIG_DEFAULT_KEY_LOAD_DELAY);
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
rewriteConfigYesNoOption(state,"tls-cluster",server.tls_cluster,0);
|
||||||
|
rewriteConfigYesNoOption(state,"tls-replication",server.tls_replication,0);
|
||||||
|
rewriteConfigYesNoOption(state,"tls-auth-clients",server.tls_auth_clients,1);
|
||||||
|
rewriteConfigStringOption(state,"tls-cert-file",server.tls_ctx_config.cert_file,NULL);
|
||||||
|
rewriteConfigStringOption(state,"tls-key-file",server.tls_ctx_config.key_file,NULL);
|
||||||
|
rewriteConfigStringOption(state,"tls-dh-params-file",server.tls_ctx_config.dh_params_file,NULL);
|
||||||
|
rewriteConfigStringOption(state,"tls-ca-cert-file",server.tls_ctx_config.ca_cert_file,NULL);
|
||||||
|
rewriteConfigStringOption(state,"tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir,NULL);
|
||||||
|
rewriteConfigStringOption(state,"tls-protocols",server.tls_ctx_config.protocols,NULL);
|
||||||
|
rewriteConfigStringOption(state,"tls-ciphers",server.tls_ctx_config.ciphers,NULL);
|
||||||
|
rewriteConfigStringOption(state,"tls-ciphersuites",server.tls_ctx_config.ciphersuites,NULL);
|
||||||
|
rewriteConfigYesNoOption(state,"tls-prefer-server-ciphers",server.tls_ctx_config.prefer_server_ciphers,0);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Rewrite Sentinel config if in Sentinel mode. */
|
/* Rewrite Sentinel config if in Sentinel mode. */
|
||||||
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
||||||
|
407
src/connection.c
Normal file
407
src/connection.c
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "server.h"
|
||||||
|
#include "connhelpers.h"
|
||||||
|
|
||||||
|
/* The connections module provides a lean abstraction of network connections
|
||||||
|
* to avoid direct socket and async event management across the Redis code base.
|
||||||
|
*
|
||||||
|
* It does NOT provide advanced connection features commonly found in similar
|
||||||
|
* libraries such as complete in/out buffer management, throttling, etc. These
|
||||||
|
* functions remain in networking.c.
|
||||||
|
*
|
||||||
|
* The primary goal is to allow transparent handling of TCP and TLS based
|
||||||
|
* connections. To do so, connections have the following properties:
|
||||||
|
*
|
||||||
|
* 1. A connection may live before its corresponding socket exists. This
|
||||||
|
* allows various context and configuration setting to be handled before
|
||||||
|
* establishing the actual connection.
|
||||||
|
* 2. The caller may register/unregister logical read/write handlers to be
|
||||||
|
* called when the connection has data to read from/can accept writes.
|
||||||
|
* These logical handlers may or may not correspond to actual AE events,
|
||||||
|
* depending on the implementation (for TCP they are; for TLS they aren't).
|
||||||
|
*/
|
||||||
|
|
||||||
|
ConnectionType CT_Socket;
|
||||||
|
|
||||||
|
/* When a connection is created we must know its type already, but the
|
||||||
|
* underlying socket may or may not exist:
|
||||||
|
*
|
||||||
|
* - For accepted connections, it exists as we do not model the listen/accept
|
||||||
|
* part; So caller calls connCreateSocket() followed by connAccept().
|
||||||
|
* - For outgoing connections, the socket is created by the connection module
|
||||||
|
* itself; So caller calls connCreateSocket() followed by connConnect(),
|
||||||
|
* which registers a connect callback that fires on connected/error state
|
||||||
|
* (and after any transport level handshake was done).
|
||||||
|
*
|
||||||
|
* NOTE: An earlier version relied on connections being part of other structs
|
||||||
|
* and not independently allocated. This could lead to further optimizations
|
||||||
|
* like using container_of(), etc. However it was discontinued in favor of
|
||||||
|
* this approach for these reasons:
|
||||||
|
*
|
||||||
|
* 1. In some cases conns are created/handled outside the context of the
|
||||||
|
* containing struct, in which case it gets a bit awkward to copy them.
|
||||||
|
* 2. Future implementations may wish to allocate arbitrary data for the
|
||||||
|
* connection.
|
||||||
|
* 3. The container_of() approach is anyway risky because connections may
|
||||||
|
* be embedded in different structs, not just client.
|
||||||
|
*/
|
||||||
|
|
||||||
|
connection *connCreateSocket() {
|
||||||
|
connection *conn = zcalloc(sizeof(connection));
|
||||||
|
conn->type = &CT_Socket;
|
||||||
|
conn->fd = -1;
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a new socket-type connection that is already associated with
|
||||||
|
* an accepted connection.
|
||||||
|
*
|
||||||
|
* The socket is not read for I/O until connAccept() was called and
|
||||||
|
* invoked the connection-level accept handler.
|
||||||
|
*/
|
||||||
|
connection *connCreateAcceptedSocket(int fd) {
|
||||||
|
connection *conn = connCreateSocket();
|
||||||
|
conn->fd = fd;
|
||||||
|
conn->state = CONN_STATE_ACCEPTING;
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connSocketConnect(connection *conn, const char *addr, int port, const char *src_addr,
|
||||||
|
ConnectionCallbackFunc connect_handler) {
|
||||||
|
int fd = anetTcpNonBlockBestEffortBindConnect(NULL,addr,port,src_addr);
|
||||||
|
if (fd == -1) {
|
||||||
|
conn->state = CONN_STATE_ERROR;
|
||||||
|
conn->last_errno = errno;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->fd = fd;
|
||||||
|
conn->state = CONN_STATE_CONNECTING;
|
||||||
|
|
||||||
|
conn->conn_handler = connect_handler;
|
||||||
|
aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,
|
||||||
|
conn->type->ae_handler, conn);
|
||||||
|
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns true if a write handler is registered */
|
||||||
|
int connHasWriteHandler(connection *conn) {
|
||||||
|
return conn->write_handler != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns true if a read handler is registered */
|
||||||
|
int connHasReadHandler(connection *conn) {
|
||||||
|
return conn->read_handler != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Associate a private data pointer with the connection */
|
||||||
|
void connSetPrivateData(connection *conn, void *data) {
|
||||||
|
conn->private_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the associated private data pointer */
|
||||||
|
void *connGetPrivateData(connection *conn) {
|
||||||
|
return conn->private_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------ Pure socket connections ------- */
|
||||||
|
|
||||||
|
/* A very incomplete list of implementation-specific calls. Much of the above shall
|
||||||
|
* move here as we implement additional connection types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Close the connection and free resources. */
|
||||||
|
static void connSocketClose(connection *conn) {
|
||||||
|
if (conn->fd != -1) {
|
||||||
|
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
|
||||||
|
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||||
|
close(conn->fd);
|
||||||
|
conn->fd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If called from within a handler, schedule the close but
|
||||||
|
* keep the connection until the handler returns.
|
||||||
|
*/
|
||||||
|
if (conn->flags & CONN_FLAG_IN_HANDLER) {
|
||||||
|
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
zfree(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connSocketWrite(connection *conn, const void *data, size_t data_len) {
|
||||||
|
int ret = write(conn->fd, data, data_len);
|
||||||
|
if (ret < 0 && errno != EAGAIN) {
|
||||||
|
conn->last_errno = errno;
|
||||||
|
conn->state = CONN_STATE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
|
||||||
|
int ret = read(conn->fd, buf, buf_len);
|
||||||
|
if (!ret) {
|
||||||
|
conn->state = CONN_STATE_CLOSED;
|
||||||
|
} else if (ret < 0 && errno != EAGAIN) {
|
||||||
|
conn->last_errno = errno;
|
||||||
|
conn->state = CONN_STATE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||||
|
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
|
||||||
|
conn->state = CONN_STATE_CONNECTED;
|
||||||
|
if (!callHandler(conn, accept_handler)) return C_ERR;
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register a write handler, to be called when the connection is writable.
|
||||||
|
* If NULL, the existing handler is removed.
|
||||||
|
*
|
||||||
|
* The barrier flag indicates a write barrier is requested, resulting with
|
||||||
|
* CONN_FLAG_WRITE_BARRIER set. This will ensure that the write handler is
|
||||||
|
* always called before and not after the read handler in a single event
|
||||||
|
* loop.
|
||||||
|
*/
|
||||||
|
static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
|
||||||
|
if (func == conn->write_handler) return C_OK;
|
||||||
|
|
||||||
|
conn->write_handler = func;
|
||||||
|
if (barrier)
|
||||||
|
conn->flags |= CONN_FLAG_WRITE_BARRIER;
|
||||||
|
else
|
||||||
|
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
|
||||||
|
if (!conn->write_handler)
|
||||||
|
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||||
|
else
|
||||||
|
if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
|
||||||
|
conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register a read handler, to be called when the connection is readable.
|
||||||
|
* If NULL, the existing handler is removed.
|
||||||
|
*/
|
||||||
|
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
|
||||||
|
if (func == conn->read_handler) return C_OK;
|
||||||
|
|
||||||
|
conn->read_handler = func;
|
||||||
|
if (!conn->read_handler)
|
||||||
|
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
|
||||||
|
else
|
||||||
|
if (aeCreateFileEvent(server.el,conn->fd,
|
||||||
|
AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *connSocketGetLastError(connection *conn) {
|
||||||
|
return strerror(conn->last_errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
|
||||||
|
{
|
||||||
|
UNUSED(el);
|
||||||
|
UNUSED(fd);
|
||||||
|
connection *conn = clientData;
|
||||||
|
|
||||||
|
if (conn->state == CONN_STATE_CONNECTING &&
|
||||||
|
(mask & AE_WRITABLE) && conn->conn_handler) {
|
||||||
|
|
||||||
|
if (connGetSocketError(conn)) {
|
||||||
|
conn->last_errno = errno;
|
||||||
|
conn->state = CONN_STATE_ERROR;
|
||||||
|
} else {
|
||||||
|
conn->state = CONN_STATE_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
|
||||||
|
|
||||||
|
if (!callHandler(conn, conn->conn_handler)) return;
|
||||||
|
conn->conn_handler = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normally we execute the readable event first, and the writable
|
||||||
|
* event later. This is useful as sometimes we may be able
|
||||||
|
* to serve the reply of a query immediately after processing the
|
||||||
|
* query.
|
||||||
|
*
|
||||||
|
* However if WRITE_BARRIER is set in the mask, our application is
|
||||||
|
* asking us to do the reverse: never fire the writable event
|
||||||
|
* after the readable. In such a case, we invert the calls.
|
||||||
|
* This is useful when, for instance, we want to do things
|
||||||
|
* in the beforeSleep() hook, like fsync'ing a file to disk,
|
||||||
|
* before replying to a client. */
|
||||||
|
int invert = conn->flags & CONN_FLAG_WRITE_BARRIER;
|
||||||
|
|
||||||
|
int call_write = (mask & AE_WRITABLE) && conn->write_handler;
|
||||||
|
int call_read = (mask & AE_READABLE) && conn->read_handler;
|
||||||
|
|
||||||
|
/* Handle normal I/O flows */
|
||||||
|
if (!invert && call_read) {
|
||||||
|
if (!callHandler(conn, conn->read_handler)) return;
|
||||||
|
}
|
||||||
|
/* Fire the writable event. */
|
||||||
|
if (call_write) {
|
||||||
|
if (!callHandler(conn, conn->write_handler)) return;
|
||||||
|
}
|
||||||
|
/* If we have to invert the call, fire the readable event now
|
||||||
|
* after the writable one. */
|
||||||
|
if (invert && call_read) {
|
||||||
|
if (!callHandler(conn, conn->read_handler)) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
|
||||||
|
int fd = anetTcpNonBlockConnect(NULL,addr,port);
|
||||||
|
if (fd == -1) {
|
||||||
|
conn->state = CONN_STATE_ERROR;
|
||||||
|
conn->last_errno = errno;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) {
|
||||||
|
conn->state = CONN_STATE_ERROR;
|
||||||
|
conn->last_errno = ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->fd = fd;
|
||||||
|
conn->state = CONN_STATE_CONNECTED;
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connection-based versions of syncio.c functions.
|
||||||
|
* NOTE: This should ideally be refactored out in favor of pure async work.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static ssize_t connSocketSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
return syncWrite(conn->fd, ptr, size, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t connSocketSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
return syncRead(conn->fd, ptr, size, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
return syncReadLine(conn->fd, ptr, size, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ConnectionType CT_Socket = {
|
||||||
|
.ae_handler = connSocketEventHandler,
|
||||||
|
.close = connSocketClose,
|
||||||
|
.write = connSocketWrite,
|
||||||
|
.read = connSocketRead,
|
||||||
|
.accept = connSocketAccept,
|
||||||
|
.connect = connSocketConnect,
|
||||||
|
.set_write_handler = connSocketSetWriteHandler,
|
||||||
|
.set_read_handler = connSocketSetReadHandler,
|
||||||
|
.get_last_error = connSocketGetLastError,
|
||||||
|
.blocking_connect = connSocketBlockingConnect,
|
||||||
|
.sync_write = connSocketSyncWrite,
|
||||||
|
.sync_read = connSocketSyncRead,
|
||||||
|
.sync_readline = connSocketSyncReadLine
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int connGetSocketError(connection *conn) {
|
||||||
|
int sockerr = 0;
|
||||||
|
socklen_t errlen = sizeof(sockerr);
|
||||||
|
|
||||||
|
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
|
||||||
|
sockerr = errno;
|
||||||
|
return sockerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||||
|
return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
|
||||||
|
return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
|
||||||
|
return anetSockName(conn->fd, ip, ip_len, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connBlock(connection *conn) {
|
||||||
|
if (conn->fd == -1) return C_ERR;
|
||||||
|
return anetBlock(NULL, conn->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connNonBlock(connection *conn) {
|
||||||
|
if (conn->fd == -1) return C_ERR;
|
||||||
|
return anetNonBlock(NULL, conn->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connEnableTcpNoDelay(connection *conn) {
|
||||||
|
if (conn->fd == -1) return C_ERR;
|
||||||
|
return anetEnableTcpNoDelay(NULL, conn->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connDisableTcpNoDelay(connection *conn) {
|
||||||
|
if (conn->fd == -1) return C_ERR;
|
||||||
|
return anetDisableTcpNoDelay(NULL, conn->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connKeepAlive(connection *conn, int interval) {
|
||||||
|
if (conn->fd == -1) return C_ERR;
|
||||||
|
return anetKeepAlive(NULL, conn->fd, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connSendTimeout(connection *conn, long long ms) {
|
||||||
|
return anetSendTimeout(NULL, conn->fd, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connRecvTimeout(connection *conn, long long ms) {
|
||||||
|
return anetRecvTimeout(NULL, conn->fd, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int connGetState(connection *conn) {
|
||||||
|
return conn->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return a text that describes the connection, suitable for inclusion
|
||||||
|
* in CLIENT LIST and similar outputs.
|
||||||
|
*
|
||||||
|
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
|
||||||
|
*/
|
||||||
|
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
|
||||||
|
snprintf(buf, buf_len-1, "fd=%i", conn->fd);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
220
src/connection.h
Normal file
220
src/connection.h
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __REDIS_CONNECTION_H
|
||||||
|
#define __REDIS_CONNECTION_H
|
||||||
|
|
||||||
|
#define CONN_INFO_LEN 32
|
||||||
|
|
||||||
|
struct aeEventLoop;
|
||||||
|
typedef struct connection connection;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CONN_STATE_NONE = 0,
|
||||||
|
CONN_STATE_CONNECTING,
|
||||||
|
CONN_STATE_ACCEPTING,
|
||||||
|
CONN_STATE_CONNECTED,
|
||||||
|
CONN_STATE_CLOSED,
|
||||||
|
CONN_STATE_ERROR
|
||||||
|
} ConnectionState;
|
||||||
|
|
||||||
|
#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
|
||||||
|
#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
|
||||||
|
#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */
|
||||||
|
|
||||||
|
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
|
||||||
|
|
||||||
|
typedef struct ConnectionType {
|
||||||
|
void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
|
||||||
|
int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
|
||||||
|
int (*write)(struct connection *conn, const void *data, size_t data_len);
|
||||||
|
int (*read)(struct connection *conn, void *buf, size_t buf_len);
|
||||||
|
void (*close)(struct connection *conn);
|
||||||
|
int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
|
||||||
|
int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier);
|
||||||
|
int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler);
|
||||||
|
const char *(*get_last_error)(struct connection *conn);
|
||||||
|
int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
|
||||||
|
ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||||
|
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||||
|
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
|
||||||
|
} ConnectionType;
|
||||||
|
|
||||||
|
struct connection {
|
||||||
|
ConnectionType *type;
|
||||||
|
ConnectionState state;
|
||||||
|
int flags;
|
||||||
|
int last_errno;
|
||||||
|
void *private_data;
|
||||||
|
ConnectionCallbackFunc conn_handler;
|
||||||
|
ConnectionCallbackFunc write_handler;
|
||||||
|
ConnectionCallbackFunc read_handler;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* The connection module does not deal with listening and accepting sockets,
|
||||||
|
* so we assume we have a socket when an incoming connection is created.
|
||||||
|
*
|
||||||
|
* The fd supplied should therefore be associated with an already accept()ed
|
||||||
|
* socket.
|
||||||
|
*
|
||||||
|
* connAccept() may directly call accept_handler(), or return and call it
|
||||||
|
* at a later time. This behavior is a bit awkward but aims to reduce the need
|
||||||
|
* to wait for the next event loop, if no additional handshake is required.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
|
||||||
|
return conn->type->accept(conn, accept_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Establish a connection. The connect_handler will be called when the connection
|
||||||
|
* is established, or if an error has occured.
|
||||||
|
*
|
||||||
|
* The connection handler will be responsible to set up any read/write handlers
|
||||||
|
* as needed.
|
||||||
|
*
|
||||||
|
* If C_ERR is returned, the operation failed and the connection handler shall
|
||||||
|
* not be expected.
|
||||||
|
*/
|
||||||
|
static inline int connConnect(connection *conn, const char *addr, int port, const char *src_addr,
|
||||||
|
ConnectionCallbackFunc connect_handler) {
|
||||||
|
return conn->type->connect(conn, addr, port, src_addr, connect_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blocking connect.
|
||||||
|
*
|
||||||
|
* NOTE: This is implemented in order to simplify the transition to the abstract
|
||||||
|
* connections, but should probably be refactored out of cluster.c and replication.c,
|
||||||
|
* in favor of a pure async implementation.
|
||||||
|
*/
|
||||||
|
static inline int connBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
|
||||||
|
return conn->type->blocking_connect(conn, addr, port, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write to connection, behaves the same as write(2).
|
||||||
|
*
|
||||||
|
* Like write(2), a short write is possible. A -1 return indicates an error.
|
||||||
|
*
|
||||||
|
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
|
||||||
|
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
|
||||||
|
*/
|
||||||
|
static inline int connWrite(connection *conn, const void *data, size_t data_len) {
|
||||||
|
return conn->type->write(conn, data, data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read from the connection, behaves the same as read(2).
|
||||||
|
*
|
||||||
|
* Like read(2), a short read is possible. A return value of 0 will indicate the
|
||||||
|
* connection was closed, and -1 will indicate an error.
|
||||||
|
*
|
||||||
|
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
|
||||||
|
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
|
||||||
|
*/
|
||||||
|
static inline int connRead(connection *conn, void *buf, size_t buf_len) {
|
||||||
|
return conn->type->read(conn, buf, buf_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register a write handler, to be called when the connection is writable.
|
||||||
|
* If NULL, the existing handler is removed.
|
||||||
|
*/
|
||||||
|
static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func) {
|
||||||
|
return conn->type->set_write_handler(conn, func, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register a read handler, to be called when the connection is readable.
|
||||||
|
* If NULL, the existing handler is removed.
|
||||||
|
*/
|
||||||
|
static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
|
||||||
|
return conn->type->set_read_handler(conn, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a write handler, and possibly enable a write barrier, this flag is
|
||||||
|
* cleared when write handler is changed or removed.
|
||||||
|
* With barroer enabled, we never fire the event if the read handler already
|
||||||
|
* fired in the same event loop iteration. Useful when you want to persist
|
||||||
|
* things to disk before sending replies, and want to do that in a group fashion. */
|
||||||
|
static inline int connSetWriteHandlerWithBarrier(connection *conn, ConnectionCallbackFunc func, int barrier) {
|
||||||
|
return conn->type->set_write_handler(conn, func, barrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void connClose(connection *conn) {
|
||||||
|
conn->type->close(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the last error encountered by the connection, as a string. If no error,
|
||||||
|
* a NULL is returned.
|
||||||
|
*/
|
||||||
|
static inline const char *connGetLastError(connection *conn) {
|
||||||
|
return conn->type->get_last_error(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline ssize_t connSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
return conn->type->sync_write(conn, ptr, size, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline ssize_t connSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
return conn->type->sync_read(conn, ptr, size, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
return conn->type->sync_readline(conn, ptr, size, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection *connCreateSocket();
|
||||||
|
connection *connCreateAcceptedSocket(int fd);
|
||||||
|
|
||||||
|
connection *connCreateTLS();
|
||||||
|
connection *connCreateAcceptedTLS(int fd, int require_auth);
|
||||||
|
|
||||||
|
void connSetPrivateData(connection *conn, void *data);
|
||||||
|
void *connGetPrivateData(connection *conn);
|
||||||
|
int connGetState(connection *conn);
|
||||||
|
int connHasWriteHandler(connection *conn);
|
||||||
|
int connHasReadHandler(connection *conn);
|
||||||
|
int connGetSocketError(connection *conn);
|
||||||
|
|
||||||
|
/* anet-style wrappers to conns */
|
||||||
|
int connBlock(connection *conn);
|
||||||
|
int connNonBlock(connection *conn);
|
||||||
|
int connEnableTcpNoDelay(connection *conn);
|
||||||
|
int connDisableTcpNoDelay(connection *conn);
|
||||||
|
int connKeepAlive(connection *conn, int interval);
|
||||||
|
int connSendTimeout(connection *conn, long long ms);
|
||||||
|
int connRecvTimeout(connection *conn, long long ms);
|
||||||
|
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
|
||||||
|
int connFormatPeer(connection *conn, char *buf, size_t buf_len);
|
||||||
|
int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
|
||||||
|
const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
|
||||||
|
|
||||||
|
/* Helpers for tls special considerations */
|
||||||
|
int tlsHasPendingData();
|
||||||
|
void tlsProcessPendingData();
|
||||||
|
|
||||||
|
#endif /* __REDIS_CONNECTION_H */
|
85
src/connhelpers.h
Normal file
85
src/connhelpers.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __REDIS_CONNHELPERS_H
|
||||||
|
#define __REDIS_CONNHELPERS_H
|
||||||
|
|
||||||
|
#include "connection.h"
|
||||||
|
|
||||||
|
/* These are helper functions that are common to different connection
|
||||||
|
* implementations (currently sockets in connection.c and TLS in tls.c).
|
||||||
|
*
|
||||||
|
* Currently helpers implement the mechanisms for invoking connection
|
||||||
|
* handlers, tracking in-handler states and dealing with deferred
|
||||||
|
* destruction (if invoked by a handler).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Called whenever a handler is invoked on a connection and sets the
|
||||||
|
* CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
|
||||||
|
*
|
||||||
|
* An attempt to close a connection while CONN_FLAG_IN_HANDLER is
|
||||||
|
* set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED
|
||||||
|
* instead of destructing it.
|
||||||
|
*/
|
||||||
|
static inline void enterHandler(connection *conn) {
|
||||||
|
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER
|
||||||
|
* flag and performs actual close/destruction if a deferred close was
|
||||||
|
* scheduled by the handler.
|
||||||
|
*/
|
||||||
|
static inline int exitHandler(connection *conn) {
|
||||||
|
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||||
|
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||||
|
connClose(conn);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helper for connection implementations to call handlers:
|
||||||
|
* 1. Mark the handler in use.
|
||||||
|
* 2. Execute the handler (if set).
|
||||||
|
* 3. Mark the handler as NOT in use and perform deferred close if was
|
||||||
|
* requested by the handler at any time.
|
||||||
|
*/
|
||||||
|
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
|
||||||
|
conn->flags |= CONN_FLAG_IN_HANDLER;
|
||||||
|
if (handler) handler(conn);
|
||||||
|
conn->flags &= ~CONN_FLAG_IN_HANDLER;
|
||||||
|
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
|
||||||
|
connClose(conn);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __REDIS_CONNHELPERS_H */
|
82
src/db.c
82
src/db.c
@ -60,10 +60,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
|||||||
/* Update the access time for the ageing algorithm.
|
/* Update the access time for the ageing algorithm.
|
||||||
* Don't do it if we have a saving child, as this will trigger
|
* Don't do it if we have a saving child, as this will trigger
|
||||||
* a copy on write madness. */
|
* a copy on write madness. */
|
||||||
if (server.rdb_child_pid == -1 &&
|
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
|
||||||
server.aof_child_pid == -1 &&
|
|
||||||
!(flags & LOOKUP_NOTOUCH))
|
|
||||||
{
|
|
||||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||||
updateLFU(val);
|
updateLFU(val);
|
||||||
} else {
|
} else {
|
||||||
@ -154,9 +151,13 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
|
|||||||
*
|
*
|
||||||
* Returns the linked value object if the key exists or NULL if the key
|
* Returns the linked value object if the key exists or NULL if the key
|
||||||
* does not exist in the specified DB. */
|
* does not exist in the specified DB. */
|
||||||
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
|
||||||
expireIfNeeded(db,key);
|
expireIfNeeded(db,key);
|
||||||
return lookupKey(db,key,LOOKUP_NONE);
|
return lookupKey(db,key,flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
robj *lookupKeyWrite(redisDb *db, robj *key) {
|
||||||
|
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
|
||||||
@ -353,6 +354,12 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fire the flushdb modules event. */
|
||||||
|
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
|
||||||
|
REDISMODULE_SUBEVENT_FLUSHDB_START,
|
||||||
|
&fi);
|
||||||
|
|
||||||
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
/* Make sure the WATCHed keys are affected by the FLUSH* commands.
|
||||||
* Note that we need to call the function while the keys are still
|
* Note that we need to call the function while the keys are still
|
||||||
* there. */
|
* there. */
|
||||||
@ -383,6 +390,13 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dbnum == -1) flushSlaveKeysWithExpireList();
|
if (dbnum == -1) flushSlaveKeysWithExpireList();
|
||||||
|
|
||||||
|
/* Also fire the end event. Note that this event will fire almost
|
||||||
|
* immediately after the start event if the flush is asynchronous. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
|
||||||
|
REDISMODULE_SUBEVENT_FLUSHDB_END,
|
||||||
|
&fi);
|
||||||
|
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,26 +465,9 @@ int getFlushCommandFlags(client *c, int *flags) {
|
|||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FLUSHDB [ASYNC]
|
/* Flushes the whole server data set. */
|
||||||
*
|
void flushAllDataAndResetRDB(int flags) {
|
||||||
* Flushes the currently SELECTed Redis DB. */
|
|
||||||
void flushdbCommand(client *c) {
|
|
||||||
int flags;
|
|
||||||
|
|
||||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
|
||||||
server.dirty += emptyDb(c->db->id,flags,NULL);
|
|
||||||
addReply(c,shared.ok);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FLUSHALL [ASYNC]
|
|
||||||
*
|
|
||||||
* Flushes the whole server data set. */
|
|
||||||
void flushallCommand(client *c) {
|
|
||||||
int flags;
|
|
||||||
|
|
||||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
|
||||||
server.dirty += emptyDb(-1,flags,NULL);
|
server.dirty += emptyDb(-1,flags,NULL);
|
||||||
addReply(c,shared.ok);
|
|
||||||
if (server.rdb_child_pid != -1) killRDBChild();
|
if (server.rdb_child_pid != -1) killRDBChild();
|
||||||
if (server.saveparamslen > 0) {
|
if (server.saveparamslen > 0) {
|
||||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||||
@ -482,6 +479,41 @@ void flushallCommand(client *c) {
|
|||||||
server.dirty = saved_dirty;
|
server.dirty = saved_dirty;
|
||||||
}
|
}
|
||||||
server.dirty++;
|
server.dirty++;
|
||||||
|
#if defined(USE_JEMALLOC)
|
||||||
|
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||||
|
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||||
|
* harm and this way the flush and purge will be synchroneus. */
|
||||||
|
if (!(flags & EMPTYDB_ASYNC))
|
||||||
|
jemalloc_purge();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FLUSHDB [ASYNC]
|
||||||
|
*
|
||||||
|
* Flushes the currently SELECTed Redis DB. */
|
||||||
|
void flushdbCommand(client *c) {
|
||||||
|
int flags;
|
||||||
|
|
||||||
|
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||||
|
server.dirty += emptyDb(c->db->id,flags,NULL);
|
||||||
|
addReply(c,shared.ok);
|
||||||
|
#if defined(USE_JEMALLOC)
|
||||||
|
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||||
|
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||||
|
* harm and this way the flush and purge will be synchroneus. */
|
||||||
|
if (!(flags & EMPTYDB_ASYNC))
|
||||||
|
jemalloc_purge();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FLUSHALL [ASYNC]
|
||||||
|
*
|
||||||
|
* Flushes the whole server data set. */
|
||||||
|
void flushallCommand(client *c) {
|
||||||
|
int flags;
|
||||||
|
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||||
|
flushAllDataAndResetRDB(flags);
|
||||||
|
addReply(c,shared.ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This command implements DEL and LAZYDEL. */
|
/* This command implements DEL and LAZYDEL. */
|
||||||
|
113
src/debug.c
113
src/debug.c
@ -297,6 +297,56 @@ void computeDatasetDigest(unsigned char *final) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_JEMALLOC
|
||||||
|
void mallctl_int(client *c, robj **argv, int argc) {
|
||||||
|
int ret;
|
||||||
|
/* start with the biggest size (int64), and if that fails, try smaller sizes (int32, bool) */
|
||||||
|
int64_t old = 0, val;
|
||||||
|
if (argc > 1) {
|
||||||
|
long long ll;
|
||||||
|
if (getLongLongFromObjectOrReply(c, argv[1], &ll, NULL) != C_OK)
|
||||||
|
return;
|
||||||
|
val = ll;
|
||||||
|
}
|
||||||
|
size_t sz = sizeof(old);
|
||||||
|
while (sz > 0) {
|
||||||
|
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
|
||||||
|
if (ret==EINVAL) {
|
||||||
|
/* size might be wrong, try a smaller one */
|
||||||
|
sz /= 2;
|
||||||
|
#if BYTE_ORDER == BIG_ENDIAN
|
||||||
|
val <<= 8*sz;
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
#if BYTE_ORDER == BIG_ENDIAN
|
||||||
|
old >>= 64 - 8*sz;
|
||||||
|
#endif
|
||||||
|
addReplyLongLong(c, old);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addReplyErrorFormat(c,"%s", strerror(EINVAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallctl_string(client *c, robj **argv, int argc) {
|
||||||
|
int ret;
|
||||||
|
char *old;
|
||||||
|
size_t sz = sizeof(old);
|
||||||
|
/* for strings, it seems we need to first get the old value, before overriding it. */
|
||||||
|
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
|
||||||
|
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addReplyBulkCString(c, old);
|
||||||
|
if(argc > 1)
|
||||||
|
je_mallctl(argv[0]->ptr, NULL, 0, &argv[1]->ptr, sizeof(char*));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void debugCommand(client *c) {
|
void debugCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
@ -319,10 +369,15 @@ void debugCommand(client *c) {
|
|||||||
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
||||||
"SEGFAULT -- Crash the server with sigsegv.",
|
"SEGFAULT -- Crash the server with sigsegv.",
|
||||||
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
|
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
|
||||||
|
"AOF-FLUSH-SLEEP <microsec> -- Server will sleep before flushing the AOF, this is used for testing",
|
||||||
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
|
||||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||||
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
||||||
|
#ifdef USE_JEMALLOC
|
||||||
|
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
|
||||||
|
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
|
||||||
|
#endif
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -362,7 +417,7 @@ NULL
|
|||||||
}
|
}
|
||||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||||
protectClient(c);
|
protectClient(c);
|
||||||
int ret = rdbLoad(server.rdb_filename,NULL);
|
int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
|
||||||
unprotectClient(c);
|
unprotectClient(c);
|
||||||
if (ret != C_OK) {
|
if (ret != C_OK) {
|
||||||
addReplyError(c,"Error trying to load the RDB dump");
|
addReplyError(c,"Error trying to load the RDB dump");
|
||||||
@ -595,6 +650,11 @@ NULL
|
|||||||
{
|
{
|
||||||
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
server.active_expire_enabled = atoi(c->argv[2]->ptr);
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
|
} else if (!strcasecmp(c->argv[1]->ptr,"aof-flush-sleep") &&
|
||||||
|
c->argc == 3)
|
||||||
|
{
|
||||||
|
server.aof_flush_sleep = atoi(c->argv[2]->ptr);
|
||||||
|
addReply(c,shared.ok);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"lua-always-replicate-commands") &&
|
} else if (!strcasecmp(c->argv[1]->ptr,"lua-always-replicate-commands") &&
|
||||||
c->argc == 3)
|
c->argc == 3)
|
||||||
{
|
{
|
||||||
@ -638,7 +698,8 @@ NULL
|
|||||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
||||||
stats = sdscat(stats,buf);
|
stats = sdscat(stats,buf);
|
||||||
|
|
||||||
addReplyBulkSds(c,stats);
|
addReplyVerbatim(c,stats,sdslen(stats),"txt");
|
||||||
|
sdsfree(stats);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
|
||||||
robj *o;
|
robj *o;
|
||||||
dict *ht = NULL;
|
dict *ht = NULL;
|
||||||
@ -665,7 +726,7 @@ NULL
|
|||||||
} else {
|
} else {
|
||||||
char buf[4096];
|
char buf[4096];
|
||||||
dictGetStats(buf,sizeof(buf),ht);
|
dictGetStats(buf,sizeof(buf),ht);
|
||||||
addReplyBulkCString(c,buf);
|
addReplyVerbatim(c,buf,strlen(buf),"txt");
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
||||||
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
||||||
@ -676,6 +737,14 @@ NULL
|
|||||||
{
|
{
|
||||||
stringmatchlen_fuzz_test();
|
stringmatchlen_fuzz_test();
|
||||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||||
|
#ifdef USE_JEMALLOC
|
||||||
|
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl") && c->argc >= 3) {
|
||||||
|
mallctl_int(c, c->argv+2, c->argc-2);
|
||||||
|
return;
|
||||||
|
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl-str") && c->argc >= 3) {
|
||||||
|
mallctl_string(c, c->argv+2, c->argc-2);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
addReplySubcommandSyntaxError(c);
|
addReplySubcommandSyntaxError(c);
|
||||||
return;
|
return;
|
||||||
@ -699,11 +768,12 @@ void _serverAssert(const char *estr, const char *file, int line) {
|
|||||||
|
|
||||||
void _serverAssertPrintClientInfo(const client *c) {
|
void _serverAssertPrintClientInfo(const client *c) {
|
||||||
int j;
|
int j;
|
||||||
|
char conninfo[CONN_INFO_LEN];
|
||||||
|
|
||||||
bugReportStart();
|
bugReportStart();
|
||||||
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
|
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
|
||||||
serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long)c->flags);
|
serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long) c->flags);
|
||||||
serverLog(LL_WARNING,"client->fd = %d", c->fd);
|
serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
|
||||||
serverLog(LL_WARNING,"client->argc = %d", c->argc);
|
serverLog(LL_WARNING,"client->argc = %d", c->argc);
|
||||||
for (j=0; j < c->argc; j++) {
|
for (j=0; j < c->argc; j++) {
|
||||||
char buf[128];
|
char buf[128];
|
||||||
@ -1110,6 +1180,33 @@ void logRegisters(ucontext_t *uc) {
|
|||||||
(unsigned long) uc->uc_mcontext.mc_cs
|
(unsigned long) uc->uc_mcontext.mc_cs
|
||||||
);
|
);
|
||||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||||
|
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"\n"
|
||||||
|
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||||
|
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||||
|
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||||
|
"X30:%016lx\n"
|
||||||
|
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[18],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[19],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[20],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[21],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[22],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[23],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[24],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[25],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[26],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[27],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[28],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[29],
|
||||||
|
(unsigned long) uc->uc_mcontext.regs[30],
|
||||||
|
(unsigned long) uc->uc_mcontext.pc,
|
||||||
|
(unsigned long) uc->uc_mcontext.sp,
|
||||||
|
(unsigned long) uc->uc_mcontext.pstate,
|
||||||
|
(unsigned long) uc->uc_mcontext.fault_address
|
||||||
|
);
|
||||||
|
logStackContent((void**)uc->uc_mcontext.sp);
|
||||||
#else
|
#else
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
" Dumping of registers not supported for this OS/arch");
|
" Dumping of registers not supported for this OS/arch");
|
||||||
@ -1337,6 +1434,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
|||||||
/* Log dump of processor registers */
|
/* Log dump of processor registers */
|
||||||
logRegisters(uc);
|
logRegisters(uc);
|
||||||
|
|
||||||
|
/* Log Modules INFO */
|
||||||
|
serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
|
||||||
|
infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
|
||||||
|
serverLogRaw(LL_WARNING|LL_RAW, infostring);
|
||||||
|
sdsfree(infostring);
|
||||||
|
|
||||||
#if defined(HAVE_PROC_MAPS)
|
#if defined(HAVE_PROC_MAPS)
|
||||||
/* Test memory */
|
/* Test memory */
|
||||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
|
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
|
||||||
|
@ -374,7 +374,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
|||||||
if ((newele = activeDefragStringOb(ele, &defragged)))
|
if ((newele = activeDefragStringOb(ele, &defragged)))
|
||||||
de->v.val = newele, defragged++;
|
de->v.val = newele, defragged++;
|
||||||
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
|
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
|
||||||
void *newptr, *ptr = ln->value;
|
void *newptr, *ptr = dictGetVal(de);
|
||||||
if ((newptr = activeDefragAlloc(ptr)))
|
if ((newptr = activeDefragAlloc(ptr)))
|
||||||
ln->value = newptr, defragged++;
|
ln->value = newptr, defragged++;
|
||||||
}
|
}
|
||||||
@ -1039,7 +1039,7 @@ void activeDefragCycle(void) {
|
|||||||
mstime_t latency;
|
mstime_t latency;
|
||||||
int quit = 0;
|
int quit = 0;
|
||||||
|
|
||||||
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1)
|
if (hasActiveChildProcess())
|
||||||
return; /* Defragging memory while there's a fork will just do damage. */
|
return; /* Defragging memory while there's a fork will just do damage. */
|
||||||
|
|
||||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||||
|
@ -444,6 +444,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
|||||||
* Otehrwise if we are over the memory limit, but not enough memory
|
* Otehrwise if we are over the memory limit, but not enough memory
|
||||||
* was freed to return back under the limit, the function returns C_ERR. */
|
* was freed to return back under the limit, the function returns C_ERR. */
|
||||||
int freeMemoryIfNeeded(void) {
|
int freeMemoryIfNeeded(void) {
|
||||||
|
int keys_freed = 0;
|
||||||
/* By default replicas should ignore maxmemory
|
/* By default replicas should ignore maxmemory
|
||||||
* and just be masters exact copies. */
|
* and just be masters exact copies. */
|
||||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||||
@ -467,7 +468,7 @@ int freeMemoryIfNeeded(void) {
|
|||||||
|
|
||||||
latencyStartMonitor(latency);
|
latencyStartMonitor(latency);
|
||||||
while (mem_freed < mem_tofree) {
|
while (mem_freed < mem_tofree) {
|
||||||
int j, k, i, keys_freed = 0;
|
int j, k, i;
|
||||||
static unsigned int next_db = 0;
|
static unsigned int next_db = 0;
|
||||||
sds bestkey = NULL;
|
sds bestkey = NULL;
|
||||||
int bestdbid;
|
int bestdbid;
|
||||||
@ -598,9 +599,7 @@ int freeMemoryIfNeeded(void) {
|
|||||||
mem_freed = mem_tofree;
|
mem_freed = mem_tofree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (!keys_freed) {
|
|
||||||
latencyEndMonitor(latency);
|
latencyEndMonitor(latency);
|
||||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||||
goto cant_free; /* nothing to free... */
|
goto cant_free; /* nothing to free... */
|
||||||
|
12
src/geo.c
12
src/geo.c
@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
|
|||||||
|
|
||||||
/* Look up the requested zset */
|
/* Look up the requested zset */
|
||||||
robj *zobj = NULL;
|
robj *zobj = NULL;
|
||||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL ||
|
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL ||
|
||||||
checkType(c, zobj, OBJ_ZSET)) {
|
checkType(c, zobj, OBJ_ZSET)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
|
|||||||
|
|
||||||
/* If no matching results, the user gets an empty reply. */
|
/* If no matching results, the user gets an empty reply. */
|
||||||
if (ga->used == 0 && storekey == NULL) {
|
if (ga->used == 0 && storekey == NULL) {
|
||||||
addReplyNull(c);
|
addReply(c,shared.emptyarray);
|
||||||
geoArrayFree(ga);
|
geoArrayFree(ga);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -734,14 +734,14 @@ void geohashCommand(client *c) {
|
|||||||
r[1].max = 90;
|
r[1].max = 90;
|
||||||
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
|
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
|
||||||
|
|
||||||
char buf[12];
|
char buf[11];
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < 11; i++) {
|
for (i = 0; i < 10; i++) {
|
||||||
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||||
buf[i] = geoalphabet[idx];
|
buf[i] = geoalphabet[idx];
|
||||||
}
|
}
|
||||||
buf[11] = '\0';
|
buf[10] = '\0';
|
||||||
addReplyBulkCBuffer(c,buf,11);
|
addReplyBulkCBuffer(c,buf,10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1242,7 +1242,7 @@ void pfcountCommand(client *c) {
|
|||||||
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
||||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||||
|
|
||||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||||
* to MAX(max[i],hll[i]). */
|
* to MAX(max[i],hll[i]). */
|
||||||
if (hllMerge(registers,o) == C_ERR) {
|
if (hllMerge(registers,o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplySds(c,sdsnew(invalid_hll_err));
|
||||||
@ -1329,7 +1329,7 @@ void pfmergeCommand(client *c) {
|
|||||||
hdr = o->ptr;
|
hdr = o->ptr;
|
||||||
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
||||||
|
|
||||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||||
* to MAX(max[i],hll[i]). */
|
* to MAX(max[i],hll[i]). */
|
||||||
if (hllMerge(max,o) == C_ERR) {
|
if (hllMerge(max,o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplySds(c,sdsnew(invalid_hll_err));
|
||||||
|
@ -95,7 +95,7 @@ void latencyMonitorInit(void) {
|
|||||||
* This function is usually called via latencyAddSampleIfNeeded(), that
|
* This function is usually called via latencyAddSampleIfNeeded(), that
|
||||||
* is a macro that only adds the sample if the latency is higher than
|
* is a macro that only adds the sample if the latency is higher than
|
||||||
* server.latency_monitor_threshold. */
|
* server.latency_monitor_threshold. */
|
||||||
void latencyAddSample(char *event, mstime_t latency) {
|
void latencyAddSample(const char *event, mstime_t latency) {
|
||||||
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
|
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
int prev;
|
int prev;
|
||||||
@ -599,7 +599,7 @@ NULL
|
|||||||
event = dictGetKey(de);
|
event = dictGetKey(de);
|
||||||
|
|
||||||
graph = latencyCommandGenSparkeline(event,ts);
|
graph = latencyCommandGenSparkeline(event,ts);
|
||||||
addReplyBulkCString(c,graph);
|
addReplyVerbatim(c,graph,sdslen(graph),"txt");
|
||||||
sdsfree(graph);
|
sdsfree(graph);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
||||||
/* LATENCY LATEST */
|
/* LATENCY LATEST */
|
||||||
@ -608,7 +608,7 @@ NULL
|
|||||||
/* LATENCY DOCTOR */
|
/* LATENCY DOCTOR */
|
||||||
sds report = createLatencyReport();
|
sds report = createLatencyReport();
|
||||||
|
|
||||||
addReplyBulkCBuffer(c,report,sdslen(report));
|
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||||
sdsfree(report);
|
sdsfree(report);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
||||||
/* LATENCY RESET */
|
/* LATENCY RESET */
|
||||||
|
@ -62,7 +62,7 @@ struct latencyStats {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void latencyMonitorInit(void);
|
void latencyMonitorInit(void);
|
||||||
void latencyAddSample(char *event, mstime_t latency);
|
void latencyAddSample(const char *event, mstime_t latency);
|
||||||
int THPIsEnabled(void);
|
int THPIsEnabled(void);
|
||||||
|
|
||||||
/* Latency monitoring macros. */
|
/* Latency monitoring macros. */
|
||||||
|
136
src/lolwut.c
136
src/lolwut.c
@ -34,8 +34,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "lolwut.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
void lolwut5Command(client *c);
|
void lolwut5Command(client *c);
|
||||||
|
void lolwut6Command(client *c);
|
||||||
|
|
||||||
/* The default target for LOLWUT if no matching version was found.
|
/* The default target for LOLWUT if no matching version was found.
|
||||||
* This is what unstable versions of Redis will display. */
|
* This is what unstable versions of Redis will display. */
|
||||||
@ -43,14 +46,143 @@ void lolwutUnstableCommand(client *c) {
|
|||||||
sds rendered = sdsnew("Redis ver. ");
|
sds rendered = sdsnew("Redis ver. ");
|
||||||
rendered = sdscat(rendered,REDIS_VERSION);
|
rendered = sdscat(rendered,REDIS_VERSION);
|
||||||
rendered = sdscatlen(rendered,"\n",1);
|
rendered = sdscatlen(rendered,"\n",1);
|
||||||
addReplyBulkSds(c,rendered);
|
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||||
|
sdsfree(rendered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LOLWUT [VERSION <version>] [... version specific arguments ...] */
|
||||||
void lolwutCommand(client *c) {
|
void lolwutCommand(client *c) {
|
||||||
char *v = REDIS_VERSION;
|
char *v = REDIS_VERSION;
|
||||||
if ((v[0] == '5' && v[1] == '.') ||
|
char verstr[64];
|
||||||
|
|
||||||
|
if (c->argc >= 3 && !strcasecmp(c->argv[1]->ptr,"version")) {
|
||||||
|
long ver;
|
||||||
|
if (getLongFromObjectOrReply(c,c->argv[2],&ver,NULL) != C_OK) return;
|
||||||
|
snprintf(verstr,sizeof(verstr),"%u.0.0",(unsigned int)ver);
|
||||||
|
v = verstr;
|
||||||
|
|
||||||
|
/* Adjust argv/argc to filter the "VERSION ..." option, since the
|
||||||
|
* specific LOLWUT version implementations don't know about it
|
||||||
|
* and expect their arguments. */
|
||||||
|
c->argv += 2;
|
||||||
|
c->argc -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((v[0] == '5' && v[1] == '.' && v[2] != '9') ||
|
||||||
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
||||||
lolwut5Command(c);
|
lolwut5Command(c);
|
||||||
|
else if ((v[0] == '6' && v[1] == '.' && v[2] != '9') ||
|
||||||
|
(v[0] == '5' && v[1] == '.' && v[2] == '9'))
|
||||||
|
lolwut6Command(c);
|
||||||
else
|
else
|
||||||
lolwutUnstableCommand(c);
|
lolwutUnstableCommand(c);
|
||||||
|
|
||||||
|
/* Fix back argc/argv in case of VERSION argument. */
|
||||||
|
if (v == verstr) {
|
||||||
|
c->argv -= 2;
|
||||||
|
c->argc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== LOLWUT Canvase ===============================
|
||||||
|
* Many LOWUT versions will likely print some computer art to the screen.
|
||||||
|
* This is the case with LOLWUT 5 and LOLWUT 6, so here there is a generic
|
||||||
|
* canvas implementation that can be reused. */
|
||||||
|
|
||||||
|
/* Allocate and return a new canvas of the specified size. */
|
||||||
|
lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
|
||||||
|
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||||
|
canvas->width = width;
|
||||||
|
canvas->height = height;
|
||||||
|
canvas->pixels = zmalloc(width*height);
|
||||||
|
memset(canvas->pixels,bgcolor,width*height);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free the canvas created by lwCreateCanvas(). */
|
||||||
|
void lwFreeCanvas(lwCanvas *canvas) {
|
||||||
|
zfree(canvas->pixels);
|
||||||
|
zfree(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
||||||
|
* dot will be displyed, and 1 means dot will be displayed.
|
||||||
|
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
||||||
|
* out of the size of the canvas without issues. */
|
||||||
|
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
||||||
|
if (x < 0 || x >= canvas->width ||
|
||||||
|
y < 0 || y >= canvas->height) return;
|
||||||
|
canvas->pixels[x+y*canvas->width] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the value of the specified pixel on the canvas. */
|
||||||
|
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
||||||
|
if (x < 0 || x >= canvas->width ||
|
||||||
|
y < 0 || y >= canvas->height) return 0;
|
||||||
|
return canvas->pixels[x+y*canvas->width];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
||||||
|
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
||||||
|
int dx = abs(x2-x1);
|
||||||
|
int dy = abs(y2-y1);
|
||||||
|
int sx = (x1 < x2) ? 1 : -1;
|
||||||
|
int sy = (y1 < y2) ? 1 : -1;
|
||||||
|
int err = dx-dy, e2;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
lwDrawPixel(canvas,x1,y1,color);
|
||||||
|
if (x1 == x2 && y1 == y2) break;
|
||||||
|
e2 = err*2;
|
||||||
|
if (e2 > -dy) {
|
||||||
|
err -= dy;
|
||||||
|
x1 += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
y1 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw a square centered at the specified x,y coordinates, with the specified
|
||||||
|
* rotation angle and size. In order to write a rotated square, we use the
|
||||||
|
* trivial fact that the parametric equation:
|
||||||
|
*
|
||||||
|
* x = sin(k)
|
||||||
|
* y = cos(k)
|
||||||
|
*
|
||||||
|
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
||||||
|
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
||||||
|
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
||||||
|
* points of the square. In order to rotate the square, we just start with
|
||||||
|
* k = PI/4 + rotation_angle, and we are done.
|
||||||
|
*
|
||||||
|
* Of course the vanilla equations above will describe the square inside a
|
||||||
|
* circle of radius 1, so in order to draw larger squares we'll have to
|
||||||
|
* multiply the obtained coordinates, and then translate them. However this
|
||||||
|
* is much simpler than implementing the abstract concept of 2D shape and then
|
||||||
|
* performing the rotation/translation transformation, so for LOLWUT it's
|
||||||
|
* a good approach. */
|
||||||
|
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color) {
|
||||||
|
int px[4], py[4];
|
||||||
|
|
||||||
|
/* Adjust the desired size according to the fact that the square inscribed
|
||||||
|
* into a circle of radius 1 has the side of length SQRT(2). This way
|
||||||
|
* size becomes a simple multiplication factor we can use with our
|
||||||
|
* coordinates to magnify them. */
|
||||||
|
size /= 1.4142135623;
|
||||||
|
size = round(size);
|
||||||
|
|
||||||
|
/* Compute the four points. */
|
||||||
|
float k = M_PI/4 + angle;
|
||||||
|
for (int j = 0; j < 4; j++) {
|
||||||
|
px[j] = round(sin(k) * size + x);
|
||||||
|
py[j] = round(cos(k) * size + y);
|
||||||
|
k += M_PI/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw the square. */
|
||||||
|
for (int j = 0; j < 4; j++)
|
||||||
|
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],color);
|
||||||
}
|
}
|
||||||
|
49
src/lolwut.h
Normal file
49
src/lolwut.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* This structure represents our canvas. Drawing functions will take a pointer
|
||||||
|
* to a canvas to write to it. Later the canvas can be rendered to a string
|
||||||
|
* suitable to be printed on the screen, using unicode Braille characters. */
|
||||||
|
|
||||||
|
/* This represents a very simple generic canvas in order to draw stuff.
|
||||||
|
* It's up to each LOLWUT versions to translate what they draw to the
|
||||||
|
* screen, depending on the result to accomplish. */
|
||||||
|
typedef struct lwCanvas {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
char *pixels;
|
||||||
|
} lwCanvas;
|
||||||
|
|
||||||
|
/* Drawing functions implemented inside lolwut.c. */
|
||||||
|
lwCanvas *lwCreateCanvas(int width, int height, int bgcolor);
|
||||||
|
void lwFreeCanvas(lwCanvas *canvas);
|
||||||
|
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color);
|
||||||
|
int lwGetPixel(lwCanvas *canvas, int x, int y);
|
||||||
|
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color);
|
||||||
|
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color);
|
119
src/lolwut5.c
119
src/lolwut5.c
@ -34,17 +34,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "lolwut.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
/* This structure represents our canvas. Drawing functions will take a pointer
|
|
||||||
* to a canvas to write to it. Later the canvas can be rendered to a string
|
|
||||||
* suitable to be printed on the screen, using unicode Braille characters. */
|
|
||||||
typedef struct lwCanvas {
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
char *pixels;
|
|
||||||
} lwCanvas;
|
|
||||||
|
|
||||||
/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
|
/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
|
||||||
* braille character. The byte should correspond to the pixels arranged as
|
* braille character. The byte should correspond to the pixels arranged as
|
||||||
* follows, where 0 is the least significant bit, and 7 the most significant
|
* follows, where 0 is the least significant bit, and 7 the most significant
|
||||||
@ -69,104 +61,6 @@ void lwTranslatePixelsGroup(int byte, char *output) {
|
|||||||
output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
|
output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate and return a new canvas of the specified size. */
|
|
||||||
lwCanvas *lwCreateCanvas(int width, int height) {
|
|
||||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
|
||||||
canvas->width = width;
|
|
||||||
canvas->height = height;
|
|
||||||
canvas->pixels = zmalloc(width*height);
|
|
||||||
memset(canvas->pixels,0,width*height);
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free the canvas created by lwCreateCanvas(). */
|
|
||||||
void lwFreeCanvas(lwCanvas *canvas) {
|
|
||||||
zfree(canvas->pixels);
|
|
||||||
zfree(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
|
||||||
* dot will be displyed, and 1 means dot will be displayed.
|
|
||||||
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
|
||||||
* out of the size of the canvas without issues. */
|
|
||||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
|
||||||
if (x < 0 || x >= canvas->width ||
|
|
||||||
y < 0 || y >= canvas->height) return;
|
|
||||||
canvas->pixels[x+y*canvas->width] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return the value of the specified pixel on the canvas. */
|
|
||||||
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
|
||||||
if (x < 0 || x >= canvas->width ||
|
|
||||||
y < 0 || y >= canvas->height) return 0;
|
|
||||||
return canvas->pixels[x+y*canvas->width];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
|
||||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
|
||||||
int dx = abs(x2-x1);
|
|
||||||
int dy = abs(y2-y1);
|
|
||||||
int sx = (x1 < x2) ? 1 : -1;
|
|
||||||
int sy = (y1 < y2) ? 1 : -1;
|
|
||||||
int err = dx-dy, e2;
|
|
||||||
|
|
||||||
while(1) {
|
|
||||||
lwDrawPixel(canvas,x1,y1,color);
|
|
||||||
if (x1 == x2 && y1 == y2) break;
|
|
||||||
e2 = err*2;
|
|
||||||
if (e2 > -dy) {
|
|
||||||
err -= dy;
|
|
||||||
x1 += sx;
|
|
||||||
}
|
|
||||||
if (e2 < dx) {
|
|
||||||
err += dx;
|
|
||||||
y1 += sy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw a square centered at the specified x,y coordinates, with the specified
|
|
||||||
* rotation angle and size. In order to write a rotated square, we use the
|
|
||||||
* trivial fact that the parametric equation:
|
|
||||||
*
|
|
||||||
* x = sin(k)
|
|
||||||
* y = cos(k)
|
|
||||||
*
|
|
||||||
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
|
||||||
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
|
||||||
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
|
||||||
* points of the square. In order to rotate the square, we just start with
|
|
||||||
* k = PI/4 + rotation_angle, and we are done.
|
|
||||||
*
|
|
||||||
* Of course the vanilla equations above will describe the square inside a
|
|
||||||
* circle of radius 1, so in order to draw larger squares we'll have to
|
|
||||||
* multiply the obtained coordinates, and then translate them. However this
|
|
||||||
* is much simpler than implementing the abstract concept of 2D shape and then
|
|
||||||
* performing the rotation/translation transformation, so for LOLWUT it's
|
|
||||||
* a good approach. */
|
|
||||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle) {
|
|
||||||
int px[4], py[4];
|
|
||||||
|
|
||||||
/* Adjust the desired size according to the fact that the square inscribed
|
|
||||||
* into a circle of radius 1 has the side of length SQRT(2). This way
|
|
||||||
* size becomes a simple multiplication factor we can use with our
|
|
||||||
* coordinates to magnify them. */
|
|
||||||
size /= 1.4142135623;
|
|
||||||
size = round(size);
|
|
||||||
|
|
||||||
/* Compute the four points. */
|
|
||||||
float k = M_PI/4 + angle;
|
|
||||||
for (int j = 0; j < 4; j++) {
|
|
||||||
px[j] = round(sin(k) * size + x);
|
|
||||||
py[j] = round(cos(k) * size + y);
|
|
||||||
k += M_PI/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw the square. */
|
|
||||||
for (int j = 0; j < 4; j++)
|
|
||||||
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
|
/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
|
||||||
* generated by Georg Nees in the 60s. It explores the relationship between
|
* generated by Georg Nees in the 60s. It explores the relationship between
|
||||||
* caos and order.
|
* caos and order.
|
||||||
@ -180,7 +74,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
|||||||
int padding = canvas_width > 4 ? 2 : 0;
|
int padding = canvas_width > 4 ? 2 : 0;
|
||||||
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
||||||
int canvas_height = square_side * squares_per_col + padding*2;
|
int canvas_height = square_side * squares_per_col + padding*2;
|
||||||
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height);
|
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height, 0);
|
||||||
|
|
||||||
for (int y = 0; y < squares_per_col; y++) {
|
for (int y = 0; y < squares_per_col; y++) {
|
||||||
for (int x = 0; x < squares_per_row; x++) {
|
for (int x = 0; x < squares_per_row; x++) {
|
||||||
@ -200,7 +94,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
|||||||
sx += r2*square_side/3;
|
sx += r2*square_side/3;
|
||||||
sy += r3*square_side/3;
|
sy += r3*square_side/3;
|
||||||
}
|
}
|
||||||
lwDrawSquare(canvas,sx,sy,square_side,angle);
|
lwDrawSquare(canvas,sx,sy,square_side,angle,1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +106,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
|||||||
* logical canvas. The actual returned string will require a terminal that is
|
* logical canvas. The actual returned string will require a terminal that is
|
||||||
* width/2 large and height/4 tall in order to hold the whole image without
|
* width/2 large and height/4 tall in order to hold the whole image without
|
||||||
* overflowing or scrolling, since each Barille character is 2x4. */
|
* overflowing or scrolling, since each Barille character is 2x4. */
|
||||||
sds lwRenderCanvas(lwCanvas *canvas) {
|
static sds renderCanvas(lwCanvas *canvas) {
|
||||||
sds text = sdsempty();
|
sds text = sdsempty();
|
||||||
for (int y = 0; y < canvas->height; y += 4) {
|
for (int y = 0; y < canvas->height; y += 4) {
|
||||||
for (int x = 0; x < canvas->width; x += 2) {
|
for (int x = 0; x < canvas->width; x += 2) {
|
||||||
@ -272,11 +166,12 @@ void lolwut5Command(client *c) {
|
|||||||
|
|
||||||
/* Generate some computer art and reply. */
|
/* Generate some computer art and reply. */
|
||||||
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
||||||
sds rendered = lwRenderCanvas(canvas);
|
sds rendered = renderCanvas(canvas);
|
||||||
rendered = sdscat(rendered,
|
rendered = sdscat(rendered,
|
||||||
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
|
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
|
||||||
rendered = sdscat(rendered,REDIS_VERSION);
|
rendered = sdscat(rendered,REDIS_VERSION);
|
||||||
rendered = sdscatlen(rendered,"\n",1);
|
rendered = sdscatlen(rendered,"\n",1);
|
||||||
addReplyBulkSds(c,rendered);
|
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||||
|
sdsfree(rendered);
|
||||||
lwFreeCanvas(canvas);
|
lwFreeCanvas(canvas);
|
||||||
}
|
}
|
||||||
|
201
src/lolwut6.c
Normal file
201
src/lolwut6.c
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* This file implements the LOLWUT command. The command should do something
|
||||||
|
* fun and interesting, and should be replaced by a new implementation at
|
||||||
|
* each new version of Redis.
|
||||||
|
*
|
||||||
|
* Thanks to Michele Hiki Falcone for the original image that ispired
|
||||||
|
* the image, part of his game, Plaguemon.
|
||||||
|
*
|
||||||
|
* Thanks to the Shhh computer art collective for the help in tuning the
|
||||||
|
* output to have a better artistic effect.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "server.h"
|
||||||
|
#include "lolwut.h"
|
||||||
|
|
||||||
|
/* Render the canvas using the four gray levels of the standard color
|
||||||
|
* terminal: they match very well to the grayscale display of the gameboy. */
|
||||||
|
static sds renderCanvas(lwCanvas *canvas) {
|
||||||
|
sds text = sdsempty();
|
||||||
|
for (int y = 0; y < canvas->height; y++) {
|
||||||
|
for (int x = 0; x < canvas->width; x++) {
|
||||||
|
int color = lwGetPixel(canvas,x,y);
|
||||||
|
char *ce; /* Color escape sequence. */
|
||||||
|
|
||||||
|
/* Note that we set both the foreground and background color.
|
||||||
|
* This way we are able to get a more consistent result among
|
||||||
|
* different terminals implementations. */
|
||||||
|
switch(color) {
|
||||||
|
case 0: ce = "0;30;40m"; break; /* Black */
|
||||||
|
case 1: ce = "0;90;100m"; break; /* Gray 1 */
|
||||||
|
case 2: ce = "0;37;47m"; break; /* Gray 2 */
|
||||||
|
case 3: ce = "0;97;107m"; break; /* White */
|
||||||
|
default: ce = "0;30;40m"; break; /* Just for safety. */
|
||||||
|
}
|
||||||
|
text = sdscatprintf(text,"\033[%s \033[0m",ce);
|
||||||
|
}
|
||||||
|
if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw a skyscraper on the canvas, according to the parameters in the
|
||||||
|
* 'skyscraper' structure. Window colors are random and are always one
|
||||||
|
* of the two grays. */
|
||||||
|
struct skyscraper {
|
||||||
|
int xoff; /* X offset. */
|
||||||
|
int width; /* Pixels width. */
|
||||||
|
int height; /* Pixels height. */
|
||||||
|
int windows; /* Draw windows if true. */
|
||||||
|
int color; /* Color of the skyscraper. */
|
||||||
|
};
|
||||||
|
|
||||||
|
void generateSkyscraper(lwCanvas *canvas, struct skyscraper *si) {
|
||||||
|
int starty = canvas->height-1;
|
||||||
|
int endy = starty - si->height + 1;
|
||||||
|
for (int y = starty; y >= endy; y--) {
|
||||||
|
for (int x = si->xoff; x < si->xoff+si->width; x++) {
|
||||||
|
/* The roof is four pixels less wide. */
|
||||||
|
if (y == endy && (x <= si->xoff+1 || x >= si->xoff+si->width-2))
|
||||||
|
continue;
|
||||||
|
int color = si->color;
|
||||||
|
/* Alter the color if this is a place where we want to
|
||||||
|
* draw a window. We check that we are in the inner part of the
|
||||||
|
* skyscraper, so that windows are far from the borders. */
|
||||||
|
if (si->windows &&
|
||||||
|
x > si->xoff+1 &&
|
||||||
|
x < si->xoff+si->width-2 &&
|
||||||
|
y > endy+1 &&
|
||||||
|
y < starty-1)
|
||||||
|
{
|
||||||
|
/* Calculate the x,y position relative to the start of
|
||||||
|
* the window area. */
|
||||||
|
int relx = x - (si->xoff+1);
|
||||||
|
int rely = y - (endy+1);
|
||||||
|
|
||||||
|
/* Note that we want the windows to be two pixels wide
|
||||||
|
* but just one pixel tall, because terminal "pixels"
|
||||||
|
* (characters) are not square. */
|
||||||
|
if (relx/2 % 2 && rely % 2) {
|
||||||
|
do {
|
||||||
|
color = 1 + rand() % 2;
|
||||||
|
} while (color == si->color);
|
||||||
|
/* Except we want adjacent pixels creating the same
|
||||||
|
* window to be the same color. */
|
||||||
|
if (relx % 2) color = lwGetPixel(canvas,x-1,y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lwDrawPixel(canvas,x,y,color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate a skyline inspired by the parallax backgrounds of 8 bit games. */
|
||||||
|
void generateSkyline(lwCanvas *canvas) {
|
||||||
|
struct skyscraper si;
|
||||||
|
|
||||||
|
/* First draw the background skyscraper without windows, using the
|
||||||
|
* two different grays. We use two passes to make sure that the lighter
|
||||||
|
* ones are always in the background. */
|
||||||
|
for (int color = 2; color >= 1; color--) {
|
||||||
|
si.color = color;
|
||||||
|
for (int offset = -10; offset < canvas->width;) {
|
||||||
|
offset += rand() % 8;
|
||||||
|
si.xoff = offset;
|
||||||
|
si.width = 10 + rand()%9;
|
||||||
|
if (color == 2)
|
||||||
|
si.height = canvas->height/2 + rand()%canvas->height/2;
|
||||||
|
else
|
||||||
|
si.height = canvas->height/2 + rand()%canvas->height/3;
|
||||||
|
si.windows = 0;
|
||||||
|
generateSkyscraper(canvas, &si);
|
||||||
|
if (color == 2)
|
||||||
|
offset += si.width/2;
|
||||||
|
else
|
||||||
|
offset += si.width+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now draw the foreground skyscraper with the windows. */
|
||||||
|
si.color = 0;
|
||||||
|
for (int offset = -10; offset < canvas->width;) {
|
||||||
|
offset += rand() % 8;
|
||||||
|
si.xoff = offset;
|
||||||
|
si.width = 5 + rand()%14;
|
||||||
|
if (si.width % 4) si.width += (si.width % 3);
|
||||||
|
si.height = canvas->height/3 + rand()%canvas->height/2;
|
||||||
|
si.windows = 1;
|
||||||
|
generateSkyscraper(canvas, &si);
|
||||||
|
offset += si.width+5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The LOLWUT 6 command:
|
||||||
|
*
|
||||||
|
* LOLWUT [columns] [rows]
|
||||||
|
*
|
||||||
|
* By default the command uses 80 columns, 40 squares per row
|
||||||
|
* per column.
|
||||||
|
*/
|
||||||
|
void lolwut6Command(client *c) {
|
||||||
|
long cols = 80;
|
||||||
|
long rows = 20;
|
||||||
|
|
||||||
|
/* Parse the optional arguments if any. */
|
||||||
|
if (c->argc > 1 &&
|
||||||
|
getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (c->argc > 2 &&
|
||||||
|
getLongFromObjectOrReply(c,c->argv[2],&rows,NULL) != C_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
|
||||||
|
* so we have maximum number of columns, rows, and output resulution. */
|
||||||
|
if (cols < 1) cols = 1;
|
||||||
|
if (cols > 1000) cols = 1000;
|
||||||
|
if (rows < 1) rows = 1;
|
||||||
|
if (rows > 1000) rows = 1000;
|
||||||
|
|
||||||
|
/* Generate the city skyline and reply. */
|
||||||
|
lwCanvas *canvas = lwCreateCanvas(cols,rows,3);
|
||||||
|
generateSkyline(canvas);
|
||||||
|
sds rendered = renderCanvas(canvas);
|
||||||
|
rendered = sdscat(rendered,
|
||||||
|
"\nDedicated to the 8 bit game developers of past and present.\n"
|
||||||
|
"Original 8 bit image from Plaguemon by hikikomori. Redis ver. ");
|
||||||
|
rendered = sdscat(rendered,REDIS_VERSION);
|
||||||
|
rendered = sdscatlen(rendered,"\n",1);
|
||||||
|
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||||
|
sdsfree(rendered);
|
||||||
|
lwFreeCanvas(canvas);
|
||||||
|
}
|
1604
src/module.c
1604
src/module.c
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,7 @@ endif
|
|||||||
|
|
||||||
.SUFFIXES: .c .so .xo .o
|
.SUFFIXES: .c .so .xo .o
|
||||||
|
|
||||||
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so
|
all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellohook.so
|
||||||
|
|
||||||
.c.xo:
|
.c.xo:
|
||||||
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||||
@ -46,6 +46,12 @@ hellotimer.so: hellotimer.xo
|
|||||||
hellodict.xo: ../redismodule.h
|
hellodict.xo: ../redismodule.h
|
||||||
|
|
||||||
hellodict.so: hellodict.xo
|
hellodict.so: hellodict.xo
|
||||||
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
|
||||||
|
hellohook.xo: ../redismodule.h
|
||||||
|
|
||||||
|
hellohook.so: hellohook.xo
|
||||||
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
|
||||||
testmodule.xo: ../redismodule.h
|
testmodule.xo: ../redismodule.h
|
||||||
|
|
||||||
|
93
src/modules/hellohook.c
Normal file
93
src/modules/hellohook.c
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/* Server hooks API example
|
||||||
|
*
|
||||||
|
* -----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define REDISMODULE_EXPERIMENTAL_API
|
||||||
|
#include "../redismodule.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Client state change callback. */
|
||||||
|
void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(ctx);
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
|
||||||
|
RedisModuleClientInfo *ci = data;
|
||||||
|
printf("Client %s event for client #%llu %s:%d\n",
|
||||||
|
(sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
|
||||||
|
"connection" : "disconnection",
|
||||||
|
ci->id,ci->addr,ci->port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(ctx);
|
||||||
|
REDISMODULE_NOT_USED(e);
|
||||||
|
|
||||||
|
RedisModuleFlushInfo *fi = data;
|
||||||
|
if (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) {
|
||||||
|
if (fi->dbnum != -1) {
|
||||||
|
RedisModuleCallReply *reply;
|
||||||
|
reply = RedisModule_Call(ctx,"DBSIZE","");
|
||||||
|
long long numkeys = RedisModule_CallReplyInteger(reply);
|
||||||
|
printf("FLUSHDB event of database %d started (%lld keys in DB)\n",
|
||||||
|
fi->dbnum, numkeys);
|
||||||
|
RedisModule_FreeCallReply(reply);
|
||||||
|
} else {
|
||||||
|
printf("FLUSHALL event started\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (fi->dbnum != -1) {
|
||||||
|
printf("FLUSHDB event of database %d ended\n",fi->dbnum);
|
||||||
|
} else {
|
||||||
|
printf("FLUSHALL event ended\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function must be present on each Redis module. It is used in order to
|
||||||
|
* register the commands into the Redis server. */
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
if (RedisModule_Init(ctx,"hellohook",1,REDISMODULE_APIVER_1)
|
||||||
|
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_ClientChange, clientChangeCallback);
|
||||||
|
RedisModule_SubscribeToServerEvent(ctx,
|
||||||
|
RedisModuleEvent_FlushDB, flushdbCallback);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
@ -129,6 +129,7 @@ int HelloTypeInsert_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
|||||||
|
|
||||||
/* Insert the new element. */
|
/* Insert the new element. */
|
||||||
HelloTypeInsert(hto,value);
|
HelloTypeInsert(hto,value);
|
||||||
|
RedisModule_SignalKeyAsReady(ctx,argv[1]);
|
||||||
|
|
||||||
RedisModule_ReplyWithLongLong(ctx,hto->len);
|
RedisModule_ReplyWithLongLong(ctx,hto->len);
|
||||||
RedisModule_ReplicateVerbatim(ctx);
|
RedisModule_ReplicateVerbatim(ctx);
|
||||||
@ -190,6 +191,77 @@ int HelloTypeLen_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ====================== Example of a blocking command ==================== */
|
||||||
|
|
||||||
|
/* Reply callback for blocking command HELLOTYPE.BRANGE, this will get
|
||||||
|
* called when the key we blocked for is ready: we need to check if we
|
||||||
|
* can really serve the client, and reply OK or ERR accordingly. */
|
||||||
|
int HelloBlock_Reply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx,keyname,REDISMODULE_READ);
|
||||||
|
int type = RedisModule_KeyType(key);
|
||||||
|
if (type != REDISMODULE_KEYTYPE_MODULE ||
|
||||||
|
RedisModule_ModuleTypeGetType(key) != HelloType)
|
||||||
|
{
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In case the key is able to serve our blocked client, let's directly
|
||||||
|
* use our original command implementation to make this example simpler. */
|
||||||
|
RedisModule_CloseKey(key);
|
||||||
|
return HelloTypeRange_RedisCommand(ctx,argv,argc-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Timeout callback for blocking command HELLOTYPE.BRANGE */
|
||||||
|
int HelloBlock_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
return RedisModule_ReplyWithSimpleString(ctx,"Request timedout");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private data freeing callback for HELLOTYPE.BRANGE command. */
|
||||||
|
void HelloBlock_FreeData(RedisModuleCtx *ctx, void *privdata) {
|
||||||
|
REDISMODULE_NOT_USED(ctx);
|
||||||
|
RedisModule_Free(privdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HELLOTYPE.BRANGE key first count timeout -- This is a blocking verison of
|
||||||
|
* the RANGE operation, in order to show how to use the API
|
||||||
|
* RedisModule_BlockClientOnKeys(). */
|
||||||
|
int HelloTypeBRange_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
if (argc != 5) return RedisModule_WrongArity(ctx);
|
||||||
|
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
|
||||||
|
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
||||||
|
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||||
|
int type = RedisModule_KeyType(key);
|
||||||
|
if (type != REDISMODULE_KEYTYPE_EMPTY &&
|
||||||
|
RedisModule_ModuleTypeGetType(key) != HelloType)
|
||||||
|
{
|
||||||
|
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the timeout before even trying to serve the client synchronously,
|
||||||
|
* so that we always fail ASAP on syntax errors. */
|
||||||
|
long long timeout;
|
||||||
|
if (RedisModule_StringToLongLong(argv[4],&timeout) != REDISMODULE_OK) {
|
||||||
|
return RedisModule_ReplyWithError(ctx,
|
||||||
|
"ERR invalid timeout parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can we serve the reply synchronously? */
|
||||||
|
if (type != REDISMODULE_KEYTYPE_EMPTY) {
|
||||||
|
return HelloTypeRange_RedisCommand(ctx,argv,argc-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Otherwise let's block on the key. */
|
||||||
|
void *privdata = RedisModule_Alloc(100);
|
||||||
|
RedisModule_BlockClientOnKeys(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout,argv+1,1,privdata);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* ========================== "hellotype" type methods ======================= */
|
/* ========================== "hellotype" type methods ======================= */
|
||||||
|
|
||||||
@ -282,5 +354,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||||||
HelloTypeLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
HelloTypeLen_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"hellotype.brange",
|
||||||
|
HelloTypeBRange_RedisCommand,"readonly",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
280
src/networking.c
280
src/networking.c
@ -84,32 +84,27 @@ void linkClient(client *c) {
|
|||||||
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
|
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
client *createClient(int fd) {
|
client *createClient(connection *conn) {
|
||||||
client *c = zmalloc(sizeof(client));
|
client *c = zmalloc(sizeof(client));
|
||||||
|
|
||||||
/* passing -1 as fd it is possible to create a non connected client.
|
/* passing NULL as conn it is possible to create a non connected client.
|
||||||
* This is useful since all the commands needs to be executed
|
* This is useful since all the commands needs to be executed
|
||||||
* in the context of a client. When commands are executed in other
|
* in the context of a client. When commands are executed in other
|
||||||
* contexts (for instance a Lua script) we need a non connected client. */
|
* contexts (for instance a Lua script) we need a non connected client. */
|
||||||
if (fd != -1) {
|
if (conn) {
|
||||||
anetNonBlock(NULL,fd);
|
connNonBlock(conn);
|
||||||
anetEnableTcpNoDelay(NULL,fd);
|
connEnableTcpNoDelay(conn);
|
||||||
if (server.tcpkeepalive)
|
if (server.tcpkeepalive)
|
||||||
anetKeepAlive(NULL,fd,server.tcpkeepalive);
|
connKeepAlive(conn,server.tcpkeepalive);
|
||||||
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
|
connSetReadHandler(conn, readQueryFromClient);
|
||||||
readQueryFromClient, c) == AE_ERR)
|
connSetPrivateData(conn, c);
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
zfree(c);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectDb(c,0);
|
selectDb(c,0);
|
||||||
uint64_t client_id = ++server.next_client_id;
|
uint64_t client_id = ++server.next_client_id;
|
||||||
c->id = client_id;
|
c->id = client_id;
|
||||||
c->resp = 2;
|
c->resp = 2;
|
||||||
c->fd = fd;
|
c->conn = conn;
|
||||||
c->name = NULL;
|
c->name = NULL;
|
||||||
c->bufpos = 0;
|
c->bufpos = 0;
|
||||||
c->qb_pos = 0;
|
c->qb_pos = 0;
|
||||||
@ -161,7 +156,7 @@ client *createClient(int fd) {
|
|||||||
c->client_tracking_redirection = 0;
|
c->client_tracking_redirection = 0;
|
||||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||||
if (fd != -1) linkClient(c);
|
if (conn) linkClient(c);
|
||||||
initClientMultiState(c);
|
initClientMultiState(c);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@ -227,7 +222,7 @@ int prepareClientToWrite(client *c) {
|
|||||||
if ((c->flags & CLIENT_MASTER) &&
|
if ((c->flags & CLIENT_MASTER) &&
|
||||||
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
|
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
|
||||||
|
|
||||||
if (c->fd <= 0) return C_ERR; /* Fake client for AOF loading. */
|
if (!c->conn) return C_ERR; /* Fake client for AOF loading. */
|
||||||
|
|
||||||
/* Schedule the client to write the output buffers to the socket, unless
|
/* Schedule the client to write the output buffers to the socket, unless
|
||||||
* it should already be setup to do so (it has already pending data). */
|
* it should already be setup to do so (it has already pending data). */
|
||||||
@ -777,28 +772,13 @@ int clientHasPendingReplies(client *c) {
|
|||||||
return c->bufpos || listLength(c->reply);
|
return c->bufpos || listLength(c->reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MAX_ACCEPTS_PER_CALL 1000
|
void clientAcceptHandler(connection *conn) {
|
||||||
static void acceptCommonHandler(int fd, int flags, char *ip) {
|
client *c = connGetPrivateData(conn);
|
||||||
client *c;
|
|
||||||
if ((c = createClient(fd)) == NULL) {
|
|
||||||
serverLog(LL_WARNING,
|
|
||||||
"Error registering fd event for the new client: %s (fd=%d)",
|
|
||||||
strerror(errno),fd);
|
|
||||||
close(fd); /* May be already closed, just ignore errors */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* If maxclient directive is set and this is one client more... close the
|
|
||||||
* connection. Note that we create the client instead to check before
|
|
||||||
* for this condition, since now the socket is already set in non-blocking
|
|
||||||
* mode and we can send an error for free using the Kernel I/O */
|
|
||||||
if (listLength(server.clients) > server.maxclients) {
|
|
||||||
char *err = "-ERR max number of clients reached\r\n";
|
|
||||||
|
|
||||||
/* That's a best effort error message, don't check write errors */
|
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||||
if (write(c->fd,err,strlen(err)) == -1) {
|
serverLog(LL_WARNING,
|
||||||
/* Nothing to do, Just to avoid the warning... */
|
"Error accepting a client connection: %s",
|
||||||
}
|
connGetLastError(conn));
|
||||||
server.stat_rejected_conn++;
|
|
||||||
freeClient(c);
|
freeClient(c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -810,10 +790,12 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
|
|||||||
if (server.protected_mode &&
|
if (server.protected_mode &&
|
||||||
server.bindaddr_count == 0 &&
|
server.bindaddr_count == 0 &&
|
||||||
DefaultUser->flags & USER_FLAG_NOPASS &&
|
DefaultUser->flags & USER_FLAG_NOPASS &&
|
||||||
!(flags & CLIENT_UNIX_SOCKET) &&
|
!(c->flags & CLIENT_UNIX_SOCKET))
|
||||||
ip != NULL)
|
|
||||||
{
|
{
|
||||||
if (strcmp(ip,"127.0.0.1") && strcmp(ip,"::1")) {
|
char cip[NET_IP_STR_LEN+1] = { 0 };
|
||||||
|
connPeerToString(conn, cip, sizeof(cip)-1, NULL);
|
||||||
|
|
||||||
|
if (strcmp(cip,"127.0.0.1") && strcmp(cip,"::1")) {
|
||||||
char *err =
|
char *err =
|
||||||
"-DENIED Redis is running in protected mode because protected "
|
"-DENIED Redis is running in protected mode because protected "
|
||||||
"mode is enabled, no bind address was specified, no "
|
"mode is enabled, no bind address was specified, no "
|
||||||
@ -835,7 +817,7 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
|
|||||||
"4) Setup a bind address or an authentication password. "
|
"4) Setup a bind address or an authentication password. "
|
||||||
"NOTE: You only need to do one of the above things in order for "
|
"NOTE: You only need to do one of the above things in order for "
|
||||||
"the server to start accepting connections from the outside.\r\n";
|
"the server to start accepting connections from the outside.\r\n";
|
||||||
if (write(c->fd,err,strlen(err)) == -1) {
|
if (connWrite(c->conn,err,strlen(err)) == -1) {
|
||||||
/* Nothing to do, Just to avoid the warning... */
|
/* Nothing to do, Just to avoid the warning... */
|
||||||
}
|
}
|
||||||
server.stat_rejected_conn++;
|
server.stat_rejected_conn++;
|
||||||
@ -845,7 +827,65 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server.stat_numconnections++;
|
server.stat_numconnections++;
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_CLIENT_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED,
|
||||||
|
c);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MAX_ACCEPTS_PER_CALL 1000
|
||||||
|
static void acceptCommonHandler(connection *conn, int flags, char *ip) {
|
||||||
|
client *c;
|
||||||
|
UNUSED(ip);
|
||||||
|
|
||||||
|
/* Admission control will happen before a client is created and connAccept()
|
||||||
|
* called, because we don't want to even start transport-level negotiation
|
||||||
|
* if rejected.
|
||||||
|
*/
|
||||||
|
if (listLength(server.clients) >= server.maxclients) {
|
||||||
|
char *err = "-ERR max number of clients reached\r\n";
|
||||||
|
|
||||||
|
/* That's a best effort error message, don't check write errors.
|
||||||
|
* Note that for TLS connections, no handshake was done yet so nothing is written
|
||||||
|
* and the connection will just drop.
|
||||||
|
*/
|
||||||
|
if (connWrite(conn,err,strlen(err)) == -1) {
|
||||||
|
/* Nothing to do, Just to avoid the warning... */
|
||||||
|
}
|
||||||
|
server.stat_rejected_conn++;
|
||||||
|
connClose(conn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create connection and client */
|
||||||
|
if ((c = createClient(conn)) == NULL) {
|
||||||
|
char conninfo[100];
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"Error registering fd event for the new client: %s (conn: %s)",
|
||||||
|
connGetLastError(conn),
|
||||||
|
connGetInfo(conn, conninfo, sizeof(conninfo)));
|
||||||
|
connClose(conn); /* May be already closed, just ignore errors */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Last chance to keep flags */
|
||||||
c->flags |= flags;
|
c->flags |= flags;
|
||||||
|
|
||||||
|
/* Initiate accept.
|
||||||
|
*
|
||||||
|
* Note that connAccept() is free to do two things here:
|
||||||
|
* 1. Call clientAcceptHandler() immediately;
|
||||||
|
* 2. Schedule a future call to clientAcceptHandler().
|
||||||
|
*
|
||||||
|
* Because of that, we must do nothing else afterwards.
|
||||||
|
*/
|
||||||
|
if (connAccept(conn, clientAcceptHandler) == C_ERR) {
|
||||||
|
char conninfo[100];
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"Error accepting a client connection: %s (conn: %s)",
|
||||||
|
connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
|
||||||
|
freeClient(connGetPrivateData(conn));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||||
@ -864,7 +904,27 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||||
acceptCommonHandler(cfd,0,cip);
|
acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||||
|
int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||||
|
char cip[NET_IP_STR_LEN];
|
||||||
|
UNUSED(el);
|
||||||
|
UNUSED(mask);
|
||||||
|
UNUSED(privdata);
|
||||||
|
|
||||||
|
while(max--) {
|
||||||
|
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||||
|
if (cfd == ANET_ERR) {
|
||||||
|
if (errno != EWOULDBLOCK)
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"Accepting client connection: %s", server.neterr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||||
|
acceptCommonHandler(connCreateAcceptedTLS(cfd, server.tls_auth_clients),0,cip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,7 +943,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||||
acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL);
|
acceptCommonHandler(connCreateAcceptedSocket(cfd),CLIENT_UNIX_SOCKET,NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -914,10 +974,10 @@ void unlinkClient(client *c) {
|
|||||||
/* If this is marked as current client unset it. */
|
/* If this is marked as current client unset it. */
|
||||||
if (server.current_client == c) server.current_client = NULL;
|
if (server.current_client == c) server.current_client = NULL;
|
||||||
|
|
||||||
/* Certain operations must be done only if the client has an active socket.
|
/* Certain operations must be done only if the client has an active connection.
|
||||||
* If the client was already unlinked or if it's a "fake client" the
|
* If the client was already unlinked or if it's a "fake client" the
|
||||||
* fd is already set to -1. */
|
* conn is already set to NULL. */
|
||||||
if (c->fd != -1) {
|
if (c->conn) {
|
||||||
/* Remove from the list of active clients. */
|
/* Remove from the list of active clients. */
|
||||||
if (c->client_list_node) {
|
if (c->client_list_node) {
|
||||||
uint64_t id = htonu64(c->id);
|
uint64_t id = htonu64(c->id);
|
||||||
@ -926,21 +986,23 @@ void unlinkClient(client *c) {
|
|||||||
c->client_list_node = NULL;
|
c->client_list_node = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* In the case of diskless replication the fork is writing to the
|
/* Check if this is a replica waiting for diskless replication (rdb pipe),
|
||||||
* sockets and just closing the fd isn't enough, if we don't also
|
* in which case it needs to be cleaned from that list */
|
||||||
* shutdown the socket the fork will continue to write to the slave
|
if (c->flags & CLIENT_SLAVE &&
|
||||||
* and the salve will only find out that it was disconnected when
|
c->replstate == SLAVE_STATE_WAIT_BGSAVE_END &&
|
||||||
* it will finish reading the rdb. */
|
server.rdb_pipe_conns)
|
||||||
if ((c->flags & CLIENT_SLAVE) &&
|
{
|
||||||
(c->replstate == SLAVE_STATE_WAIT_BGSAVE_END)) {
|
int i;
|
||||||
shutdown(c->fd, SHUT_RDWR);
|
for (i=0; i < server.rdb_pipe_numconns; i++) {
|
||||||
|
if (server.rdb_pipe_conns[i] == c->conn) {
|
||||||
|
rdbPipeWriteHandlerConnRemoved(c->conn);
|
||||||
|
server.rdb_pipe_conns[i] = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
connClose(c->conn);
|
||||||
/* Unregister async I/O handlers and close the socket. */
|
c->conn = NULL;
|
||||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
|
||||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
|
||||||
close(c->fd);
|
|
||||||
c->fd = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove from the list of pending writes if needed. */
|
/* Remove from the list of pending writes if needed. */
|
||||||
@ -982,6 +1044,13 @@ void freeClient(client *c) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For connected clients, call the disconnection event of modules hooks. */
|
||||||
|
if (c->conn) {
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_CLIENT_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED,
|
||||||
|
c);
|
||||||
|
}
|
||||||
|
|
||||||
/* If it is our master that's beging disconnected we should make sure
|
/* If it is our master that's beging disconnected we should make sure
|
||||||
* to cache the state to try a partial resynchronization later.
|
* to cache the state to try a partial resynchronization later.
|
||||||
*
|
*
|
||||||
@ -1049,6 +1118,11 @@ void freeClient(client *c) {
|
|||||||
if (c->flags & CLIENT_SLAVE && listLength(server.slaves) == 0)
|
if (c->flags & CLIENT_SLAVE && listLength(server.slaves) == 0)
|
||||||
server.repl_no_slaves_since = server.unixtime;
|
server.repl_no_slaves_since = server.unixtime;
|
||||||
refreshGoodSlavesCount();
|
refreshGoodSlavesCount();
|
||||||
|
/* Fire the replica change modules event. */
|
||||||
|
if (c->replstate == SLAVE_STATE_ONLINE)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Master/slave cleanup Case 2:
|
/* Master/slave cleanup Case 2:
|
||||||
@ -1112,19 +1186,20 @@ client *lookupClientByID(uint64_t id) {
|
|||||||
|
|
||||||
/* Write data in output buffers to client. Return C_OK if the client
|
/* Write data in output buffers to client. Return C_OK if the client
|
||||||
* is still valid after the call, C_ERR if it was freed because of some
|
* is still valid after the call, C_ERR if it was freed because of some
|
||||||
* error.
|
* error. If handler_installed is set, it will attempt to clear the
|
||||||
|
* write event.
|
||||||
*
|
*
|
||||||
* This function is called by threads, but always with handler_installed
|
* This function is called by threads, but always with handler_installed
|
||||||
* set to 0. So when handler_installed is set to 0 the function must be
|
* set to 0. So when handler_installed is set to 0 the function must be
|
||||||
* thread safe. */
|
* thread safe. */
|
||||||
int writeToClient(int fd, client *c, int handler_installed) {
|
int writeToClient(client *c, int handler_installed) {
|
||||||
ssize_t nwritten = 0, totwritten = 0;
|
ssize_t nwritten = 0, totwritten = 0;
|
||||||
size_t objlen;
|
size_t objlen;
|
||||||
clientReplyBlock *o;
|
clientReplyBlock *o;
|
||||||
|
|
||||||
while(clientHasPendingReplies(c)) {
|
while(clientHasPendingReplies(c)) {
|
||||||
if (c->bufpos > 0) {
|
if (c->bufpos > 0) {
|
||||||
nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
|
nwritten = connWrite(c->conn,c->buf+c->sentlen,c->bufpos-c->sentlen);
|
||||||
if (nwritten <= 0) break;
|
if (nwritten <= 0) break;
|
||||||
c->sentlen += nwritten;
|
c->sentlen += nwritten;
|
||||||
totwritten += nwritten;
|
totwritten += nwritten;
|
||||||
@ -1145,7 +1220,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
nwritten = write(fd, o->buf + c->sentlen, objlen - c->sentlen);
|
nwritten = connWrite(c->conn, o->buf + c->sentlen, objlen - c->sentlen);
|
||||||
if (nwritten <= 0) break;
|
if (nwritten <= 0) break;
|
||||||
c->sentlen += nwritten;
|
c->sentlen += nwritten;
|
||||||
totwritten += nwritten;
|
totwritten += nwritten;
|
||||||
@ -1180,11 +1255,11 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
|||||||
}
|
}
|
||||||
server.stat_net_output_bytes += totwritten;
|
server.stat_net_output_bytes += totwritten;
|
||||||
if (nwritten == -1) {
|
if (nwritten == -1) {
|
||||||
if (errno == EAGAIN) {
|
if (connGetState(c->conn) == CONN_STATE_CONNECTED) {
|
||||||
nwritten = 0;
|
nwritten = 0;
|
||||||
} else {
|
} else {
|
||||||
serverLog(LL_VERBOSE,
|
serverLog(LL_VERBOSE,
|
||||||
"Error writing to client: %s", strerror(errno));
|
"Error writing to client: %s", connGetLastError(c->conn));
|
||||||
freeClientAsync(c);
|
freeClientAsync(c);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
@ -1202,7 +1277,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
|||||||
* adDeleteFileEvent() is not thread safe: however writeToClient()
|
* adDeleteFileEvent() is not thread safe: however writeToClient()
|
||||||
* is always called with handler_installed set to 0 from threads
|
* is always called with handler_installed set to 0 from threads
|
||||||
* so we are fine. */
|
* so we are fine. */
|
||||||
if (handler_installed) aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
if (handler_installed) connSetWriteHandler(c->conn, NULL);
|
||||||
|
|
||||||
/* Close connection after entire reply has been sent. */
|
/* Close connection after entire reply has been sent. */
|
||||||
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
|
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
|
||||||
@ -1214,10 +1289,9 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Write event handler. Just send data to the client. */
|
/* Write event handler. Just send data to the client. */
|
||||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void sendReplyToClient(connection *conn) {
|
||||||
UNUSED(el);
|
client *c = connGetPrivateData(conn);
|
||||||
UNUSED(mask);
|
writeToClient(c,1);
|
||||||
writeToClient(fd,privdata,1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called just before entering the event loop, in the hope
|
/* This function is called just before entering the event loop, in the hope
|
||||||
@ -1240,26 +1314,24 @@ int handleClientsWithPendingWrites(void) {
|
|||||||
if (c->flags & CLIENT_PROTECTED) continue;
|
if (c->flags & CLIENT_PROTECTED) continue;
|
||||||
|
|
||||||
/* Try to write buffers to the client socket. */
|
/* Try to write buffers to the client socket. */
|
||||||
if (writeToClient(c->fd,c,0) == C_ERR) continue;
|
if (writeToClient(c,0) == C_ERR) continue;
|
||||||
|
|
||||||
/* If after the synchronous writes above we still have data to
|
/* If after the synchronous writes above we still have data to
|
||||||
* output to the client, we need to install the writable handler. */
|
* output to the client, we need to install the writable handler. */
|
||||||
if (clientHasPendingReplies(c)) {
|
if (clientHasPendingReplies(c)) {
|
||||||
int ae_flags = AE_WRITABLE;
|
int ae_barrier = 0;
|
||||||
/* For the fsync=always policy, we want that a given FD is never
|
/* For the fsync=always policy, we want that a given FD is never
|
||||||
* served for reading and writing in the same event loop iteration,
|
* served for reading and writing in the same event loop iteration,
|
||||||
* so that in the middle of receiving the query, and serving it
|
* so that in the middle of receiving the query, and serving it
|
||||||
* to the client, we'll call beforeSleep() that will do the
|
* to the client, we'll call beforeSleep() that will do the
|
||||||
* actual fsync of AOF to disk. AE_BARRIER ensures that. */
|
* actual fsync of AOF to disk. the write barrier ensures that. */
|
||||||
if (server.aof_state == AOF_ON &&
|
if (server.aof_state == AOF_ON &&
|
||||||
server.aof_fsync == AOF_FSYNC_ALWAYS)
|
server.aof_fsync == AOF_FSYNC_ALWAYS)
|
||||||
{
|
{
|
||||||
ae_flags |= AE_BARRIER;
|
ae_barrier = 1;
|
||||||
}
|
}
|
||||||
if (aeCreateFileEvent(server.el, c->fd, ae_flags,
|
if (connSetWriteHandlerWithBarrier(c->conn, sendReplyToClient, ae_barrier) == C_ERR) {
|
||||||
sendReplyToClient, c) == AE_ERR)
|
freeClientAsync(c);
|
||||||
{
|
|
||||||
freeClientAsync(c);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1305,15 +1377,15 @@ void resetClient(client *c) {
|
|||||||
* path, it is not really released, but only marked for later release. */
|
* path, it is not really released, but only marked for later release. */
|
||||||
void protectClient(client *c) {
|
void protectClient(client *c) {
|
||||||
c->flags |= CLIENT_PROTECTED;
|
c->flags |= CLIENT_PROTECTED;
|
||||||
aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
|
connSetReadHandler(c->conn,NULL);
|
||||||
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
|
connSetWriteHandler(c->conn,NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This will undo the client protection done by protectClient() */
|
/* This will undo the client protection done by protectClient() */
|
||||||
void unprotectClient(client *c) {
|
void unprotectClient(client *c) {
|
||||||
if (c->flags & CLIENT_PROTECTED) {
|
if (c->flags & CLIENT_PROTECTED) {
|
||||||
c->flags &= ~CLIENT_PROTECTED;
|
c->flags &= ~CLIENT_PROTECTED;
|
||||||
aeCreateFileEvent(server.el,c->fd,AE_READABLE,readQueryFromClient,c);
|
connSetReadHandler(c->conn,readQueryFromClient);
|
||||||
if (clientHasPendingReplies(c)) clientInstallWriteHandler(c);
|
if (clientHasPendingReplies(c)) clientInstallWriteHandler(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1710,12 +1782,10 @@ void processInputBufferAndReplicate(client *c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void readQueryFromClient(connection *conn) {
|
||||||
client *c = (client*) privdata;
|
client *c = connGetPrivateData(conn);
|
||||||
int nread, readlen;
|
int nread, readlen;
|
||||||
size_t qblen;
|
size_t qblen;
|
||||||
UNUSED(el);
|
|
||||||
UNUSED(mask);
|
|
||||||
|
|
||||||
/* Check if we want to read from the client later when exiting from
|
/* Check if we want to read from the client later when exiting from
|
||||||
* the event loop. This is the case if threaded I/O is enabled. */
|
* the event loop. This is the case if threaded I/O is enabled. */
|
||||||
@ -1741,12 +1811,12 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
qblen = sdslen(c->querybuf);
|
qblen = sdslen(c->querybuf);
|
||||||
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
|
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
|
||||||
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
|
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
|
||||||
nread = read(fd, c->querybuf+qblen, readlen);
|
nread = connRead(c->conn, c->querybuf+qblen, readlen);
|
||||||
if (nread == -1) {
|
if (nread == -1) {
|
||||||
if (errno == EAGAIN) {
|
if (connGetState(conn) == CONN_STATE_CONNECTED) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno));
|
serverLog(LL_VERBOSE, "Reading from client: %s",connGetLastError(c->conn));
|
||||||
freeClientAsync(c);
|
freeClientAsync(c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1818,7 +1888,7 @@ void genClientPeerId(client *client, char *peerid,
|
|||||||
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
|
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
|
||||||
} else {
|
} else {
|
||||||
/* TCP client. */
|
/* TCP client. */
|
||||||
anetFormatPeer(client->fd,peerid,peerid_len);
|
connFormatPeer(client->conn,peerid,peerid_len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1839,8 +1909,7 @@ char *getClientPeerId(client *c) {
|
|||||||
/* Concatenate a string representing the state of a client in an human
|
/* Concatenate a string representing the state of a client in an human
|
||||||
* readable format, into the sds string 's'. */
|
* readable format, into the sds string 's'. */
|
||||||
sds catClientInfoString(sds s, client *client) {
|
sds catClientInfoString(sds s, client *client) {
|
||||||
char flags[16], events[3], *p;
|
char flags[16], events[3], conninfo[CONN_INFO_LEN], *p;
|
||||||
int emask;
|
|
||||||
|
|
||||||
p = flags;
|
p = flags;
|
||||||
if (client->flags & CLIENT_SLAVE) {
|
if (client->flags & CLIENT_SLAVE) {
|
||||||
@ -1864,16 +1933,17 @@ sds catClientInfoString(sds s, client *client) {
|
|||||||
if (p == flags) *p++ = 'N';
|
if (p == flags) *p++ = 'N';
|
||||||
*p++ = '\0';
|
*p++ = '\0';
|
||||||
|
|
||||||
emask = client->fd == -1 ? 0 : aeGetFileEvents(server.el,client->fd);
|
|
||||||
p = events;
|
p = events;
|
||||||
if (emask & AE_READABLE) *p++ = 'r';
|
if (client->conn) {
|
||||||
if (emask & AE_WRITABLE) *p++ = 'w';
|
if (connHasReadHandler(client->conn)) *p++ = 'r';
|
||||||
|
if (connHasWriteHandler(client->conn)) *p++ = 'w';
|
||||||
|
}
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
return sdscatfmt(s,
|
return sdscatfmt(s,
|
||||||
"id=%U addr=%s fd=%i name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s user=%s",
|
"id=%U addr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s user=%s",
|
||||||
(unsigned long long) client->id,
|
(unsigned long long) client->id,
|
||||||
getClientPeerId(client),
|
getClientPeerId(client),
|
||||||
client->fd,
|
connGetInfo(client->conn, conninfo, sizeof(conninfo)),
|
||||||
client->name ? (char*)client->name->ptr : "",
|
client->name ? (char*)client->name->ptr : "",
|
||||||
(long long)(server.unixtime - client->ctime),
|
(long long)(server.unixtime - client->ctime),
|
||||||
(long long)(server.unixtime - client->lastinteraction),
|
(long long)(server.unixtime - client->lastinteraction),
|
||||||
@ -1990,7 +2060,7 @@ NULL
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sds o = getAllClientsInfoString(type);
|
sds o = getAllClientsInfoString(type);
|
||||||
addReplyBulkCBuffer(c,o,sdslen(o));
|
addReplyVerbatim(c,o,sdslen(o),"txt");
|
||||||
sdsfree(o);
|
sdsfree(o);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
|
||||||
/* CLIENT REPLY ON|OFF|SKIP */
|
/* CLIENT REPLY ON|OFF|SKIP */
|
||||||
@ -2445,7 +2515,7 @@ int checkClientOutputBufferLimits(client *c) {
|
|||||||
* called from contexts where the client can't be freed safely, i.e. from the
|
* called from contexts where the client can't be freed safely, i.e. from the
|
||||||
* lower level functions pushing data inside the client output buffers. */
|
* lower level functions pushing data inside the client output buffers. */
|
||||||
void asyncCloseClientOnOutputBufferLimitReached(client *c) {
|
void asyncCloseClientOnOutputBufferLimitReached(client *c) {
|
||||||
if (c->fd == -1) return; /* It is unsafe to free fake clients. */
|
if (!c->conn) return; /* It is unsafe to free fake clients. */
|
||||||
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
|
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
|
||||||
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
|
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
|
||||||
if (checkClientOutputBufferLimits(c)) {
|
if (checkClientOutputBufferLimits(c)) {
|
||||||
@ -2468,8 +2538,7 @@ void flushSlavesOutputBuffers(void) {
|
|||||||
listRewind(server.slaves,&li);
|
listRewind(server.slaves,&li);
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
client *slave = listNodeValue(ln);
|
client *slave = listNodeValue(ln);
|
||||||
int events = aeGetFileEvents(server.el,slave->fd);
|
int can_receive_writes = connHasWriteHandler(slave->conn) ||
|
||||||
int can_receive_writes = (events & AE_WRITABLE) ||
|
|
||||||
(slave->flags & CLIENT_PENDING_WRITE);
|
(slave->flags & CLIENT_PENDING_WRITE);
|
||||||
|
|
||||||
/* We don't want to send the pending data to the replica in a few
|
/* We don't want to send the pending data to the replica in a few
|
||||||
@ -2491,7 +2560,7 @@ void flushSlavesOutputBuffers(void) {
|
|||||||
!slave->repl_put_online_on_ack &&
|
!slave->repl_put_online_on_ack &&
|
||||||
clientHasPendingReplies(slave))
|
clientHasPendingReplies(slave))
|
||||||
{
|
{
|
||||||
writeToClient(slave->fd,slave,0);
|
writeToClient(slave,0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2618,9 +2687,9 @@ void *IOThreadMain(void *myid) {
|
|||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
client *c = listNodeValue(ln);
|
client *c = listNodeValue(ln);
|
||||||
if (io_threads_op == IO_THREADS_OP_WRITE) {
|
if (io_threads_op == IO_THREADS_OP_WRITE) {
|
||||||
writeToClient(c->fd,c,0);
|
writeToClient(c,0);
|
||||||
} else if (io_threads_op == IO_THREADS_OP_READ) {
|
} else if (io_threads_op == IO_THREADS_OP_READ) {
|
||||||
readQueryFromClient(NULL,c->fd,c,0);
|
readQueryFromClient(c->conn);
|
||||||
} else {
|
} else {
|
||||||
serverPanic("io_threads_op value is unknown");
|
serverPanic("io_threads_op value is unknown");
|
||||||
}
|
}
|
||||||
@ -2761,8 +2830,7 @@ int handleClientsWithPendingWritesUsingThreads(void) {
|
|||||||
/* Install the write handler if there are pending writes in some
|
/* Install the write handler if there are pending writes in some
|
||||||
* of the clients. */
|
* of the clients. */
|
||||||
if (clientHasPendingReplies(c) &&
|
if (clientHasPendingReplies(c) &&
|
||||||
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
|
connSetWriteHandler(c->conn, sendReplyToClient) == AE_ERR)
|
||||||
sendReplyToClient, c) == AE_ERR)
|
|
||||||
{
|
{
|
||||||
freeClientAsync(c);
|
freeClientAsync(c);
|
||||||
}
|
}
|
||||||
|
31
src/object.c
31
src/object.c
@ -1209,12 +1209,13 @@ sds getMemoryDoctorReport(void) {
|
|||||||
* The lru_idle and lru_clock args are only relevant if policy
|
* The lru_idle and lru_clock args are only relevant if policy
|
||||||
* is MAXMEMORY_FLAG_LRU.
|
* is MAXMEMORY_FLAG_LRU.
|
||||||
* Either or both of them may be <0, in that case, nothing is set. */
|
* Either or both of them may be <0, in that case, nothing is set. */
|
||||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||||
long long lru_clock) {
|
long long lru_clock) {
|
||||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||||
if (lfu_freq >= 0) {
|
if (lfu_freq >= 0) {
|
||||||
serverAssert(lfu_freq <= 255);
|
serverAssert(lfu_freq <= 255);
|
||||||
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
} else if (lru_idle >= 0) {
|
} else if (lru_idle >= 0) {
|
||||||
/* Provided LRU idle time is in seconds. Scale
|
/* Provided LRU idle time is in seconds. Scale
|
||||||
@ -1231,7 +1232,9 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
|||||||
if (lru_abs < 0)
|
if (lru_abs < 0)
|
||||||
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
|
||||||
val->lru = lru_abs;
|
val->lru = lru_abs;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ======================= The OBJECT and MEMORY commands =================== */
|
/* ======================= The OBJECT and MEMORY commands =================== */
|
||||||
@ -1440,30 +1443,20 @@ NULL
|
|||||||
#if defined(USE_JEMALLOC)
|
#if defined(USE_JEMALLOC)
|
||||||
sds info = sdsempty();
|
sds info = sdsempty();
|
||||||
je_malloc_stats_print(inputCatSds, &info, NULL);
|
je_malloc_stats_print(inputCatSds, &info, NULL);
|
||||||
addReplyBulkSds(c, info);
|
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||||
|
sdsfree(info);
|
||||||
#else
|
#else
|
||||||
addReplyBulkCString(c,"Stats not supported for the current allocator");
|
addReplyBulkCString(c,"Stats not supported for the current allocator");
|
||||||
#endif
|
#endif
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
||||||
sds report = getMemoryDoctorReport();
|
sds report = getMemoryDoctorReport();
|
||||||
addReplyBulkSds(c,report);
|
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||||
|
sdsfree(report);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) {
|
||||||
#if defined(USE_JEMALLOC)
|
if (jemalloc_purge() == 0)
|
||||||
char tmp[32];
|
addReply(c, shared.ok);
|
||||||
unsigned narenas = 0;
|
else
|
||||||
size_t sz = sizeof(unsigned);
|
addReplyError(c, "Error purging dirty pages");
|
||||||
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
|
|
||||||
sprintf(tmp, "arena.%d.purge", narenas);
|
|
||||||
if (!je_mallctl(tmp, NULL, 0, NULL, 0)) {
|
|
||||||
addReply(c, shared.ok);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addReplyError(c, "Error purging dirty pages");
|
|
||||||
#else
|
|
||||||
addReply(c, shared.ok);
|
|
||||||
/* Nothing to do for other allocators. */
|
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
|
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
|
||||||
}
|
}
|
||||||
|
@ -1791,7 +1791,8 @@ int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key
|
|||||||
if (eq && key_len == iter->key_len) return 1;
|
if (eq && key_len == iter->key_len) return 1;
|
||||||
else if (lt) return iter->key_len < key_len;
|
else if (lt) return iter->key_len < key_len;
|
||||||
else if (gt) return iter->key_len > key_len;
|
else if (gt) return iter->key_len > key_len;
|
||||||
} if (cmp > 0) {
|
return 0;
|
||||||
|
} else if (cmp > 0) {
|
||||||
return gt ? 1 : 0;
|
return gt ? 1 : 0;
|
||||||
} else /* (cmp < 0) */ {
|
} else /* (cmp < 0) */ {
|
||||||
return lt ? 1 : 0;
|
return lt ? 1 : 0;
|
||||||
|
336
src/rdb.c
336
src/rdb.c
@ -260,7 +260,7 @@ int rdbEncodeInteger(long long value, unsigned char *enc) {
|
|||||||
|
|
||||||
/* Loads an integer-encoded object with the specified encoding type "enctype".
|
/* Loads an integer-encoded object with the specified encoding type "enctype".
|
||||||
* The returned value changes according to the flags, see
|
* The returned value changes according to the flags, see
|
||||||
* rdbGenerincLoadStringObject() for more info. */
|
* rdbGenericLoadStringObject() for more info. */
|
||||||
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||||
int plain = flags & RDB_LOAD_PLAIN;
|
int plain = flags & RDB_LOAD_PLAIN;
|
||||||
int sds = flags & RDB_LOAD_SDS;
|
int sds = flags & RDB_LOAD_SDS;
|
||||||
@ -1080,9 +1080,9 @@ ssize_t rdbSaveAuxFieldStrInt(rio *rdb, char *key, long long val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Save a few default AUX fields with information about the RDB generated. */
|
/* Save a few default AUX fields with information about the RDB generated. */
|
||||||
int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
|
int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||||
int redis_bits = (sizeof(void*) == 8) ? 64 : 32;
|
int redis_bits = (sizeof(void*) == 8) ? 64 : 32;
|
||||||
int aof_preamble = (flags & RDB_SAVE_AOF_PREAMBLE) != 0;
|
int aof_preamble = (rdbflags & RDBFLAGS_AOF_PREAMBLE) != 0;
|
||||||
|
|
||||||
/* Add a few fields about the state when the RDB was created. */
|
/* Add a few fields about the state when the RDB was created. */
|
||||||
if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
|
if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
|
||||||
@ -1150,7 +1150,7 @@ ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
|
|||||||
* When the function returns C_ERR and if 'error' is not NULL, the
|
* When the function returns C_ERR and if 'error' is not NULL, the
|
||||||
* integer pointed by 'error' is set to the value of errno just after the I/O
|
* integer pointed by 'error' is set to the value of errno just after the I/O
|
||||||
* error. */
|
* error. */
|
||||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
|
||||||
dictIterator *di = NULL;
|
dictIterator *di = NULL;
|
||||||
dictEntry *de;
|
dictEntry *de;
|
||||||
char magic[10];
|
char magic[10];
|
||||||
@ -1162,7 +1162,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
|||||||
rdb->update_cksum = rioGenericUpdateChecksum;
|
rdb->update_cksum = rioGenericUpdateChecksum;
|
||||||
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
||||||
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
||||||
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
if (rdbSaveInfoAuxFields(rdb,rdbflags,rsi) == -1) goto werr;
|
||||||
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
|
||||||
|
|
||||||
for (j = 0; j < server.dbnum; j++) {
|
for (j = 0; j < server.dbnum; j++) {
|
||||||
@ -1199,7 +1199,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
|||||||
/* When this RDB is produced as part of an AOF rewrite, move
|
/* When this RDB is produced as part of an AOF rewrite, move
|
||||||
* accumulated diff from parent to child while rewriting in
|
* accumulated diff from parent to child while rewriting in
|
||||||
* order to have a smaller final write. */
|
* order to have a smaller final write. */
|
||||||
if (flags & RDB_SAVE_AOF_PREAMBLE &&
|
if (rdbflags & RDBFLAGS_AOF_PREAMBLE &&
|
||||||
rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
|
rdb->processed_bytes > processed+AOF_READ_DIFF_INTERVAL_BYTES)
|
||||||
{
|
{
|
||||||
processed = rdb->processed_bytes;
|
processed = rdb->processed_bytes;
|
||||||
@ -1254,18 +1254,21 @@ werr:
|
|||||||
int rdbSaveRioWithEOFMark(rio *rdb, int *error, rdbSaveInfo *rsi) {
|
int rdbSaveRioWithEOFMark(rio *rdb, int *error, rdbSaveInfo *rsi) {
|
||||||
char eofmark[RDB_EOF_MARK_SIZE];
|
char eofmark[RDB_EOF_MARK_SIZE];
|
||||||
|
|
||||||
|
startSaving(RDBFLAGS_REPLICATION);
|
||||||
getRandomHexChars(eofmark,RDB_EOF_MARK_SIZE);
|
getRandomHexChars(eofmark,RDB_EOF_MARK_SIZE);
|
||||||
if (error) *error = 0;
|
if (error) *error = 0;
|
||||||
if (rioWrite(rdb,"$EOF:",5) == 0) goto werr;
|
if (rioWrite(rdb,"$EOF:",5) == 0) goto werr;
|
||||||
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
||||||
if (rioWrite(rdb,"\r\n",2) == 0) goto werr;
|
if (rioWrite(rdb,"\r\n",2) == 0) goto werr;
|
||||||
if (rdbSaveRio(rdb,error,RDB_SAVE_NONE,rsi) == C_ERR) goto werr;
|
if (rdbSaveRio(rdb,error,RDBFLAGS_NONE,rsi) == C_ERR) goto werr;
|
||||||
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
||||||
|
stopSaving(1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
|
|
||||||
werr: /* Write error. */
|
werr: /* Write error. */
|
||||||
/* Set 'error' only if not already set by rdbSaveRio() call. */
|
/* Set 'error' only if not already set by rdbSaveRio() call. */
|
||||||
if (error && *error == 0) *error = errno;
|
if (error && *error == 0) *error = errno;
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1291,11 +1294,12 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rioInitWithFile(&rdb,fp);
|
rioInitWithFile(&rdb,fp);
|
||||||
|
startSaving(RDBFLAGS_NONE);
|
||||||
|
|
||||||
if (server.rdb_save_incremental_fsync)
|
if (server.rdb_save_incremental_fsync)
|
||||||
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
|
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
|
||||||
|
|
||||||
if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {
|
if (rdbSaveRio(&rdb,&error,RDBFLAGS_NONE,rsi) == C_ERR) {
|
||||||
errno = error;
|
errno = error;
|
||||||
goto werr;
|
goto werr;
|
||||||
}
|
}
|
||||||
@ -1317,6 +1321,7 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
|||||||
cwdp ? cwdp : "unknown",
|
cwdp ? cwdp : "unknown",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1324,51 +1329,38 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
|
|||||||
server.dirty = 0;
|
server.dirty = 0;
|
||||||
server.lastsave = time(NULL);
|
server.lastsave = time(NULL);
|
||||||
server.lastbgsave_status = C_OK;
|
server.lastbgsave_status = C_OK;
|
||||||
|
stopSaving(1);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
|
|
||||||
werr:
|
werr:
|
||||||
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
serverLog(LL_WARNING,"Write error saving DB on disk: %s", strerror(errno));
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
unlink(tmpfile);
|
unlink(tmpfile);
|
||||||
|
stopSaving(0);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
||||||
pid_t childpid;
|
pid_t childpid;
|
||||||
long long start;
|
|
||||||
|
|
||||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
|
|
||||||
server.dirty_before_bgsave = server.dirty;
|
server.dirty_before_bgsave = server.dirty;
|
||||||
server.lastbgsave_try = time(NULL);
|
server.lastbgsave_try = time(NULL);
|
||||||
openChildInfoPipe();
|
openChildInfoPipe();
|
||||||
|
|
||||||
start = ustime();
|
if ((childpid = redisFork()) == 0) {
|
||||||
if ((childpid = fork()) == 0) {
|
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
/* Child */
|
/* Child */
|
||||||
closeListeningSockets(0);
|
|
||||||
redisSetProcTitle("redis-rdb-bgsave");
|
redisSetProcTitle("redis-rdb-bgsave");
|
||||||
retval = rdbSave(filename,rsi);
|
retval = rdbSave(filename,rsi);
|
||||||
if (retval == C_OK) {
|
if (retval == C_OK) {
|
||||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||||
|
|
||||||
if (private_dirty) {
|
|
||||||
serverLog(LL_NOTICE,
|
|
||||||
"RDB: %zu MB of memory used by copy-on-write",
|
|
||||||
private_dirty/(1024*1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
server.child_info_data.cow_size = private_dirty;
|
|
||||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
|
||||||
}
|
}
|
||||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
server.stat_fork_time = ustime()-start;
|
|
||||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
|
||||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
server.lastbgsave_status = C_ERR;
|
server.lastbgsave_status = C_ERR;
|
||||||
@ -1380,7 +1372,6 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
|||||||
server.rdb_save_time_start = time(NULL);
|
server.rdb_save_time_start = time(NULL);
|
||||||
server.rdb_child_pid = childpid;
|
server.rdb_child_pid = childpid;
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
||||||
updateDictResizePolicy();
|
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
return C_OK; /* unreached */
|
return C_OK; /* unreached */
|
||||||
@ -1934,23 +1925,33 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
|||||||
|
|
||||||
/* Mark that we are loading in the global state and setup the fields
|
/* Mark that we are loading in the global state and setup the fields
|
||||||
* needed to provide loading stats. */
|
* needed to provide loading stats. */
|
||||||
void startLoading(size_t size) {
|
void startLoading(size_t size, int rdbflags) {
|
||||||
/* Load the DB */
|
/* Load the DB */
|
||||||
server.loading = 1;
|
server.loading = 1;
|
||||||
server.loading_start_time = time(NULL);
|
server.loading_start_time = time(NULL);
|
||||||
server.loading_loaded_bytes = 0;
|
server.loading_loaded_bytes = 0;
|
||||||
server.loading_total_bytes = size;
|
server.loading_total_bytes = size;
|
||||||
|
|
||||||
|
/* Fire the loading modules start event. */
|
||||||
|
int subevent;
|
||||||
|
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_LOADING_AOF_START;
|
||||||
|
else if(rdbflags & RDBFLAGS_REPLICATION)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_LOADING_REPL_START;
|
||||||
|
else
|
||||||
|
subevent = REDISMODULE_SUBEVENT_LOADING_RDB_START;
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_LOADING,subevent,NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark that we are loading in the global state and setup the fields
|
/* Mark that we are loading in the global state and setup the fields
|
||||||
* needed to provide loading stats.
|
* needed to provide loading stats.
|
||||||
* 'filename' is optional and used for rdb-check on error */
|
* 'filename' is optional and used for rdb-check on error */
|
||||||
void startLoadingFile(FILE *fp, char* filename) {
|
void startLoadingFile(FILE *fp, char* filename, int rdbflags) {
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
if (fstat(fileno(fp), &sb) == -1)
|
if (fstat(fileno(fp), &sb) == -1)
|
||||||
sb.st_size = 0;
|
sb.st_size = 0;
|
||||||
rdbFileBeingLoaded = filename;
|
rdbFileBeingLoaded = filename;
|
||||||
startLoading(sb.st_size);
|
startLoading(sb.st_size, rdbflags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Refresh the loading progress info */
|
/* Refresh the loading progress info */
|
||||||
@ -1961,9 +1962,37 @@ void loadingProgress(off_t pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Loading finished */
|
/* Loading finished */
|
||||||
void stopLoading(void) {
|
void stopLoading(int success) {
|
||||||
server.loading = 0;
|
server.loading = 0;
|
||||||
rdbFileBeingLoaded = NULL;
|
rdbFileBeingLoaded = NULL;
|
||||||
|
|
||||||
|
/* Fire the loading modules end event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_LOADING,
|
||||||
|
success?
|
||||||
|
REDISMODULE_SUBEVENT_LOADING_ENDED:
|
||||||
|
REDISMODULE_SUBEVENT_LOADING_FAILED,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startSaving(int rdbflags) {
|
||||||
|
/* Fire the persistence modules end event. */
|
||||||
|
int subevent;
|
||||||
|
if (rdbflags & RDBFLAGS_AOF_PREAMBLE)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START;
|
||||||
|
else if (getpid()!=server.pid)
|
||||||
|
subevent = REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START;
|
||||||
|
else
|
||||||
|
subevent = REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START;
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_PERSISTENCE,subevent,NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopSaving(int success) {
|
||||||
|
/* Fire the persistence modules end event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_PERSISTENCE,
|
||||||
|
success?
|
||||||
|
REDISMODULE_SUBEVENT_PERSISTENCE_ENDED:
|
||||||
|
REDISMODULE_SUBEVENT_PERSISTENCE_FAILED,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Track loading progress in order to serve client's from time to time
|
/* Track loading progress in order to serve client's from time to time
|
||||||
@ -1982,12 +2011,13 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
|||||||
replicationSendNewlineToMaster();
|
replicationSendNewlineToMaster();
|
||||||
loadingProgress(r->processed_bytes);
|
loadingProgress(r->processed_bytes);
|
||||||
processEventsWhileBlocked();
|
processEventsWhileBlocked();
|
||||||
|
processModuleLoadingProgressEvent(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
|
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
|
||||||
* otherwise C_ERR is returned and 'errno' is set accordingly. */
|
* otherwise C_ERR is returned and 'errno' is set accordingly. */
|
||||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||||
uint64_t dbid;
|
uint64_t dbid;
|
||||||
int type, rdbver;
|
int type, rdbver;
|
||||||
redisDb *db = server.db+0;
|
redisDb *db = server.db+0;
|
||||||
@ -2188,13 +2218,17 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
|||||||
/* Read key */
|
/* Read key */
|
||||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||||
/* Read value */
|
/* Read value */
|
||||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) goto eoferr;
|
if ((val = rdbLoadObject(type,rdb,key)) == NULL) {
|
||||||
|
decrRefCount(key);
|
||||||
|
goto eoferr;
|
||||||
|
}
|
||||||
|
|
||||||
/* Check if the key already expired. This function is used when loading
|
/* Check if the key already expired. This function is used when loading
|
||||||
* an RDB file from disk, either at startup, or when an RDB was
|
* an RDB file from disk, either at startup, or when an RDB was
|
||||||
* received from the master. In the latter case, the master is
|
* received from the master. In the latter case, the master is
|
||||||
* responsible for key expiry. If we would expire keys here, the
|
* responsible for key expiry. If we would expire keys here, the
|
||||||
* snapshot taken by the master may not be reflected on the slave. */
|
* snapshot taken by the master may not be reflected on the slave. */
|
||||||
if (server.masterhost == NULL && !loading_aof && expiretime != -1 && expiretime < now) {
|
if (server.masterhost == NULL && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
|
||||||
decrRefCount(key);
|
decrRefCount(key);
|
||||||
decrRefCount(val);
|
decrRefCount(val);
|
||||||
} else {
|
} else {
|
||||||
@ -2211,6 +2245,8 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
|||||||
* own reference. */
|
* own reference. */
|
||||||
decrRefCount(key);
|
decrRefCount(key);
|
||||||
}
|
}
|
||||||
|
if (server.key_load_delay)
|
||||||
|
usleep(server.key_load_delay);
|
||||||
|
|
||||||
/* Reset the state that is key-specified and is populated by
|
/* Reset the state that is key-specified and is populated by
|
||||||
* opcodes before the key, so that we start from scratch again. */
|
* opcodes before the key, so that we start from scratch again. */
|
||||||
@ -2253,17 +2289,17 @@ eoferr:
|
|||||||
*
|
*
|
||||||
* If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
|
* If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
|
||||||
* loading code will fiil the information fields in the structure. */
|
* loading code will fiil the information fields in the structure. */
|
||||||
int rdbLoad(char *filename, rdbSaveInfo *rsi) {
|
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags) {
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
rio rdb;
|
rio rdb;
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
|
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
|
||||||
startLoadingFile(fp, filename);
|
startLoadingFile(fp, filename,rdbflags);
|
||||||
rioInitWithFile(&rdb,fp);
|
rioInitWithFile(&rdb,fp);
|
||||||
retval = rdbLoadRio(&rdb,rsi,0);
|
retval = rdbLoadRio(&rdb,rdbflags,rsi);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
stopLoading();
|
stopLoading(retval==C_OK);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2303,11 +2339,9 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* A background saving child (BGSAVE) terminated its work. Handle this.
|
/* A background saving child (BGSAVE) terminated its work. Handle this.
|
||||||
* This function covers the case of RDB -> Salves socket transfers for
|
* This function covers the case of RDB -> Slaves socket transfers for
|
||||||
* diskless replication. */
|
* diskless replication. */
|
||||||
void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||||
uint64_t *ok_slaves;
|
|
||||||
|
|
||||||
if (!bysignal && exitcode == 0) {
|
if (!bysignal && exitcode == 0) {
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Background RDB transfer terminated with success");
|
"Background RDB transfer terminated with success");
|
||||||
@ -2321,79 +2355,6 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
|||||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||||
server.rdb_save_time_start = -1;
|
server.rdb_save_time_start = -1;
|
||||||
|
|
||||||
/* If the child returns an OK exit code, read the set of slave client
|
|
||||||
* IDs and the associated status code. We'll terminate all the slaves
|
|
||||||
* in error state.
|
|
||||||
*
|
|
||||||
* If the process returned an error, consider the list of slaves that
|
|
||||||
* can continue to be empty, so that it's just a special case of the
|
|
||||||
* normal code path. */
|
|
||||||
ok_slaves = zmalloc(sizeof(uint64_t)); /* Make space for the count. */
|
|
||||||
ok_slaves[0] = 0;
|
|
||||||
if (!bysignal && exitcode == 0) {
|
|
||||||
int readlen = sizeof(uint64_t);
|
|
||||||
|
|
||||||
if (read(server.rdb_pipe_read_result_from_child, ok_slaves, readlen) ==
|
|
||||||
readlen)
|
|
||||||
{
|
|
||||||
readlen = ok_slaves[0]*sizeof(uint64_t)*2;
|
|
||||||
|
|
||||||
/* Make space for enough elements as specified by the first
|
|
||||||
* uint64_t element in the array. */
|
|
||||||
ok_slaves = zrealloc(ok_slaves,sizeof(uint64_t)+readlen);
|
|
||||||
if (readlen &&
|
|
||||||
read(server.rdb_pipe_read_result_from_child, ok_slaves+1,
|
|
||||||
readlen) != readlen)
|
|
||||||
{
|
|
||||||
ok_slaves[0] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(server.rdb_pipe_read_result_from_child);
|
|
||||||
close(server.rdb_pipe_write_result_to_parent);
|
|
||||||
|
|
||||||
/* We can continue the replication process with all the slaves that
|
|
||||||
* correctly received the full payload. Others are terminated. */
|
|
||||||
listNode *ln;
|
|
||||||
listIter li;
|
|
||||||
|
|
||||||
listRewind(server.slaves,&li);
|
|
||||||
while((ln = listNext(&li))) {
|
|
||||||
client *slave = ln->value;
|
|
||||||
|
|
||||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
|
|
||||||
uint64_t j;
|
|
||||||
int errorcode = 0;
|
|
||||||
|
|
||||||
/* Search for the slave ID in the reply. In order for a slave to
|
|
||||||
* continue the replication process, we need to find it in the list,
|
|
||||||
* and it must have an error code set to 0 (which means success). */
|
|
||||||
for (j = 0; j < ok_slaves[0]; j++) {
|
|
||||||
if (slave->id == ok_slaves[2*j+1]) {
|
|
||||||
errorcode = ok_slaves[2*j+2];
|
|
||||||
break; /* Found in slaves list. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (j == ok_slaves[0] || errorcode != 0) {
|
|
||||||
serverLog(LL_WARNING,
|
|
||||||
"Closing slave %s: child->slave RDB transfer failed: %s",
|
|
||||||
replicationGetSlaveName(slave),
|
|
||||||
(errorcode == 0) ? "RDB transfer child aborted"
|
|
||||||
: strerror(errorcode));
|
|
||||||
freeClient(slave);
|
|
||||||
} else {
|
|
||||||
serverLog(LL_WARNING,
|
|
||||||
"Slave %s correctly received the streamed RDB file.",
|
|
||||||
replicationGetSlaveName(slave));
|
|
||||||
/* Restore the socket as non-blocking. */
|
|
||||||
anetNonBlock(NULL,slave->fd);
|
|
||||||
anetSendTimeout(NULL,slave->fd,0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zfree(ok_slaves);
|
|
||||||
|
|
||||||
updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? C_OK : C_ERR, RDB_CHILD_TYPE_SOCKET);
|
updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? C_OK : C_ERR, RDB_CHILD_TYPE_SOCKET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2425,120 +2386,61 @@ void killRDBChild(void) {
|
|||||||
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
|
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
|
||||||
* that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */
|
* that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */
|
||||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||||
int *fds;
|
|
||||||
uint64_t *clientids;
|
|
||||||
int numfds;
|
|
||||||
listNode *ln;
|
listNode *ln;
|
||||||
listIter li;
|
listIter li;
|
||||||
pid_t childpid;
|
pid_t childpid;
|
||||||
long long start;
|
|
||||||
int pipefds[2];
|
int pipefds[2];
|
||||||
|
|
||||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
|
|
||||||
/* Before to fork, create a pipe that will be used in order to
|
/* Even if the previous fork child exited, don't start a new one until we
|
||||||
* send back to the parent the IDs of the slaves that successfully
|
* drained the pipe. */
|
||||||
* received all the writes. */
|
if (server.rdb_pipe_conns) return C_ERR;
|
||||||
|
|
||||||
|
/* Before to fork, create a pipe that is used to transfer the rdb bytes to
|
||||||
|
* the parent, we can't let it write directly to the sockets, since in case
|
||||||
|
* of TLS we must let the parent handle a continuous TLS state when the
|
||||||
|
* child terminates and parent takes over. */
|
||||||
if (pipe(pipefds) == -1) return C_ERR;
|
if (pipe(pipefds) == -1) return C_ERR;
|
||||||
server.rdb_pipe_read_result_from_child = pipefds[0];
|
server.rdb_pipe_read = pipefds[0];
|
||||||
server.rdb_pipe_write_result_to_parent = pipefds[1];
|
server.rdb_pipe_write = pipefds[1];
|
||||||
|
anetNonBlock(NULL, server.rdb_pipe_read);
|
||||||
|
|
||||||
/* Collect the file descriptors of the slaves we want to transfer
|
/* Collect the connections of the replicas we want to transfer
|
||||||
* the RDB to, which are i WAIT_BGSAVE_START state. */
|
* the RDB to, which are i WAIT_BGSAVE_START state. */
|
||||||
fds = zmalloc(sizeof(int)*listLength(server.slaves));
|
server.rdb_pipe_conns = zmalloc(sizeof(connection *)*listLength(server.slaves));
|
||||||
/* We also allocate an array of corresponding client IDs. This will
|
server.rdb_pipe_numconns = 0;
|
||||||
* be useful for the child process in order to build the report
|
server.rdb_pipe_numconns_writing = 0;
|
||||||
* (sent via unix pipe) that will be sent to the parent. */
|
|
||||||
clientids = zmalloc(sizeof(uint64_t)*listLength(server.slaves));
|
|
||||||
numfds = 0;
|
|
||||||
|
|
||||||
listRewind(server.slaves,&li);
|
listRewind(server.slaves,&li);
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
client *slave = ln->value;
|
client *slave = ln->value;
|
||||||
|
|
||||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
||||||
clientids[numfds] = slave->id;
|
server.rdb_pipe_conns[server.rdb_pipe_numconns++] = slave->conn;
|
||||||
fds[numfds++] = slave->fd;
|
|
||||||
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
|
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
|
||||||
/* Put the socket in blocking mode to simplify RDB transfer.
|
|
||||||
* We'll restore it when the children returns (since duped socket
|
|
||||||
* will share the O_NONBLOCK attribute with the parent). */
|
|
||||||
anetBlock(NULL,slave->fd);
|
|
||||||
anetSendTimeout(NULL,slave->fd,server.repl_timeout*1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create the child process. */
|
/* Create the child process. */
|
||||||
openChildInfoPipe();
|
openChildInfoPipe();
|
||||||
start = ustime();
|
if ((childpid = redisFork()) == 0) {
|
||||||
if ((childpid = fork()) == 0) {
|
|
||||||
/* Child */
|
/* Child */
|
||||||
int retval;
|
int retval;
|
||||||
rio slave_sockets;
|
rio rdb;
|
||||||
|
|
||||||
rioInitWithFdset(&slave_sockets,fds,numfds);
|
rioInitWithFd(&rdb,server.rdb_pipe_write);
|
||||||
zfree(fds);
|
|
||||||
|
|
||||||
closeListeningSockets(0);
|
|
||||||
redisSetProcTitle("redis-rdb-to-slaves");
|
redisSetProcTitle("redis-rdb-to-slaves");
|
||||||
|
|
||||||
retval = rdbSaveRioWithEOFMark(&slave_sockets,NULL,rsi);
|
retval = rdbSaveRioWithEOFMark(&rdb,NULL,rsi);
|
||||||
if (retval == C_OK && rioFlush(&slave_sockets) == 0)
|
if (retval == C_OK && rioFlush(&rdb) == 0)
|
||||||
retval = C_ERR;
|
retval = C_ERR;
|
||||||
|
|
||||||
if (retval == C_OK) {
|
if (retval == C_OK) {
|
||||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||||
|
|
||||||
if (private_dirty) {
|
|
||||||
serverLog(LL_NOTICE,
|
|
||||||
"RDB: %zu MB of memory used by copy-on-write",
|
|
||||||
private_dirty/(1024*1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
server.child_info_data.cow_size = private_dirty;
|
|
||||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
|
||||||
|
|
||||||
/* If we are returning OK, at least one slave was served
|
|
||||||
* with the RDB file as expected, so we need to send a report
|
|
||||||
* to the parent via the pipe. The format of the message is:
|
|
||||||
*
|
|
||||||
* <len> <slave[0].id> <slave[0].error> ...
|
|
||||||
*
|
|
||||||
* len, slave IDs, and slave errors, are all uint64_t integers,
|
|
||||||
* so basically the reply is composed of 64 bits for the len field
|
|
||||||
* plus 2 additional 64 bit integers for each entry, for a total
|
|
||||||
* of 'len' entries.
|
|
||||||
*
|
|
||||||
* The 'id' represents the slave's client ID, so that the master
|
|
||||||
* can match the report with a specific slave, and 'error' is
|
|
||||||
* set to 0 if the replication process terminated with a success
|
|
||||||
* or the error code if an error occurred. */
|
|
||||||
void *msg = zmalloc(sizeof(uint64_t)*(1+2*numfds));
|
|
||||||
uint64_t *len = msg;
|
|
||||||
uint64_t *ids = len+1;
|
|
||||||
int j, msglen;
|
|
||||||
|
|
||||||
*len = numfds;
|
|
||||||
for (j = 0; j < numfds; j++) {
|
|
||||||
*ids++ = clientids[j];
|
|
||||||
*ids++ = slave_sockets.io.fdset.state[j];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write the message to the parent. If we have no good slaves or
|
|
||||||
* we are unable to transfer the message to the parent, we exit
|
|
||||||
* with an error so that the parent will abort the replication
|
|
||||||
* process with all the childre that were waiting. */
|
|
||||||
msglen = sizeof(uint64_t)*(1+2*numfds);
|
|
||||||
if (*len == 0 ||
|
|
||||||
write(server.rdb_pipe_write_result_to_parent,msg,msglen)
|
|
||||||
!= msglen)
|
|
||||||
{
|
|
||||||
retval = C_ERR;
|
|
||||||
}
|
|
||||||
zfree(msg);
|
|
||||||
}
|
}
|
||||||
zfree(clientids);
|
|
||||||
rioFreeFdset(&slave_sockets);
|
rioFreeFd(&rdb);
|
||||||
|
close(server.rdb_pipe_write); /* wake up the reader, tell it we're done. */
|
||||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
@ -2552,32 +2454,28 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
listRewind(server.slaves,&li);
|
listRewind(server.slaves,&li);
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
client *slave = ln->value;
|
client *slave = ln->value;
|
||||||
int j;
|
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
|
||||||
|
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||||
for (j = 0; j < numfds; j++) {
|
|
||||||
if (slave->id == clientids[j]) {
|
|
||||||
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(pipefds[0]);
|
close(server.rdb_pipe_write);
|
||||||
close(pipefds[1]);
|
close(server.rdb_pipe_read);
|
||||||
|
zfree(server.rdb_pipe_conns);
|
||||||
|
server.rdb_pipe_conns = NULL;
|
||||||
|
server.rdb_pipe_numconns = 0;
|
||||||
|
server.rdb_pipe_numconns_writing = 0;
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
} else {
|
} else {
|
||||||
server.stat_fork_time = ustime()-start;
|
|
||||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
|
||||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
|
||||||
|
|
||||||
serverLog(LL_NOTICE,"Background RDB transfer started by pid %d",
|
serverLog(LL_NOTICE,"Background RDB transfer started by pid %d",
|
||||||
childpid);
|
childpid);
|
||||||
server.rdb_save_time_start = time(NULL);
|
server.rdb_save_time_start = time(NULL);
|
||||||
server.rdb_child_pid = childpid;
|
server.rdb_child_pid = childpid;
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_SOCKET;
|
server.rdb_child_type = RDB_CHILD_TYPE_SOCKET;
|
||||||
updateDictResizePolicy();
|
close(server.rdb_pipe_write); /* close write in parent so that it can detect the close on the child. */
|
||||||
|
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
|
||||||
|
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
zfree(clientids);
|
|
||||||
zfree(fds);
|
|
||||||
return (childpid == -1) ? C_ERR : C_OK;
|
return (childpid == -1) ? C_ERR : C_OK;
|
||||||
}
|
}
|
||||||
return C_OK; /* Unreached. */
|
return C_OK; /* Unreached. */
|
||||||
@ -2617,15 +2515,15 @@ void bgsaveCommand(client *c) {
|
|||||||
|
|
||||||
if (server.rdb_child_pid != -1) {
|
if (server.rdb_child_pid != -1) {
|
||||||
addReplyError(c,"Background save already in progress");
|
addReplyError(c,"Background save already in progress");
|
||||||
} else if (server.aof_child_pid != -1) {
|
} else if (hasActiveChildProcess()) {
|
||||||
if (schedule) {
|
if (schedule) {
|
||||||
server.rdb_bgsave_scheduled = 1;
|
server.rdb_bgsave_scheduled = 1;
|
||||||
addReplyStatus(c,"Background saving scheduled");
|
addReplyStatus(c,"Background saving scheduled");
|
||||||
} else {
|
} else {
|
||||||
addReplyError(c,
|
addReplyError(c,
|
||||||
"An AOF log rewriting in progress: can't BGSAVE right now. "
|
"Another child process is active (AOF?): can't BGSAVE right now. "
|
||||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||||
"possible.");
|
"possible.");
|
||||||
}
|
}
|
||||||
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
||||||
addReplyStatus(c,"Background saving started");
|
addReplyStatus(c,"Background saving started");
|
||||||
|
11
src/rdb.h
11
src/rdb.h
@ -121,8 +121,10 @@
|
|||||||
#define RDB_LOAD_PLAIN (1<<1)
|
#define RDB_LOAD_PLAIN (1<<1)
|
||||||
#define RDB_LOAD_SDS (1<<2)
|
#define RDB_LOAD_SDS (1<<2)
|
||||||
|
|
||||||
#define RDB_SAVE_NONE 0
|
/* flags on the purpose of rdb save or load */
|
||||||
#define RDB_SAVE_AOF_PREAMBLE (1<<0)
|
#define RDBFLAGS_NONE 0
|
||||||
|
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
|
||||||
|
#define RDBFLAGS_REPLICATION (1<<1)
|
||||||
|
|
||||||
int rdbSaveType(rio *rdb, unsigned char type);
|
int rdbSaveType(rio *rdb, unsigned char type);
|
||||||
int rdbLoadType(rio *rdb);
|
int rdbLoadType(rio *rdb);
|
||||||
@ -135,7 +137,7 @@ uint64_t rdbLoadLen(rio *rdb, int *isencoded);
|
|||||||
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||||
int rdbSaveObjectType(rio *rdb, robj *o);
|
int rdbSaveObjectType(rio *rdb, robj *o);
|
||||||
int rdbLoadObjectType(rio *rdb);
|
int rdbLoadObjectType(rio *rdb);
|
||||||
int rdbLoad(char *filename, rdbSaveInfo *rsi);
|
int rdbLoad(char *filename, rdbSaveInfo *rsi, int rdbflags);
|
||||||
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
|
||||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||||
void rdbRemoveTempFile(pid_t childpid);
|
void rdbRemoveTempFile(pid_t childpid);
|
||||||
@ -154,7 +156,8 @@ int rdbSaveBinaryDoubleValue(rio *rdb, double val);
|
|||||||
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
|
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
|
||||||
int rdbSaveBinaryFloatValue(rio *rdb, float val);
|
int rdbSaveBinaryFloatValue(rio *rdb, float val);
|
||||||
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
|
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
|
||||||
int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof);
|
int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi);
|
||||||
|
int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi);
|
||||||
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
|
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -202,7 +202,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expiretime = -1;
|
expiretime = -1;
|
||||||
startLoadingFile(fp, rdbfilename);
|
startLoadingFile(fp, rdbfilename, RDBFLAGS_NONE);
|
||||||
while(1) {
|
while(1) {
|
||||||
robj *key, *val;
|
robj *key, *val;
|
||||||
|
|
||||||
@ -316,7 +316,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (closefile) fclose(fp);
|
if (closefile) fclose(fp);
|
||||||
stopLoading();
|
stopLoading(1);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||||
@ -327,7 +327,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
|||||||
}
|
}
|
||||||
err:
|
err:
|
||||||
if (closefile) fclose(fp);
|
if (closefile) fclose(fp);
|
||||||
stopLoading();
|
stopLoading(0);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
233
src/redis-cli.c
233
src/redis-cli.c
@ -47,6 +47,10 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include <hiredis.h>
|
#include <hiredis.h>
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <hiredis_ssl.h>
|
||||||
|
#endif
|
||||||
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
|
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
|
||||||
#include "dict.h"
|
#include "dict.h"
|
||||||
#include "adlist.h"
|
#include "adlist.h"
|
||||||
@ -188,6 +192,12 @@ static struct config {
|
|||||||
char *hostip;
|
char *hostip;
|
||||||
int hostport;
|
int hostport;
|
||||||
char *hostsocket;
|
char *hostsocket;
|
||||||
|
int tls;
|
||||||
|
char *sni;
|
||||||
|
char *cacert;
|
||||||
|
char *cacertdir;
|
||||||
|
char *cert;
|
||||||
|
char *key;
|
||||||
long repeat;
|
long repeat;
|
||||||
long interval;
|
long interval;
|
||||||
int dbnum;
|
int dbnum;
|
||||||
@ -218,6 +228,7 @@ static struct config {
|
|||||||
int hotkeys;
|
int hotkeys;
|
||||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||||
char *auth;
|
char *auth;
|
||||||
|
char *user;
|
||||||
int output; /* output mode, see OUTPUT_* defines */
|
int output; /* output mode, see OUTPUT_* defines */
|
||||||
sds mb_delim;
|
sds mb_delim;
|
||||||
char prompt[128];
|
char prompt[128];
|
||||||
@ -230,6 +241,7 @@ static struct config {
|
|||||||
int verbose;
|
int verbose;
|
||||||
clusterManagerCommand cluster_manager_command;
|
clusterManagerCommand cluster_manager_command;
|
||||||
int no_auth_warning;
|
int no_auth_warning;
|
||||||
|
int resp3;
|
||||||
} config;
|
} config;
|
||||||
|
|
||||||
/* User preferences. */
|
/* User preferences. */
|
||||||
@ -358,7 +370,7 @@ static sds percentDecode(const char *pe, size_t len) {
|
|||||||
* URI scheme is based on the the provisional specification[1] excluding support
|
* URI scheme is based on the the provisional specification[1] excluding support
|
||||||
* for query parameters. Valid URIs are:
|
* for query parameters. Valid URIs are:
|
||||||
* scheme: "redis://"
|
* scheme: "redis://"
|
||||||
* authority: [<username> ":"] <password> "@"] [<hostname> [":" <port>]]
|
* authority: [[<username> ":"] <password> "@"] [<hostname> [":" <port>]]
|
||||||
* path: ["/" [<db>]]
|
* path: ["/" [<db>]]
|
||||||
*
|
*
|
||||||
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
|
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
|
||||||
@ -728,8 +740,13 @@ static int cliAuth(void) {
|
|||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
if (config.auth == NULL) return REDIS_OK;
|
if (config.auth == NULL) return REDIS_OK;
|
||||||
|
|
||||||
reply = redisCommand(context,"AUTH %s",config.auth);
|
if (config.user == NULL)
|
||||||
|
reply = redisCommand(context,"AUTH %s",config.auth);
|
||||||
|
else
|
||||||
|
reply = redisCommand(context,"AUTH %s %s",config.user,config.auth);
|
||||||
if (reply != NULL) {
|
if (reply != NULL) {
|
||||||
|
if (reply->type == REDIS_REPLY_ERROR)
|
||||||
|
fprintf(stderr,"Warning: AUTH failed\n");
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
@ -751,6 +768,86 @@ static int cliSelect(void) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
|
||||||
|
* not building with TLS support.
|
||||||
|
*/
|
||||||
|
static int cliSecureConnection(redisContext *c, const char **err) {
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
static SSL_CTX *ssl_ctx = NULL;
|
||||||
|
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
|
||||||
|
if (!ssl_ctx) {
|
||||||
|
*err = "Failed to create SSL_CTX";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||||
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||||
|
|
||||||
|
if (config.cacert || config.cacertdir) {
|
||||||
|
if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) {
|
||||||
|
*err = "Invalid CA Certificate File/Directory";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
|
||||||
|
*err = "Failed to use default CA paths";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) {
|
||||||
|
*err = "Invalid client certificate";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) {
|
||||||
|
*err = "Invalid private key";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL *ssl = SSL_new(ssl_ctx);
|
||||||
|
if (!ssl) {
|
||||||
|
*err = "Failed to create SSL object";
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) {
|
||||||
|
*err = "Failed to configure SNI";
|
||||||
|
SSL_free(ssl);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redisInitiateSSL(c, ssl);
|
||||||
|
|
||||||
|
error:
|
||||||
|
SSL_CTX_free(ssl_ctx);
|
||||||
|
ssl_ctx = NULL;
|
||||||
|
return REDIS_ERR;
|
||||||
|
#else
|
||||||
|
(void) c;
|
||||||
|
(void) err;
|
||||||
|
return REDIS_OK;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select RESP3 mode if redis-cli was started with the -3 option. */
|
||||||
|
static int cliSwitchProto(void) {
|
||||||
|
redisReply *reply;
|
||||||
|
if (config.resp3 == 0) return REDIS_OK;
|
||||||
|
|
||||||
|
reply = redisCommand(context,"HELLO 3");
|
||||||
|
if (reply != NULL) {
|
||||||
|
int result = REDIS_OK;
|
||||||
|
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||||
|
freeReplyObject(reply);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
/* Connect to the server. It is possible to pass certain flags to the function:
|
/* Connect to the server. It is possible to pass certain flags to the function:
|
||||||
* CC_FORCE: The connection is performed even if there is already
|
* CC_FORCE: The connection is performed even if there is already
|
||||||
* a connected socket.
|
* a connected socket.
|
||||||
@ -767,6 +864,16 @@ static int cliConnect(int flags) {
|
|||||||
context = redisConnectUnix(config.hostsocket);
|
context = redisConnectUnix(config.hostsocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context->err && config.tls) {
|
||||||
|
const char *err = NULL;
|
||||||
|
if (cliSecureConnection(context, &err) == REDIS_ERR && err) {
|
||||||
|
fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
|
||||||
|
context = NULL;
|
||||||
|
redisFree(context);
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (context->err) {
|
if (context->err) {
|
||||||
if (!(flags & CC_QUIET)) {
|
if (!(flags & CC_QUIET)) {
|
||||||
fprintf(stderr,"Could not connect to Redis at ");
|
fprintf(stderr,"Could not connect to Redis at ");
|
||||||
@ -782,17 +889,20 @@ static int cliConnect(int flags) {
|
|||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket
|
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket
|
||||||
* in order to prevent timeouts caused by the execution of long
|
* in order to prevent timeouts caused by the execution of long
|
||||||
* commands. At the same time this improves the detection of real
|
* commands. At the same time this improves the detection of real
|
||||||
* errors. */
|
* errors. */
|
||||||
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
||||||
|
|
||||||
/* Do AUTH and select the right DB. */
|
/* Do AUTH, select the right DB, switch to RESP3 if needed. */
|
||||||
if (cliAuth() != REDIS_OK)
|
if (cliAuth() != REDIS_OK)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
if (cliSelect() != REDIS_OK)
|
if (cliSelect() != REDIS_OK)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
if (cliSwitchProto() != REDIS_OK)
|
||||||
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
@ -819,10 +929,17 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
|||||||
out = sdscatprintf(out,"(double) %s\n",r->str);
|
out = sdscatprintf(out,"(double) %s\n",r->str);
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
/* If you are producing output for the standard output we want
|
/* If you are producing output for the standard output we want
|
||||||
* a more interesting output with quoted characters and so forth */
|
* a more interesting output with quoted characters and so forth,
|
||||||
out = sdscatrepr(out,r->str,r->len);
|
* unless it's a verbatim string type. */
|
||||||
out = sdscat(out,"\n");
|
if (r->type == REDIS_REPLY_STRING) {
|
||||||
|
out = sdscatrepr(out,r->str,r->len);
|
||||||
|
out = sdscat(out,"\n");
|
||||||
|
} else {
|
||||||
|
out = sdscatlen(out,r->str,r->len);
|
||||||
|
out = sdscat(out,"\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_NIL:
|
case REDIS_REPLY_NIL:
|
||||||
out = sdscat(out,"(nil)\n");
|
out = sdscat(out,"(nil)\n");
|
||||||
@ -961,6 +1078,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
|||||||
break;
|
break;
|
||||||
case REDIS_REPLY_STATUS:
|
case REDIS_REPLY_STATUS:
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
|
if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
|
||||||
/* The Lua debugger replies with arrays of simple (status)
|
/* The Lua debugger replies with arrays of simple (status)
|
||||||
* strings. We colorize the output for more fun if this
|
* strings. We colorize the output for more fun if this
|
||||||
@ -980,9 +1098,15 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
|||||||
out = sdscatlen(out,r->str,r->len);
|
out = sdscatlen(out,r->str,r->len);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_BOOL:
|
||||||
|
out = sdscat(out,r->integer ? "(true)" : "(false)");
|
||||||
|
break;
|
||||||
case REDIS_REPLY_INTEGER:
|
case REDIS_REPLY_INTEGER:
|
||||||
out = sdscatprintf(out,"%lld",r->integer);
|
out = sdscatprintf(out,"%lld",r->integer);
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_DOUBLE:
|
||||||
|
out = sdscatprintf(out,"%s",r->str);
|
||||||
|
break;
|
||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
for (i = 0; i < r->elements; i++) {
|
for (i = 0; i < r->elements; i++) {
|
||||||
if (i > 0) out = sdscat(out,config.mb_delim);
|
if (i > 0) out = sdscat(out,config.mb_delim);
|
||||||
@ -991,6 +1115,19 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
|||||||
sdsfree(tmp);
|
sdsfree(tmp);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_MAP:
|
||||||
|
for (i = 0; i < r->elements; i += 2) {
|
||||||
|
if (i > 0) out = sdscat(out,config.mb_delim);
|
||||||
|
tmp = cliFormatReplyRaw(r->element[i]);
|
||||||
|
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||||
|
sdsfree(tmp);
|
||||||
|
|
||||||
|
out = sdscatlen(out," ",1);
|
||||||
|
tmp = cliFormatReplyRaw(r->element[i+1]);
|
||||||
|
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||||
|
sdsfree(tmp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
||||||
exit(1);
|
exit(1);
|
||||||
@ -1013,13 +1150,21 @@ static sds cliFormatReplyCSV(redisReply *r) {
|
|||||||
case REDIS_REPLY_INTEGER:
|
case REDIS_REPLY_INTEGER:
|
||||||
out = sdscatprintf(out,"%lld",r->integer);
|
out = sdscatprintf(out,"%lld",r->integer);
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_DOUBLE:
|
||||||
|
out = sdscatprintf(out,"%s",r->str);
|
||||||
|
break;
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
out = sdscatrepr(out,r->str,r->len);
|
out = sdscatrepr(out,r->str,r->len);
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_NIL:
|
case REDIS_REPLY_NIL:
|
||||||
out = sdscat(out,"NIL");
|
out = sdscat(out,"NULL");
|
||||||
|
break;
|
||||||
|
case REDIS_REPLY_BOOL:
|
||||||
|
out = sdscat(out,r->integer ? "true" : "false");
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
|
case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
|
||||||
for (i = 0; i < r->elements; i++) {
|
for (i = 0; i < r->elements; i++) {
|
||||||
sds tmp = cliFormatReplyCSV(r->element[i]);
|
sds tmp = cliFormatReplyCSV(r->element[i]);
|
||||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||||
@ -1213,7 +1358,8 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
|||||||
if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
|
if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
|
||||||
config.dbnum = atoi(argv[1]);
|
config.dbnum = atoi(argv[1]);
|
||||||
cliRefreshPrompt();
|
cliRefreshPrompt();
|
||||||
} else if (!strcasecmp(command,"auth") && argc == 2) {
|
} else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3))
|
||||||
|
{
|
||||||
cliSelect();
|
cliSelect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1245,6 +1391,13 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
|
|||||||
|
|
||||||
redisFree(c);
|
redisFree(c);
|
||||||
c = redisConnect(config.hostip,config.hostport);
|
c = redisConnect(config.hostip,config.hostport);
|
||||||
|
if (!c->err && config.tls) {
|
||||||
|
const char *err = NULL;
|
||||||
|
if (cliSecureConnection(c, &err) == REDIS_ERR && err) {
|
||||||
|
fprintf(stderr, "TLS Error: %s\n", err);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
usleep(1000000);
|
usleep(1000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1296,8 +1449,12 @@ static int parseOptions(int argc, char **argv) {
|
|||||||
config.dbnum = atoi(argv[++i]);
|
config.dbnum = atoi(argv[++i]);
|
||||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||||
config.no_auth_warning = 1;
|
config.no_auth_warning = 1;
|
||||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
} else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
|
||||||
|
&& !lastarg)
|
||||||
|
{
|
||||||
config.auth = argv[++i];
|
config.auth = argv[++i];
|
||||||
|
} else if (!strcmp(argv[i],"--user") && !lastarg) {
|
||||||
|
config.user = argv[++i];
|
||||||
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
||||||
parseRedisUri(argv[++i]);
|
parseRedisUri(argv[++i]);
|
||||||
} else if (!strcmp(argv[i],"--raw")) {
|
} else if (!strcmp(argv[i],"--raw")) {
|
||||||
@ -1434,11 +1591,27 @@ static int parseOptions(int argc, char **argv) {
|
|||||||
} else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
|
} else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
|
||||||
config.cluster_manager_command.flags |=
|
config.cluster_manager_command.flags |=
|
||||||
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
|
CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
} else if (!strcmp(argv[i],"--tls")) {
|
||||||
|
config.tls = 1;
|
||||||
|
} else if (!strcmp(argv[i],"--sni")) {
|
||||||
|
config.sni = argv[++i];
|
||||||
|
} else if (!strcmp(argv[i],"--cacertdir")) {
|
||||||
|
config.cacertdir = argv[++i];
|
||||||
|
} else if (!strcmp(argv[i],"--cacert")) {
|
||||||
|
config.cacert = argv[++i];
|
||||||
|
} else if (!strcmp(argv[i],"--cert")) {
|
||||||
|
config.cert = argv[++i];
|
||||||
|
} else if (!strcmp(argv[i],"--key")) {
|
||||||
|
config.key = argv[++i];
|
||||||
|
#endif
|
||||||
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
|
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
|
||||||
sds version = cliVersion();
|
sds version = cliVersion();
|
||||||
printf("redis-cli %s\n", version);
|
printf("redis-cli %s\n", version);
|
||||||
sdsfree(version);
|
sdsfree(version);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
} else if (!strcmp(argv[i],"-3")) {
|
||||||
|
config.resp3 = 1;
|
||||||
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
|
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
|
||||||
if (config.cluster_manager_command.argc == 0) {
|
if (config.cluster_manager_command.argc == 0) {
|
||||||
int j = i + 1;
|
int j = i + 1;
|
||||||
@ -1514,14 +1687,26 @@ static void usage(void) {
|
|||||||
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
|
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
|
||||||
" variable to pass this password more safely\n"
|
" variable to pass this password more safely\n"
|
||||||
" (if both are used, this argument takes predecence).\n"
|
" (if both are used, this argument takes predecence).\n"
|
||||||
|
" -user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||||
|
" -pass <password> Alias of -a for consistency with the new --user option.\n"
|
||||||
" -u <uri> Server URI.\n"
|
" -u <uri> Server URI.\n"
|
||||||
" -r <repeat> Execute specified command N times.\n"
|
" -r <repeat> Execute specified command N times.\n"
|
||||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||||
" It is possible to specify sub-second times like -i 0.1.\n"
|
" It is possible to specify sub-second times like -i 0.1.\n"
|
||||||
" -n <db> Database number.\n"
|
" -n <db> Database number.\n"
|
||||||
|
" -3 Start session in RESP3 protocol mode.\n"
|
||||||
" -x Read last argument from STDIN.\n"
|
" -x Read last argument from STDIN.\n"
|
||||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
|
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
|
||||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
" --tls Establish a secure TLS connection.\n"
|
||||||
|
" --cacert CA Certificate file to verify with.\n"
|
||||||
|
" --cacertdir Directory where trusted CA certificates are stored.\n"
|
||||||
|
" If neither cacert nor cacertdir are specified, the default\n"
|
||||||
|
" system-wide trusted root certs configuration will apply.\n"
|
||||||
|
" --cert Client certificate to authenticate with.\n"
|
||||||
|
" --key Private key file to authenticate with.\n"
|
||||||
|
#endif
|
||||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||||
" not a tty).\n"
|
" not a tty).\n"
|
||||||
" --no-raw Force formatted output even when STDOUT is not a tty.\n"
|
" --no-raw Force formatted output even when STDOUT is not a tty.\n"
|
||||||
@ -1533,7 +1718,9 @@ static void usage(void) {
|
|||||||
" --csv is specified, or if you redirect the output to a non\n"
|
" --csv is specified, or if you redirect the output to a non\n"
|
||||||
" TTY, it samples the latency for 1 second (you can use\n"
|
" TTY, it samples the latency for 1 second (you can use\n"
|
||||||
" -i to change the interval), then produces a single output\n"
|
" -i to change the interval), then produces a single output\n"
|
||||||
" and exits.\n"
|
" and exits.\n",version);
|
||||||
|
|
||||||
|
fprintf(stderr,
|
||||||
" --latency-history Like --latency but tracking latency changes over time.\n"
|
" --latency-history Like --latency but tracking latency changes over time.\n"
|
||||||
" Default time interval is 15 sec. Change it using -i.\n"
|
" Default time interval is 15 sec. Change it using -i.\n"
|
||||||
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
|
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
|
||||||
@ -1544,7 +1731,9 @@ static void usage(void) {
|
|||||||
" --pipe Transfer raw Redis protocol from stdin to server.\n"
|
" --pipe Transfer raw Redis protocol from stdin to server.\n"
|
||||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
||||||
" no reply is received within <n> seconds.\n"
|
" no reply is received within <n> seconds.\n"
|
||||||
" Default timeout: %d. Use 0 to wait forever.\n"
|
" Default timeout: %d. Use 0 to wait forever.\n",
|
||||||
|
REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||||
|
fprintf(stderr,
|
||||||
" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
|
" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
|
||||||
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
|
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
|
||||||
" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
|
" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
|
||||||
@ -1567,8 +1756,7 @@ static void usage(void) {
|
|||||||
" line interface.\n"
|
" line interface.\n"
|
||||||
" --help Output this help and exit.\n"
|
" --help Output this help and exit.\n"
|
||||||
" --version Output version and exit.\n"
|
" --version Output version and exit.\n"
|
||||||
"\n",
|
"\n");
|
||||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
|
||||||
/* Using another fprintf call to avoid -Woverlength-strings compile warning */
|
/* Using another fprintf call to avoid -Woverlength-strings compile warning */
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Cluster Manager Commands:\n"
|
"Cluster Manager Commands:\n"
|
||||||
@ -2336,6 +2524,15 @@ cleanup:
|
|||||||
static int clusterManagerNodeConnect(clusterManagerNode *node) {
|
static int clusterManagerNodeConnect(clusterManagerNode *node) {
|
||||||
if (node->context) redisFree(node->context);
|
if (node->context) redisFree(node->context);
|
||||||
node->context = redisConnect(node->ip, node->port);
|
node->context = redisConnect(node->ip, node->port);
|
||||||
|
if (!node->context->err && config.tls) {
|
||||||
|
const char *err = NULL;
|
||||||
|
if (cliSecureConnection(node->context, &err) == REDIS_ERR && err) {
|
||||||
|
fprintf(stderr,"TLS Error: %s\n", err);
|
||||||
|
redisFree(node->context);
|
||||||
|
node->context = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (node->context->err) {
|
if (node->context->err) {
|
||||||
fprintf(stderr,"Could not connect to Redis at ");
|
fprintf(stderr,"Could not connect to Redis at ");
|
||||||
fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,
|
fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,
|
||||||
@ -2350,7 +2547,12 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) {
|
|||||||
* errors. */
|
* errors. */
|
||||||
anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
||||||
if (config.auth) {
|
if (config.auth) {
|
||||||
redisReply *reply = redisCommand(node->context,"AUTH %s",config.auth);
|
redisReply *reply;
|
||||||
|
if (config.user == NULL)
|
||||||
|
reply = redisCommand(node->context,"AUTH %s", config.auth);
|
||||||
|
else
|
||||||
|
reply = redisCommand(node->context,"AUTH %s %s",
|
||||||
|
config.user,config.auth);
|
||||||
int ok = clusterManagerCheckRedisReply(node, reply, NULL);
|
int ok = clusterManagerCheckRedisReply(node, reply, NULL);
|
||||||
if (reply != NULL) freeReplyObject(reply);
|
if (reply != NULL) freeReplyObject(reply);
|
||||||
if (!ok) return 0;
|
if (!ok) return 0;
|
||||||
@ -3222,7 +3424,7 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
|
|||||||
redisReply *entry = reply->element[i];
|
redisReply *entry = reply->element[i];
|
||||||
size_t idx = i + offset;
|
size_t idx = i + offset;
|
||||||
assert(entry->type == REDIS_REPLY_STRING);
|
assert(entry->type == REDIS_REPLY_STRING);
|
||||||
argv[idx] = (char *) sdsnew(entry->str);
|
argv[idx] = (char *) sdsnewlen(entry->str, entry->len);
|
||||||
argv_len[idx] = entry->len;
|
argv_len[idx] = entry->len;
|
||||||
if (dots) dots[i] = '.';
|
if (dots) dots[i] = '.';
|
||||||
}
|
}
|
||||||
@ -7678,6 +7880,7 @@ int main(int argc, char **argv) {
|
|||||||
config.hotkeys = 0;
|
config.hotkeys = 0;
|
||||||
config.stdinarg = 0;
|
config.stdinarg = 0;
|
||||||
config.auth = NULL;
|
config.auth = NULL;
|
||||||
|
config.user = NULL;
|
||||||
config.eval = NULL;
|
config.eval = NULL;
|
||||||
config.eval_ldb = 0;
|
config.eval_ldb = 0;
|
||||||
config.eval_ldb_end = 0;
|
config.eval_ldb_end = 0;
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
#define REDISMODULE_READ (1<<0)
|
#define REDISMODULE_READ (1<<0)
|
||||||
#define REDISMODULE_WRITE (1<<1)
|
#define REDISMODULE_WRITE (1<<1)
|
||||||
|
|
||||||
|
/* RedisModule_OpenKey extra flags for the 'mode' argument.
|
||||||
|
* Avoid touching the LRU/LFU of the key when opened. */
|
||||||
|
#define REDISMODULE_OPEN_KEY_NOTOUCH (1<<16)
|
||||||
|
|
||||||
#define REDISMODULE_LIST_HEAD 0
|
#define REDISMODULE_LIST_HEAD 0
|
||||||
#define REDISMODULE_LIST_TAIL 1
|
#define REDISMODULE_LIST_TAIL 1
|
||||||
|
|
||||||
@ -89,8 +93,28 @@
|
|||||||
#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
|
#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
|
||||||
/* Redis is currently loading either from AOF or RDB. */
|
/* Redis is currently loading either from AOF or RDB. */
|
||||||
#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
|
#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
|
||||||
|
/* The replica has no link with its master, note that
|
||||||
|
* there is the inverse flag as well:
|
||||||
|
*
|
||||||
|
* REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE
|
||||||
|
*
|
||||||
|
* The two flags are exclusive, one or the other can be set. */
|
||||||
|
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE (1<<14)
|
||||||
|
/* The replica is trying to connect with the master.
|
||||||
|
* (REPL_STATE_CONNECT and REPL_STATE_CONNECTING states) */
|
||||||
|
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING (1<<15)
|
||||||
|
/* THe replica is receiving an RDB file from its master. */
|
||||||
|
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING (1<<16)
|
||||||
|
/* The replica is online, receiving updates from its master. */
|
||||||
|
#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17)
|
||||||
|
/* There is currently some background process active. */
|
||||||
|
#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18)
|
||||||
|
|
||||||
|
/* Keyspace changes notification classes. Every class is associated with a
|
||||||
|
* character for configuration purposes.
|
||||||
|
* NOTE: These have to be in sync with NOTIFY_* in server.h */
|
||||||
|
#define REDISMODULE_NOTIFY_KEYSPACE (1<<0) /* K */
|
||||||
|
#define REDISMODULE_NOTIFY_KEYEVENT (1<<1) /* E */
|
||||||
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
|
||||||
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
|
||||||
#define REDISMODULE_NOTIFY_LIST (1<<4) /* l */
|
#define REDISMODULE_NOTIFY_LIST (1<<4) /* l */
|
||||||
@ -144,6 +168,209 @@ typedef uint64_t RedisModuleTimerID;
|
|||||||
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
||||||
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
||||||
|
|
||||||
|
/* Declare that the module can handle errors with RedisModule_SetModuleOptions. */
|
||||||
|
#define REDISMODULE_OPTIONS_HANDLE_IO_ERRORS (1<<0)
|
||||||
|
/* When set, Redis will not call RedisModule_SignalModifiedKey(), implicitly in
|
||||||
|
* RedisModule_CloseKey, and the module needs to do that when manually when keys
|
||||||
|
* are modified from the user's sperspective, to invalidate WATCH. */
|
||||||
|
#define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1)
|
||||||
|
|
||||||
|
/* Server events definitions. */
|
||||||
|
#define REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED 0
|
||||||
|
#define REDISMODULE_EVENT_PERSISTENCE 1
|
||||||
|
#define REDISMODULE_EVENT_FLUSHDB 2
|
||||||
|
#define REDISMODULE_EVENT_LOADING 3
|
||||||
|
#define REDISMODULE_EVENT_CLIENT_CHANGE 4
|
||||||
|
#define REDISMODULE_EVENT_SHUTDOWN 5
|
||||||
|
#define REDISMODULE_EVENT_REPLICA_CHANGE 6
|
||||||
|
#define REDISMODULE_EVENT_MASTER_LINK_CHANGE 7
|
||||||
|
#define REDISMODULE_EVENT_CRON_LOOP 8
|
||||||
|
#define REDISMODULE_EVENT_MODULE_CHANGE 9
|
||||||
|
#define REDISMODULE_EVENT_LOADING_PROGRESS 10
|
||||||
|
|
||||||
|
typedef struct RedisModuleEvent {
|
||||||
|
uint64_t id; /* REDISMODULE_EVENT_... defines. */
|
||||||
|
uint64_t dataver; /* Version of the structure we pass as 'data'. */
|
||||||
|
} RedisModuleEvent;
|
||||||
|
|
||||||
|
struct RedisModuleCtx;
|
||||||
|
typedef void (*RedisModuleEventCallback)(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data);
|
||||||
|
|
||||||
|
static const RedisModuleEvent
|
||||||
|
RedisModuleEvent_ReplicationRoleChanged = {
|
||||||
|
REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_Persistence = {
|
||||||
|
REDISMODULE_EVENT_PERSISTENCE,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_FlushDB = {
|
||||||
|
REDISMODULE_EVENT_FLUSHDB,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_Loading = {
|
||||||
|
REDISMODULE_EVENT_LOADING,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_ClientChange = {
|
||||||
|
REDISMODULE_EVENT_CLIENT_CHANGE,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_Shutdown = {
|
||||||
|
REDISMODULE_EVENT_SHUTDOWN,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_ReplicaChange = {
|
||||||
|
REDISMODULE_EVENT_REPLICA_CHANGE,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_CronLoop = {
|
||||||
|
REDISMODULE_EVENT_CRON_LOOP,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_MasterLinkChange = {
|
||||||
|
REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_ModuleChange = {
|
||||||
|
REDISMODULE_EVENT_MODULE_CHANGE,
|
||||||
|
1
|
||||||
|
},
|
||||||
|
RedisModuleEvent_LoadingProgress = {
|
||||||
|
REDISMODULE_EVENT_LOADING_PROGRESS,
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Those are values that are used for the 'subevent' callback argument. */
|
||||||
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0
|
||||||
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START 1
|
||||||
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START 2
|
||||||
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_ENDED 3
|
||||||
|
#define REDISMODULE_SUBEVENT_PERSISTENCE_FAILED 4
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_RDB_START 0
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_AOF_START 1
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_REPL_START 2
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_ENDED 3
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_FAILED 4
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0
|
||||||
|
#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_MASTER_LINK_UP 0
|
||||||
|
#define REDISMODULE_SUBEVENT_MASTER_LINK_DOWN 1
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE 0
|
||||||
|
#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE 1
|
||||||
|
|
||||||
|
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER 0
|
||||||
|
#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA 1
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_FLUSHDB_START 0
|
||||||
|
#define REDISMODULE_SUBEVENT_FLUSHDB_END 1
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_MODULE_LOADED 0
|
||||||
|
#define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1
|
||||||
|
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0
|
||||||
|
#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1
|
||||||
|
|
||||||
|
/* RedisModuleClientInfo flags. */
|
||||||
|
#define REDISMODULE_CLIENTINFO_FLAG_SSL (1<<0)
|
||||||
|
#define REDISMODULE_CLIENTINFO_FLAG_PUBSUB (1<<1)
|
||||||
|
#define REDISMODULE_CLIENTINFO_FLAG_BLOCKED (1<<2)
|
||||||
|
#define REDISMODULE_CLIENTINFO_FLAG_TRACKING (1<<3)
|
||||||
|
#define REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET (1<<4)
|
||||||
|
#define REDISMODULE_CLIENTINFO_FLAG_MULTI (1<<5)
|
||||||
|
|
||||||
|
/* Here we take all the structures that the module pass to the core
|
||||||
|
* and the other way around. Notably the list here contains the structures
|
||||||
|
* used by the hooks API RedisModule_RegisterToServerEvent().
|
||||||
|
*
|
||||||
|
* The structures always start with a 'version' field. This is useful
|
||||||
|
* when we want to pass a reference to the structure to the core APIs,
|
||||||
|
* for the APIs to fill the structure. In that case, the structure 'version'
|
||||||
|
* field is initialized before passing it to the core, so that the core is
|
||||||
|
* able to cast the pointer to the appropriate structure version. In this
|
||||||
|
* way we obtain ABI compatibility.
|
||||||
|
*
|
||||||
|
* Here we'll list all the structure versions in case they evolve over time,
|
||||||
|
* however using a define, we'll make sure to use the last version as the
|
||||||
|
* public name for the module to use. */
|
||||||
|
|
||||||
|
#define REDISMODULE_CLIENTINFO_VERSION 1
|
||||||
|
typedef struct RedisModuleClientInfo {
|
||||||
|
uint64_t version; /* Version of this structure for ABI compat. */
|
||||||
|
uint64_t flags; /* REDISMODULE_CLIENTINFO_FLAG_* */
|
||||||
|
uint64_t id; /* Client ID. */
|
||||||
|
char addr[46]; /* IPv4 or IPv6 address. */
|
||||||
|
uint16_t port; /* TCP port. */
|
||||||
|
uint16_t db; /* Selected DB. */
|
||||||
|
} RedisModuleClientInfoV1;
|
||||||
|
|
||||||
|
#define RedisModuleClientInfo RedisModuleClientInfoV1
|
||||||
|
|
||||||
|
#define REDISMODULE_REPLICATIONINFO_VERSION 1
|
||||||
|
typedef struct RedisModuleReplicationInfo {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
int master; /* true if master, false if replica */
|
||||||
|
char *masterhost; /* master instance hostname for NOW_REPLICA */
|
||||||
|
int masterport; /* master instance port for NOW_REPLICA */
|
||||||
|
char *replid1; /* Main replication ID */
|
||||||
|
char *replid2; /* Secondary replication ID */
|
||||||
|
uint64_t repl1_offset; /* Main replication offset */
|
||||||
|
uint64_t repl2_offset; /* Offset of replid2 validity */
|
||||||
|
} RedisModuleReplicationInfoV1;
|
||||||
|
|
||||||
|
#define RedisModuleReplicationInfo RedisModuleReplicationInfoV1
|
||||||
|
|
||||||
|
#define REDISMODULE_FLUSHINFO_VERSION 1
|
||||||
|
typedef struct RedisModuleFlushInfo {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
int32_t sync; /* Synchronous or threaded flush?. */
|
||||||
|
int32_t dbnum; /* Flushed database number, -1 for ALL. */
|
||||||
|
} RedisModuleFlushInfoV1;
|
||||||
|
|
||||||
|
#define RedisModuleFlushInfo RedisModuleFlushInfoV1
|
||||||
|
|
||||||
|
#define REDISMODULE_MODULE_CHANGE_VERSION 1
|
||||||
|
typedef struct RedisModuleModuleChange {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
const char* module_name;/* Name of module loaded or unloaded. */
|
||||||
|
int32_t module_version; /* Module version. */
|
||||||
|
} RedisModuleModuleChangeV1;
|
||||||
|
|
||||||
|
#define RedisModuleModuleChange RedisModuleModuleChangeV1
|
||||||
|
|
||||||
|
#define REDISMODULE_CRON_LOOP_VERSION 1
|
||||||
|
typedef struct RedisModuleCronLoopInfo {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
int32_t hz; /* Approximate number of events per second. */
|
||||||
|
} RedisModuleCronLoopV1;
|
||||||
|
|
||||||
|
#define RedisModuleCronLoop RedisModuleCronLoopV1
|
||||||
|
|
||||||
|
#define REDISMODULE_LOADING_PROGRESS_VERSION 1
|
||||||
|
typedef struct RedisModuleLoadingProgressInfo {
|
||||||
|
uint64_t version; /* Not used since this structure is never passed
|
||||||
|
from the module to the core right now. Here
|
||||||
|
for future compatibility. */
|
||||||
|
int32_t hz; /* Approximate number of events per second. */
|
||||||
|
int32_t progress; /* Approximate progress between 0 and 1024, or -1
|
||||||
|
* if unknown. */
|
||||||
|
} RedisModuleLoadingProgressV1;
|
||||||
|
|
||||||
|
#define RedisModuleLoadingProgress RedisModuleLoadingProgressV1
|
||||||
|
|
||||||
/* ------------------------- End of common defines ------------------------ */
|
/* ------------------------- End of common defines ------------------------ */
|
||||||
|
|
||||||
#ifndef REDISMODULE_CORE
|
#ifndef REDISMODULE_CORE
|
||||||
@ -164,6 +391,7 @@ typedef struct RedisModuleDict RedisModuleDict;
|
|||||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||||
|
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
|
||||||
|
|
||||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||||
@ -179,6 +407,8 @@ typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
|||||||
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
||||||
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||||
|
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
|
||||||
|
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
|
||||||
|
|
||||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||||
typedef struct RedisModuleTypeMethods {
|
typedef struct RedisModuleTypeMethods {
|
||||||
@ -235,10 +465,14 @@ const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleStri
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
|
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
|
int REDISMODULE_API_FUNC(RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply);
|
||||||
@ -256,6 +490,9 @@ char *REDISMODULE_API_FUNC(RedisModule_StringDMA)(RedisModuleKey *key, size_t *l
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
int REDISMODULE_API_FUNC(RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen);
|
||||||
mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
|
mstime_t REDISMODULE_API_FUNC(RedisModule_GetExpire)(RedisModuleKey *key);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
|
int REDISMODULE_API_FUNC(RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_ResetDataset)(int restart_aof, int async);
|
||||||
|
unsigned long long REDISMODULE_API_FUNC(RedisModule_DbSize)(RedisModuleCtx *ctx);
|
||||||
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_RandomKey)(RedisModuleCtx *ctx);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
|
int REDISMODULE_API_FUNC(RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
|
int REDISMODULE_API_FUNC(RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
|
int REDISMODULE_API_FUNC(RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score);
|
||||||
@ -274,12 +511,17 @@ int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ..
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
|
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
|
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
|
||||||
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
|
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_GetClientInfoById)(void *ci, uint64_t id);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
|
||||||
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
|
||||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
||||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
||||||
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_SignalModifiedKey)(RedisModuleCtx *ctx, RedisModuleString *keyname);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
|
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
|
||||||
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
|
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
|
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
|
||||||
@ -295,11 +537,14 @@ void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value)
|
|||||||
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_LatencyAddSample)(const char *event, mstime_t latency);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
|
||||||
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
|
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
|
||||||
|
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key);
|
||||||
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
|
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
|
||||||
@ -326,6 +571,21 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ct
|
|||||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_SetLRUOrLFU)(RedisModuleKey *key, long long lfu_freq, long long lru_idle);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_GetLRUOrLFU)(RedisModuleKey *key, long long *lfu_freq, long long *lru_idle);
|
||||||
|
RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key);
|
||||||
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx);
|
||||||
|
|
||||||
/* Experimental APIs */
|
/* Experimental APIs */
|
||||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||||
@ -342,6 +602,8 @@ void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx
|
|||||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
|
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
|
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb);
|
int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_GetNotifyKeyspaceEvents)();
|
||||||
int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx);
|
int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback);
|
void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len);
|
int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len);
|
||||||
@ -366,8 +628,13 @@ const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(R
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||||
|
|
||||||
/* This is included inline inside each Redis module. */
|
/* This is included inline inside each Redis module. */
|
||||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
||||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
|
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
|
||||||
@ -386,14 +653,17 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(ReplyWithError);
|
REDISMODULE_GET_API(ReplyWithError);
|
||||||
REDISMODULE_GET_API(ReplyWithSimpleString);
|
REDISMODULE_GET_API(ReplyWithSimpleString);
|
||||||
REDISMODULE_GET_API(ReplyWithArray);
|
REDISMODULE_GET_API(ReplyWithArray);
|
||||||
|
REDISMODULE_GET_API(ReplyWithNullArray);
|
||||||
|
REDISMODULE_GET_API(ReplyWithEmptyArray);
|
||||||
REDISMODULE_GET_API(ReplySetArrayLength);
|
REDISMODULE_GET_API(ReplySetArrayLength);
|
||||||
REDISMODULE_GET_API(ReplyWithStringBuffer);
|
REDISMODULE_GET_API(ReplyWithStringBuffer);
|
||||||
REDISMODULE_GET_API(ReplyWithCString);
|
REDISMODULE_GET_API(ReplyWithCString);
|
||||||
REDISMODULE_GET_API(ReplyWithString);
|
REDISMODULE_GET_API(ReplyWithString);
|
||||||
|
REDISMODULE_GET_API(ReplyWithEmptyString);
|
||||||
|
REDISMODULE_GET_API(ReplyWithVerbatimString);
|
||||||
REDISMODULE_GET_API(ReplyWithNull);
|
REDISMODULE_GET_API(ReplyWithNull);
|
||||||
REDISMODULE_GET_API(ReplyWithCallReply);
|
REDISMODULE_GET_API(ReplyWithCallReply);
|
||||||
REDISMODULE_GET_API(ReplyWithDouble);
|
REDISMODULE_GET_API(ReplyWithDouble);
|
||||||
REDISMODULE_GET_API(ReplySetArrayLength);
|
|
||||||
REDISMODULE_GET_API(GetSelectedDb);
|
REDISMODULE_GET_API(GetSelectedDb);
|
||||||
REDISMODULE_GET_API(SelectDb);
|
REDISMODULE_GET_API(SelectDb);
|
||||||
REDISMODULE_GET_API(OpenKey);
|
REDISMODULE_GET_API(OpenKey);
|
||||||
@ -429,6 +699,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(StringTruncate);
|
REDISMODULE_GET_API(StringTruncate);
|
||||||
REDISMODULE_GET_API(GetExpire);
|
REDISMODULE_GET_API(GetExpire);
|
||||||
REDISMODULE_GET_API(SetExpire);
|
REDISMODULE_GET_API(SetExpire);
|
||||||
|
REDISMODULE_GET_API(ResetDataset);
|
||||||
|
REDISMODULE_GET_API(DbSize);
|
||||||
|
REDISMODULE_GET_API(RandomKey);
|
||||||
REDISMODULE_GET_API(ZsetAdd);
|
REDISMODULE_GET_API(ZsetAdd);
|
||||||
REDISMODULE_GET_API(ZsetIncrby);
|
REDISMODULE_GET_API(ZsetIncrby);
|
||||||
REDISMODULE_GET_API(ZsetScore);
|
REDISMODULE_GET_API(ZsetScore);
|
||||||
@ -453,6 +726,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(ModuleTypeSetValue);
|
REDISMODULE_GET_API(ModuleTypeSetValue);
|
||||||
REDISMODULE_GET_API(ModuleTypeGetType);
|
REDISMODULE_GET_API(ModuleTypeGetType);
|
||||||
REDISMODULE_GET_API(ModuleTypeGetValue);
|
REDISMODULE_GET_API(ModuleTypeGetValue);
|
||||||
|
REDISMODULE_GET_API(IsIOError);
|
||||||
|
REDISMODULE_GET_API(SetModuleOptions);
|
||||||
|
REDISMODULE_GET_API(SignalModifiedKey);
|
||||||
REDISMODULE_GET_API(SaveUnsigned);
|
REDISMODULE_GET_API(SaveUnsigned);
|
||||||
REDISMODULE_GET_API(LoadUnsigned);
|
REDISMODULE_GET_API(LoadUnsigned);
|
||||||
REDISMODULE_GET_API(SaveSigned);
|
REDISMODULE_GET_API(SaveSigned);
|
||||||
@ -468,11 +744,14 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(EmitAOF);
|
REDISMODULE_GET_API(EmitAOF);
|
||||||
REDISMODULE_GET_API(Log);
|
REDISMODULE_GET_API(Log);
|
||||||
REDISMODULE_GET_API(LogIOError);
|
REDISMODULE_GET_API(LogIOError);
|
||||||
|
REDISMODULE_GET_API(_Assert);
|
||||||
|
REDISMODULE_GET_API(LatencyAddSample);
|
||||||
REDISMODULE_GET_API(StringAppendBuffer);
|
REDISMODULE_GET_API(StringAppendBuffer);
|
||||||
REDISMODULE_GET_API(RetainString);
|
REDISMODULE_GET_API(RetainString);
|
||||||
REDISMODULE_GET_API(StringCompare);
|
REDISMODULE_GET_API(StringCompare);
|
||||||
REDISMODULE_GET_API(GetContextFromIO);
|
REDISMODULE_GET_API(GetContextFromIO);
|
||||||
REDISMODULE_GET_API(GetKeyNameFromIO);
|
REDISMODULE_GET_API(GetKeyNameFromIO);
|
||||||
|
REDISMODULE_GET_API(GetKeyNameFromModuleKey);
|
||||||
REDISMODULE_GET_API(Milliseconds);
|
REDISMODULE_GET_API(Milliseconds);
|
||||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||||
REDISMODULE_GET_API(DigestAddLongLong);
|
REDISMODULE_GET_API(DigestAddLongLong);
|
||||||
@ -499,6 +778,23 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(DictPrev);
|
REDISMODULE_GET_API(DictPrev);
|
||||||
REDISMODULE_GET_API(DictCompare);
|
REDISMODULE_GET_API(DictCompare);
|
||||||
REDISMODULE_GET_API(DictCompareC);
|
REDISMODULE_GET_API(DictCompareC);
|
||||||
|
REDISMODULE_GET_API(RegisterInfoFunc);
|
||||||
|
REDISMODULE_GET_API(InfoAddSection);
|
||||||
|
REDISMODULE_GET_API(InfoBeginDictField);
|
||||||
|
REDISMODULE_GET_API(InfoEndDictField);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldString);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldCString);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldDouble);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldLongLong);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldULongLong);
|
||||||
|
REDISMODULE_GET_API(GetClientInfoById);
|
||||||
|
REDISMODULE_GET_API(PublishMessage);
|
||||||
|
REDISMODULE_GET_API(SubscribeToServerEvent);
|
||||||
|
REDISMODULE_GET_API(SetLRUOrLFU);
|
||||||
|
REDISMODULE_GET_API(GetLRUOrLFU);
|
||||||
|
REDISMODULE_GET_API(BlockClientOnKeys);
|
||||||
|
REDISMODULE_GET_API(SignalKeyAsReady);
|
||||||
|
REDISMODULE_GET_API(GetBlockedClientReadyKey);
|
||||||
|
|
||||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||||
@ -514,6 +810,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(AbortBlock);
|
REDISMODULE_GET_API(AbortBlock);
|
||||||
REDISMODULE_GET_API(SetDisconnectCallback);
|
REDISMODULE_GET_API(SetDisconnectCallback);
|
||||||
REDISMODULE_GET_API(SubscribeToKeyspaceEvents);
|
REDISMODULE_GET_API(SubscribeToKeyspaceEvents);
|
||||||
|
REDISMODULE_GET_API(NotifyKeyspaceEvent);
|
||||||
|
REDISMODULE_GET_API(GetNotifyKeyspaceEvents);
|
||||||
REDISMODULE_GET_API(BlockedClientDisconnected);
|
REDISMODULE_GET_API(BlockedClientDisconnected);
|
||||||
REDISMODULE_GET_API(RegisterClusterMessageReceiver);
|
REDISMODULE_GET_API(RegisterClusterMessageReceiver);
|
||||||
REDISMODULE_GET_API(SendClusterMessage);
|
REDISMODULE_GET_API(SendClusterMessage);
|
||||||
@ -537,6 +835,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(CommandFilterArgInsert);
|
REDISMODULE_GET_API(CommandFilterArgInsert);
|
||||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||||
|
REDISMODULE_GET_API(Fork);
|
||||||
|
REDISMODULE_GET_API(ExitFromChild);
|
||||||
|
REDISMODULE_GET_API(KillForkChild);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||||
@ -544,6 +845,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define RedisModule_Assert(_e) ((_e)?(void)0 : (RedisModule__Assert(#_e,__FILE__,__LINE__),exit(1)))
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/* Things only defined for the modules core, not exported to modules
|
/* Things only defined for the modules core, not exported to modules
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
* files using this functions. */
|
* files using this functions. */
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "release.h"
|
#include "release.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
@ -50,3 +51,16 @@ uint64_t redisBuildId(void) {
|
|||||||
|
|
||||||
return crc64(0,(unsigned char*)buildid,strlen(buildid));
|
return crc64(0,(unsigned char*)buildid,strlen(buildid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return a cached value of the build string in order to avoid recomputing
|
||||||
|
* and converting it in hex every time: this string is shown in the INFO
|
||||||
|
* output that should be fast. */
|
||||||
|
char *redisBuildIdString(void) {
|
||||||
|
static char buf[32];
|
||||||
|
static int cached = 0;
|
||||||
|
if (!cached) {
|
||||||
|
snprintf(buf,sizeof(buf),"%llx",(unsigned long long) redisBuildId());
|
||||||
|
cached = 1;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
void replicationDiscardCachedMaster(void);
|
void replicationDiscardCachedMaster(void);
|
||||||
void replicationResurrectCachedMaster(int newfd);
|
void replicationResurrectCachedMaster(connection *conn);
|
||||||
void replicationSendAck(void);
|
void replicationSendAck(void);
|
||||||
void putSlaveOnline(client *slave);
|
void putSlaveOnline(client *slave);
|
||||||
int cancelReplicationHandshake(void);
|
int cancelReplicationHandshake(void);
|
||||||
@ -57,7 +57,7 @@ char *replicationGetSlaveName(client *c) {
|
|||||||
ip[0] = '\0';
|
ip[0] = '\0';
|
||||||
buf[0] = '\0';
|
buf[0] = '\0';
|
||||||
if (c->slave_ip[0] != '\0' ||
|
if (c->slave_ip[0] != '\0' ||
|
||||||
anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1)
|
connPeerToString(c->conn,ip,sizeof(ip),NULL) != -1)
|
||||||
{
|
{
|
||||||
/* Note that the 'ip' buffer is always larger than 'c->slave_ip' */
|
/* Note that the 'ip' buffer is always larger than 'c->slave_ip' */
|
||||||
if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip));
|
if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip));
|
||||||
@ -256,7 +256,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
|||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
client *slave = ln->value;
|
client *slave = ln->value;
|
||||||
|
|
||||||
/* Don't feed slaves that are still waiting for BGSAVE to start */
|
/* Don't feed slaves that are still waiting for BGSAVE to start. */
|
||||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
|
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
|
||||||
|
|
||||||
/* Feed slaves that are waiting for the initial SYNC (so these commands
|
/* Feed slaves that are waiting for the initial SYNC (so these commands
|
||||||
@ -295,7 +295,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
|
|||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
client *slave = ln->value;
|
client *slave = ln->value;
|
||||||
|
|
||||||
/* Don't feed slaves that are still waiting for BGSAVE to start */
|
/* Don't feed slaves that are still waiting for BGSAVE to start. */
|
||||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
|
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
|
||||||
addReplyProto(slave,buf,buflen);
|
addReplyProto(slave,buf,buflen);
|
||||||
}
|
}
|
||||||
@ -432,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
|
|||||||
if (!(slave->flags & CLIENT_PRE_PSYNC)) {
|
if (!(slave->flags & CLIENT_PRE_PSYNC)) {
|
||||||
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
|
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
|
||||||
server.replid,offset);
|
server.replid,offset);
|
||||||
if (write(slave->fd,buf,buflen) != buflen) {
|
if (connWrite(slave->conn,buf,buflen) != buflen) {
|
||||||
freeClientAsync(slave);
|
freeClientAsync(slave);
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
@ -519,7 +519,7 @@ int masterTryPartialResynchronization(client *c) {
|
|||||||
} else {
|
} else {
|
||||||
buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
|
buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
|
||||||
}
|
}
|
||||||
if (write(c->fd,buf,buflen) != buflen) {
|
if (connWrite(c->conn,buf,buflen) != buflen) {
|
||||||
freeClientAsync(c);
|
freeClientAsync(c);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
@ -533,6 +533,12 @@ int masterTryPartialResynchronization(client *c) {
|
|||||||
* has this state from the previous connection with the master. */
|
* has this state from the previous connection with the master. */
|
||||||
|
|
||||||
refreshGoodSlavesCount();
|
refreshGoodSlavesCount();
|
||||||
|
|
||||||
|
/* Fire the replica change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE,
|
||||||
|
NULL);
|
||||||
|
|
||||||
return C_OK; /* The caller can return, no full resync needed. */
|
return C_OK; /* The caller can return, no full resync needed. */
|
||||||
|
|
||||||
need_full_resync:
|
need_full_resync:
|
||||||
@ -585,7 +591,7 @@ int startBgsaveForReplication(int mincapa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
||||||
* resynchorinization from the list of salves, inform them with
|
* resynchronization from the list of slaves, inform them with
|
||||||
* an error about what happened, close the connection ASAP. */
|
* an error about what happened, close the connection ASAP. */
|
||||||
if (retval == C_ERR) {
|
if (retval == C_ERR) {
|
||||||
serverLog(LL_WARNING,"BGSAVE for replication failed");
|
serverLog(LL_WARNING,"BGSAVE for replication failed");
|
||||||
@ -606,7 +612,7 @@ int startBgsaveForReplication(int mincapa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* If the target is socket, rdbSaveToSlavesSockets() already setup
|
/* If the target is socket, rdbSaveToSlavesSockets() already setup
|
||||||
* the salves for a full resync. Otherwise for disk target do it now.*/
|
* the slaves for a full resync. Otherwise for disk target do it now.*/
|
||||||
if (!socket_target) {
|
if (!socket_target) {
|
||||||
listRewind(server.slaves,&li);
|
listRewind(server.slaves,&li);
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
@ -685,7 +691,7 @@ void syncCommand(client *c) {
|
|||||||
* paths will change the state if we handle the slave differently. */
|
* paths will change the state if we handle the slave differently. */
|
||||||
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||||
if (server.repl_disable_tcp_nodelay)
|
if (server.repl_disable_tcp_nodelay)
|
||||||
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
|
connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */
|
||||||
c->repldbfd = -1;
|
c->repldbfd = -1;
|
||||||
c->flags |= CLIENT_SLAVE;
|
c->flags |= CLIENT_SLAVE;
|
||||||
listAddNodeTail(server.slaves,c);
|
listAddNodeTail(server.slaves,c);
|
||||||
@ -751,11 +757,11 @@ void syncCommand(client *c) {
|
|||||||
/* Target is disk (or the slave is not capable of supporting
|
/* Target is disk (or the slave is not capable of supporting
|
||||||
* diskless replication) and we don't have a BGSAVE in progress,
|
* diskless replication) and we don't have a BGSAVE in progress,
|
||||||
* let's start one. */
|
* let's start one. */
|
||||||
if (server.aof_child_pid == -1) {
|
if (!hasActiveChildProcess()) {
|
||||||
startBgsaveForReplication(c->slave_capa);
|
startBgsaveForReplication(c->slave_capa);
|
||||||
} else {
|
} else {
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"No BGSAVE in progress, but an AOF rewrite is active. "
|
"No BGSAVE in progress, but another BG operation is active. "
|
||||||
"BGSAVE for replication delayed");
|
"BGSAVE for replication delayed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -862,21 +868,22 @@ void putSlaveOnline(client *slave) {
|
|||||||
slave->replstate = SLAVE_STATE_ONLINE;
|
slave->replstate = SLAVE_STATE_ONLINE;
|
||||||
slave->repl_put_online_on_ack = 0;
|
slave->repl_put_online_on_ack = 0;
|
||||||
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
|
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
|
||||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
|
if (connSetWriteHandler(slave->conn, sendReplyToClient) == C_ERR) {
|
||||||
sendReplyToClient, slave) == AE_ERR) {
|
|
||||||
serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
|
serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
|
||||||
freeClient(slave);
|
freeClient(slave);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshGoodSlavesCount();
|
refreshGoodSlavesCount();
|
||||||
|
/* Fire the replica change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE,
|
||||||
|
NULL);
|
||||||
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
|
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
|
||||||
replicationGetSlaveName(slave));
|
replicationGetSlaveName(slave));
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void sendBulkToSlave(connection *conn) {
|
||||||
client *slave = privdata;
|
client *slave = connGetPrivateData(conn);
|
||||||
UNUSED(el);
|
|
||||||
UNUSED(mask);
|
|
||||||
char buf[PROTO_IOBUF_LEN];
|
char buf[PROTO_IOBUF_LEN];
|
||||||
ssize_t nwritten, buflen;
|
ssize_t nwritten, buflen;
|
||||||
|
|
||||||
@ -884,10 +891,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
* replication process. Currently the preamble is just the bulk count of
|
* replication process. Currently the preamble is just the bulk count of
|
||||||
* the file in the form "$<length>\r\n". */
|
* the file in the form "$<length>\r\n". */
|
||||||
if (slave->replpreamble) {
|
if (slave->replpreamble) {
|
||||||
nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble));
|
nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
|
||||||
if (nwritten == -1) {
|
if (nwritten == -1) {
|
||||||
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
|
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
|
||||||
strerror(errno));
|
connGetLastError(conn));
|
||||||
freeClient(slave);
|
freeClient(slave);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -911,10 +918,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
freeClient(slave);
|
freeClient(slave);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((nwritten = write(fd,buf,buflen)) == -1) {
|
if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
|
||||||
if (errno != EAGAIN) {
|
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||||
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
|
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
|
||||||
strerror(errno));
|
connGetLastError(conn));
|
||||||
freeClient(slave);
|
freeClient(slave);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -924,11 +931,157 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
if (slave->repldboff == slave->repldbsize) {
|
if (slave->repldboff == slave->repldbsize) {
|
||||||
close(slave->repldbfd);
|
close(slave->repldbfd);
|
||||||
slave->repldbfd = -1;
|
slave->repldbfd = -1;
|
||||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
connSetWriteHandler(slave->conn,NULL);
|
||||||
putSlaveOnline(slave);
|
putSlaveOnline(slave);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove one write handler from the list of connections waiting to be writable
|
||||||
|
* during rdb pipe transfer. */
|
||||||
|
void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
|
||||||
|
if (!connHasWriteHandler(conn))
|
||||||
|
return;
|
||||||
|
connSetWriteHandler(conn, NULL);
|
||||||
|
server.rdb_pipe_numconns_writing--;
|
||||||
|
/* if there are no more writes for now for this conn, or write error: */
|
||||||
|
if (server.rdb_pipe_numconns_writing == 0) {
|
||||||
|
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
|
||||||
|
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called in diskless master during transfer of data from the rdb pipe, when
|
||||||
|
* the replica becomes writable again. */
|
||||||
|
void rdbPipeWriteHandler(struct connection *conn) {
|
||||||
|
serverAssert(server.rdb_pipe_bufflen>0);
|
||||||
|
client *slave = connGetPrivateData(conn);
|
||||||
|
int nwritten;
|
||||||
|
if ((nwritten = connWrite(conn, server.rdb_pipe_buff + slave->repldboff,
|
||||||
|
server.rdb_pipe_bufflen - slave->repldboff)) == -1)
|
||||||
|
{
|
||||||
|
if (connGetState(conn) == CONN_STATE_CONNECTED)
|
||||||
|
return; /* equivalent to EAGAIN */
|
||||||
|
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
|
||||||
|
connGetLastError(conn));
|
||||||
|
freeClient(slave);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
slave->repldboff += nwritten;
|
||||||
|
server.stat_net_output_bytes += nwritten;
|
||||||
|
if (slave->repldboff < server.rdb_pipe_bufflen)
|
||||||
|
return; /* more data to write.. */
|
||||||
|
}
|
||||||
|
rdbPipeWriteHandlerConnRemoved(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When the the pipe serving diskless rdb transfer is drained (write end was
|
||||||
|
* closed), we can clean up all the temporary variables, and cleanup after the
|
||||||
|
* fork child. */
|
||||||
|
void RdbPipeCleanup() {
|
||||||
|
close(server.rdb_pipe_read);
|
||||||
|
zfree(server.rdb_pipe_conns);
|
||||||
|
server.rdb_pipe_conns = NULL;
|
||||||
|
server.rdb_pipe_numconns = 0;
|
||||||
|
server.rdb_pipe_numconns_writing = 0;
|
||||||
|
zfree(server.rdb_pipe_buff);
|
||||||
|
server.rdb_pipe_buff = NULL;
|
||||||
|
server.rdb_pipe_bufflen = 0;
|
||||||
|
|
||||||
|
/* Since we're avoiding to detect the child exited as long as the pipe is
|
||||||
|
* not drained, so now is the time to check. */
|
||||||
|
checkChildrenDone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called in diskless master, when there's data to read from the child's rdb pipe */
|
||||||
|
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask) {
|
||||||
|
UNUSED(mask);
|
||||||
|
UNUSED(clientData);
|
||||||
|
UNUSED(eventLoop);
|
||||||
|
int i;
|
||||||
|
if (!server.rdb_pipe_buff)
|
||||||
|
server.rdb_pipe_buff = zmalloc(PROTO_IOBUF_LEN);
|
||||||
|
serverAssert(server.rdb_pipe_numconns_writing==0);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
server.rdb_pipe_bufflen = read(fd, server.rdb_pipe_buff, PROTO_IOBUF_LEN);
|
||||||
|
if (server.rdb_pipe_bufflen < 0) {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||||
|
return;
|
||||||
|
serverLog(LL_WARNING,"Diskless rdb transfer, read error sending DB to replicas: %s", strerror(errno));
|
||||||
|
for (i=0; i < server.rdb_pipe_numconns; i++) {
|
||||||
|
connection *conn = server.rdb_pipe_conns[i];
|
||||||
|
if (!conn)
|
||||||
|
continue;
|
||||||
|
client *slave = connGetPrivateData(conn);
|
||||||
|
freeClient(slave);
|
||||||
|
server.rdb_pipe_conns[i] = NULL;
|
||||||
|
}
|
||||||
|
killRDBChild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server.rdb_pipe_bufflen == 0) {
|
||||||
|
/* EOF - write end was closed. */
|
||||||
|
int stillUp = 0;
|
||||||
|
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
|
||||||
|
for (i=0; i < server.rdb_pipe_numconns; i++)
|
||||||
|
{
|
||||||
|
connection *conn = server.rdb_pipe_conns[i];
|
||||||
|
if (!conn)
|
||||||
|
continue;
|
||||||
|
stillUp++;
|
||||||
|
}
|
||||||
|
serverLog(LL_WARNING,"Diskless rdb transfer, done reading from pipe, %d replicas still up.", stillUp);
|
||||||
|
RdbPipeCleanup();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stillAlive = 0;
|
||||||
|
for (i=0; i < server.rdb_pipe_numconns; i++)
|
||||||
|
{
|
||||||
|
int nwritten;
|
||||||
|
connection *conn = server.rdb_pipe_conns[i];
|
||||||
|
if (!conn)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
client *slave = connGetPrivateData(conn);
|
||||||
|
if ((nwritten = connWrite(conn, server.rdb_pipe_buff, server.rdb_pipe_bufflen)) == -1) {
|
||||||
|
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||||
|
serverLog(LL_WARNING,"Diskless rdb transfer, write error sending DB to replica: %s",
|
||||||
|
connGetLastError(conn));
|
||||||
|
freeClient(slave);
|
||||||
|
server.rdb_pipe_conns[i] = NULL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* An error and still in connected state, is equivalent to EAGAIN */
|
||||||
|
slave->repldboff = 0;
|
||||||
|
} else {
|
||||||
|
slave->repldboff = nwritten;
|
||||||
|
server.stat_net_output_bytes += nwritten;
|
||||||
|
}
|
||||||
|
/* If we were unable to write all the data to one of the replicas,
|
||||||
|
* setup write handler (and disable pipe read handler, below) */
|
||||||
|
if (nwritten != server.rdb_pipe_bufflen) {
|
||||||
|
server.rdb_pipe_numconns_writing++;
|
||||||
|
connSetWriteHandler(conn, rdbPipeWriteHandler);
|
||||||
|
}
|
||||||
|
stillAlive++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stillAlive == 0) {
|
||||||
|
serverLog(LL_WARNING,"Diskless rdb transfer, last replica dropped, killing fork child.");
|
||||||
|
killRDBChild();
|
||||||
|
RdbPipeCleanup();
|
||||||
|
}
|
||||||
|
/* Remove the pipe read handler if at least one write handler was set. */
|
||||||
|
if (server.rdb_pipe_numconns_writing || stillAlive == 0) {
|
||||||
|
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* This function is called at the end of every background saving,
|
/* This function is called at the end of every background saving,
|
||||||
* or when the replication RDB transfer strategy is modified from
|
* or when the replication RDB transfer strategy is modified from
|
||||||
* disk to socket or the other way around.
|
* disk to socket or the other way around.
|
||||||
@ -1015,8 +1168,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
|
|||||||
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
|
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
|
||||||
(unsigned long long) slave->repldbsize);
|
(unsigned long long) slave->repldbsize);
|
||||||
|
|
||||||
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
|
connSetWriteHandler(slave->conn,NULL);
|
||||||
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
|
if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) {
|
||||||
freeClient(slave);
|
freeClient(slave);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -1084,9 +1237,8 @@ void replicationSendNewlineToMaster(void) {
|
|||||||
static time_t newline_sent;
|
static time_t newline_sent;
|
||||||
if (time(NULL) != newline_sent) {
|
if (time(NULL) != newline_sent) {
|
||||||
newline_sent = time(NULL);
|
newline_sent = time(NULL);
|
||||||
if (write(server.repl_transfer_s,"\n",1) == -1) {
|
/* Pinging back in this stage is best-effort. */
|
||||||
/* Pinging back in this stage is best-effort. */
|
if (server.repl_transfer_s) connWrite(server.repl_transfer_s, "\n", 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1100,8 +1252,10 @@ void replicationEmptyDbCallback(void *privdata) {
|
|||||||
/* Once we have a link with the master and the synchroniziation was
|
/* Once we have a link with the master and the synchroniziation was
|
||||||
* performed, this function materializes the master client we store
|
* performed, this function materializes the master client we store
|
||||||
* at server.master, starting from the specified file descriptor. */
|
* at server.master, starting from the specified file descriptor. */
|
||||||
void replicationCreateMasterClient(int fd, int dbid) {
|
void replicationCreateMasterClient(connection *conn, int dbid) {
|
||||||
server.master = createClient(fd);
|
server.master = createClient(conn);
|
||||||
|
if (conn)
|
||||||
|
connSetReadHandler(server.master->conn, readQueryFromClient);
|
||||||
server.master->flags |= CLIENT_MASTER;
|
server.master->flags |= CLIENT_MASTER;
|
||||||
server.master->authenticated = 1;
|
server.master->authenticated = 1;
|
||||||
server.master->reploff = server.master_initial_offset;
|
server.master->reploff = server.master_initial_offset;
|
||||||
@ -1139,8 +1293,15 @@ void restartAOFAfterSYNC() {
|
|||||||
|
|
||||||
static int useDisklessLoad() {
|
static int useDisklessLoad() {
|
||||||
/* compute boolean decision to use diskless load */
|
/* compute boolean decision to use diskless load */
|
||||||
return server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
|
int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
|
||||||
(server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0);
|
(server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0);
|
||||||
|
/* Check all modules handle read errors, otherwise it's not safe to use diskless load. */
|
||||||
|
if (enabled && !moduleAllDatatypesHandleErrors()) {
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"Skipping diskless-load because there are modules that don't handle read errors.");
|
||||||
|
enabled = 0;
|
||||||
|
}
|
||||||
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper function for readSyncBulkPayload() to make backups of the current
|
/* Helper function for readSyncBulkPayload() to make backups of the current
|
||||||
@ -1189,7 +1350,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
|
|||||||
|
|
||||||
/* Asynchronously read the SYNC payload we receive from a master */
|
/* Asynchronously read the SYNC payload we receive from a master */
|
||||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||||
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void readSyncBulkPayload(connection *conn) {
|
||||||
char buf[4096];
|
char buf[4096];
|
||||||
ssize_t nread, readlen, nwritten;
|
ssize_t nread, readlen, nwritten;
|
||||||
int use_diskless_load;
|
int use_diskless_load;
|
||||||
@ -1197,9 +1358,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
|
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
|
||||||
EMPTYDB_NO_FLAGS;
|
EMPTYDB_NO_FLAGS;
|
||||||
off_t left;
|
off_t left;
|
||||||
UNUSED(el);
|
|
||||||
UNUSED(privdata);
|
|
||||||
UNUSED(mask);
|
|
||||||
|
|
||||||
/* Static vars used to hold the EOF mark, and the last bytes received
|
/* Static vars used to hold the EOF mark, and the last bytes received
|
||||||
* form the server: when they match, we reached the end of the transfer. */
|
* form the server: when they match, we reached the end of the transfer. */
|
||||||
@ -1210,7 +1368,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
/* If repl_transfer_size == -1 we still have to read the bulk length
|
/* If repl_transfer_size == -1 we still have to read the bulk length
|
||||||
* from the master reply. */
|
* from the master reply. */
|
||||||
if (server.repl_transfer_size == -1) {
|
if (server.repl_transfer_size == -1) {
|
||||||
if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) {
|
if (connSyncReadLine(conn,buf,1024,server.repl_syncio_timeout*1000) == -1) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"I/O error reading bulk count from MASTER: %s",
|
"I/O error reading bulk count from MASTER: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
@ -1275,7 +1433,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
|
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
nread = read(fd,buf,readlen);
|
nread = connRead(conn,buf,readlen);
|
||||||
if (nread <= 0) {
|
if (nread <= 0) {
|
||||||
serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
|
serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
|
||||||
(nread == -1) ? strerror(errno) : "connection lost");
|
(nread == -1) ? strerror(errno) : "connection lost");
|
||||||
@ -1383,27 +1541,27 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
* handler, otherwise it will get called recursively since
|
* handler, otherwise it will get called recursively since
|
||||||
* rdbLoad() will call the event loop to process events from time to
|
* rdbLoad() will call the event loop to process events from time to
|
||||||
* time for non blocking loading. */
|
* time for non blocking loading. */
|
||||||
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
|
connSetReadHandler(conn, NULL);
|
||||||
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
|
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
|
||||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||||
if (use_diskless_load) {
|
if (use_diskless_load) {
|
||||||
rio rdb;
|
rio rdb;
|
||||||
rioInitWithFd(&rdb,fd,server.repl_transfer_size);
|
rioInitWithConn(&rdb,conn,server.repl_transfer_size);
|
||||||
|
|
||||||
/* Put the socket in blocking mode to simplify RDB transfer.
|
/* Put the socket in blocking mode to simplify RDB transfer.
|
||||||
* We'll restore it when the RDB is received. */
|
* We'll restore it when the RDB is received. */
|
||||||
anetBlock(NULL,fd);
|
connBlock(conn);
|
||||||
anetRecvTimeout(NULL,fd,server.repl_timeout*1000);
|
connRecvTimeout(conn, server.repl_timeout*1000);
|
||||||
startLoading(server.repl_transfer_size);
|
startLoading(server.repl_transfer_size, RDBFLAGS_REPLICATION);
|
||||||
|
|
||||||
if (rdbLoadRio(&rdb,&rsi,0) != C_OK) {
|
if (rdbLoadRio(&rdb,RDBFLAGS_REPLICATION,&rsi) != C_OK) {
|
||||||
/* RDB loading failed. */
|
/* RDB loading failed. */
|
||||||
stopLoading();
|
stopLoading(0);
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Failed trying to load the MASTER synchronization DB "
|
"Failed trying to load the MASTER synchronization DB "
|
||||||
"from socket");
|
"from socket");
|
||||||
cancelReplicationHandshake();
|
cancelReplicationHandshake();
|
||||||
rioFreeFd(&rdb, NULL);
|
rioFreeConn(&rdb, NULL);
|
||||||
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
||||||
/* Restore the backed up databases. */
|
/* Restore the backed up databases. */
|
||||||
disklessLoadRestoreBackups(diskless_load_backup,1,
|
disklessLoadRestoreBackups(diskless_load_backup,1,
|
||||||
@ -1419,7 +1577,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
* gets promoted. */
|
* gets promoted. */
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopLoading();
|
stopLoading(1);
|
||||||
|
|
||||||
/* RDB loading succeeded if we reach this point. */
|
/* RDB loading succeeded if we reach this point. */
|
||||||
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
|
||||||
@ -1436,16 +1594,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
{
|
{
|
||||||
serverLog(LL_WARNING,"Replication stream EOF marker is broken");
|
serverLog(LL_WARNING,"Replication stream EOF marker is broken");
|
||||||
cancelReplicationHandshake();
|
cancelReplicationHandshake();
|
||||||
rioFreeFd(&rdb, NULL);
|
rioFreeConn(&rdb, NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cleanup and restore the socket to the original state to continue
|
/* Cleanup and restore the socket to the original state to continue
|
||||||
* with the normal replication. */
|
* with the normal replication. */
|
||||||
rioFreeFd(&rdb, NULL);
|
rioFreeConn(&rdb, NULL);
|
||||||
anetNonBlock(NULL,fd);
|
connNonBlock(conn);
|
||||||
anetRecvTimeout(NULL,fd,0);
|
connRecvTimeout(conn,0);
|
||||||
} else {
|
} else {
|
||||||
/* Ensure background save doesn't overwrite synced data */
|
/* Ensure background save doesn't overwrite synced data */
|
||||||
if (server.rdb_child_pid != -1) {
|
if (server.rdb_child_pid != -1) {
|
||||||
@ -1466,7 +1624,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
cancelReplicationHandshake();
|
cancelReplicationHandshake();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rdbLoad(server.rdb_filename,&rsi) != C_OK) {
|
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Failed trying to load the MASTER synchronization "
|
"Failed trying to load the MASTER synchronization "
|
||||||
"DB from disk");
|
"DB from disk");
|
||||||
@ -1488,6 +1646,11 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
server.repl_state = REPL_STATE_CONNECTED;
|
server.repl_state = REPL_STATE_CONNECTED;
|
||||||
server.repl_down_since = 0;
|
server.repl_down_since = 0;
|
||||||
|
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_UP,
|
||||||
|
NULL);
|
||||||
|
|
||||||
/* After a full resynchroniziation we use the replication ID and
|
/* After a full resynchroniziation we use the replication ID and
|
||||||
* offset of the master. The secondary ID / offset are cleared since
|
* offset of the master. The secondary ID / offset are cleared since
|
||||||
* we are starting a new history. */
|
* we are starting a new history. */
|
||||||
@ -1522,7 +1685,7 @@ error:
|
|||||||
#define SYNC_CMD_READ (1<<0)
|
#define SYNC_CMD_READ (1<<0)
|
||||||
#define SYNC_CMD_WRITE (1<<1)
|
#define SYNC_CMD_WRITE (1<<1)
|
||||||
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
|
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
|
||||||
char *sendSynchronousCommand(int flags, int fd, ...) {
|
char *sendSynchronousCommand(int flags, connection *conn, ...) {
|
||||||
|
|
||||||
/* Create the command to send to the master, we use redis binary
|
/* Create the command to send to the master, we use redis binary
|
||||||
* protocol to make sure correct arguments are sent. This function
|
* protocol to make sure correct arguments are sent. This function
|
||||||
@ -1533,7 +1696,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
|||||||
sds cmd = sdsempty();
|
sds cmd = sdsempty();
|
||||||
sds cmdargs = sdsempty();
|
sds cmdargs = sdsempty();
|
||||||
size_t argslen = 0;
|
size_t argslen = 0;
|
||||||
va_start(ap,fd);
|
va_start(ap,conn);
|
||||||
|
|
||||||
while(1) {
|
while(1) {
|
||||||
arg = va_arg(ap, char*);
|
arg = va_arg(ap, char*);
|
||||||
@ -1550,12 +1713,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
|||||||
sdsfree(cmdargs);
|
sdsfree(cmdargs);
|
||||||
|
|
||||||
/* Transfer command to the server. */
|
/* Transfer command to the server. */
|
||||||
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
|
if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
|
||||||
== -1)
|
== -1)
|
||||||
{
|
{
|
||||||
sdsfree(cmd);
|
sdsfree(cmd);
|
||||||
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
return sdscatprintf(sdsempty(),"-Writing to master: %s",
|
||||||
strerror(errno));
|
connGetLastError(conn));
|
||||||
}
|
}
|
||||||
sdsfree(cmd);
|
sdsfree(cmd);
|
||||||
}
|
}
|
||||||
@ -1564,7 +1727,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
|||||||
if (flags & SYNC_CMD_READ) {
|
if (flags & SYNC_CMD_READ) {
|
||||||
char buf[256];
|
char buf[256];
|
||||||
|
|
||||||
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000)
|
if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000)
|
||||||
== -1)
|
== -1)
|
||||||
{
|
{
|
||||||
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
return sdscatprintf(sdsempty(),"-Reading from master: %s",
|
||||||
@ -1630,7 +1793,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
|
|||||||
#define PSYNC_FULLRESYNC 3
|
#define PSYNC_FULLRESYNC 3
|
||||||
#define PSYNC_NOT_SUPPORTED 4
|
#define PSYNC_NOT_SUPPORTED 4
|
||||||
#define PSYNC_TRY_LATER 5
|
#define PSYNC_TRY_LATER 5
|
||||||
int slaveTryPartialResynchronization(int fd, int read_reply) {
|
int slaveTryPartialResynchronization(connection *conn, int read_reply) {
|
||||||
char *psync_replid;
|
char *psync_replid;
|
||||||
char psync_offset[32];
|
char psync_offset[32];
|
||||||
sds reply;
|
sds reply;
|
||||||
@ -1655,18 +1818,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Issue the PSYNC command */
|
/* Issue the PSYNC command */
|
||||||
reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_replid,psync_offset,NULL);
|
reply = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PSYNC",psync_replid,psync_offset,NULL);
|
||||||
if (reply != NULL) {
|
if (reply != NULL) {
|
||||||
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
|
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
|
||||||
sdsfree(reply);
|
sdsfree(reply);
|
||||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
connSetReadHandler(conn, NULL);
|
||||||
return PSYNC_WRITE_ERROR;
|
return PSYNC_WRITE_ERROR;
|
||||||
}
|
}
|
||||||
return PSYNC_WAIT_REPLY;
|
return PSYNC_WAIT_REPLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reading half */
|
/* Reading half */
|
||||||
reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
reply = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
||||||
if (sdslen(reply) == 0) {
|
if (sdslen(reply) == 0) {
|
||||||
/* The master may send empty newlines after it receives PSYNC
|
/* The master may send empty newlines after it receives PSYNC
|
||||||
* and before to reply, just to keep the connection alive. */
|
* and before to reply, just to keep the connection alive. */
|
||||||
@ -1674,7 +1837,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
|
|||||||
return PSYNC_WAIT_REPLY;
|
return PSYNC_WAIT_REPLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
aeDeleteFileEvent(server.el,fd,AE_READABLE);
|
connSetReadHandler(conn, NULL);
|
||||||
|
|
||||||
if (!strncmp(reply,"+FULLRESYNC",11)) {
|
if (!strncmp(reply,"+FULLRESYNC",11)) {
|
||||||
char *replid = NULL, *offset = NULL;
|
char *replid = NULL, *offset = NULL;
|
||||||
@ -1748,7 +1911,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
|
|||||||
|
|
||||||
/* Setup the replication to continue. */
|
/* Setup the replication to continue. */
|
||||||
sdsfree(reply);
|
sdsfree(reply);
|
||||||
replicationResurrectCachedMaster(fd);
|
replicationResurrectCachedMaster(conn);
|
||||||
|
|
||||||
/* If this instance was restarted and we read the metadata to
|
/* If this instance was restarted and we read the metadata to
|
||||||
* PSYNC from the persistence file, our replication backlog could
|
* PSYNC from the persistence file, our replication backlog could
|
||||||
@ -1790,29 +1953,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
|
|||||||
|
|
||||||
/* This handler fires when the non blocking connect was able to
|
/* This handler fires when the non blocking connect was able to
|
||||||
* establish a connection with the master. */
|
* establish a connection with the master. */
|
||||||
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
void syncWithMaster(connection *conn) {
|
||||||
char tmpfile[256], *err = NULL;
|
char tmpfile[256], *err = NULL;
|
||||||
int dfd = -1, maxtries = 5;
|
int dfd = -1, maxtries = 5;
|
||||||
int sockerr = 0, psync_result;
|
int psync_result;
|
||||||
socklen_t errlen = sizeof(sockerr);
|
|
||||||
UNUSED(el);
|
|
||||||
UNUSED(privdata);
|
|
||||||
UNUSED(mask);
|
|
||||||
|
|
||||||
/* If this event fired after the user turned the instance into a master
|
/* If this event fired after the user turned the instance into a master
|
||||||
* with SLAVEOF NO ONE we must just return ASAP. */
|
* with SLAVEOF NO ONE we must just return ASAP. */
|
||||||
if (server.repl_state == REPL_STATE_NONE) {
|
if (server.repl_state == REPL_STATE_NONE) {
|
||||||
close(fd);
|
connClose(conn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for errors in the socket: after a non blocking connect() we
|
/* Check for errors in the socket: after a non blocking connect() we
|
||||||
* may find that the socket is in error state. */
|
* may find that the socket is in error state. */
|
||||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
|
if (connGetState(conn) != CONN_STATE_CONNECTED) {
|
||||||
sockerr = errno;
|
|
||||||
if (sockerr) {
|
|
||||||
serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
|
serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
|
||||||
strerror(sockerr));
|
connGetLastError(conn));
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1821,18 +1978,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
|
serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
|
||||||
/* Delete the writable event so that the readable event remains
|
/* Delete the writable event so that the readable event remains
|
||||||
* registered and we can wait for the PONG reply. */
|
* registered and we can wait for the PONG reply. */
|
||||||
aeDeleteFileEvent(server.el,fd,AE_WRITABLE);
|
connSetReadHandler(conn, syncWithMaster);
|
||||||
|
connSetWriteHandler(conn, NULL);
|
||||||
server.repl_state = REPL_STATE_RECEIVE_PONG;
|
server.repl_state = REPL_STATE_RECEIVE_PONG;
|
||||||
/* Send the PING, don't check for errors at all, we have the timeout
|
/* Send the PING, don't check for errors at all, we have the timeout
|
||||||
* that will take care about this. */
|
* that will take care about this. */
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL);
|
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Receive the PONG command. */
|
/* Receive the PONG command. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
|
if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
||||||
|
|
||||||
/* We accept only two replies as valid, a positive +PONG reply
|
/* We accept only two replies as valid, a positive +PONG reply
|
||||||
* (we just check for "+") or an authentication error.
|
* (we just check for "+") or an authentication error.
|
||||||
@ -1857,13 +2015,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
/* AUTH with the master if required. */
|
/* AUTH with the master if required. */
|
||||||
if (server.repl_state == REPL_STATE_SEND_AUTH) {
|
if (server.repl_state == REPL_STATE_SEND_AUTH) {
|
||||||
if (server.masteruser && server.masterauth) {
|
if (server.masteruser && server.masterauth) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",
|
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",
|
||||||
server.masteruser,server.masterauth,NULL);
|
server.masteruser,server.masterauth,NULL);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
server.repl_state = REPL_STATE_RECEIVE_AUTH;
|
server.repl_state = REPL_STATE_RECEIVE_AUTH;
|
||||||
return;
|
return;
|
||||||
} else if (server.masterauth) {
|
} else if (server.masterauth) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL);
|
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",server.masterauth,NULL);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
server.repl_state = REPL_STATE_RECEIVE_AUTH;
|
server.repl_state = REPL_STATE_RECEIVE_AUTH;
|
||||||
return;
|
return;
|
||||||
@ -1874,7 +2032,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
|
|
||||||
/* Receive AUTH reply. */
|
/* Receive AUTH reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
|
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
|
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
@ -1887,11 +2045,14 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
/* Set the slave port, so that Master's INFO command can list the
|
/* Set the slave port, so that Master's INFO command can list the
|
||||||
* slave listening port correctly. */
|
* slave listening port correctly. */
|
||||||
if (server.repl_state == REPL_STATE_SEND_PORT) {
|
if (server.repl_state == REPL_STATE_SEND_PORT) {
|
||||||
sds port = sdsfromlonglong(server.slave_announce_port ?
|
int port;
|
||||||
server.slave_announce_port : server.port);
|
if (server.slave_announce_port) port = server.slave_announce_port;
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
|
else if (server.tls_replication && server.tls_port) port = server.tls_port;
|
||||||
"listening-port",port, NULL);
|
else port = server.port;
|
||||||
sdsfree(port);
|
sds portstr = sdsfromlonglong(port);
|
||||||
|
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
|
||||||
|
"listening-port",portstr, NULL);
|
||||||
|
sdsfree(portstr);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
server.repl_state = REPL_STATE_RECEIVE_PORT;
|
server.repl_state = REPL_STATE_RECEIVE_PORT;
|
||||||
@ -1900,7 +2061,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
|
|
||||||
/* Receive REPLCONF listening-port reply. */
|
/* Receive REPLCONF listening-port reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
|
if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
||||||
/* Ignore the error if any, not all the Redis versions support
|
/* Ignore the error if any, not all the Redis versions support
|
||||||
* REPLCONF listening-port. */
|
* REPLCONF listening-port. */
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
@ -1921,7 +2082,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
/* Set the slave ip, so that Master's INFO command can list the
|
/* Set the slave ip, so that Master's INFO command can list the
|
||||||
* slave IP address port correctly in case of port forwarding or NAT. */
|
* slave IP address port correctly in case of port forwarding or NAT. */
|
||||||
if (server.repl_state == REPL_STATE_SEND_IP) {
|
if (server.repl_state == REPL_STATE_SEND_IP) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
|
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
|
||||||
"ip-address",server.slave_announce_ip, NULL);
|
"ip-address",server.slave_announce_ip, NULL);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
@ -1931,7 +2092,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
|
|
||||||
/* Receive REPLCONF ip-address reply. */
|
/* Receive REPLCONF ip-address reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_IP) {
|
if (server.repl_state == REPL_STATE_RECEIVE_IP) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
||||||
/* Ignore the error if any, not all the Redis versions support
|
/* Ignore the error if any, not all the Redis versions support
|
||||||
* REPLCONF listening-port. */
|
* REPLCONF listening-port. */
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
@ -1949,7 +2110,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
*
|
*
|
||||||
* The master will ignore capabilities it does not understand. */
|
* The master will ignore capabilities it does not understand. */
|
||||||
if (server.repl_state == REPL_STATE_SEND_CAPA) {
|
if (server.repl_state == REPL_STATE_SEND_CAPA) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
|
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
|
||||||
"capa","eof","capa","psync2",NULL);
|
"capa","eof","capa","psync2",NULL);
|
||||||
if (err) goto write_error;
|
if (err) goto write_error;
|
||||||
sdsfree(err);
|
sdsfree(err);
|
||||||
@ -1959,7 +2120,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
|
|
||||||
/* Receive CAPA reply. */
|
/* Receive CAPA reply. */
|
||||||
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
|
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
|
||||||
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
|
err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
|
||||||
/* Ignore the error if any, not all the Redis versions support
|
/* Ignore the error if any, not all the Redis versions support
|
||||||
* REPLCONF capa. */
|
* REPLCONF capa. */
|
||||||
if (err[0] == '-') {
|
if (err[0] == '-') {
|
||||||
@ -1976,7 +2137,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
* and the global offset, to try a partial resync at the next
|
* and the global offset, to try a partial resync at the next
|
||||||
* reconnection attempt. */
|
* reconnection attempt. */
|
||||||
if (server.repl_state == REPL_STATE_SEND_PSYNC) {
|
if (server.repl_state == REPL_STATE_SEND_PSYNC) {
|
||||||
if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) {
|
if (slaveTryPartialResynchronization(conn,0) == PSYNC_WRITE_ERROR) {
|
||||||
err = sdsnew("Write error sending the PSYNC command.");
|
err = sdsnew("Write error sending the PSYNC command.");
|
||||||
goto write_error;
|
goto write_error;
|
||||||
}
|
}
|
||||||
@ -1992,7 +2153,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
psync_result = slaveTryPartialResynchronization(fd,1);
|
psync_result = slaveTryPartialResynchronization(conn,1);
|
||||||
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
|
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
|
||||||
|
|
||||||
/* If the master is in an transient error, we should try to PSYNC
|
/* If the master is in an transient error, we should try to PSYNC
|
||||||
@ -2021,7 +2182,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
* already populated. */
|
* already populated. */
|
||||||
if (psync_result == PSYNC_NOT_SUPPORTED) {
|
if (psync_result == PSYNC_NOT_SUPPORTED) {
|
||||||
serverLog(LL_NOTICE,"Retrying with SYNC...");
|
serverLog(LL_NOTICE,"Retrying with SYNC...");
|
||||||
if (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
|
if (connSyncWrite(conn,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
|
||||||
serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
|
serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
goto error;
|
goto error;
|
||||||
@ -2046,12 +2207,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Setup the non blocking download of the bulk file. */
|
/* Setup the non blocking download of the bulk file. */
|
||||||
if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL)
|
if (connSetReadHandler(conn, readSyncBulkPayload)
|
||||||
== AE_ERR)
|
== C_ERR)
|
||||||
{
|
{
|
||||||
|
char conninfo[CONN_INFO_LEN];
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Can't create readable event for SYNC: %s (fd=%d)",
|
"Can't create readable event for SYNC: %s (%s)",
|
||||||
strerror(errno),fd);
|
strerror(errno), connGetInfo(conn, conninfo, sizeof(conninfo)));
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2063,16 +2225,15 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
|
||||||
if (dfd != -1) close(dfd);
|
if (dfd != -1) close(dfd);
|
||||||
close(fd);
|
connClose(conn);
|
||||||
|
server.repl_transfer_s = NULL;
|
||||||
if (server.repl_transfer_fd != -1)
|
if (server.repl_transfer_fd != -1)
|
||||||
close(server.repl_transfer_fd);
|
close(server.repl_transfer_fd);
|
||||||
if (server.repl_transfer_tmpfile)
|
if (server.repl_transfer_tmpfile)
|
||||||
zfree(server.repl_transfer_tmpfile);
|
zfree(server.repl_transfer_tmpfile);
|
||||||
server.repl_transfer_tmpfile = NULL;
|
server.repl_transfer_tmpfile = NULL;
|
||||||
server.repl_transfer_fd = -1;
|
server.repl_transfer_fd = -1;
|
||||||
server.repl_transfer_s = -1;
|
|
||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -2083,26 +2244,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
|
|||||||
}
|
}
|
||||||
|
|
||||||
int connectWithMaster(void) {
|
int connectWithMaster(void) {
|
||||||
int fd;
|
server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();
|
||||||
|
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
|
||||||
fd = anetTcpNonBlockBestEffortBindConnect(NULL,
|
NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
|
||||||
server.masterhost,server.masterport,NET_FIRST_BIND_ADDR);
|
|
||||||
if (fd == -1) {
|
|
||||||
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
|
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
|
||||||
strerror(errno));
|
connGetLastError(server.repl_transfer_s));
|
||||||
|
connClose(server.repl_transfer_s);
|
||||||
|
server.repl_transfer_s = NULL;
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
|
|
||||||
AE_ERR)
|
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
serverLog(LL_WARNING,"Can't create readable event for SYNC");
|
|
||||||
return C_ERR;
|
|
||||||
}
|
|
||||||
|
|
||||||
server.repl_transfer_lastio = server.unixtime;
|
server.repl_transfer_lastio = server.unixtime;
|
||||||
server.repl_transfer_s = fd;
|
|
||||||
server.repl_state = REPL_STATE_CONNECTING;
|
server.repl_state = REPL_STATE_CONNECTING;
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
@ -2112,11 +2265,8 @@ int connectWithMaster(void) {
|
|||||||
* Never call this function directly, use cancelReplicationHandshake() instead.
|
* Never call this function directly, use cancelReplicationHandshake() instead.
|
||||||
*/
|
*/
|
||||||
void undoConnectWithMaster(void) {
|
void undoConnectWithMaster(void) {
|
||||||
int fd = server.repl_transfer_s;
|
connClose(server.repl_transfer_s);
|
||||||
|
server.repl_transfer_s = NULL;
|
||||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
|
||||||
close(fd);
|
|
||||||
server.repl_transfer_s = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Abort the async download of the bulk dataset while SYNC-ing with master.
|
/* Abort the async download of the bulk dataset while SYNC-ing with master.
|
||||||
@ -2175,13 +2325,35 @@ void replicationSetMaster(char *ip, int port) {
|
|||||||
cancelReplicationHandshake();
|
cancelReplicationHandshake();
|
||||||
/* Before destroying our master state, create a cached master using
|
/* Before destroying our master state, create a cached master using
|
||||||
* our own parameters, to later PSYNC with the new master. */
|
* our own parameters, to later PSYNC with the new master. */
|
||||||
if (was_master) replicationCacheMasterUsingMyself();
|
if (was_master) {
|
||||||
|
replicationDiscardCachedMaster();
|
||||||
|
replicationCacheMasterUsingMyself();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fire the role change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
|
||||||
|
REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
if (server.repl_state == REPL_STATE_CONNECTED)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
|
||||||
|
NULL);
|
||||||
|
|
||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cancel replication, setting the instance as a master itself. */
|
/* Cancel replication, setting the instance as a master itself. */
|
||||||
void replicationUnsetMaster(void) {
|
void replicationUnsetMaster(void) {
|
||||||
if (server.masterhost == NULL) return; /* Nothing to do. */
|
if (server.masterhost == NULL) return; /* Nothing to do. */
|
||||||
|
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
if (server.repl_state == REPL_STATE_CONNECTED)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
|
||||||
|
NULL);
|
||||||
|
|
||||||
sdsfree(server.masterhost);
|
sdsfree(server.masterhost);
|
||||||
server.masterhost = NULL;
|
server.masterhost = NULL;
|
||||||
/* When a slave is turned into a master, the current replication ID
|
/* When a slave is turned into a master, the current replication ID
|
||||||
@ -2210,11 +2382,22 @@ void replicationUnsetMaster(void) {
|
|||||||
* starting from now. Otherwise the backlog will be freed after a
|
* starting from now. Otherwise the backlog will be freed after a
|
||||||
* failover if slaves do not connect immediately. */
|
* failover if slaves do not connect immediately. */
|
||||||
server.repl_no_slaves_since = server.unixtime;
|
server.repl_no_slaves_since = server.unixtime;
|
||||||
|
|
||||||
|
/* Fire the role change modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
|
||||||
|
REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER,
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called when the slave lose the connection with the
|
/* This function is called when the slave lose the connection with the
|
||||||
* master into an unexpected way. */
|
* master into an unexpected way. */
|
||||||
void replicationHandleMasterDisconnection(void) {
|
void replicationHandleMasterDisconnection(void) {
|
||||||
|
/* Fire the master link modules event. */
|
||||||
|
if (server.repl_state == REPL_STATE_CONNECTED)
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
|
||||||
|
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
|
||||||
|
NULL);
|
||||||
|
|
||||||
server.master = NULL;
|
server.master = NULL;
|
||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
server.repl_down_since = server.unixtime;
|
server.repl_down_since = server.unixtime;
|
||||||
@ -2301,7 +2484,7 @@ void roleCommand(client *c) {
|
|||||||
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
|
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
|
||||||
|
|
||||||
if (slaveip[0] == '\0') {
|
if (slaveip[0] == '\0') {
|
||||||
if (anetPeerToString(slave->fd,ip,sizeof(ip),NULL) == -1)
|
if (connPeerToString(slave->conn,ip,sizeof(ip),NULL) == -1)
|
||||||
continue;
|
continue;
|
||||||
slaveip = ip;
|
slaveip = ip;
|
||||||
}
|
}
|
||||||
@ -2423,7 +2606,7 @@ void replicationCacheMasterUsingMyself(void) {
|
|||||||
/* The master client we create can be set to any DBID, because
|
/* The master client we create can be set to any DBID, because
|
||||||
* the new master will start its replication stream with SELECT. */
|
* the new master will start its replication stream with SELECT. */
|
||||||
server.master_initial_offset = server.master_repl_offset;
|
server.master_initial_offset = server.master_repl_offset;
|
||||||
replicationCreateMasterClient(-1,-1);
|
replicationCreateMasterClient(NULL,-1);
|
||||||
|
|
||||||
/* Use our own ID / offset. */
|
/* Use our own ID / offset. */
|
||||||
memcpy(server.master->replid, server.replid, sizeof(server.replid));
|
memcpy(server.master->replid, server.replid, sizeof(server.replid));
|
||||||
@ -2452,10 +2635,11 @@ void replicationDiscardCachedMaster(void) {
|
|||||||
* This function is called when successfully setup a partial resynchronization
|
* This function is called when successfully setup a partial resynchronization
|
||||||
* so the stream of data that we'll receive will start from were this
|
* so the stream of data that we'll receive will start from were this
|
||||||
* master left. */
|
* master left. */
|
||||||
void replicationResurrectCachedMaster(int newfd) {
|
void replicationResurrectCachedMaster(connection *conn) {
|
||||||
server.master = server.cached_master;
|
server.master = server.cached_master;
|
||||||
server.cached_master = NULL;
|
server.cached_master = NULL;
|
||||||
server.master->fd = newfd;
|
server.master->conn = conn;
|
||||||
|
connSetPrivateData(server.master->conn, server.master);
|
||||||
server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
|
server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
|
||||||
server.master->authenticated = 1;
|
server.master->authenticated = 1;
|
||||||
server.master->lastinteraction = server.unixtime;
|
server.master->lastinteraction = server.unixtime;
|
||||||
@ -2464,8 +2648,7 @@ void replicationResurrectCachedMaster(int newfd) {
|
|||||||
|
|
||||||
/* Re-add to the list of clients. */
|
/* Re-add to the list of clients. */
|
||||||
linkClient(server.master);
|
linkClient(server.master);
|
||||||
if (aeCreateFileEvent(server.el, newfd, AE_READABLE,
|
if (connSetReadHandler(server.master->conn, readQueryFromClient)) {
|
||||||
readQueryFromClient, server.master)) {
|
|
||||||
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
|
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
|
||||||
freeClientAsync(server.master); /* Close ASAP. */
|
freeClientAsync(server.master); /* Close ASAP. */
|
||||||
}
|
}
|
||||||
@ -2473,8 +2656,7 @@ void replicationResurrectCachedMaster(int newfd) {
|
|||||||
/* We may also need to install the write handler as well if there is
|
/* We may also need to install the write handler as well if there is
|
||||||
* pending data in the write buffers. */
|
* pending data in the write buffers. */
|
||||||
if (clientHasPendingReplies(server.master)) {
|
if (clientHasPendingReplies(server.master)) {
|
||||||
if (aeCreateFileEvent(server.el, newfd, AE_WRITABLE,
|
if (connSetWriteHandler(server.master->conn, sendReplyToClient)) {
|
||||||
sendReplyToClient, server.master)) {
|
|
||||||
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
|
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
|
||||||
freeClientAsync(server.master); /* Close ASAP. */
|
freeClientAsync(server.master); /* Close ASAP. */
|
||||||
}
|
}
|
||||||
@ -2844,9 +3026,7 @@ void replicationCron(void) {
|
|||||||
server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));
|
server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));
|
||||||
|
|
||||||
if (is_presync) {
|
if (is_presync) {
|
||||||
if (write(slave->fd, "\n", 1) == -1) {
|
connWrite(slave->conn, "\n", 1);
|
||||||
/* Don't worry about socket errors, it's just a ping. */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2923,7 +3103,7 @@ void replicationCron(void) {
|
|||||||
* In case of diskless replication, we make sure to wait the specified
|
* In case of diskless replication, we make sure to wait the specified
|
||||||
* number of seconds (according to configuration) so that other slaves
|
* number of seconds (according to configuration) so that other slaves
|
||||||
* have the time to arrive before we start streaming. */
|
* have the time to arrive before we start streaming. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
if (!hasActiveChildProcess()) {
|
||||||
time_t idle, max_idle = 0;
|
time_t idle, max_idle = 0;
|
||||||
int slaves_waiting = 0;
|
int slaves_waiting = 0;
|
||||||
int mincapa = -1;
|
int mincapa = -1;
|
||||||
|
320
src/rio.c
320
src/rio.c
@ -159,13 +159,13 @@ void rioInitWithFile(rio *r, FILE *fp) {
|
|||||||
r->io.file.autosync = 0;
|
r->io.file.autosync = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------- File descriptor implementation -------------------
|
/* ------------------- Connection implementation -------------------
|
||||||
* We use this RIO implemetnation when reading an RDB file directly from
|
* We use this RIO implemetnation when reading an RDB file directly from
|
||||||
* the socket to the memory via rdbLoadRio(), thus this implementation
|
* the connection to the memory via rdbLoadRio(), thus this implementation
|
||||||
* only implements reading from a file descriptor that is, normally,
|
* only implements reading from a connection that is, normally,
|
||||||
* just a socket. */
|
* just a socket. */
|
||||||
|
|
||||||
static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
|
static size_t rioConnWrite(rio *r, const void *buf, size_t len) {
|
||||||
UNUSED(r);
|
UNUSED(r);
|
||||||
UNUSED(buf);
|
UNUSED(buf);
|
||||||
UNUSED(len);
|
UNUSED(len);
|
||||||
@ -173,57 +173,175 @@ static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Returns 1 or 0 for success/failure. */
|
/* Returns 1 or 0 for success/failure. */
|
||||||
static size_t rioFdRead(rio *r, void *buf, size_t len) {
|
static size_t rioConnRead(rio *r, void *buf, size_t len) {
|
||||||
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos;
|
size_t avail = sdslen(r->io.conn.buf)-r->io.conn.pos;
|
||||||
|
|
||||||
/* If the buffer is too small for the entire request: realloc. */
|
/* If the buffer is too small for the entire request: realloc. */
|
||||||
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len)
|
if (sdslen(r->io.conn.buf) + sdsavail(r->io.conn.buf) < len)
|
||||||
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf));
|
r->io.conn.buf = sdsMakeRoomFor(r->io.conn.buf, len - sdslen(r->io.conn.buf));
|
||||||
|
|
||||||
/* If the remaining unused buffer is not large enough: memmove so that we
|
/* If the remaining unused buffer is not large enough: memmove so that we
|
||||||
* can read the rest. */
|
* can read the rest. */
|
||||||
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) {
|
if (len > avail && sdsavail(r->io.conn.buf) < len - avail) {
|
||||||
sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
|
sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
|
||||||
r->io.fd.pos = 0;
|
r->io.conn.pos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we don't already have all the data in the sds, read more */
|
/* If we don't already have all the data in the sds, read more */
|
||||||
while (len > sdslen(r->io.fd.buf) - r->io.fd.pos) {
|
while (len > sdslen(r->io.conn.buf) - r->io.conn.pos) {
|
||||||
size_t buffered = sdslen(r->io.fd.buf) - r->io.fd.pos;
|
size_t buffered = sdslen(r->io.conn.buf) - r->io.conn.pos;
|
||||||
size_t toread = len - buffered;
|
size_t toread = len - buffered;
|
||||||
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
|
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
|
||||||
* the two. */
|
* the two. */
|
||||||
if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
|
if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
|
||||||
if (toread > sdsavail(r->io.fd.buf)) toread = sdsavail(r->io.fd.buf);
|
if (toread > sdsavail(r->io.conn.buf)) toread = sdsavail(r->io.conn.buf);
|
||||||
if (r->io.fd.read_limit != 0 &&
|
if (r->io.conn.read_limit != 0 &&
|
||||||
r->io.fd.read_so_far + buffered + toread > r->io.fd.read_limit)
|
r->io.conn.read_so_far + buffered + toread > r->io.conn.read_limit)
|
||||||
{
|
{
|
||||||
if (r->io.fd.read_limit >= r->io.fd.read_so_far - buffered)
|
if (r->io.conn.read_limit >= r->io.conn.read_so_far - buffered)
|
||||||
toread = r->io.fd.read_limit - r->io.fd.read_so_far - buffered;
|
toread = r->io.conn.read_limit - r->io.conn.read_so_far - buffered;
|
||||||
else {
|
else {
|
||||||
errno = EOVERFLOW;
|
errno = EOVERFLOW;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int retval = read(r->io.fd.fd,
|
int retval = connRead(r->io.conn.conn,
|
||||||
(char*)r->io.fd.buf + sdslen(r->io.fd.buf),
|
(char*)r->io.conn.buf + sdslen(r->io.conn.buf),
|
||||||
toread);
|
toread);
|
||||||
if (retval <= 0) {
|
if (retval <= 0) {
|
||||||
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
sdsIncrLen(r->io.fd.buf, retval);
|
sdsIncrLen(r->io.conn.buf, retval);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len);
|
memcpy(buf, (char*)r->io.conn.buf + r->io.conn.pos, len);
|
||||||
r->io.fd.read_so_far += len;
|
r->io.conn.read_so_far += len;
|
||||||
r->io.fd.pos += len;
|
r->io.conn.pos += len;
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns read/write position in file. */
|
||||||
|
static off_t rioConnTell(rio *r) {
|
||||||
|
return r->io.conn.read_so_far;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||||
|
* and 0 on failures. */
|
||||||
|
static int rioConnFlush(rio *r) {
|
||||||
|
/* Our flush is implemented by the write method, that recognizes a
|
||||||
|
* buffer set to NULL with a count of zero as a flush request. */
|
||||||
|
return rioConnWrite(r,NULL,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const rio rioConnIO = {
|
||||||
|
rioConnRead,
|
||||||
|
rioConnWrite,
|
||||||
|
rioConnTell,
|
||||||
|
rioConnFlush,
|
||||||
|
NULL, /* update_checksum */
|
||||||
|
0, /* current checksum */
|
||||||
|
0, /* flags */
|
||||||
|
0, /* bytes read or written */
|
||||||
|
0, /* read/write chunk size */
|
||||||
|
{ { NULL, 0 } } /* union for io-specific vars */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Create an RIO that implements a buffered read from an fd
|
||||||
|
* read_limit argument stops buffering when the reaching the limit. */
|
||||||
|
void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
|
||||||
|
*r = rioConnIO;
|
||||||
|
r->io.conn.conn = conn;
|
||||||
|
r->io.conn.pos = 0;
|
||||||
|
r->io.conn.read_limit = read_limit;
|
||||||
|
r->io.conn.read_so_far = 0;
|
||||||
|
r->io.conn.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
|
||||||
|
sdsclear(r->io.conn.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release the RIO tream. Optionally returns the unread buffered data
|
||||||
|
* when the SDS pointer 'remaining' is passed. */
|
||||||
|
void rioFreeConn(rio *r, sds *remaining) {
|
||||||
|
if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {
|
||||||
|
if (r->io.conn.pos > 0) sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
|
||||||
|
*remaining = r->io.conn.buf;
|
||||||
|
} else {
|
||||||
|
sdsfree(r->io.conn.buf);
|
||||||
|
if (remaining) *remaining = NULL;
|
||||||
|
}
|
||||||
|
r->io.conn.buf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------- File descriptor implementation ------------------
|
||||||
|
* This target is used to write the RDB file to pipe, when the master just
|
||||||
|
* streams the data to the replicas without creating an RDB on-disk image
|
||||||
|
* (diskless replication option).
|
||||||
|
* It only implements writes. */
|
||||||
|
|
||||||
|
/* Returns 1 or 0 for success/failure.
|
||||||
|
*
|
||||||
|
* When buf is NULL and len is 0, the function performs a flush operation
|
||||||
|
* if there is some pending buffer, so this function is also used in order
|
||||||
|
* to implement rioFdFlush(). */
|
||||||
|
static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
|
||||||
|
ssize_t retval;
|
||||||
|
unsigned char *p = (unsigned char*) buf;
|
||||||
|
int doflush = (buf == NULL && len == 0);
|
||||||
|
|
||||||
|
/* For small writes, we rather keep the data in user-space buffer, and flush
|
||||||
|
* it only when it grows. however for larger writes, we prefer to flush
|
||||||
|
* any pre-existing buffer, and write the new one directly without reallocs
|
||||||
|
* and memory copying. */
|
||||||
|
if (len > PROTO_IOBUF_LEN) {
|
||||||
|
/* First, flush any pre-existing buffered data. */
|
||||||
|
if (sdslen(r->io.fd.buf)) {
|
||||||
|
if (rioFdWrite(r, NULL, 0) == 0)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* Write the new data, keeping 'p' and 'len' from the input. */
|
||||||
|
} else {
|
||||||
|
if (len) {
|
||||||
|
r->io.fd.buf = sdscatlen(r->io.fd.buf,buf,len);
|
||||||
|
if (sdslen(r->io.fd.buf) > PROTO_IOBUF_LEN)
|
||||||
|
doflush = 1;
|
||||||
|
if (!doflush)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/* Flusing the buffered data. set 'p' and 'len' accordintly. */
|
||||||
|
p = (unsigned char*) r->io.fd.buf;
|
||||||
|
len = sdslen(r->io.fd.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nwritten = 0;
|
||||||
|
while(nwritten != len) {
|
||||||
|
retval = write(r->io.fd.fd,p+nwritten,len-nwritten);
|
||||||
|
if (retval <= 0) {
|
||||||
|
/* With blocking io, which is the sole user of this
|
||||||
|
* rio target, EWOULDBLOCK is returned only because of
|
||||||
|
* the SO_SNDTIMEO socket option, so we translate the error
|
||||||
|
* into one more recognizable by the user. */
|
||||||
|
if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
||||||
|
return 0; /* error. */
|
||||||
|
}
|
||||||
|
nwritten += retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->io.fd.pos += len;
|
||||||
|
sdsclear(r->io.fd.buf);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns 1 or 0 for success/failure. */
|
||||||
|
static size_t rioFdRead(rio *r, void *buf, size_t len) {
|
||||||
|
UNUSED(r);
|
||||||
|
UNUSED(buf);
|
||||||
|
UNUSED(len);
|
||||||
|
return 0; /* Error, this target does not support reading. */
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns read/write position in file. */
|
/* Returns read/write position in file. */
|
||||||
static off_t rioFdTell(rio *r) {
|
static off_t rioFdTell(rio *r) {
|
||||||
return r->io.fd.read_so_far;
|
return r->io.fd.pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
||||||
@ -247,160 +365,16 @@ static const rio rioFdIO = {
|
|||||||
{ { NULL, 0 } } /* union for io-specific vars */
|
{ { NULL, 0 } } /* union for io-specific vars */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Create an RIO that implements a buffered read from an fd
|
void rioInitWithFd(rio *r, int fd) {
|
||||||
* read_limit argument stops buffering when the reaching the limit. */
|
|
||||||
void rioInitWithFd(rio *r, int fd, size_t read_limit) {
|
|
||||||
*r = rioFdIO;
|
*r = rioFdIO;
|
||||||
r->io.fd.fd = fd;
|
r->io.fd.fd = fd;
|
||||||
r->io.fd.pos = 0;
|
r->io.fd.pos = 0;
|
||||||
r->io.fd.read_limit = read_limit;
|
r->io.fd.buf = sdsempty();
|
||||||
r->io.fd.read_so_far = 0;
|
|
||||||
r->io.fd.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
|
|
||||||
sdsclear(r->io.fd.buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Release the RIO tream. Optionally returns the unread buffered data
|
|
||||||
* when the SDS pointer 'remaining' is passed. */
|
|
||||||
void rioFreeFd(rio *r, sds *remaining) {
|
|
||||||
if (remaining && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) {
|
|
||||||
if (r->io.fd.pos > 0) sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
|
|
||||||
*remaining = r->io.fd.buf;
|
|
||||||
} else {
|
|
||||||
sdsfree(r->io.fd.buf);
|
|
||||||
if (remaining) *remaining = NULL;
|
|
||||||
}
|
|
||||||
r->io.fd.buf = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------- File descriptors set implementation ------------------
|
|
||||||
* This target is used to write the RDB file to N different replicas via
|
|
||||||
* sockets, when the master just streams the data to the replicas without
|
|
||||||
* creating an RDB on-disk image (diskless replication option).
|
|
||||||
* It only implements writes. */
|
|
||||||
|
|
||||||
/* Returns 1 or 0 for success/failure.
|
|
||||||
* The function returns success as long as we are able to correctly write
|
|
||||||
* to at least one file descriptor.
|
|
||||||
*
|
|
||||||
* When buf is NULL and len is 0, the function performs a flush operation
|
|
||||||
* if there is some pending buffer, so this function is also used in order
|
|
||||||
* to implement rioFdsetFlush(). */
|
|
||||||
static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
|
|
||||||
ssize_t retval;
|
|
||||||
int j;
|
|
||||||
unsigned char *p = (unsigned char*) buf;
|
|
||||||
int doflush = (buf == NULL && len == 0);
|
|
||||||
|
|
||||||
/* To start we always append to our buffer. If it gets larger than
|
|
||||||
* a given size, we actually write to the sockets. */
|
|
||||||
if (len) {
|
|
||||||
r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len);
|
|
||||||
len = 0; /* Prevent entering the while below if we don't flush. */
|
|
||||||
if (sdslen(r->io.fdset.buf) > PROTO_IOBUF_LEN) doflush = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doflush) {
|
|
||||||
p = (unsigned char*) r->io.fdset.buf;
|
|
||||||
len = sdslen(r->io.fdset.buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write in little chunchs so that when there are big writes we
|
|
||||||
* parallelize while the kernel is sending data in background to
|
|
||||||
* the TCP socket. */
|
|
||||||
while(len) {
|
|
||||||
size_t count = len < 1024 ? len : 1024;
|
|
||||||
int broken = 0;
|
|
||||||
for (j = 0; j < r->io.fdset.numfds; j++) {
|
|
||||||
if (r->io.fdset.state[j] != 0) {
|
|
||||||
/* Skip FDs alraedy in error. */
|
|
||||||
broken++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make sure to write 'count' bytes to the socket regardless
|
|
||||||
* of short writes. */
|
|
||||||
size_t nwritten = 0;
|
|
||||||
while(nwritten != count) {
|
|
||||||
retval = write(r->io.fdset.fds[j],p+nwritten,count-nwritten);
|
|
||||||
if (retval <= 0) {
|
|
||||||
/* With blocking sockets, which is the sole user of this
|
|
||||||
* rio target, EWOULDBLOCK is returned only because of
|
|
||||||
* the SO_SNDTIMEO socket option, so we translate the error
|
|
||||||
* into one more recognizable by the user. */
|
|
||||||
if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
nwritten += retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nwritten != count) {
|
|
||||||
/* Mark this FD as broken. */
|
|
||||||
r->io.fdset.state[j] = errno;
|
|
||||||
if (r->io.fdset.state[j] == 0) r->io.fdset.state[j] = EIO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (broken == r->io.fdset.numfds) return 0; /* All the FDs in error. */
|
|
||||||
p += count;
|
|
||||||
len -= count;
|
|
||||||
r->io.fdset.pos += count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doflush) sdsclear(r->io.fdset.buf);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns 1 or 0 for success/failure. */
|
|
||||||
static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
|
|
||||||
UNUSED(r);
|
|
||||||
UNUSED(buf);
|
|
||||||
UNUSED(len);
|
|
||||||
return 0; /* Error, this target does not support reading. */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns read/write position in file. */
|
|
||||||
static off_t rioFdsetTell(rio *r) {
|
|
||||||
return r->io.fdset.pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Flushes any buffer to target device if applicable. Returns 1 on success
|
|
||||||
* and 0 on failures. */
|
|
||||||
static int rioFdsetFlush(rio *r) {
|
|
||||||
/* Our flush is implemented by the write method, that recognizes a
|
|
||||||
* buffer set to NULL with a count of zero as a flush request. */
|
|
||||||
return rioFdsetWrite(r,NULL,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const rio rioFdsetIO = {
|
|
||||||
rioFdsetRead,
|
|
||||||
rioFdsetWrite,
|
|
||||||
rioFdsetTell,
|
|
||||||
rioFdsetFlush,
|
|
||||||
NULL, /* update_checksum */
|
|
||||||
0, /* current checksum */
|
|
||||||
0, /* flags */
|
|
||||||
0, /* bytes read or written */
|
|
||||||
0, /* read/write chunk size */
|
|
||||||
{ { NULL, 0 } } /* union for io-specific vars */
|
|
||||||
};
|
|
||||||
|
|
||||||
void rioInitWithFdset(rio *r, int *fds, int numfds) {
|
|
||||||
int j;
|
|
||||||
|
|
||||||
*r = rioFdsetIO;
|
|
||||||
r->io.fdset.fds = zmalloc(sizeof(int)*numfds);
|
|
||||||
r->io.fdset.state = zmalloc(sizeof(int)*numfds);
|
|
||||||
memcpy(r->io.fdset.fds,fds,sizeof(int)*numfds);
|
|
||||||
for (j = 0; j < numfds; j++) r->io.fdset.state[j] = 0;
|
|
||||||
r->io.fdset.numfds = numfds;
|
|
||||||
r->io.fdset.pos = 0;
|
|
||||||
r->io.fdset.buf = sdsempty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* release the rio stream. */
|
/* release the rio stream. */
|
||||||
void rioFreeFdset(rio *r) {
|
void rioFreeFd(rio *r) {
|
||||||
zfree(r->io.fdset.fds);
|
sdsfree(r->io.fd.buf);
|
||||||
zfree(r->io.fdset.state);
|
|
||||||
sdsfree(r->io.fdset.buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------- Generic functions ---------------------------- */
|
/* ---------------------------- Generic functions ---------------------------- */
|
||||||
|
23
src/rio.h
23
src/rio.h
@ -35,6 +35,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "sds.h"
|
#include "sds.h"
|
||||||
|
#include "connection.h"
|
||||||
|
|
||||||
#define RIO_FLAG_READ_ERROR (1<<0)
|
#define RIO_FLAG_READ_ERROR (1<<0)
|
||||||
#define RIO_FLAG_WRITE_ERROR (1<<1)
|
#define RIO_FLAG_WRITE_ERROR (1<<1)
|
||||||
@ -76,22 +77,20 @@ struct _rio {
|
|||||||
off_t buffered; /* Bytes written since last fsync. */
|
off_t buffered; /* Bytes written since last fsync. */
|
||||||
off_t autosync; /* fsync after 'autosync' bytes written. */
|
off_t autosync; /* fsync after 'autosync' bytes written. */
|
||||||
} file;
|
} file;
|
||||||
/* file descriptor */
|
/* Connection object (used to read from socket) */
|
||||||
struct {
|
struct {
|
||||||
int fd; /* File descriptor. */
|
connection *conn; /* Connection */
|
||||||
off_t pos; /* pos in buf that was returned */
|
off_t pos; /* pos in buf that was returned */
|
||||||
sds buf; /* buffered data */
|
sds buf; /* buffered data */
|
||||||
size_t read_limit; /* don't allow to buffer/read more than that */
|
size_t read_limit; /* don't allow to buffer/read more than that */
|
||||||
size_t read_so_far; /* amount of data read from the rio (not buffered) */
|
size_t read_so_far; /* amount of data read from the rio (not buffered) */
|
||||||
} fd;
|
} conn;
|
||||||
/* Multiple FDs target (used to write to N sockets). */
|
/* FD target (used to write to pipe). */
|
||||||
struct {
|
struct {
|
||||||
int *fds; /* File descriptors. */
|
int fd; /* File descriptor. */
|
||||||
int *state; /* Error state of each fd. 0 (if ok) or errno. */
|
|
||||||
int numfds;
|
|
||||||
off_t pos;
|
off_t pos;
|
||||||
sds buf;
|
sds buf;
|
||||||
} fdset;
|
} fd;
|
||||||
} io;
|
} io;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,11 +158,11 @@ static inline void rioClearErrors(rio *r) {
|
|||||||
|
|
||||||
void rioInitWithFile(rio *r, FILE *fp);
|
void rioInitWithFile(rio *r, FILE *fp);
|
||||||
void rioInitWithBuffer(rio *r, sds s);
|
void rioInitWithBuffer(rio *r, sds s);
|
||||||
void rioInitWithFd(rio *r, int fd, size_t read_limit);
|
void rioInitWithConn(rio *r, connection *conn, size_t read_limit);
|
||||||
void rioInitWithFdset(rio *r, int *fds, int numfds);
|
void rioInitWithFd(rio *r, int fd);
|
||||||
|
|
||||||
void rioFreeFdset(rio *r);
|
void rioFreeFd(rio *r);
|
||||||
void rioFreeFd(rio *r, sds* out_remainingBufferedData);
|
void rioFreeConn(rio *r, sds* out_remainingBufferedData);
|
||||||
|
|
||||||
size_t rioWriteBulkCount(rio *r, char prefix, long count);
|
size_t rioWriteBulkCount(rio *r, char prefix, long count);
|
||||||
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
|
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
|
||||||
|
@ -61,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx);
|
|||||||
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
|
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
|
||||||
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
|
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
|
||||||
struct ldbState {
|
struct ldbState {
|
||||||
int fd; /* Socket of the debugging client. */
|
connection *conn; /* Connection of the debugging client. */
|
||||||
int active; /* Are we debugging EVAL right now? */
|
int active; /* Are we debugging EVAL right now? */
|
||||||
int forked; /* Is this a fork()ed debugging session? */
|
int forked; /* Is this a fork()ed debugging session? */
|
||||||
list *logs; /* List of messages to send to the client. */
|
list *logs; /* List of messages to send to the client. */
|
||||||
@ -139,8 +139,8 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
|||||||
case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
||||||
case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
||||||
case '_': p = redisProtocolToLuaType_Null(lua,reply); break;
|
case '_': p = redisProtocolToLuaType_Null(lua,reply); break;
|
||||||
case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]);
|
case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); break;
|
||||||
case ',': p = redisProtocolToLuaType_Double(lua,reply);
|
case ',': p = redisProtocolToLuaType_Double(lua,reply); break;
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -1083,6 +1083,7 @@ void scriptingInit(int setup) {
|
|||||||
if (setup) {
|
if (setup) {
|
||||||
server.lua_client = NULL;
|
server.lua_client = NULL;
|
||||||
server.lua_caller = NULL;
|
server.lua_caller = NULL;
|
||||||
|
server.lua_cur_script = NULL;
|
||||||
server.lua_timedout = 0;
|
server.lua_timedout = 0;
|
||||||
ldbInit();
|
ldbInit();
|
||||||
}
|
}
|
||||||
@ -1242,7 +1243,7 @@ void scriptingInit(int setup) {
|
|||||||
* Note: there is no need to create it again when this function is called
|
* Note: there is no need to create it again when this function is called
|
||||||
* by scriptingReset(). */
|
* by scriptingReset(). */
|
||||||
if (server.lua_client == NULL) {
|
if (server.lua_client == NULL) {
|
||||||
server.lua_client = createClient(-1);
|
server.lua_client = createClient(NULL);
|
||||||
server.lua_client->flags |= CLIENT_LUA;
|
server.lua_client->flags |= CLIENT_LUA;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1407,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
|||||||
/* Set the timeout condition if not already set and the maximum
|
/* Set the timeout condition if not already set and the maximum
|
||||||
* execution time was reached. */
|
* execution time was reached. */
|
||||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||||
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
serverLog(LL_WARNING,
|
||||||
|
"Lua slow script detected: still in execution after %lld milliseconds. "
|
||||||
|
"You can try killing the script using the SCRIPT KILL command. "
|
||||||
|
"Script SHA1 is: %s",
|
||||||
|
elapsed, server.lua_cur_script);
|
||||||
server.lua_timedout = 1;
|
server.lua_timedout = 1;
|
||||||
/* Once the script timeouts we reenter the event loop to permit others
|
/* Once the script timeouts we reenter the event loop to permit others
|
||||||
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
|
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
|
||||||
@ -1524,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
|||||||
* If we are debugging, we set instead a "line" hook so that the
|
* If we are debugging, we set instead a "line" hook so that the
|
||||||
* debugger is call-back at every line executed by the script. */
|
* debugger is call-back at every line executed by the script. */
|
||||||
server.lua_caller = c;
|
server.lua_caller = c;
|
||||||
|
server.lua_cur_script = funcname + 2;
|
||||||
server.lua_time_start = mstime();
|
server.lua_time_start = mstime();
|
||||||
server.lua_kill = 0;
|
server.lua_kill = 0;
|
||||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||||
@ -1550,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
|||||||
queueClientForReprocessing(server.master);
|
queueClientForReprocessing(server.master);
|
||||||
}
|
}
|
||||||
server.lua_caller = NULL;
|
server.lua_caller = NULL;
|
||||||
|
server.lua_cur_script = NULL;
|
||||||
|
|
||||||
/* Call the Lua garbage collector from time to time to avoid a
|
/* Call the Lua garbage collector from time to time to avoid a
|
||||||
* full cycle performed by Lua, which adds too latency.
|
* full cycle performed by Lua, which adds too latency.
|
||||||
@ -1727,7 +1734,7 @@ NULL
|
|||||||
|
|
||||||
/* Initialize Lua debugger data structures. */
|
/* Initialize Lua debugger data structures. */
|
||||||
void ldbInit(void) {
|
void ldbInit(void) {
|
||||||
ldb.fd = -1;
|
ldb.conn = NULL;
|
||||||
ldb.active = 0;
|
ldb.active = 0;
|
||||||
ldb.logs = listCreate();
|
ldb.logs = listCreate();
|
||||||
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
|
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
|
||||||
@ -1749,7 +1756,7 @@ void ldbFlushLog(list *log) {
|
|||||||
void ldbEnable(client *c) {
|
void ldbEnable(client *c) {
|
||||||
c->flags |= CLIENT_LUA_DEBUG;
|
c->flags |= CLIENT_LUA_DEBUG;
|
||||||
ldbFlushLog(ldb.logs);
|
ldbFlushLog(ldb.logs);
|
||||||
ldb.fd = c->fd;
|
ldb.conn = c->conn;
|
||||||
ldb.step = 1;
|
ldb.step = 1;
|
||||||
ldb.bpcount = 0;
|
ldb.bpcount = 0;
|
||||||
ldb.luabp = 0;
|
ldb.luabp = 0;
|
||||||
@ -1804,7 +1811,7 @@ void ldbSendLogs(void) {
|
|||||||
proto = sdscatlen(proto,"\r\n",2);
|
proto = sdscatlen(proto,"\r\n",2);
|
||||||
listDelNode(ldb.logs,ln);
|
listDelNode(ldb.logs,ln);
|
||||||
}
|
}
|
||||||
if (write(ldb.fd,proto,sdslen(proto)) == -1) {
|
if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) {
|
||||||
/* Avoid warning. We don't check the return value of write()
|
/* Avoid warning. We don't check the return value of write()
|
||||||
* since the next read() will catch the I/O error and will
|
* since the next read() will catch the I/O error and will
|
||||||
* close the debugging session. */
|
* close the debugging session. */
|
||||||
@ -1827,7 +1834,7 @@ void ldbSendLogs(void) {
|
|||||||
int ldbStartSession(client *c) {
|
int ldbStartSession(client *c) {
|
||||||
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
|
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
|
||||||
if (ldb.forked) {
|
if (ldb.forked) {
|
||||||
pid_t cp = fork();
|
pid_t cp = redisFork();
|
||||||
if (cp == -1) {
|
if (cp == -1) {
|
||||||
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
|
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
|
||||||
return 0;
|
return 0;
|
||||||
@ -1844,7 +1851,6 @@ int ldbStartSession(client *c) {
|
|||||||
* socket to make sure if the parent crashes a reset is sent
|
* socket to make sure if the parent crashes a reset is sent
|
||||||
* to the clients. */
|
* to the clients. */
|
||||||
serverLog(LL_WARNING,"Redis forked for debugging eval");
|
serverLog(LL_WARNING,"Redis forked for debugging eval");
|
||||||
closeListeningSockets(0);
|
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
|
listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
|
||||||
@ -1857,8 +1863,8 @@ int ldbStartSession(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Setup our debugging session. */
|
/* Setup our debugging session. */
|
||||||
anetBlock(NULL,ldb.fd);
|
connBlock(ldb.conn);
|
||||||
anetSendTimeout(NULL,ldb.fd,5000);
|
connSendTimeout(ldb.conn,5000);
|
||||||
ldb.active = 1;
|
ldb.active = 1;
|
||||||
|
|
||||||
/* First argument of EVAL is the script itself. We split it into different
|
/* First argument of EVAL is the script itself. We split it into different
|
||||||
@ -1885,7 +1891,7 @@ void ldbEndSession(client *c) {
|
|||||||
|
|
||||||
/* If it's a fork()ed session, we just exit. */
|
/* If it's a fork()ed session, we just exit. */
|
||||||
if (ldb.forked) {
|
if (ldb.forked) {
|
||||||
writeToClient(c->fd, c, 0);
|
writeToClient(c,0);
|
||||||
serverLog(LL_WARNING,"Lua debugging session child exiting");
|
serverLog(LL_WARNING,"Lua debugging session child exiting");
|
||||||
exitFromChild(0);
|
exitFromChild(0);
|
||||||
} else {
|
} else {
|
||||||
@ -1894,8 +1900,8 @@ void ldbEndSession(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Otherwise let's restore client's state. */
|
/* Otherwise let's restore client's state. */
|
||||||
anetNonBlock(NULL,ldb.fd);
|
connNonBlock(ldb.conn);
|
||||||
anetSendTimeout(NULL,ldb.fd,0);
|
connSendTimeout(ldb.conn,0);
|
||||||
|
|
||||||
/* Close the client connectin after sending the final EVAL reply
|
/* Close the client connectin after sending the final EVAL reply
|
||||||
* in order to signal the end of the debugging session. */
|
* in order to signal the end of the debugging session. */
|
||||||
@ -2532,7 +2538,7 @@ int ldbRepl(lua_State *lua) {
|
|||||||
while(1) {
|
while(1) {
|
||||||
while((argv = ldbReplParseCommand(&argc)) == NULL) {
|
while((argv = ldbReplParseCommand(&argc)) == NULL) {
|
||||||
char buf[1024];
|
char buf[1024];
|
||||||
int nread = read(ldb.fd,buf,sizeof(buf));
|
int nread = connRead(ldb.conn,buf,sizeof(buf));
|
||||||
if (nread <= 0) {
|
if (nread <= 0) {
|
||||||
/* Make sure the script runs without user input since the
|
/* Make sure the script runs without user input since the
|
||||||
* client is no longer connected. */
|
* client is no longer connected. */
|
||||||
|
@ -603,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
|||||||
long i;
|
long i;
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
|
/* To avoid continuous reallocations, let's start with a buffer that
|
||||||
|
* can hold at least two times the format string itself. It's not the
|
||||||
|
* best heuristic but seems to work in practice. */
|
||||||
|
s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);
|
||||||
va_start(ap,fmt);
|
va_start(ap,fmt);
|
||||||
f = fmt; /* Next format specifier byte to process. */
|
f = fmt; /* Next format specifier byte to process. */
|
||||||
i = initlen; /* Position of the next byte to write to dest str. */
|
i = initlen; /* Position of the next byte to write to dest str. */
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
#include "hiredis.h"
|
#include "hiredis.h"
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
#include "openssl/ssl.h"
|
||||||
|
#include "hiredis_ssl.h"
|
||||||
|
#endif
|
||||||
#include "async.h"
|
#include "async.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
@ -40,6 +44,10 @@
|
|||||||
|
|
||||||
extern char **environ;
|
extern char **environ;
|
||||||
|
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
extern SSL_CTX *redis_tls_ctx;
|
||||||
|
#endif
|
||||||
|
|
||||||
#define REDIS_SENTINEL_PORT 26379
|
#define REDIS_SENTINEL_PORT 26379
|
||||||
|
|
||||||
/* ======================== Sentinel global state =========================== */
|
/* ======================== Sentinel global state =========================== */
|
||||||
@ -1995,6 +2003,19 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
|
||||||
|
#ifndef USE_OPENSSL
|
||||||
|
(void) context;
|
||||||
|
#else
|
||||||
|
if (!redis_tls_ctx) return C_ERR;
|
||||||
|
SSL *ssl = SSL_new(redis_tls_ctx);
|
||||||
|
if (!ssl) return C_ERR;
|
||||||
|
|
||||||
|
if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) return C_ERR;
|
||||||
|
#endif
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* Create the async connections for the instance link if the link
|
/* Create the async connections for the instance link if the link
|
||||||
* is disconnected. Note that link->disconnected is true even if just
|
* is disconnected. Note that link->disconnected is true even if just
|
||||||
* one of the two links (commands and pub/sub) is missing. */
|
* one of the two links (commands and pub/sub) is missing. */
|
||||||
@ -2010,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
|||||||
/* Commands connection. */
|
/* Commands connection. */
|
||||||
if (link->cc == NULL) {
|
if (link->cc == NULL) {
|
||||||
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
|
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
|
||||||
if (link->cc->err) {
|
if (!link->cc->err && server.tls_replication &&
|
||||||
|
(instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
|
||||||
|
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
|
||||||
|
instanceLinkCloseConnection(link,link->cc);
|
||||||
|
} else if (link->cc->err) {
|
||||||
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
|
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
|
||||||
link->cc->errstr);
|
link->cc->errstr);
|
||||||
instanceLinkCloseConnection(link,link->cc);
|
instanceLinkCloseConnection(link,link->cc);
|
||||||
@ -2033,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
|
|||||||
/* Pub / Sub */
|
/* Pub / Sub */
|
||||||
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
|
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
|
||||||
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
|
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
|
||||||
if (link->pc->err) {
|
if (!link->pc->err && server.tls_replication &&
|
||||||
|
(instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
|
||||||
|
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
|
||||||
|
} else if (link->pc->err) {
|
||||||
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
|
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
|
||||||
link->pc->errstr);
|
link->pc->errstr);
|
||||||
instanceLinkCloseConnection(link,link->pc);
|
instanceLinkCloseConnection(link,link->pc);
|
||||||
@ -2584,8 +2612,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
|
|||||||
return C_ERR;
|
return C_ERR;
|
||||||
announce_ip = ip;
|
announce_ip = ip;
|
||||||
}
|
}
|
||||||
announce_port = sentinel.announce_port ?
|
if (sentinel.announce_port) announce_port = sentinel.announce_port;
|
||||||
sentinel.announce_port : server.port;
|
else if (server.tls_replication && server.tls_port) announce_port = server.tls_port;
|
||||||
|
else announce_port = server.port;
|
||||||
|
|
||||||
/* Format and send the Hello message. */
|
/* Format and send the Hello message. */
|
||||||
snprintf(payload,sizeof(payload),
|
snprintf(payload,sizeof(payload),
|
||||||
|
352
src/server.c
352
src/server.c
@ -146,7 +146,7 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
|
|||||||
* in this condition but just a few.
|
* in this condition but just a few.
|
||||||
*
|
*
|
||||||
* no-monitor: Do not automatically propagate the command on MONITOR.
|
* no-monitor: Do not automatically propagate the command on MONITOR.
|
||||||
*
|
*
|
||||||
* no-slowlog: Do not automatically propagate the command to the slowlog.
|
* no-slowlog: Do not automatically propagate the command to the slowlog.
|
||||||
*
|
*
|
||||||
* cluster-asking: Perform an implicit ASKING for this command, so the
|
* cluster-asking: Perform an implicit ASKING for this command, so the
|
||||||
@ -1449,12 +1449,18 @@ int incrementallyRehash(int dbid) {
|
|||||||
* for dict.c to resize the hash tables accordingly to the fact we have o not
|
* for dict.c to resize the hash tables accordingly to the fact we have o not
|
||||||
* running childs. */
|
* running childs. */
|
||||||
void updateDictResizePolicy(void) {
|
void updateDictResizePolicy(void) {
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
|
if (!hasActiveChildProcess())
|
||||||
dictEnableResize();
|
dictEnableResize();
|
||||||
else
|
else
|
||||||
dictDisableResize();
|
dictDisableResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int hasActiveChildProcess() {
|
||||||
|
return server.rdb_child_pid != -1 ||
|
||||||
|
server.aof_child_pid != -1 ||
|
||||||
|
server.module_child_pid != -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ======================= Cron: called every 100 ms ======================== */
|
/* ======================= Cron: called every 100 ms ======================== */
|
||||||
|
|
||||||
/* Add a sample to the operations per second array of samples. */
|
/* Add a sample to the operations per second array of samples. */
|
||||||
@ -1691,7 +1697,7 @@ void databasesCron(void) {
|
|||||||
/* Perform hash tables rehashing if needed, but only if there are no
|
/* Perform hash tables rehashing if needed, but only if there are no
|
||||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||||
* as will cause a lot of copy-on-write of memory pages. */
|
* as will cause a lot of copy-on-write of memory pages. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
if (!hasActiveChildProcess()) {
|
||||||
/* We use global counters so if we stop the computation at a given
|
/* We use global counters so if we stop the computation at a given
|
||||||
* DB we'll be able to start from the successive in the next
|
* DB we'll be able to start from the successive in the next
|
||||||
* cron loop iteration. */
|
* cron loop iteration. */
|
||||||
@ -1746,6 +1752,62 @@ void updateCachedTime(void) {
|
|||||||
server.daylight_active = tm.tm_isdst;
|
server.daylight_active = tm.tm_isdst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkChildrenDone(void) {
|
||||||
|
int statloc;
|
||||||
|
pid_t pid;
|
||||||
|
|
||||||
|
/* If we have a diskless rdb child (note that we support only one concurrent
|
||||||
|
* child), we want to avoid collecting it's exit status and acting on it
|
||||||
|
* as long as we didn't finish to drain the pipe, since then we're at risk
|
||||||
|
* of starting a new fork and a new pipe before we're done with the previous
|
||||||
|
* one. */
|
||||||
|
if (server.rdb_child_pid != -1 && server.rdb_pipe_conns)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
||||||
|
int exitcode = WEXITSTATUS(statloc);
|
||||||
|
int bysignal = 0;
|
||||||
|
|
||||||
|
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||||
|
|
||||||
|
/* sigKillChildHandler catches the signal and calls exit(), but we
|
||||||
|
* must make sure not to flag lastbgsave_status, etc incorrectly.
|
||||||
|
* We could directly terminate the child process via SIGUSR1
|
||||||
|
* without handling it, but in this case Valgrind will log an
|
||||||
|
* annoying error. */
|
||||||
|
if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
|
||||||
|
bysignal = SIGUSR1;
|
||||||
|
exitcode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == -1) {
|
||||||
|
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
||||||
|
"rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
|
||||||
|
strerror(errno),
|
||||||
|
(int) server.rdb_child_pid,
|
||||||
|
(int) server.aof_child_pid,
|
||||||
|
(int) server.module_child_pid);
|
||||||
|
} else if (pid == server.rdb_child_pid) {
|
||||||
|
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||||
|
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||||
|
} else if (pid == server.aof_child_pid) {
|
||||||
|
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||||
|
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||||
|
} else if (pid == server.module_child_pid) {
|
||||||
|
ModuleForkDoneHandler(exitcode,bysignal);
|
||||||
|
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||||
|
} else {
|
||||||
|
if (!ldbRemoveChild(pid)) {
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"Warning, detected child with unmatched pid: %ld",
|
||||||
|
(long)pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateDictResizePolicy();
|
||||||
|
closeChildInfoPipe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* This is our timer interrupt, called server.hz times per second.
|
/* This is our timer interrupt, called server.hz times per second.
|
||||||
* Here is where we do a number of things that need to be done asynchronously.
|
* Here is where we do a number of things that need to be done asynchronously.
|
||||||
* For instance:
|
* For instance:
|
||||||
@ -1888,47 +1950,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
|
|
||||||
/* Start a scheduled AOF rewrite if this was requested by the user while
|
/* Start a scheduled AOF rewrite if this was requested by the user while
|
||||||
* a BGSAVE was in progress. */
|
* a BGSAVE was in progress. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
|
if (!hasActiveChildProcess() &&
|
||||||
server.aof_rewrite_scheduled)
|
server.aof_rewrite_scheduled)
|
||||||
{
|
{
|
||||||
rewriteAppendOnlyFileBackground();
|
rewriteAppendOnlyFileBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if a background saving or AOF rewrite in progress terminated. */
|
/* Check if a background saving or AOF rewrite in progress terminated. */
|
||||||
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
|
if (hasActiveChildProcess() || ldbPendingChildren())
|
||||||
ldbPendingChildren())
|
|
||||||
{
|
{
|
||||||
int statloc;
|
checkChildrenDone();
|
||||||
pid_t pid;
|
|
||||||
|
|
||||||
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
|
||||||
int exitcode = WEXITSTATUS(statloc);
|
|
||||||
int bysignal = 0;
|
|
||||||
|
|
||||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
|
||||||
|
|
||||||
if (pid == -1) {
|
|
||||||
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
|
||||||
"rdb_child_pid = %d, aof_child_pid = %d",
|
|
||||||
strerror(errno),
|
|
||||||
(int) server.rdb_child_pid,
|
|
||||||
(int) server.aof_child_pid);
|
|
||||||
} else if (pid == server.rdb_child_pid) {
|
|
||||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
|
||||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
|
||||||
} else if (pid == server.aof_child_pid) {
|
|
||||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
|
||||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
|
||||||
} else {
|
|
||||||
if (!ldbRemoveChild(pid)) {
|
|
||||||
serverLog(LL_WARNING,
|
|
||||||
"Warning, detected child with unmatched pid: %ld",
|
|
||||||
(long)pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateDictResizePolicy();
|
|
||||||
closeChildInfoPipe();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/* If there is not a background saving/rewrite in progress check if
|
/* If there is not a background saving/rewrite in progress check if
|
||||||
* we have to save/rewrite now. */
|
* we have to save/rewrite now. */
|
||||||
@ -1956,8 +1987,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
|
|
||||||
/* Trigger an AOF rewrite if needed. */
|
/* Trigger an AOF rewrite if needed. */
|
||||||
if (server.aof_state == AOF_ON &&
|
if (server.aof_state == AOF_ON &&
|
||||||
server.rdb_child_pid == -1 &&
|
!hasActiveChildProcess() &&
|
||||||
server.aof_child_pid == -1 &&
|
|
||||||
server.aof_rewrite_perc &&
|
server.aof_rewrite_perc &&
|
||||||
server.aof_current_size > server.aof_rewrite_min_size)
|
server.aof_current_size > server.aof_rewrite_min_size)
|
||||||
{
|
{
|
||||||
@ -2015,7 +2045,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
* Note: this code must be after the replicationCron() call above so
|
* Note: this code must be after the replicationCron() call above so
|
||||||
* make sure when refactoring this file to keep this order. This is useful
|
* make sure when refactoring this file to keep this order. This is useful
|
||||||
* because we want to give priority to RDB savings for replication. */
|
* because we want to give priority to RDB savings for replication. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
|
if (!hasActiveChildProcess() &&
|
||||||
server.rdb_bgsave_scheduled &&
|
server.rdb_bgsave_scheduled &&
|
||||||
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
|
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
|
||||||
server.lastbgsave_status == C_OK))
|
server.lastbgsave_status == C_OK))
|
||||||
@ -2026,6 +2056,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
server.rdb_bgsave_scheduled = 0;
|
server.rdb_bgsave_scheduled = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fire the cron loop modules event. */
|
||||||
|
RedisModuleCronLoopV1 ei = {REDISMODULE_CRON_LOOP_VERSION,server.hz};
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_CRON_LOOP,
|
||||||
|
0,
|
||||||
|
&ei);
|
||||||
|
|
||||||
server.cronloops++;
|
server.cronloops++;
|
||||||
return 1000/server.hz;
|
return 1000/server.hz;
|
||||||
}
|
}
|
||||||
@ -2036,6 +2072,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
void beforeSleep(struct aeEventLoop *eventLoop) {
|
void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||||
UNUSED(eventLoop);
|
UNUSED(eventLoop);
|
||||||
|
|
||||||
|
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
|
||||||
|
tlsProcessPendingData();
|
||||||
|
/* If tls still has pending unread data don't sleep at all. */
|
||||||
|
aeSetDontWait(server.el, tlsHasPendingData());
|
||||||
|
|
||||||
/* Call the Redis Cluster before sleep function. Note that this function
|
/* Call the Redis Cluster before sleep function. Note that this function
|
||||||
* may change the state of Redis Cluster (from ok to fail or vice versa),
|
* may change the state of Redis Cluster (from ok to fail or vice versa),
|
||||||
* so it's a good idea to call it before serving the unblocked clients
|
* so it's a good idea to call it before serving the unblocked clients
|
||||||
@ -2069,7 +2110,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
|||||||
|
|
||||||
/* Check if there are clients unblocked by modules that implement
|
/* Check if there are clients unblocked by modules that implement
|
||||||
* blocking commands. */
|
* blocking commands. */
|
||||||
moduleHandleBlockedClients();
|
if (moduleCount()) moduleHandleBlockedClients();
|
||||||
|
|
||||||
/* Try to process pending commands for clients that were just unblocked. */
|
/* Try to process pending commands for clients that were just unblocked. */
|
||||||
if (listLength(server.unblocked_clients))
|
if (listLength(server.unblocked_clients))
|
||||||
@ -2229,11 +2270,13 @@ void initServerConfig(void) {
|
|||||||
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
|
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
|
||||||
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
|
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
|
||||||
server.port = CONFIG_DEFAULT_SERVER_PORT;
|
server.port = CONFIG_DEFAULT_SERVER_PORT;
|
||||||
|
server.tls_port = CONFIG_DEFAULT_SERVER_TLS_PORT;
|
||||||
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
|
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
|
||||||
server.bindaddr_count = 0;
|
server.bindaddr_count = 0;
|
||||||
server.unixsocket = NULL;
|
server.unixsocket = NULL;
|
||||||
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
|
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
|
||||||
server.ipfd_count = 0;
|
server.ipfd_count = 0;
|
||||||
|
server.tlsfd_count = 0;
|
||||||
server.sofd = -1;
|
server.sofd = -1;
|
||||||
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
|
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
|
||||||
server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
|
server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
|
||||||
@ -2242,6 +2285,7 @@ void initServerConfig(void) {
|
|||||||
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
||||||
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
||||||
server.active_expire_enabled = 1;
|
server.active_expire_enabled = 1;
|
||||||
|
server.jemalloc_bg_thread = 1;
|
||||||
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
||||||
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
||||||
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
|
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
|
||||||
@ -2267,6 +2311,7 @@ void initServerConfig(void) {
|
|||||||
server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
|
server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
|
||||||
server.aof_rewrite_base_size = 0;
|
server.aof_rewrite_base_size = 0;
|
||||||
server.aof_rewrite_scheduled = 0;
|
server.aof_rewrite_scheduled = 0;
|
||||||
|
server.aof_flush_sleep = 0;
|
||||||
server.aof_last_fsync = time(NULL);
|
server.aof_last_fsync = time(NULL);
|
||||||
server.aof_rewrite_time_last = -1;
|
server.aof_rewrite_time_last = -1;
|
||||||
server.aof_rewrite_time_start = -1;
|
server.aof_rewrite_time_start = -1;
|
||||||
@ -2278,6 +2323,7 @@ void initServerConfig(void) {
|
|||||||
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
|
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
|
||||||
server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
|
server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
|
||||||
server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY;
|
server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY;
|
||||||
|
server.key_load_delay = CONFIG_DEFAULT_KEY_LOAD_DELAY;
|
||||||
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
|
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
|
||||||
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
|
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
|
||||||
server.pidfile = NULL;
|
server.pidfile = NULL;
|
||||||
@ -2349,7 +2395,7 @@ void initServerConfig(void) {
|
|||||||
server.repl_state = REPL_STATE_NONE;
|
server.repl_state = REPL_STATE_NONE;
|
||||||
server.repl_transfer_tmpfile = NULL;
|
server.repl_transfer_tmpfile = NULL;
|
||||||
server.repl_transfer_fd = -1;
|
server.repl_transfer_fd = -1;
|
||||||
server.repl_transfer_s = -1;
|
server.repl_transfer_s = NULL;
|
||||||
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
|
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
|
||||||
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
|
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
|
||||||
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
|
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
|
||||||
@ -2746,6 +2792,11 @@ void initServer(void) {
|
|||||||
server.clients_paused = 0;
|
server.clients_paused = 0;
|
||||||
server.system_memory_size = zmalloc_get_memory_size();
|
server.system_memory_size = zmalloc_get_memory_size();
|
||||||
|
|
||||||
|
if (server.tls_port && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
|
||||||
|
serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info.");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
createSharedObjects();
|
createSharedObjects();
|
||||||
adjustOpenFilesLimit();
|
adjustOpenFilesLimit();
|
||||||
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
|
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
|
||||||
@ -2761,6 +2812,9 @@ void initServer(void) {
|
|||||||
if (server.port != 0 &&
|
if (server.port != 0 &&
|
||||||
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
|
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
|
||||||
exit(1);
|
exit(1);
|
||||||
|
if (server.tls_port != 0 &&
|
||||||
|
listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
|
||||||
|
exit(1);
|
||||||
|
|
||||||
/* Open the listening Unix domain socket. */
|
/* Open the listening Unix domain socket. */
|
||||||
if (server.unixsocket != NULL) {
|
if (server.unixsocket != NULL) {
|
||||||
@ -2775,7 +2829,7 @@ void initServer(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Abort if there are no listening sockets at all. */
|
/* Abort if there are no listening sockets at all. */
|
||||||
if (server.ipfd_count == 0 && server.sofd < 0) {
|
if (server.ipfd_count == 0 && server.tlsfd_count == 0 && server.sofd < 0) {
|
||||||
serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
|
serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@ -2799,7 +2853,13 @@ void initServer(void) {
|
|||||||
server.cronloops = 0;
|
server.cronloops = 0;
|
||||||
server.rdb_child_pid = -1;
|
server.rdb_child_pid = -1;
|
||||||
server.aof_child_pid = -1;
|
server.aof_child_pid = -1;
|
||||||
|
server.module_child_pid = -1;
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||||
|
server.rdb_pipe_conns = NULL;
|
||||||
|
server.rdb_pipe_numconns = 0;
|
||||||
|
server.rdb_pipe_numconns_writing = 0;
|
||||||
|
server.rdb_pipe_buff = NULL;
|
||||||
|
server.rdb_pipe_bufflen = 0;
|
||||||
server.rdb_bgsave_scheduled = 0;
|
server.rdb_bgsave_scheduled = 0;
|
||||||
server.child_info_pipe[0] = -1;
|
server.child_info_pipe[0] = -1;
|
||||||
server.child_info_pipe[1] = -1;
|
server.child_info_pipe[1] = -1;
|
||||||
@ -2817,6 +2877,7 @@ void initServer(void) {
|
|||||||
server.stat_peak_memory = 0;
|
server.stat_peak_memory = 0;
|
||||||
server.stat_rdb_cow_bytes = 0;
|
server.stat_rdb_cow_bytes = 0;
|
||||||
server.stat_aof_cow_bytes = 0;
|
server.stat_aof_cow_bytes = 0;
|
||||||
|
server.stat_module_cow_bytes = 0;
|
||||||
server.cron_malloc_stats.zmalloc_used = 0;
|
server.cron_malloc_stats.zmalloc_used = 0;
|
||||||
server.cron_malloc_stats.process_rss = 0;
|
server.cron_malloc_stats.process_rss = 0;
|
||||||
server.cron_malloc_stats.allocator_allocated = 0;
|
server.cron_malloc_stats.allocator_allocated = 0;
|
||||||
@ -2845,6 +2906,14 @@ void initServer(void) {
|
|||||||
"Unrecoverable error creating server.ipfd file event.");
|
"Unrecoverable error creating server.ipfd file event.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (j = 0; j < server.tlsfd_count; j++) {
|
||||||
|
if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
|
||||||
|
acceptTLSHandler,NULL) == AE_ERR)
|
||||||
|
{
|
||||||
|
serverPanic(
|
||||||
|
"Unrecoverable error creating server.tlsfd file event.");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
|
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
|
||||||
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
|
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
|
||||||
|
|
||||||
@ -2884,8 +2953,17 @@ void initServer(void) {
|
|||||||
scriptingInit(1);
|
scriptingInit(1);
|
||||||
slowlogInit();
|
slowlogInit();
|
||||||
latencyMonitorInit();
|
latencyMonitorInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Some steps in server initialization need to be done last (after modules
|
||||||
|
* are loaded).
|
||||||
|
* Specifically, creation of threads due to a race bug in ld.so, in which
|
||||||
|
* Thread Local Storage initialization collides with dlopen call.
|
||||||
|
* see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
|
||||||
|
void InitServerLast() {
|
||||||
bioInit();
|
bioInit();
|
||||||
initThreadedIO();
|
initThreadedIO();
|
||||||
|
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
|
||||||
server.initial_memory_usage = zmalloc_used_memory();
|
server.initial_memory_usage = zmalloc_used_memory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3136,7 +3214,7 @@ void preventCommandReplication(client *c) {
|
|||||||
* CMD_CALL_STATS Populate command stats.
|
* CMD_CALL_STATS Populate command stats.
|
||||||
* CMD_CALL_PROPAGATE_AOF Append command to AOF if it modified the dataset
|
* CMD_CALL_PROPAGATE_AOF Append command to AOF if it modified the dataset
|
||||||
* or if the client flags are forcing propagation.
|
* or if the client flags are forcing propagation.
|
||||||
* CMD_CALL_PROPAGATE_REPL Send command to salves if it modified the dataset
|
* CMD_CALL_PROPAGATE_REPL Send command to slaves if it modified the dataset
|
||||||
* or if the client flags are forcing propagation.
|
* or if the client flags are forcing propagation.
|
||||||
* CMD_CALL_PROPAGATE Alias for PROPAGATE_AOF|PROPAGATE_REPL.
|
* CMD_CALL_PROPAGATE Alias for PROPAGATE_AOF|PROPAGATE_REPL.
|
||||||
* CMD_CALL_FULL Alias for SLOWLOG|STATS|PROPAGATE.
|
* CMD_CALL_FULL Alias for SLOWLOG|STATS|PROPAGATE.
|
||||||
@ -3341,11 +3419,12 @@ int processCommand(client *c) {
|
|||||||
|
|
||||||
/* Check if the user is authenticated. This check is skipped in case
|
/* Check if the user is authenticated. This check is skipped in case
|
||||||
* the default user is flagged as "nopass" and is active. */
|
* the default user is flagged as "nopass" and is active. */
|
||||||
int auth_required = !(DefaultUser->flags & USER_FLAG_NOPASS) &&
|
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||||
|
DefaultUser->flags & USER_FLAG_DISABLED) &&
|
||||||
!c->authenticated;
|
!c->authenticated;
|
||||||
if (auth_required || DefaultUser->flags & USER_FLAG_DISABLED) {
|
if (auth_required) {
|
||||||
/* AUTH and HELLO are valid even in non authenticated state. */
|
/* AUTH and HELLO are valid even in non authenticated state. */
|
||||||
if (c->cmd->proc != authCommand || c->cmd->proc == helloCommand) {
|
if (c->cmd->proc != authCommand && c->cmd->proc != helloCommand) {
|
||||||
flagTransaction(c);
|
flagTransaction(c);
|
||||||
addReply(c,shared.noautherr);
|
addReply(c,shared.noautherr);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
@ -3411,7 +3490,10 @@ int processCommand(client *c) {
|
|||||||
* is in MULTI/EXEC context? Error. */
|
* is in MULTI/EXEC context? Error. */
|
||||||
if (out_of_memory &&
|
if (out_of_memory &&
|
||||||
(c->cmd->flags & CMD_DENYOOM ||
|
(c->cmd->flags & CMD_DENYOOM ||
|
||||||
(c->flags & CLIENT_MULTI && c->cmd->proc != execCommand))) {
|
(c->flags & CLIENT_MULTI &&
|
||||||
|
c->cmd->proc != execCommand &&
|
||||||
|
c->cmd->proc != discardCommand)))
|
||||||
|
{
|
||||||
flagTransaction(c);
|
flagTransaction(c);
|
||||||
addReply(c, shared.oomerr);
|
addReply(c, shared.oomerr);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
@ -3536,6 +3618,7 @@ void closeListeningSockets(int unlink_unix_socket) {
|
|||||||
int j;
|
int j;
|
||||||
|
|
||||||
for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]);
|
for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]);
|
||||||
|
for (j = 0; j < server.tlsfd_count; j++) close(server.tlsfd[j]);
|
||||||
if (server.sofd != -1) close(server.sofd);
|
if (server.sofd != -1) close(server.sofd);
|
||||||
if (server.cluster_enabled)
|
if (server.cluster_enabled)
|
||||||
for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]);
|
for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]);
|
||||||
@ -3562,6 +3645,12 @@ int prepareForShutdown(int flags) {
|
|||||||
killRDBChild();
|
killRDBChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kill module child if there is one. */
|
||||||
|
if (server.module_child_pid != -1) {
|
||||||
|
serverLog(LL_WARNING,"There is a module fork child. Killing it!");
|
||||||
|
TerminateModuleForkChild(server.module_child_pid,0);
|
||||||
|
}
|
||||||
|
|
||||||
if (server.aof_state != AOF_OFF) {
|
if (server.aof_state != AOF_OFF) {
|
||||||
/* Kill the AOF saving child as the AOF we already have may be longer
|
/* Kill the AOF saving child as the AOF we already have may be longer
|
||||||
* but contains the full dataset anyway. */
|
* but contains the full dataset anyway. */
|
||||||
@ -3599,6 +3688,9 @@ int prepareForShutdown(int flags) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fire the shutdown modules event. */
|
||||||
|
moduleFireServerEvent(REDISMODULE_EVENT_SHUTDOWN,0,NULL);
|
||||||
|
|
||||||
/* Remove the pid file if possible and needed. */
|
/* Remove the pid file if possible and needed. */
|
||||||
if (server.daemonize || server.pidfile) {
|
if (server.daemonize || server.pidfile) {
|
||||||
serverLog(LL_NOTICE,"Removing the pid file.");
|
serverLog(LL_NOTICE,"Removing the pid file.");
|
||||||
@ -3831,12 +3923,15 @@ sds genRedisInfoString(char *section) {
|
|||||||
time_t uptime = server.unixtime-server.stat_starttime;
|
time_t uptime = server.unixtime-server.stat_starttime;
|
||||||
int j;
|
int j;
|
||||||
struct rusage self_ru, c_ru;
|
struct rusage self_ru, c_ru;
|
||||||
int allsections = 0, defsections = 0;
|
int allsections = 0, defsections = 0, everything = 0, modules = 0;
|
||||||
int sections = 0;
|
int sections = 0;
|
||||||
|
|
||||||
if (section == NULL) section = "default";
|
if (section == NULL) section = "default";
|
||||||
allsections = strcasecmp(section,"all") == 0;
|
allsections = strcasecmp(section,"all") == 0;
|
||||||
defsections = strcasecmp(section,"default") == 0;
|
defsections = strcasecmp(section,"default") == 0;
|
||||||
|
everything = strcasecmp(section,"everything") == 0;
|
||||||
|
modules = strcasecmp(section,"modules") == 0;
|
||||||
|
if (everything) allsections = 1;
|
||||||
|
|
||||||
getrusage(RUSAGE_SELF, &self_ru);
|
getrusage(RUSAGE_SELF, &self_ru);
|
||||||
getrusage(RUSAGE_CHILDREN, &c_ru);
|
getrusage(RUSAGE_CHILDREN, &c_ru);
|
||||||
@ -3859,32 +3954,32 @@ sds genRedisInfoString(char *section) {
|
|||||||
call_uname = 0;
|
call_uname = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
info = sdscatprintf(info,
|
info = sdscatfmt(info,
|
||||||
"# Server\r\n"
|
"# Server\r\n"
|
||||||
"redis_version:%s\r\n"
|
"redis_version:%s\r\n"
|
||||||
"redis_git_sha1:%s\r\n"
|
"redis_git_sha1:%s\r\n"
|
||||||
"redis_git_dirty:%d\r\n"
|
"redis_git_dirty:%i\r\n"
|
||||||
"redis_build_id:%llx\r\n"
|
"redis_build_id:%s\r\n"
|
||||||
"redis_mode:%s\r\n"
|
"redis_mode:%s\r\n"
|
||||||
"os:%s %s %s\r\n"
|
"os:%s %s %s\r\n"
|
||||||
"arch_bits:%d\r\n"
|
"arch_bits:%i\r\n"
|
||||||
"multiplexing_api:%s\r\n"
|
"multiplexing_api:%s\r\n"
|
||||||
"atomicvar_api:%s\r\n"
|
"atomicvar_api:%s\r\n"
|
||||||
"gcc_version:%d.%d.%d\r\n"
|
"gcc_version:%i.%i.%i\r\n"
|
||||||
"process_id:%ld\r\n"
|
"process_id:%I\r\n"
|
||||||
"run_id:%s\r\n"
|
"run_id:%s\r\n"
|
||||||
"tcp_port:%d\r\n"
|
"tcp_port:%i\r\n"
|
||||||
"uptime_in_seconds:%jd\r\n"
|
"uptime_in_seconds:%I\r\n"
|
||||||
"uptime_in_days:%jd\r\n"
|
"uptime_in_days:%I\r\n"
|
||||||
"hz:%d\r\n"
|
"hz:%i\r\n"
|
||||||
"configured_hz:%d\r\n"
|
"configured_hz:%i\r\n"
|
||||||
"lru_clock:%ld\r\n"
|
"lru_clock:%u\r\n"
|
||||||
"executable:%s\r\n"
|
"executable:%s\r\n"
|
||||||
"config_file:%s\r\n",
|
"config_file:%s\r\n",
|
||||||
REDIS_VERSION,
|
REDIS_VERSION,
|
||||||
redisGitSHA1(),
|
redisGitSHA1(),
|
||||||
strtol(redisGitDirty(),NULL,10) > 0,
|
strtol(redisGitDirty(),NULL,10) > 0,
|
||||||
(unsigned long long) redisBuildId(),
|
redisBuildIdString(),
|
||||||
mode,
|
mode,
|
||||||
name.sysname, name.release, name.machine,
|
name.sysname, name.release, name.machine,
|
||||||
server.arch_bits,
|
server.arch_bits,
|
||||||
@ -3895,14 +3990,14 @@ sds genRedisInfoString(char *section) {
|
|||||||
#else
|
#else
|
||||||
0,0,0,
|
0,0,0,
|
||||||
#endif
|
#endif
|
||||||
(long) getpid(),
|
(int64_t) getpid(),
|
||||||
server.runid,
|
server.runid,
|
||||||
server.port,
|
server.port ? server.port : server.tls_port,
|
||||||
(intmax_t)uptime,
|
(int64_t)uptime,
|
||||||
(intmax_t)(uptime/(3600*24)),
|
(int64_t)(uptime/(3600*24)),
|
||||||
server.hz,
|
server.hz,
|
||||||
server.config_hz,
|
server.config_hz,
|
||||||
(unsigned long) server.lruclock,
|
server.lruclock,
|
||||||
server.executable ? server.executable : "",
|
server.executable ? server.executable : "",
|
||||||
server.configfile ? server.configfile : "");
|
server.configfile ? server.configfile : "");
|
||||||
}
|
}
|
||||||
@ -4028,8 +4123,11 @@ sds genRedisInfoString(char *section) {
|
|||||||
mh->allocator_rss_bytes,
|
mh->allocator_rss_bytes,
|
||||||
mh->rss_extra,
|
mh->rss_extra,
|
||||||
mh->rss_extra_bytes,
|
mh->rss_extra_bytes,
|
||||||
mh->total_frag, /* this is the total RSS overhead, including fragmentation, */
|
mh->total_frag, /* This is the total RSS overhead, including
|
||||||
mh->total_frag_bytes, /* named so for backwards compatibility */
|
fragmentation, but not just it. This field
|
||||||
|
(and the next one) is named like that just
|
||||||
|
for backward compatibility. */
|
||||||
|
mh->total_frag_bytes,
|
||||||
freeMemoryGetNotCountedMemory(),
|
freeMemoryGetNotCountedMemory(),
|
||||||
mh->repl_backlog,
|
mh->repl_backlog,
|
||||||
mh->clients_slaves,
|
mh->clients_slaves,
|
||||||
@ -4062,7 +4160,9 @@ sds genRedisInfoString(char *section) {
|
|||||||
"aof_current_rewrite_time_sec:%jd\r\n"
|
"aof_current_rewrite_time_sec:%jd\r\n"
|
||||||
"aof_last_bgrewrite_status:%s\r\n"
|
"aof_last_bgrewrite_status:%s\r\n"
|
||||||
"aof_last_write_status:%s\r\n"
|
"aof_last_write_status:%s\r\n"
|
||||||
"aof_last_cow_size:%zu\r\n",
|
"aof_last_cow_size:%zu\r\n"
|
||||||
|
"module_fork_in_progress:%d\r\n"
|
||||||
|
"module_fork_last_cow_size:%zu\r\n",
|
||||||
server.loading,
|
server.loading,
|
||||||
server.dirty,
|
server.dirty,
|
||||||
server.rdb_child_pid != -1,
|
server.rdb_child_pid != -1,
|
||||||
@ -4080,7 +4180,9 @@ sds genRedisInfoString(char *section) {
|
|||||||
-1 : time(NULL)-server.aof_rewrite_time_start),
|
-1 : time(NULL)-server.aof_rewrite_time_start),
|
||||||
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
|
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
|
||||||
(server.aof_last_write_status == C_OK) ? "ok" : "err",
|
(server.aof_last_write_status == C_OK) ? "ok" : "err",
|
||||||
server.stat_aof_cow_bytes);
|
server.stat_aof_cow_bytes,
|
||||||
|
server.module_child_pid != -1,
|
||||||
|
server.stat_module_cow_bytes);
|
||||||
|
|
||||||
if (server.aof_enabled) {
|
if (server.aof_enabled) {
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
@ -4238,7 +4340,7 @@ sds genRedisInfoString(char *section) {
|
|||||||
if (server.repl_state != REPL_STATE_CONNECTED) {
|
if (server.repl_state != REPL_STATE_CONNECTED) {
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"master_link_down_since_seconds:%jd\r\n",
|
"master_link_down_since_seconds:%jd\r\n",
|
||||||
(intmax_t)server.unixtime-server.repl_down_since);
|
(intmax_t)(server.unixtime-server.repl_down_since));
|
||||||
}
|
}
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"slave_priority:%d\r\n"
|
"slave_priority:%d\r\n"
|
||||||
@ -4274,7 +4376,7 @@ sds genRedisInfoString(char *section) {
|
|||||||
long lag = 0;
|
long lag = 0;
|
||||||
|
|
||||||
if (slaveip[0] == '\0') {
|
if (slaveip[0] == '\0') {
|
||||||
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1)
|
if (connPeerToString(slave->conn,ip,sizeof(ip),&port) == -1)
|
||||||
continue;
|
continue;
|
||||||
slaveip = ip;
|
slaveip = ip;
|
||||||
}
|
}
|
||||||
@ -4336,6 +4438,13 @@ sds genRedisInfoString(char *section) {
|
|||||||
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
|
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
if (allsections || defsections || !strcasecmp(section,"modules")) {
|
||||||
|
if (sections++) info = sdscat(info,"\r\n");
|
||||||
|
info = sdscatprintf(info,"# Modules\r\n");
|
||||||
|
info = genModulesInfoString(info);
|
||||||
|
}
|
||||||
|
|
||||||
/* Command statistics */
|
/* Command statistics */
|
||||||
if (allsections || !strcasecmp(section,"commandstats")) {
|
if (allsections || !strcasecmp(section,"commandstats")) {
|
||||||
if (sections++) info = sdscat(info,"\r\n");
|
if (sections++) info = sdscat(info,"\r\n");
|
||||||
@ -4381,6 +4490,17 @@ sds genRedisInfoString(char *section) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get info from modules.
|
||||||
|
* if user asked for "everything" or "modules", or a specific section
|
||||||
|
* that's not found yet. */
|
||||||
|
if (everything || modules ||
|
||||||
|
(!allsections && !defsections && sections==0)) {
|
||||||
|
info = modulesCollectInfo(info,
|
||||||
|
everything || modules ? NULL: section,
|
||||||
|
0, /* not a crash report */
|
||||||
|
sections);
|
||||||
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4391,7 +4511,9 @@ void infoCommand(client *c) {
|
|||||||
addReply(c,shared.syntaxerr);
|
addReply(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addReplyBulkSds(c, genRedisInfoString(section));
|
sds info = genRedisInfoString(section);
|
||||||
|
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||||
|
sdsfree(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void monitorCommand(client *c) {
|
void monitorCommand(client *c) {
|
||||||
@ -4508,7 +4630,7 @@ void redisAsciiArt(void) {
|
|||||||
if (!show_logo) {
|
if (!show_logo) {
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"Running mode=%s, port=%d.",
|
"Running mode=%s, port=%d.",
|
||||||
mode, server.port
|
mode, server.port ? server.port : server.tls_port
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
snprintf(buf,1024*16,ascii_logo,
|
snprintf(buf,1024*16,ascii_logo,
|
||||||
@ -4516,7 +4638,7 @@ void redisAsciiArt(void) {
|
|||||||
redisGitSHA1(),
|
redisGitSHA1(),
|
||||||
strtol(redisGitDirty(),NULL,10) > 0,
|
strtol(redisGitDirty(),NULL,10) > 0,
|
||||||
(sizeof(long) == 8) ? "64" : "32",
|
(sizeof(long) == 8) ? "64" : "32",
|
||||||
mode, server.port,
|
mode, server.port ? server.port : server.tls_port,
|
||||||
(long) getpid()
|
(long) getpid()
|
||||||
);
|
);
|
||||||
serverLogRaw(LL_NOTICE|LL_RAW,buf);
|
serverLogRaw(LL_NOTICE|LL_RAW,buf);
|
||||||
@ -4578,6 +4700,61 @@ void setupSignalHandlers(void) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This is the signal handler for children process. It is currently useful
|
||||||
|
* in order to track the SIGUSR1, that we send to a child in order to terminate
|
||||||
|
* it in a clean way, without the parent detecting an error and stop
|
||||||
|
* accepting writes because of a write error condition. */
|
||||||
|
static void sigKillChildHandler(int sig) {
|
||||||
|
UNUSED(sig);
|
||||||
|
serverLogFromHandler(LL_WARNING, "Received SIGUSR1 in child, exiting now.");
|
||||||
|
exitFromChild(SERVER_CHILD_NOERROR_RETVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupChildSignalHandlers(void) {
|
||||||
|
struct sigaction act;
|
||||||
|
|
||||||
|
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
|
||||||
|
* Otherwise, sa_handler is used. */
|
||||||
|
sigemptyset(&act.sa_mask);
|
||||||
|
act.sa_flags = 0;
|
||||||
|
act.sa_handler = sigKillChildHandler;
|
||||||
|
sigaction(SIGUSR1, &act, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisFork() {
|
||||||
|
int childpid;
|
||||||
|
long long start = ustime();
|
||||||
|
if ((childpid = fork()) == 0) {
|
||||||
|
/* Child */
|
||||||
|
closeListeningSockets(0);
|
||||||
|
setupChildSignalHandlers();
|
||||||
|
} else {
|
||||||
|
/* Parent */
|
||||||
|
server.stat_fork_time = ustime()-start;
|
||||||
|
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||||
|
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||||
|
if (childpid == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
updateDictResizePolicy();
|
||||||
|
}
|
||||||
|
return childpid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendChildCOWInfo(int ptype, char *pname) {
|
||||||
|
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||||
|
|
||||||
|
if (private_dirty) {
|
||||||
|
serverLog(LL_NOTICE,
|
||||||
|
"%s: %zu MB of memory used by copy-on-write",
|
||||||
|
pname, private_dirty/(1024*1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
server.child_info_data.cow_size = private_dirty;
|
||||||
|
sendChildInfo(ptype);
|
||||||
|
}
|
||||||
|
|
||||||
void memtest(size_t megabytes, int passes);
|
void memtest(size_t megabytes, int passes);
|
||||||
|
|
||||||
/* Returns 1 if there is --sentinel among the arguments or if
|
/* Returns 1 if there is --sentinel among the arguments or if
|
||||||
@ -4599,17 +4776,19 @@ void loadDataFromDisk(void) {
|
|||||||
serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
|
serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
|
||||||
} else {
|
} else {
|
||||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||||
if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {
|
if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_NONE) == C_OK) {
|
||||||
serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
|
serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
|
||||||
(float)(ustime()-start)/1000000);
|
(float)(ustime()-start)/1000000);
|
||||||
|
|
||||||
/* Restore the replication ID / offset from the RDB file. */
|
/* Restore the replication ID / offset from the RDB file. */
|
||||||
if ((server.masterhost || (server.cluster_enabled && nodeIsSlave(server.cluster->myself)))&&
|
if ((server.masterhost ||
|
||||||
|
(server.cluster_enabled &&
|
||||||
|
nodeIsSlave(server.cluster->myself))) &&
|
||||||
rsi.repl_id_is_set &&
|
rsi.repl_id_is_set &&
|
||||||
rsi.repl_offset != -1 &&
|
rsi.repl_offset != -1 &&
|
||||||
/* Note that older implementations may save a repl_stream_db
|
/* Note that older implementations may save a repl_stream_db
|
||||||
* of -1 inside the RDB file in a wrong way, see more information
|
* of -1 inside the RDB file in a wrong way, see more
|
||||||
* in function rdbPopulateSaveInfo. */
|
* information in function rdbPopulateSaveInfo. */
|
||||||
rsi.repl_stream_db != -1)
|
rsi.repl_stream_db != -1)
|
||||||
{
|
{
|
||||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||||
@ -4642,7 +4821,7 @@ void redisSetProcTitle(char *title) {
|
|||||||
setproctitle("%s %s:%d%s",
|
setproctitle("%s %s:%d%s",
|
||||||
title,
|
title,
|
||||||
server.bindaddr_count ? server.bindaddr[0] : "*",
|
server.bindaddr_count ? server.bindaddr[0] : "*",
|
||||||
server.port,
|
server.port ? server.port : server.tls_port,
|
||||||
server_mode);
|
server_mode);
|
||||||
#else
|
#else
|
||||||
UNUSED(title);
|
UNUSED(title);
|
||||||
@ -4785,14 +4964,15 @@ int main(int argc, char **argv) {
|
|||||||
srand(time(NULL)^getpid());
|
srand(time(NULL)^getpid());
|
||||||
gettimeofday(&tv,NULL);
|
gettimeofday(&tv,NULL);
|
||||||
|
|
||||||
char hashseed[16];
|
uint8_t hashseed[16];
|
||||||
getRandomHexChars(hashseed,sizeof(hashseed));
|
getRandomBytes(hashseed,sizeof(hashseed));
|
||||||
dictSetHashFunctionSeed((uint8_t*)hashseed);
|
dictSetHashFunctionSeed(hashseed);
|
||||||
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
||||||
initServerConfig();
|
initServerConfig();
|
||||||
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
|
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
|
||||||
basic networking code and client creation depends on it. */
|
basic networking code and client creation depends on it. */
|
||||||
moduleInitModulesSystem();
|
moduleInitModulesSystem();
|
||||||
|
tlsInit();
|
||||||
|
|
||||||
/* Store the executable path and arguments in a safe place in order
|
/* Store the executable path and arguments in a safe place in order
|
||||||
* to be able to restart the server later. */
|
* to be able to restart the server later. */
|
||||||
@ -4916,6 +5096,7 @@ int main(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
moduleLoadFromQueue();
|
moduleLoadFromQueue();
|
||||||
ACLLoadUsersAtStartup();
|
ACLLoadUsersAtStartup();
|
||||||
|
InitServerLast();
|
||||||
loadDataFromDisk();
|
loadDataFromDisk();
|
||||||
if (server.cluster_enabled) {
|
if (server.cluster_enabled) {
|
||||||
if (verifyClusterConfigWithData() == C_ERR) {
|
if (verifyClusterConfigWithData() == C_ERR) {
|
||||||
@ -4925,11 +5106,12 @@ int main(int argc, char **argv) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (server.ipfd_count > 0)
|
if (server.ipfd_count > 0 || server.tlsfd_count > 0)
|
||||||
serverLog(LL_NOTICE,"Ready to accept connections");
|
serverLog(LL_NOTICE,"Ready to accept connections");
|
||||||
if (server.sofd > 0)
|
if (server.sofd > 0)
|
||||||
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
||||||
} else {
|
} else {
|
||||||
|
InitServerLast();
|
||||||
sentinelIsRunning();
|
sentinelIsRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
112
src/server.h
112
src/server.h
@ -66,6 +66,10 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#include "quicklist.h" /* Lists are encoded as linked lists of
|
#include "quicklist.h" /* Lists are encoded as linked lists of
|
||||||
N-elements flat arrays */
|
N-elements flat arrays */
|
||||||
#include "rax.h" /* Radix tree */
|
#include "rax.h" /* Radix tree */
|
||||||
|
#include "connection.h" /* Connection abstraction */
|
||||||
|
|
||||||
|
#define REDISMODULE_CORE 1
|
||||||
|
#include "redismodule.h" /* Redis modules API defines. */
|
||||||
|
|
||||||
/* Following includes allow test functions to be called from Redis main() */
|
/* Following includes allow test functions to be called from Redis main() */
|
||||||
#include "zipmap.h"
|
#include "zipmap.h"
|
||||||
@ -84,6 +88,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define CONFIG_MAX_HZ 500
|
#define CONFIG_MAX_HZ 500
|
||||||
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
|
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
|
||||||
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
|
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
|
||||||
|
#define CONFIG_DEFAULT_SERVER_TLS_PORT 0 /* TCP port. */
|
||||||
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
|
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
|
||||||
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
|
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
|
||||||
#define CONFIG_DEFAULT_DBNUM 16
|
#define CONFIG_DEFAULT_DBNUM 16
|
||||||
@ -133,6 +138,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
|
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
|
||||||
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
|
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
|
||||||
#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
|
#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
|
||||||
|
#define CONFIG_DEFAULT_KEY_LOAD_DELAY 0
|
||||||
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||||
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
|
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
|
||||||
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
|
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
|
||||||
@ -179,6 +185,14 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
||||||
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
||||||
|
|
||||||
|
/* Children process will exit with this status code to signal that the
|
||||||
|
* process terminated without an error: this is useful in order to kill
|
||||||
|
* a saving child (RDB or AOF one), without triggering in the parent the
|
||||||
|
* write protection that is normally turned on on write errors.
|
||||||
|
* Usually children that are terminated with SIGUSR1 will exit with this
|
||||||
|
* special code. */
|
||||||
|
#define SERVER_CHILD_NOERROR_RETVAL 255
|
||||||
|
|
||||||
/* Instantaneous metrics tracking. */
|
/* Instantaneous metrics tracking. */
|
||||||
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
|
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
|
||||||
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
|
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
|
||||||
@ -816,9 +830,14 @@ typedef struct user {
|
|||||||
|
|
||||||
/* With multiplexing we need to take per-client state.
|
/* With multiplexing we need to take per-client state.
|
||||||
* Clients are taken in a linked list. */
|
* Clients are taken in a linked list. */
|
||||||
|
|
||||||
|
#define CLIENT_ID_AOF (UINT64_MAX) /* Reserved ID for the AOF client. If you
|
||||||
|
need more reserved IDs use UINT64_MAX-1,
|
||||||
|
-2, ... and so forth. */
|
||||||
|
|
||||||
typedef struct client {
|
typedef struct client {
|
||||||
uint64_t id; /* Client incremental unique ID. */
|
uint64_t id; /* Client incremental unique ID. */
|
||||||
int fd; /* Client socket. */
|
connection *conn;
|
||||||
int resp; /* RESP protocol version. Can be 2 or 3. */
|
int resp; /* RESP protocol version. Can be 2 or 3. */
|
||||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||||
robj *name; /* As set by CLIENT SETNAME. */
|
robj *name; /* As set by CLIENT SETNAME. */
|
||||||
@ -1026,6 +1045,22 @@ struct malloc_stats {
|
|||||||
size_t allocator_resident;
|
size_t allocator_resident;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*-----------------------------------------------------------------------------
|
||||||
|
* TLS Context Configuration
|
||||||
|
*----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
typedef struct redisTLSContextConfig {
|
||||||
|
char *cert_file;
|
||||||
|
char *key_file;
|
||||||
|
char *dh_params_file;
|
||||||
|
char *ca_cert_file;
|
||||||
|
char *ca_cert_dir;
|
||||||
|
char *protocols;
|
||||||
|
char *ciphers;
|
||||||
|
char *ciphersuites;
|
||||||
|
int prefer_server_ciphers;
|
||||||
|
} redisTLSContextConfig;
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
/*-----------------------------------------------------------------------------
|
||||||
* Global server state
|
* Global server state
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
@ -1041,6 +1076,7 @@ struct clusterState;
|
|||||||
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
|
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
|
||||||
#define CHILD_INFO_TYPE_RDB 0
|
#define CHILD_INFO_TYPE_RDB 0
|
||||||
#define CHILD_INFO_TYPE_AOF 1
|
#define CHILD_INFO_TYPE_AOF 1
|
||||||
|
#define CHILD_INFO_TYPE_MODULE 3
|
||||||
|
|
||||||
struct redisServer {
|
struct redisServer {
|
||||||
/* General */
|
/* General */
|
||||||
@ -1076,8 +1112,10 @@ struct redisServer {
|
|||||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||||
client blocked on a module command needs
|
client blocked on a module command needs
|
||||||
to be processed. */
|
to be processed. */
|
||||||
|
pid_t module_child_pid; /* PID of module child */
|
||||||
/* Networking */
|
/* Networking */
|
||||||
int port; /* TCP listening port */
|
int port; /* TCP listening port */
|
||||||
|
int tls_port; /* TLS listening port */
|
||||||
int tcp_backlog; /* TCP listen() backlog */
|
int tcp_backlog; /* TCP listen() backlog */
|
||||||
char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */
|
char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */
|
||||||
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
|
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
|
||||||
@ -1085,6 +1123,8 @@ struct redisServer {
|
|||||||
mode_t unixsocketperm; /* UNIX socket permission */
|
mode_t unixsocketperm; /* UNIX socket permission */
|
||||||
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
|
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
|
||||||
int ipfd_count; /* Used slots in ipfd[] */
|
int ipfd_count; /* Used slots in ipfd[] */
|
||||||
|
int tlsfd[CONFIG_BINDADDR_MAX]; /* TLS socket file descriptors */
|
||||||
|
int tlsfd_count; /* Used slots in tlsfd[] */
|
||||||
int sofd; /* Unix socket file descriptor */
|
int sofd; /* Unix socket file descriptor */
|
||||||
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
|
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
|
||||||
int cfd_count; /* Used slots in cfd[] */
|
int cfd_count; /* Used slots in cfd[] */
|
||||||
@ -1149,6 +1189,7 @@ struct redisServer {
|
|||||||
_Atomic long long stat_net_output_bytes; /* Bytes written to network. */
|
_Atomic long long stat_net_output_bytes; /* Bytes written to network. */
|
||||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||||
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
||||||
|
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
|
||||||
/* The following two are used to track instantaneous metrics, like
|
/* The following two are used to track instantaneous metrics, like
|
||||||
* number of operations per second, network traffic. */
|
* number of operations per second, network traffic. */
|
||||||
struct {
|
struct {
|
||||||
@ -1163,6 +1204,7 @@ struct redisServer {
|
|||||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||||
int active_defrag_enabled;
|
int active_defrag_enabled;
|
||||||
|
int jemalloc_bg_thread; /* Enable jemalloc background thread */
|
||||||
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
|
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
|
||||||
int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */
|
int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */
|
||||||
int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */
|
int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */
|
||||||
@ -1186,6 +1228,7 @@ struct redisServer {
|
|||||||
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
|
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
|
||||||
off_t aof_current_size; /* AOF current size. */
|
off_t aof_current_size; /* AOF current size. */
|
||||||
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
|
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
|
||||||
|
int aof_flush_sleep; /* Micros to sleep before flush. (used by tests) */
|
||||||
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
|
||||||
pid_t aof_child_pid; /* PID if rewriting process */
|
pid_t aof_child_pid; /* PID if rewriting process */
|
||||||
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
|
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
|
||||||
@ -1231,10 +1274,17 @@ struct redisServer {
|
|||||||
int rdb_child_type; /* Type of save by active child. */
|
int rdb_child_type; /* Type of save by active child. */
|
||||||
int lastbgsave_status; /* C_OK or C_ERR */
|
int lastbgsave_status; /* C_OK or C_ERR */
|
||||||
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
|
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
|
||||||
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
|
int rdb_pipe_write; /* RDB pipes used to transfer the rdb */
|
||||||
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
|
int rdb_pipe_read; /* data to the parent process in diskless repl. */
|
||||||
|
connection **rdb_pipe_conns; /* Connections which are currently the */
|
||||||
|
int rdb_pipe_numconns; /* target of diskless rdb fork child. */
|
||||||
|
int rdb_pipe_numconns_writing; /* Number of rdb conns with pending writes. */
|
||||||
|
char *rdb_pipe_buff; /* In diskless replication, this buffer holds data */
|
||||||
|
int rdb_pipe_bufflen; /* that was read from the the rdb pipe. */
|
||||||
int rdb_key_save_delay; /* Delay in microseconds between keys while
|
int rdb_key_save_delay; /* Delay in microseconds between keys while
|
||||||
* writing the RDB. (for testings) */
|
* writing the RDB. (for testings) */
|
||||||
|
int key_load_delay; /* Delay in microseconds between keys while
|
||||||
|
* loading aof or rdb. (for testings) */
|
||||||
/* Pipe and data structures for child -> parent info sharing. */
|
/* Pipe and data structures for child -> parent info sharing. */
|
||||||
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
|
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
|
||||||
struct {
|
struct {
|
||||||
@ -1287,7 +1337,7 @@ struct redisServer {
|
|||||||
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
|
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
|
||||||
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
|
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
|
||||||
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
|
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
|
||||||
int repl_transfer_s; /* Slave -> Master SYNC socket */
|
connection *repl_transfer_s; /* Slave -> Master SYNC connection */
|
||||||
int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */
|
int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */
|
||||||
char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
|
char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
|
||||||
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
|
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
|
||||||
@ -1378,6 +1428,7 @@ struct redisServer {
|
|||||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||||
client *lua_client; /* The "fake client" to query Redis from Lua */
|
client *lua_client; /* The "fake client" to query Redis from Lua */
|
||||||
client *lua_caller; /* The client running EVAL right now, or NULL */
|
client *lua_caller; /* The client running EVAL right now, or NULL */
|
||||||
|
char* lua_cur_script; /* SHA1 of the script currently running, or NULL */
|
||||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||||
@ -1410,6 +1461,11 @@ struct redisServer {
|
|||||||
int watchdog_period; /* Software watchdog period in ms. 0 = off */
|
int watchdog_period; /* Software watchdog period in ms. 0 = off */
|
||||||
/* System hardware info */
|
/* System hardware info */
|
||||||
size_t system_memory_size; /* Total memory in system as reported by OS */
|
size_t system_memory_size; /* Total memory in system as reported by OS */
|
||||||
|
/* TLS Configuration */
|
||||||
|
int tls_cluster;
|
||||||
|
int tls_replication;
|
||||||
|
int tls_auth_clients;
|
||||||
|
redisTLSContextConfig tls_ctx_config;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct pubsubPattern {
|
typedef struct pubsubPattern {
|
||||||
@ -1540,7 +1596,16 @@ void moduleAcquireGIL(void);
|
|||||||
void moduleReleaseGIL(void);
|
void moduleReleaseGIL(void);
|
||||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||||
void moduleCallCommandFilters(client *c);
|
void moduleCallCommandFilters(client *c);
|
||||||
|
void ModuleForkDoneHandler(int exitcode, int bysignal);
|
||||||
|
int TerminateModuleForkChild(int child_pid, int wait);
|
||||||
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||||
|
int moduleAllDatatypesHandleErrors();
|
||||||
|
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
|
||||||
|
void moduleFireServerEvent(uint64_t eid, int subid, void *data);
|
||||||
|
void processModuleLoadingProgressEvent(int is_aof);
|
||||||
|
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
|
||||||
|
void moduleUnblockClient(client *c);
|
||||||
|
int moduleClientIsBlockedOnKeys(client *c);
|
||||||
|
|
||||||
/* Utils */
|
/* Utils */
|
||||||
long long ustime(void);
|
long long ustime(void);
|
||||||
@ -1553,12 +1618,12 @@ size_t redisPopcount(void *s, long count);
|
|||||||
void redisSetProcTitle(char *title);
|
void redisSetProcTitle(char *title);
|
||||||
|
|
||||||
/* networking.c -- Networking and Client related operations */
|
/* networking.c -- Networking and Client related operations */
|
||||||
client *createClient(int fd);
|
client *createClient(connection *conn);
|
||||||
void closeTimedoutClients(void);
|
void closeTimedoutClients(void);
|
||||||
void freeClient(client *c);
|
void freeClient(client *c);
|
||||||
void freeClientAsync(client *c);
|
void freeClientAsync(client *c);
|
||||||
void resetClient(client *c);
|
void resetClient(client *c);
|
||||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
void sendReplyToClient(connection *conn);
|
||||||
void *addReplyDeferredLen(client *c);
|
void *addReplyDeferredLen(client *c);
|
||||||
void setDeferredArrayLen(client *c, void *node, long length);
|
void setDeferredArrayLen(client *c, void *node, long length);
|
||||||
void setDeferredMapLen(client *c, void *node, long length);
|
void setDeferredMapLen(client *c, void *node, long length);
|
||||||
@ -1570,8 +1635,9 @@ void processInputBufferAndReplicate(client *c);
|
|||||||
void processGopherRequest(client *c);
|
void processGopherRequest(client *c);
|
||||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||||
|
void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
void readQueryFromClient(connection *conn);
|
||||||
void addReplyNull(client *c);
|
void addReplyNull(client *c);
|
||||||
void addReplyNullArray(client *c);
|
void addReplyNullArray(client *c);
|
||||||
void addReplyBool(client *c, int b);
|
void addReplyBool(client *c, int b);
|
||||||
@ -1629,7 +1695,7 @@ int handleClientsWithPendingReadsUsingThreads(void);
|
|||||||
int stopThreadedIOIfNeeded(void);
|
int stopThreadedIOIfNeeded(void);
|
||||||
int clientHasPendingReplies(client *c);
|
int clientHasPendingReplies(client *c);
|
||||||
void unlinkClient(client *c);
|
void unlinkClient(client *c);
|
||||||
int writeToClient(int fd, client *c, int handler_installed);
|
int writeToClient(client *c, int handler_installed);
|
||||||
void linkClient(client *c);
|
void linkClient(client *c);
|
||||||
void protectClient(client *c);
|
void protectClient(client *c);
|
||||||
void unprotectClient(client *c);
|
void unprotectClient(client *c);
|
||||||
@ -1765,12 +1831,16 @@ void clearReplicationId2(void);
|
|||||||
void chopReplicationBacklog(void);
|
void chopReplicationBacklog(void);
|
||||||
void replicationCacheMasterUsingMyself(void);
|
void replicationCacheMasterUsingMyself(void);
|
||||||
void feedReplicationBacklog(void *ptr, size_t len);
|
void feedReplicationBacklog(void *ptr, size_t len);
|
||||||
|
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
|
||||||
|
void rdbPipeWriteHandlerConnRemoved(struct connection *conn);
|
||||||
|
|
||||||
/* Generic persistence functions */
|
/* Generic persistence functions */
|
||||||
void startLoadingFile(FILE* fp, char* filename);
|
void startLoadingFile(FILE* fp, char* filename, int rdbflags);
|
||||||
void startLoading(size_t size);
|
void startLoading(size_t size, int rdbflags);
|
||||||
void loadingProgress(off_t pos);
|
void loadingProgress(off_t pos);
|
||||||
void stopLoading(void);
|
void stopLoading(int success);
|
||||||
|
void startSaving(int rdbflags);
|
||||||
|
void stopSaving(int success);
|
||||||
|
|
||||||
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
|
||||||
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
|
||||||
@ -1779,7 +1849,6 @@ int writeCommandsDeniedByDiskError(void);
|
|||||||
|
|
||||||
/* RDB persistence */
|
/* RDB persistence */
|
||||||
#include "rdb.h"
|
#include "rdb.h"
|
||||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
|
|
||||||
void killRDBChild(void);
|
void killRDBChild(void);
|
||||||
|
|
||||||
/* AOF persistence */
|
/* AOF persistence */
|
||||||
@ -1795,6 +1864,7 @@ void aofRewriteBufferReset(void);
|
|||||||
unsigned long aofRewriteBufferSize(void);
|
unsigned long aofRewriteBufferSize(void);
|
||||||
ssize_t aofReadDiffFromParent(void);
|
ssize_t aofReadDiffFromParent(void);
|
||||||
void killAppendOnlyChild(void);
|
void killAppendOnlyChild(void);
|
||||||
|
void restartAOFAfterSYNC();
|
||||||
|
|
||||||
/* Child info */
|
/* Child info */
|
||||||
void openChildInfoPipe(void);
|
void openChildInfoPipe(void);
|
||||||
@ -1802,6 +1872,11 @@ void closeChildInfoPipe(void);
|
|||||||
void sendChildInfo(int process_type);
|
void sendChildInfo(int process_type);
|
||||||
void receiveChildInfo(void);
|
void receiveChildInfo(void);
|
||||||
|
|
||||||
|
/* Fork helpers */
|
||||||
|
int redisFork();
|
||||||
|
int hasActiveChildProcess();
|
||||||
|
void sendChildCOWInfo(int ptype, char *pname);
|
||||||
|
|
||||||
/* acl.c -- Authentication related prototypes. */
|
/* acl.c -- Authentication related prototypes. */
|
||||||
extern rax *Users;
|
extern rax *Users;
|
||||||
extern user *DefaultUser;
|
extern user *DefaultUser;
|
||||||
@ -1902,6 +1977,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
|
|||||||
void call(client *c, int flags);
|
void call(client *c, int flags);
|
||||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
||||||
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
||||||
|
void redisOpArrayInit(redisOpArray *oa);
|
||||||
|
void redisOpArrayFree(redisOpArray *oa);
|
||||||
void forceCommandPropagation(client *c, int flags);
|
void forceCommandPropagation(client *c, int flags);
|
||||||
void preventCommandPropagation(client *c);
|
void preventCommandPropagation(client *c);
|
||||||
void preventCommandAOF(client *c);
|
void preventCommandAOF(client *c);
|
||||||
@ -1930,6 +2007,7 @@ unsigned int LRU_CLOCK(void);
|
|||||||
const char *evictPolicyToString(void);
|
const char *evictPolicyToString(void);
|
||||||
struct redisMemOverhead *getMemoryOverheadData(void);
|
struct redisMemOverhead *getMemoryOverheadData(void);
|
||||||
void freeMemoryOverheadData(struct redisMemOverhead *mh);
|
void freeMemoryOverheadData(struct redisMemOverhead *mh);
|
||||||
|
void checkChildrenDone(void);
|
||||||
|
|
||||||
#define RESTART_SERVER_NONE 0
|
#define RESTART_SERVER_NONE 0
|
||||||
#define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */
|
#define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */
|
||||||
@ -2007,9 +2085,10 @@ robj *lookupKeyWrite(redisDb *db, robj *key);
|
|||||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
||||||
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
||||||
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||||
|
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags);
|
||||||
robj *objectCommandLookup(client *c, robj *key);
|
robj *objectCommandLookup(client *c, robj *key);
|
||||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||||
long long lru_clock);
|
long long lru_clock);
|
||||||
#define LOOKUP_NONE 0
|
#define LOOKUP_NONE 0
|
||||||
#define LOOKUP_NOTOUCH (1<<0)
|
#define LOOKUP_NOTOUCH (1<<0)
|
||||||
@ -2026,6 +2105,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
|
|||||||
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
|
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
|
||||||
long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
||||||
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
|
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
|
||||||
|
void flushAllDataAndResetRDB(int flags);
|
||||||
long long dbTotalServerKeyCount();
|
long long dbTotalServerKeyCount();
|
||||||
|
|
||||||
int selectDb(client *c, int id);
|
int selectDb(client *c, int id);
|
||||||
@ -2120,6 +2200,7 @@ void dictSdsDestructor(void *privdata, void *val);
|
|||||||
char *redisGitSHA1(void);
|
char *redisGitSHA1(void);
|
||||||
char *redisGitDirty(void);
|
char *redisGitDirty(void);
|
||||||
uint64_t redisBuildId(void);
|
uint64_t redisBuildId(void);
|
||||||
|
char *redisBuildIdString(void);
|
||||||
|
|
||||||
/* Commands prototypes */
|
/* Commands prototypes */
|
||||||
void authCommand(client *c);
|
void authCommand(client *c);
|
||||||
@ -2334,6 +2415,7 @@ void bugReportStart(void);
|
|||||||
void serverLogObjectDebugInfo(const robj *o);
|
void serverLogObjectDebugInfo(const robj *o);
|
||||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
||||||
sds genRedisInfoString(char *section);
|
sds genRedisInfoString(char *section);
|
||||||
|
sds genModulesInfoString(sds info);
|
||||||
void enableWatchdog(int period);
|
void enableWatchdog(int period);
|
||||||
void disableWatchdog(void);
|
void disableWatchdog(void);
|
||||||
void watchdogScheduleSignal(int period);
|
void watchdogScheduleSignal(int period);
|
||||||
@ -2343,6 +2425,10 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len);
|
|||||||
void xorDigest(unsigned char *digest, void *ptr, size_t len);
|
void xorDigest(unsigned char *digest, void *ptr, size_t len);
|
||||||
int populateCommandTableParseFlags(struct redisCommand *c, char *strflags);
|
int populateCommandTableParseFlags(struct redisCommand *c, char *strflags);
|
||||||
|
|
||||||
|
/* TLS stuff */
|
||||||
|
void tlsInit(void);
|
||||||
|
int tlsConfigure(redisTLSContextConfig *ctx_config);
|
||||||
|
|
||||||
#define redisDebug(fmt, ...) \
|
#define redisDebug(fmt, ...) \
|
||||||
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
|
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||||
#define redisDebugMark() \
|
#define redisDebugMark() \
|
||||||
|
@ -58,7 +58,8 @@ int siptlw(int c) {
|
|||||||
/* Test of the CPU is Little Endian and supports not aligned accesses.
|
/* Test of the CPU is Little Endian and supports not aligned accesses.
|
||||||
* Two interesting conditions to speedup the function that happen to be
|
* Two interesting conditions to speedup the function that happen to be
|
||||||
* in most of x86 servers. */
|
* in most of x86 servers. */
|
||||||
#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__)
|
#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__) \
|
||||||
|
|| defined (__aarch64__) || defined (__arm64__)
|
||||||
#define UNALIGNED_LE_CPU
|
#define UNALIGNED_LE_CPU
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ typedef struct streamNACK {
|
|||||||
|
|
||||||
/* Stream propagation informations, passed to functions in order to propagate
|
/* Stream propagation informations, passed to functions in order to propagate
|
||||||
* XCLAIM commands to AOF and slaves. */
|
* XCLAIM commands to AOF and slaves. */
|
||||||
typedef struct sreamPropInfo {
|
typedef struct streamPropInfo {
|
||||||
robj *keyname;
|
robj *keyname;
|
||||||
robj *groupname;
|
robj *groupname;
|
||||||
} streamPropInfo;
|
} streamPropInfo;
|
||||||
|
@ -242,17 +242,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
|||||||
* the current node is full. */
|
* the current node is full. */
|
||||||
if (lp != NULL) {
|
if (lp != NULL) {
|
||||||
if (server.stream_node_max_bytes &&
|
if (server.stream_node_max_bytes &&
|
||||||
lp_bytes > server.stream_node_max_bytes)
|
lp_bytes >= server.stream_node_max_bytes)
|
||||||
{
|
{
|
||||||
lp = NULL;
|
lp = NULL;
|
||||||
} else if (server.stream_node_max_entries) {
|
} else if (server.stream_node_max_entries) {
|
||||||
int64_t count = lpGetInteger(lpFirst(lp));
|
int64_t count = lpGetInteger(lpFirst(lp));
|
||||||
if (count > server.stream_node_max_entries) lp = NULL;
|
if (count >= server.stream_node_max_entries) lp = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int flags = STREAM_ITEM_FLAG_NONE;
|
int flags = STREAM_ITEM_FLAG_NONE;
|
||||||
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) {
|
if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
|
||||||
master_id = id;
|
master_id = id;
|
||||||
streamEncodeID(rax_key,&id);
|
streamEncodeID(rax_key,&id);
|
||||||
/* Create the listpack having the master entry ID and fields. */
|
/* Create the listpack having the master entry ID and fields. */
|
||||||
|
808
src/tls.c
Normal file
808
src/tls.c
Normal file
@ -0,0 +1,808 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Redis Labs
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "server.h"
|
||||||
|
#include "connhelpers.h"
|
||||||
|
#include "adlist.h"
|
||||||
|
|
||||||
|
#ifdef USE_OPENSSL
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
|
#define REDIS_TLS_PROTO_TLSv1 (1<<0)
|
||||||
|
#define REDIS_TLS_PROTO_TLSv1_1 (1<<1)
|
||||||
|
#define REDIS_TLS_PROTO_TLSv1_2 (1<<2)
|
||||||
|
#define REDIS_TLS_PROTO_TLSv1_3 (1<<3)
|
||||||
|
|
||||||
|
/* Use safe defaults */
|
||||||
|
#ifdef TLS1_3_VERSION
|
||||||
|
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2|REDIS_TLS_PROTO_TLSv1_3)
|
||||||
|
#else
|
||||||
|
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern ConnectionType CT_Socket;
|
||||||
|
|
||||||
|
SSL_CTX *redis_tls_ctx;
|
||||||
|
|
||||||
|
static int parseProtocolsConfig(const char *str) {
|
||||||
|
int i, count = 0;
|
||||||
|
int protocols = 0;
|
||||||
|
|
||||||
|
if (!str) return REDIS_TLS_PROTO_DEFAULT;
|
||||||
|
sds *tokens = sdssplitlen(str, strlen(str), " ", 1, &count);
|
||||||
|
|
||||||
|
if (!tokens) {
|
||||||
|
serverLog(LL_WARNING, "Invalid tls-protocols configuration string");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
if (!strcasecmp(tokens[i], "tlsv1")) protocols |= REDIS_TLS_PROTO_TLSv1;
|
||||||
|
else if (!strcasecmp(tokens[i], "tlsv1.1")) protocols |= REDIS_TLS_PROTO_TLSv1_1;
|
||||||
|
else if (!strcasecmp(tokens[i], "tlsv1.2")) protocols |= REDIS_TLS_PROTO_TLSv1_2;
|
||||||
|
else if (!strcasecmp(tokens[i], "tlsv1.3")) {
|
||||||
|
#ifdef TLS1_3_VERSION
|
||||||
|
protocols |= REDIS_TLS_PROTO_TLSv1_3;
|
||||||
|
#else
|
||||||
|
serverLog(LL_WARNING, "TLSv1.3 is specified in tls-protocols but not supported by OpenSSL.");
|
||||||
|
protocols = -1;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
serverLog(LL_WARNING, "Invalid tls-protocols specified. "
|
||||||
|
"Use a combination of 'TLSv1', 'TLSv1.1', 'TLSv1.2' and 'TLSv1.3'.");
|
||||||
|
protocols = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sdsfreesplitres(tokens, count);
|
||||||
|
|
||||||
|
return protocols;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* list of connections with pending data already read from the socket, but not
|
||||||
|
* served to the reader yet. */
|
||||||
|
static list *pending_list = NULL;
|
||||||
|
|
||||||
|
void tlsInit(void) {
|
||||||
|
ERR_load_crypto_strings();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
SSL_library_init();
|
||||||
|
|
||||||
|
if (!RAND_poll()) {
|
||||||
|
serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_list = listCreate();
|
||||||
|
|
||||||
|
/* Server configuration */
|
||||||
|
server.tls_auth_clients = 1; /* Secure by default */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Attempt to configure/reconfigure TLS. This operation is atomic and will
|
||||||
|
* leave the SSL_CTX unchanged if fails.
|
||||||
|
*/
|
||||||
|
int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||||
|
char errbuf[256];
|
||||||
|
SSL_CTX *ctx = NULL;
|
||||||
|
|
||||||
|
if (!ctx_config->cert_file) {
|
||||||
|
serverLog(LL_WARNING, "No tls-cert-file configured!");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx_config->key_file) {
|
||||||
|
serverLog(LL_WARNING, "No tls-key-file configured!");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) {
|
||||||
|
serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be configured!");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = SSL_CTX_new(SSLv23_method());
|
||||||
|
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
|
||||||
|
|
||||||
|
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int protocols = parseProtocolsConfig(ctx_config->protocols);
|
||||||
|
if (protocols == -1) goto error;
|
||||||
|
|
||||||
|
if (!(protocols & REDIS_TLS_PROTO_TLSv1))
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
|
||||||
|
if (!(protocols & REDIS_TLS_PROTO_TLSv1_1))
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
|
||||||
|
#ifdef SSL_OP_NO_TLSv1_2
|
||||||
|
if (!(protocols & REDIS_TLS_PROTO_TLSv1_2))
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
|
||||||
|
#endif
|
||||||
|
#ifdef SSL_OP_NO_TLSv1_3
|
||||||
|
if (!(protocols & REDIS_TLS_PROTO_TLSv1_3))
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SSL_OP_NO_COMPRESSION
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
|
||||||
|
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ctx_config->prefer_server_ciphers)
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
|
||||||
|
|
||||||
|
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
|
||||||
|
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
|
||||||
|
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||||
|
|
||||||
|
if (SSL_CTX_use_certificate_file(ctx, ctx_config->cert_file, SSL_FILETYPE_PEM) <= 0) {
|
||||||
|
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||||
|
serverLog(LL_WARNING, "Failed to load certificate: %s: %s", ctx_config->cert_file, errbuf);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(ctx, ctx_config->key_file, SSL_FILETYPE_PEM) <= 0) {
|
||||||
|
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||||
|
serverLog(LL_WARNING, "Failed to load private key: %s: %s", ctx_config->key_file, errbuf);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) {
|
||||||
|
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||||
|
serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx_config->dh_params_file) {
|
||||||
|
FILE *dhfile = fopen(ctx_config->dh_params_file, "r");
|
||||||
|
DH *dh = NULL;
|
||||||
|
if (!dhfile) {
|
||||||
|
serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
|
||||||
|
fclose(dhfile);
|
||||||
|
if (!dh) {
|
||||||
|
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_set_tmp_dh(ctx, dh) <= 0) {
|
||||||
|
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||||
|
serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf);
|
||||||
|
DH_free(dh);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
DH_free(dh);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx_config->ciphers && !SSL_CTX_set_cipher_list(ctx, ctx_config->ciphers)) {
|
||||||
|
serverLog(LL_WARNING, "Failed to configure ciphers: %s", ctx_config->ciphers);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef TLS1_3_VERSION
|
||||||
|
if (ctx_config->ciphersuites && !SSL_CTX_set_ciphersuites(ctx, ctx_config->ciphersuites)) {
|
||||||
|
serverLog(LL_WARNING, "Failed to configure ciphersuites: %s", ctx_config->ciphersuites);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SSL_CTX_free(redis_tls_ctx);
|
||||||
|
redis_tls_ctx = ctx;
|
||||||
|
|
||||||
|
return C_OK;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (ctx) SSL_CTX_free(ctx);
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef TLS_DEBUGGING
|
||||||
|
#define TLSCONN_DEBUG(fmt, ...) \
|
||||||
|
serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define TLSCONN_DEBUG(fmt, ...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ConnectionType CT_TLS;
|
||||||
|
|
||||||
|
/* Normal socket connections have a simple events/handler correlation.
|
||||||
|
*
|
||||||
|
* With TLS connections we need to handle cases where during a logical read
|
||||||
|
* or write operation, the SSL library asks to block for the opposite
|
||||||
|
* socket operation.
|
||||||
|
*
|
||||||
|
* When this happens, we need to do two things:
|
||||||
|
* 1. Make sure we register for the even.
|
||||||
|
* 2. Make sure we know which handler needs to execute when the
|
||||||
|
* event fires. That is, if we notify the caller of a write operation
|
||||||
|
* that it blocks, and SSL asks for a read, we need to trigger the
|
||||||
|
* write handler again on the next read event.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WANT_READ = 1,
|
||||||
|
WANT_WRITE
|
||||||
|
} WantIOType;
|
||||||
|
|
||||||
|
#define TLS_CONN_FLAG_READ_WANT_WRITE (1<<0)
|
||||||
|
#define TLS_CONN_FLAG_WRITE_WANT_READ (1<<1)
|
||||||
|
#define TLS_CONN_FLAG_FD_SET (1<<2)
|
||||||
|
|
||||||
|
typedef struct tls_connection {
|
||||||
|
connection c;
|
||||||
|
int flags;
|
||||||
|
SSL *ssl;
|
||||||
|
char *ssl_error;
|
||||||
|
listNode *pending_list_node;
|
||||||
|
} tls_connection;
|
||||||
|
|
||||||
|
connection *connCreateTLS(void) {
|
||||||
|
tls_connection *conn = zcalloc(sizeof(tls_connection));
|
||||||
|
conn->c.type = &CT_TLS;
|
||||||
|
conn->c.fd = -1;
|
||||||
|
conn->ssl = SSL_new(redis_tls_ctx);
|
||||||
|
return (connection *) conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection *connCreateAcceptedTLS(int fd, int require_auth) {
|
||||||
|
tls_connection *conn = (tls_connection *) connCreateTLS();
|
||||||
|
conn->c.fd = fd;
|
||||||
|
conn->c.state = CONN_STATE_ACCEPTING;
|
||||||
|
|
||||||
|
if (!require_auth) {
|
||||||
|
/* We still verify certificates if provided, but don't require them.
|
||||||
|
*/
|
||||||
|
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_set_fd(conn->ssl, conn->c.fd);
|
||||||
|
SSL_set_accept_state(conn->ssl);
|
||||||
|
|
||||||
|
return (connection *) conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask);
|
||||||
|
|
||||||
|
/* Process the return code received from OpenSSL>
|
||||||
|
* Update the want parameter with expected I/O.
|
||||||
|
* Update the connection's error state if a real error has occured.
|
||||||
|
* Returns an SSL error code, or 0 if no further handling is required.
|
||||||
|
*/
|
||||||
|
static int handleSSLReturnCode(tls_connection *conn, int ret_value, WantIOType *want) {
|
||||||
|
if (ret_value <= 0) {
|
||||||
|
int ssl_err = SSL_get_error(conn->ssl, ret_value);
|
||||||
|
switch (ssl_err) {
|
||||||
|
case SSL_ERROR_WANT_WRITE:
|
||||||
|
*want = WANT_WRITE;
|
||||||
|
return 0;
|
||||||
|
case SSL_ERROR_WANT_READ:
|
||||||
|
*want = WANT_READ;
|
||||||
|
return 0;
|
||||||
|
case SSL_ERROR_SYSCALL:
|
||||||
|
conn->c.last_errno = errno;
|
||||||
|
if (conn->ssl_error) zfree(conn->ssl_error);
|
||||||
|
conn->ssl_error = errno ? zstrdup(strerror(errno)) : NULL;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Error! */
|
||||||
|
conn->c.last_errno = 0;
|
||||||
|
if (conn->ssl_error) zfree(conn->ssl_error);
|
||||||
|
conn->ssl_error = zmalloc(512);
|
||||||
|
ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssl_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerSSLEvent(tls_connection *conn, WantIOType want) {
|
||||||
|
int mask = aeGetFileEvents(server.el, conn->c.fd);
|
||||||
|
|
||||||
|
switch (want) {
|
||||||
|
case WANT_READ:
|
||||||
|
if (mask & AE_WRITABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
|
||||||
|
if (!(mask & AE_READABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE,
|
||||||
|
tlsEventHandler, conn);
|
||||||
|
break;
|
||||||
|
case WANT_WRITE:
|
||||||
|
if (mask & AE_READABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
|
||||||
|
if (!(mask & AE_WRITABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE,
|
||||||
|
tlsEventHandler, conn);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
serverAssert(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateSSLEvent(tls_connection *conn) {
|
||||||
|
int mask = aeGetFileEvents(server.el, conn->c.fd);
|
||||||
|
int need_read = conn->c.read_handler || (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ);
|
||||||
|
int need_write = conn->c.write_handler || (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE);
|
||||||
|
|
||||||
|
if (need_read && !(mask & AE_READABLE))
|
||||||
|
aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
|
||||||
|
if (!need_read && (mask & AE_READABLE))
|
||||||
|
aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
|
||||||
|
|
||||||
|
if (need_write && !(mask & AE_WRITABLE))
|
||||||
|
aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
|
||||||
|
if (!need_write && (mask & AE_WRITABLE))
|
||||||
|
aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tlsHandleEvent(tls_connection *conn, int mask) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d",
|
||||||
|
fd, conn->c.state, mask, conn->c.read_handler != NULL, conn->c.write_handler != NULL,
|
||||||
|
conn->flags);
|
||||||
|
|
||||||
|
ERR_clear_error();
|
||||||
|
|
||||||
|
switch (conn->c.state) {
|
||||||
|
case CONN_STATE_CONNECTING:
|
||||||
|
if (connGetSocketError((connection *) conn)) {
|
||||||
|
conn->c.last_errno = errno;
|
||||||
|
conn->c.state = CONN_STATE_ERROR;
|
||||||
|
} else {
|
||||||
|
if (!(conn->flags & TLS_CONN_FLAG_FD_SET)) {
|
||||||
|
SSL_set_fd(conn->ssl, conn->c.fd);
|
||||||
|
conn->flags |= TLS_CONN_FLAG_FD_SET;
|
||||||
|
}
|
||||||
|
ret = SSL_connect(conn->ssl);
|
||||||
|
if (ret <= 0) {
|
||||||
|
WantIOType want = 0;
|
||||||
|
if (!handleSSLReturnCode(conn, ret, &want)) {
|
||||||
|
registerSSLEvent(conn, want);
|
||||||
|
|
||||||
|
/* Avoid hitting UpdateSSLEvent, which knows nothing
|
||||||
|
* of what SSL_connect() wants and instead looks at our
|
||||||
|
* R/W handlers.
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If not handled, it's an error */
|
||||||
|
conn->c.state = CONN_STATE_ERROR;
|
||||||
|
} else {
|
||||||
|
conn->c.state = CONN_STATE_CONNECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
|
||||||
|
conn->c.conn_handler = NULL;
|
||||||
|
break;
|
||||||
|
case CONN_STATE_ACCEPTING:
|
||||||
|
ret = SSL_accept(conn->ssl);
|
||||||
|
if (ret <= 0) {
|
||||||
|
WantIOType want = 0;
|
||||||
|
if (!handleSSLReturnCode(conn, ret, &want)) {
|
||||||
|
/* Avoid hitting UpdateSSLEvent, which knows nothing
|
||||||
|
* of what SSL_connect() wants and instead looks at our
|
||||||
|
* R/W handlers.
|
||||||
|
*/
|
||||||
|
registerSSLEvent(conn, want);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If not handled, it's an error */
|
||||||
|
conn->c.state = CONN_STATE_ERROR;
|
||||||
|
} else {
|
||||||
|
conn->c.state = CONN_STATE_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
|
||||||
|
conn->c.conn_handler = NULL;
|
||||||
|
break;
|
||||||
|
case CONN_STATE_CONNECTED:
|
||||||
|
{
|
||||||
|
int call_read = ((mask & AE_READABLE) && conn->c.read_handler) ||
|
||||||
|
((mask & AE_WRITABLE) && (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE));
|
||||||
|
int call_write = ((mask & AE_WRITABLE) && conn->c.write_handler) ||
|
||||||
|
((mask & AE_READABLE) && (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ));
|
||||||
|
|
||||||
|
/* Normally we execute the readable event first, and the writable
|
||||||
|
* event laster. This is useful as sometimes we may be able
|
||||||
|
* to serve the reply of a query immediately after processing the
|
||||||
|
* query.
|
||||||
|
*
|
||||||
|
* However if WRITE_BARRIER is set in the mask, our application is
|
||||||
|
* asking us to do the reverse: never fire the writable event
|
||||||
|
* after the readable. In such a case, we invert the calls.
|
||||||
|
* This is useful when, for instance, we want to do things
|
||||||
|
* in the beforeSleep() hook, like fsynching a file to disk,
|
||||||
|
* before replying to a client. */
|
||||||
|
int invert = conn->c.flags & CONN_FLAG_WRITE_BARRIER;
|
||||||
|
|
||||||
|
if (!invert && call_read) {
|
||||||
|
conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
|
||||||
|
if (!callHandler((connection *) conn, conn->c.read_handler)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fire the writable event. */
|
||||||
|
if (call_write) {
|
||||||
|
conn->flags &= ~TLS_CONN_FLAG_WRITE_WANT_READ;
|
||||||
|
if (!callHandler((connection *) conn, conn->c.write_handler)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we have to invert the call, fire the readable event now
|
||||||
|
* after the writable one. */
|
||||||
|
if (invert && call_read) {
|
||||||
|
conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
|
||||||
|
if (!callHandler((connection *) conn, conn->c.read_handler)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If SSL has pending that, already read from the socket, we're at
|
||||||
|
* risk of not calling the read handler again, make sure to add it
|
||||||
|
* to a list of pending connection that should be handled anyway. */
|
||||||
|
if ((mask & AE_READABLE)) {
|
||||||
|
if (SSL_pending(conn->ssl) > 0) {
|
||||||
|
if (!conn->pending_list_node) {
|
||||||
|
listAddNodeTail(pending_list, conn);
|
||||||
|
conn->pending_list_node = listLast(pending_list);
|
||||||
|
}
|
||||||
|
} else if (conn->pending_list_node) {
|
||||||
|
listDelNode(pending_list, conn->pending_list_node);
|
||||||
|
conn->pending_list_node = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSSLEvent(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) {
|
||||||
|
UNUSED(el);
|
||||||
|
UNUSED(fd);
|
||||||
|
tls_connection *conn = clientData;
|
||||||
|
tlsHandleEvent(conn, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void connTLSClose(connection *conn_) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
|
||||||
|
if (conn->ssl) {
|
||||||
|
SSL_free(conn->ssl);
|
||||||
|
conn->ssl = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->ssl_error) {
|
||||||
|
zfree(conn->ssl_error);
|
||||||
|
conn->ssl_error = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn->pending_list_node) {
|
||||||
|
listDelNode(pending_list, conn->pending_list_node);
|
||||||
|
conn->pending_list_node = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CT_Socket.close(conn_);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connTLSAccept(connection *_conn, ConnectionCallbackFunc accept_handler) {
|
||||||
|
tls_connection *conn = (tls_connection *) _conn;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (conn->c.state != CONN_STATE_ACCEPTING) return C_ERR;
|
||||||
|
ERR_clear_error();
|
||||||
|
|
||||||
|
/* Try to accept */
|
||||||
|
conn->c.conn_handler = accept_handler;
|
||||||
|
ret = SSL_accept(conn->ssl);
|
||||||
|
|
||||||
|
if (ret <= 0) {
|
||||||
|
WantIOType want = 0;
|
||||||
|
if (!handleSSLReturnCode(conn, ret, &want)) {
|
||||||
|
registerSSLEvent(conn, want); /* We'll fire back */
|
||||||
|
return C_OK;
|
||||||
|
} else {
|
||||||
|
conn->c.state = CONN_STATE_ERROR;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->c.state = CONN_STATE_CONNECTED;
|
||||||
|
if (!callHandler((connection *) conn, conn->c.conn_handler)) return C_OK;
|
||||||
|
conn->c.conn_handler = NULL;
|
||||||
|
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connTLSConnect(connection *conn_, const char *addr, int port, const char *src_addr, ConnectionCallbackFunc connect_handler) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
|
||||||
|
if (conn->c.state != CONN_STATE_NONE) return C_ERR;
|
||||||
|
ERR_clear_error();
|
||||||
|
|
||||||
|
/* Initiate Socket connection first */
|
||||||
|
if (CT_Socket.connect(conn_, addr, port, src_addr, connect_handler) == C_ERR) return C_ERR;
|
||||||
|
|
||||||
|
/* Return now, once the socket is connected we'll initiate
|
||||||
|
* TLS connection from the event handler.
|
||||||
|
*/
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
int ret, ssl_err;
|
||||||
|
|
||||||
|
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
|
||||||
|
ERR_clear_error();
|
||||||
|
ret = SSL_write(conn->ssl, data, data_len);
|
||||||
|
|
||||||
|
if (ret <= 0) {
|
||||||
|
WantIOType want = 0;
|
||||||
|
if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
|
||||||
|
if (want == WANT_READ) conn->flags |= TLS_CONN_FLAG_WRITE_WANT_READ;
|
||||||
|
updateSSLEvent(conn);
|
||||||
|
errno = EAGAIN;
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
||||||
|
((ssl_err == SSL_ERROR_SYSCALL && !errno))) {
|
||||||
|
conn->c.state = CONN_STATE_CLOSED;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
conn->c.state = CONN_STATE_ERROR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connTLSRead(connection *conn_, void *buf, size_t buf_len) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
int ret;
|
||||||
|
int ssl_err;
|
||||||
|
|
||||||
|
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
|
||||||
|
ERR_clear_error();
|
||||||
|
ret = SSL_read(conn->ssl, buf, buf_len);
|
||||||
|
if (ret <= 0) {
|
||||||
|
WantIOType want = 0;
|
||||||
|
if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
|
||||||
|
if (want == WANT_WRITE) conn->flags |= TLS_CONN_FLAG_READ_WANT_WRITE;
|
||||||
|
updateSSLEvent(conn);
|
||||||
|
|
||||||
|
errno = EAGAIN;
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
|
||||||
|
((ssl_err == SSL_ERROR_SYSCALL) && !errno)) {
|
||||||
|
conn->c.state = CONN_STATE_CLOSED;
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
conn->c.state = CONN_STATE_ERROR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *connTLSGetLastError(connection *conn_) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
|
||||||
|
if (conn->ssl_error) return conn->ssl_error;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connTLSSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
|
||||||
|
conn->write_handler = func;
|
||||||
|
if (barrier)
|
||||||
|
conn->flags |= CONN_FLAG_WRITE_BARRIER;
|
||||||
|
else
|
||||||
|
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
|
||||||
|
updateSSLEvent((tls_connection *) conn);
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int connTLSSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
|
||||||
|
conn->read_handler = func;
|
||||||
|
updateSSLEvent((tls_connection *) conn);
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setBlockingTimeout(tls_connection *conn, long long timeout) {
|
||||||
|
anetBlock(NULL, conn->c.fd);
|
||||||
|
anetSendTimeout(NULL, conn->c.fd, timeout);
|
||||||
|
anetRecvTimeout(NULL, conn->c.fd, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unsetBlockingTimeout(tls_connection *conn) {
|
||||||
|
anetNonBlock(NULL, conn->c.fd);
|
||||||
|
anetSendTimeout(NULL, conn->c.fd, 0);
|
||||||
|
anetRecvTimeout(NULL, conn->c.fd, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connTLSBlockingConnect(connection *conn_, const char *addr, int port, long long timeout) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (conn->c.state != CONN_STATE_NONE) return C_ERR;
|
||||||
|
|
||||||
|
/* Initiate socket blocking connect first */
|
||||||
|
if (CT_Socket.blocking_connect(conn_, addr, port, timeout) == C_ERR) return C_ERR;
|
||||||
|
|
||||||
|
/* Initiate TLS connection now. We set up a send/recv timeout on the socket,
|
||||||
|
* which means the specified timeout will not be enforced accurately. */
|
||||||
|
SSL_set_fd(conn->ssl, conn->c.fd);
|
||||||
|
setBlockingTimeout(conn, timeout);
|
||||||
|
|
||||||
|
if ((ret = SSL_connect(conn->ssl)) <= 0) {
|
||||||
|
conn->c.state = CONN_STATE_ERROR;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
unsetBlockingTimeout(conn);
|
||||||
|
|
||||||
|
conn->c.state = CONN_STATE_CONNECTED;
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t connTLSSyncWrite(connection *conn_, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
|
||||||
|
setBlockingTimeout(conn, timeout);
|
||||||
|
SSL_clear_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||||
|
int ret = SSL_write(conn->ssl, ptr, size);
|
||||||
|
SSL_set_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||||
|
unsetBlockingTimeout(conn);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t connTLSSyncRead(connection *conn_, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
|
||||||
|
setBlockingTimeout(conn, timeout);
|
||||||
|
int ret = SSL_read(conn->ssl, ptr, size);
|
||||||
|
unsetBlockingTimeout(conn);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t connTLSSyncReadLine(connection *conn_, char *ptr, ssize_t size, long long timeout) {
|
||||||
|
tls_connection *conn = (tls_connection *) conn_;
|
||||||
|
ssize_t nread = 0;
|
||||||
|
|
||||||
|
setBlockingTimeout(conn, timeout);
|
||||||
|
|
||||||
|
size--;
|
||||||
|
while(size) {
|
||||||
|
char c;
|
||||||
|
|
||||||
|
if (SSL_read(conn->ssl,&c,1) <= 0) {
|
||||||
|
nread = -1;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
if (c == '\n') {
|
||||||
|
*ptr = '\0';
|
||||||
|
if (nread && *(ptr-1) == '\r') *(ptr-1) = '\0';
|
||||||
|
goto exit;
|
||||||
|
} else {
|
||||||
|
*ptr++ = c;
|
||||||
|
*ptr = '\0';
|
||||||
|
nread++;
|
||||||
|
}
|
||||||
|
size--;
|
||||||
|
}
|
||||||
|
exit:
|
||||||
|
unsetBlockingTimeout(conn);
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionType CT_TLS = {
|
||||||
|
.ae_handler = tlsEventHandler,
|
||||||
|
.accept = connTLSAccept,
|
||||||
|
.connect = connTLSConnect,
|
||||||
|
.blocking_connect = connTLSBlockingConnect,
|
||||||
|
.read = connTLSRead,
|
||||||
|
.write = connTLSWrite,
|
||||||
|
.close = connTLSClose,
|
||||||
|
.set_write_handler = connTLSSetWriteHandler,
|
||||||
|
.set_read_handler = connTLSSetReadHandler,
|
||||||
|
.get_last_error = connTLSGetLastError,
|
||||||
|
.sync_write = connTLSSyncWrite,
|
||||||
|
.sync_read = connTLSSyncRead,
|
||||||
|
.sync_readline = connTLSSyncReadLine,
|
||||||
|
};
|
||||||
|
|
||||||
|
int tlsHasPendingData() {
|
||||||
|
if (!pending_list)
|
||||||
|
return 0;
|
||||||
|
return listLength(pending_list) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tlsProcessPendingData() {
|
||||||
|
listIter li;
|
||||||
|
listNode *ln;
|
||||||
|
|
||||||
|
listRewind(pending_list,&li);
|
||||||
|
while((ln = listNext(&li))) {
|
||||||
|
tls_connection *conn = listNodeValue(ln);
|
||||||
|
tlsHandleEvent(conn, AE_READABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* USE_OPENSSL */
|
||||||
|
|
||||||
|
void tlsInit(void) {
|
||||||
|
}
|
||||||
|
|
||||||
|
int tlsConfigure(redisTLSContextConfig *ctx_config) {
|
||||||
|
UNUSED(ctx_config);
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection *connCreateTLS(void) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection *connCreateAcceptedTLS(int fd, int require_auth) {
|
||||||
|
UNUSED(fd);
|
||||||
|
UNUSED(require_auth);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tlsHasPendingData() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tlsProcessPendingData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -326,6 +326,7 @@ size_t zmalloc_get_rss(void) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_JEMALLOC)
|
#if defined(USE_JEMALLOC)
|
||||||
|
|
||||||
int zmalloc_get_allocator_info(size_t *allocated,
|
int zmalloc_get_allocator_info(size_t *allocated,
|
||||||
size_t *active,
|
size_t *active,
|
||||||
size_t *resident) {
|
size_t *resident) {
|
||||||
@ -347,13 +348,44 @@ int zmalloc_get_allocator_info(size_t *allocated,
|
|||||||
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
|
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_jemalloc_bg_thread(int enable) {
|
||||||
|
/* let jemalloc do purging asynchronously, required when there's no traffic
|
||||||
|
* after flushdb */
|
||||||
|
char val = !!enable;
|
||||||
|
je_mallctl("background_thread", NULL, 0, &val, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int jemalloc_purge() {
|
||||||
|
/* return all unused (reserved) pages to the OS */
|
||||||
|
char tmp[32];
|
||||||
|
unsigned narenas = 0;
|
||||||
|
size_t sz = sizeof(unsigned);
|
||||||
|
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
|
||||||
|
sprintf(tmp, "arena.%d.purge", narenas);
|
||||||
|
if (!je_mallctl(tmp, NULL, 0, NULL, 0))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
int zmalloc_get_allocator_info(size_t *allocated,
|
int zmalloc_get_allocator_info(size_t *allocated,
|
||||||
size_t *active,
|
size_t *active,
|
||||||
size_t *resident) {
|
size_t *resident) {
|
||||||
*allocated = *resident = *active = 0;
|
*allocated = *resident = *active = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_jemalloc_bg_thread(int enable) {
|
||||||
|
((void)(enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
int jemalloc_purge() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Get the sum of the specified field (converted form kb to bytes) in
|
/* Get the sum of the specified field (converted form kb to bytes) in
|
||||||
|
@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void);
|
|||||||
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
|
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
|
||||||
size_t zmalloc_get_rss(void);
|
size_t zmalloc_get_rss(void);
|
||||||
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
|
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
|
||||||
|
void set_jemalloc_bg_thread(int enable);
|
||||||
|
int jemalloc_purge();
|
||||||
size_t zmalloc_get_private_dirty(long pid);
|
size_t zmalloc_get_private_dirty(long pid);
|
||||||
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
|
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
|
||||||
size_t zmalloc_get_memory_size(void);
|
size_t zmalloc_get_memory_size(void);
|
||||||
|
@ -8,6 +8,7 @@ source ../instances.tcl
|
|||||||
source ../../support/cluster.tcl ; # Redis Cluster client.
|
source ../../support/cluster.tcl ; # Redis Cluster client.
|
||||||
|
|
||||||
set ::instances_count 20 ; # How many instances we use at max.
|
set ::instances_count 20 ; # How many instances we use at max.
|
||||||
|
set ::tlsdir "../../tls"
|
||||||
|
|
||||||
proc main {} {
|
proc main {} {
|
||||||
parse_options
|
parse_options
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# are preseved across iterations.
|
# are preseved across iterations.
|
||||||
|
|
||||||
source "../tests/includes/init-tests.tcl"
|
source "../tests/includes/init-tests.tcl"
|
||||||
|
source "../../../tests/support/cli.tcl"
|
||||||
|
|
||||||
test "Create a 5 nodes cluster" {
|
test "Create a 5 nodes cluster" {
|
||||||
create_cluster 5 5
|
create_cluster 5 5
|
||||||
@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
|
|||||||
--cluster-to $target \
|
--cluster-to $target \
|
||||||
--cluster-slots 100 \
|
--cluster-slots 100 \
|
||||||
--cluster-yes \
|
--cluster-yes \
|
||||||
|
{*}[rediscli_tls_config "../../../tests"] \
|
||||||
| [info nameofexecutable] \
|
| [info nameofexecutable] \
|
||||||
../tests/helpers/onlydots.tcl \
|
../tests/helpers/onlydots.tcl \
|
||||||
&] 0]
|
&] 0]
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
# other masters have slaves.
|
# other masters have slaves.
|
||||||
|
|
||||||
source "../tests/includes/init-tests.tcl"
|
source "../tests/includes/init-tests.tcl"
|
||||||
|
source "../../../tests/support/cli.tcl"
|
||||||
|
|
||||||
# Create a cluster with 5 master and 15 slaves, to make sure there are no
|
# Create a cluster with 5 master and 15 slaves, to make sure there are no
|
||||||
# empty masters and make rebalancing simpler to handle during the test.
|
# empty masters and make rebalancing simpler to handle during the test.
|
||||||
@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" {
|
|||||||
set output [exec \
|
set output [exec \
|
||||||
../../../src/redis-cli --cluster rebalance \
|
../../../src/redis-cli --cluster rebalance \
|
||||||
127.0.0.1:[get_instance_attrib redis 0 port] \
|
127.0.0.1:[get_instance_attrib redis 0 port] \
|
||||||
|
{*}[rediscli_tls_config "../../../tests"] \
|
||||||
--cluster-weight ${master0_id}=0 >@ stdout ]
|
--cluster-weight ${master0_id}=0 >@ stdout ]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "Master #0 should lose its replicas" {
|
test "Master #0 should lose its replicas" {
|
||||||
@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
|
|||||||
set output [exec \
|
set output [exec \
|
||||||
../../../src/redis-cli --cluster rebalance \
|
../../../src/redis-cli --cluster rebalance \
|
||||||
127.0.0.1:[get_instance_attrib redis 0 port] \
|
127.0.0.1:[get_instance_attrib redis 0 port] \
|
||||||
|
{*}[rediscli_tls_config "../../../tests"] \
|
||||||
--cluster-weight ${master0_id}=.01 \
|
--cluster-weight ${master0_id}=.01 \
|
||||||
--cluster-use-empty-masters >@ stdout]
|
--cluster-use-empty-masters >@ stdout]
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
source tests/support/redis.tcl
|
source tests/support/redis.tcl
|
||||||
source tests/support/util.tcl
|
source tests/support/util.tcl
|
||||||
|
|
||||||
|
set ::tlsdir "tests/tls"
|
||||||
|
|
||||||
# This function sometimes writes sometimes blocking-reads from lists/sorted
|
# This function sometimes writes sometimes blocking-reads from lists/sorted
|
||||||
# sets. There are multiple processes like this executing at the same time
|
# sets. There are multiple processes like this executing at the same time
|
||||||
# so that we have some chance to trap some corner condition if there is
|
# so that we have some chance to trap some corner condition if there is
|
||||||
@ -8,8 +10,8 @@ source tests/support/util.tcl
|
|||||||
# space to just a few elements, and balance the operations so that it is
|
# space to just a few elements, and balance the operations so that it is
|
||||||
# unlikely that lists and zsets just get more data without ever causing
|
# unlikely that lists and zsets just get more data without ever causing
|
||||||
# blocking.
|
# blocking.
|
||||||
proc bg_block_op {host port db ops} {
|
proc bg_block_op {host port db ops tls} {
|
||||||
set r [redis $host $port]
|
set r [redis $host $port 0 $tls]
|
||||||
$r select $db
|
$r select $db
|
||||||
|
|
||||||
for {set j 0} {$j < $ops} {incr j} {
|
for {set j 0} {$j < $ops} {incr j} {
|
||||||
@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
|
bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
source tests/support/redis.tcl
|
source tests/support/redis.tcl
|
||||||
source tests/support/util.tcl
|
source tests/support/util.tcl
|
||||||
|
|
||||||
proc bg_complex_data {host port db ops} {
|
set ::tlsdir "tests/tls"
|
||||||
set r [redis $host $port]
|
|
||||||
|
proc bg_complex_data {host port db ops tls} {
|
||||||
|
set r [redis $host $port 0 $tls]
|
||||||
$r select $db
|
$r select $db
|
||||||
createComplexDataset $r $ops
|
createComplexDataset $r $ops
|
||||||
}
|
}
|
||||||
|
|
||||||
bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
|
bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
source tests/support/redis.tcl
|
source tests/support/redis.tcl
|
||||||
|
|
||||||
proc gen_write_load {host port seconds} {
|
set ::tlsdir "tests/tls"
|
||||||
|
|
||||||
|
proc gen_write_load {host port seconds tls} {
|
||||||
set start_time [clock seconds]
|
set start_time [clock seconds]
|
||||||
set r [redis $host $port 1]
|
set r [redis $host $port 0 $tls]
|
||||||
$r select 9
|
$r select 9
|
||||||
while 1 {
|
while 1 {
|
||||||
$r set [expr rand()] [expr rand()]
|
$r set [expr rand()] [expr rand()]
|
||||||
@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2]
|
gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user