Compare commits
193
Commits
queue-in-multi
...
4.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05b81d2b02 | ||
|
|
c29852ffd2 | ||
|
|
b73f186aac | ||
|
|
09d93ec963 | ||
|
|
87aabb1afa | ||
|
|
44f89d1d98 | ||
|
|
0df24b6803 | ||
|
|
884ceb692e | ||
|
|
ccbdd762c5 | ||
|
|
10925e46d1 | ||
|
|
99bb1c74e9 | ||
|
|
cfdcd440d7 | ||
|
|
1cefb1c54b | ||
|
|
db791a1eee | ||
|
|
419dacfeaf | ||
|
|
5d03b831d0 | ||
|
|
ed437b82cf | ||
|
|
4ebfe2653c | ||
|
|
b6cab88c1d | ||
|
|
5c5e8a500c | ||
|
|
c63a97f8d2 | ||
|
|
eeb905713b | ||
|
|
670456a7b3 | ||
|
|
64db804415 | ||
|
|
e43c890e74 | ||
|
|
3f3dc3b85e | ||
|
|
ba7737245a | ||
|
|
bdd6de963d | ||
|
|
de391ff11c | ||
|
|
5af0fc0c8e | ||
|
|
6516958efb | ||
|
|
3669f96e11 | ||
|
|
896c4690dd | ||
|
|
deeb795acc | ||
|
|
a6615423e2 | ||
|
|
0b367871c4 | ||
|
|
2ae733d924 | ||
|
|
63e1c9f224 | ||
|
|
0231156f6a | ||
|
|
e99954e4d4 | ||
|
|
d048f9721c | ||
|
|
b5f22939c2 | ||
|
|
0b7ba621b7 | ||
|
|
1fbc90fe03 | ||
|
|
c7a6b711f3 | ||
|
|
73d358f79f | ||
|
|
c782d1894b | ||
|
|
cb548bf3c0 | ||
|
|
8cd6a2bd86 | ||
|
|
a3941aa569 | ||
|
|
6b21cebd3d | ||
|
|
54bd224f0e | ||
|
|
a864d25c6e | ||
|
|
b338f2b908 | ||
|
|
7e9c658d13 | ||
|
|
e69af32fd7 | ||
|
|
26e57f177d | ||
|
|
2acf003c05 | ||
|
|
12fd298fe7 | ||
|
|
5b1afa4a22 | ||
|
|
717b2eeab3 | ||
|
|
a839036a1d | ||
|
|
eda5ee5e91 | ||
|
|
fb8734fe9c | ||
|
|
c4b884958e | ||
|
|
fcd9a07df0 | ||
|
|
8affa3e78f | ||
|
|
31b1f3c1ae | ||
|
|
74f3a84390 | ||
|
|
5021fda2b9 | ||
|
|
166bdbda03 | ||
|
|
b506eb74ac | ||
|
|
806905627c | ||
|
|
8c4b0f411f | ||
|
|
6839c759b8 | ||
|
|
51b12ed1b5 | ||
|
|
635bbe573a | ||
|
|
94a7090705 | ||
|
|
1a7a532e96 | ||
|
|
6bc6bd4c38 | ||
|
|
a91cc5bc2d | ||
|
|
278972ceb1 | ||
|
|
2028501776 | ||
|
|
05ac217f2f | ||
|
|
8d44c52ae3 | ||
|
|
5c107c627e | ||
|
|
2299641417 | ||
|
|
d98ef35a11 | ||
|
|
e47c8e3f6a | ||
|
|
1e659a04cf | ||
|
|
39d3448703 | ||
|
|
78148d0e5a | ||
|
|
a5c1c77eb8 | ||
|
|
51901396ea | ||
|
|
f7b91b6c89 | ||
|
|
6e1489ae50 | ||
|
|
5fd841c069 | ||
|
|
185b361aa8 | ||
|
|
b740fc1ee3 | ||
|
|
56cafcceac | ||
|
|
a5b66da883 | ||
|
|
f60d6f09ce | ||
|
|
c56668c89e | ||
|
|
c4716d3345 | ||
|
|
a9fefbce2e | ||
|
|
17250409ba | ||
|
|
8aced9e9c5 | ||
|
|
3aa656abf5 | ||
|
|
42d6a6c36f | ||
|
|
e082d0569b | ||
|
|
7269d5471c | ||
|
|
1552058881 | ||
|
|
9faeed04ac | ||
|
|
b3440b3559 | ||
|
|
5a4133034e | ||
|
|
a8cbc3ec87 | ||
|
|
857e6d5641 | ||
|
|
34387ceae3 | ||
|
|
ba647598b4 | ||
|
|
2ee19d9805 | ||
|
|
eb62cfeadb | ||
|
|
620e48b1d7 | ||
|
|
980d8805da | ||
|
|
522b10e4f6 | ||
|
|
03f557223b | ||
|
|
8d55aeb5de | ||
|
|
7c22d76869 | ||
|
|
8327b8136f | ||
|
|
1688ccff26 | ||
|
|
36c1acc222 | ||
|
|
37b4c954a9 | ||
|
|
aee1ddca5d | ||
|
|
af292b54a8 | ||
|
|
c3c2aa3bbf | ||
|
|
7c2153dafa | ||
|
|
dc83ddf068 | ||
|
|
3ef81eb301 | ||
|
|
96f75faac6 | ||
|
|
bcd51a6acb | ||
|
|
354ccf0ce9 | ||
|
|
57c81853b7 | ||
|
|
e36d52227c | ||
|
|
82ec0fe6fa | ||
|
|
19bf0249d7 | ||
|
|
fa0d8b6204 | ||
|
|
ebb9a7e7e7 | ||
|
|
da84b9c47a | ||
|
|
a18f3cf389 | ||
|
|
1ad4883710 | ||
|
|
67def2611f | ||
|
|
b4f3c5a499 | ||
|
|
6549c6cfaf | ||
|
|
b87fd12075 | ||
|
|
bd84549386 | ||
|
|
952e870696 | ||
|
|
f3add0692f | ||
|
|
056c81e4a7 | ||
|
|
d5036018b6 | ||
|
|
47b462538c | ||
|
|
a0e9511894 | ||
|
|
3334a409b3 | ||
|
|
db53c23037 | ||
|
|
bc00ef454c | ||
|
|
075a3381af | ||
|
|
8d82b3b166 | ||
|
|
69282df839 | ||
|
|
869dda8494 | ||
|
|
7f870fadc2 | ||
|
|
2e375d4f42 | ||
|
|
735b928b33 | ||
|
|
6cae609e8a | ||
|
|
4d475e0f88 | ||
|
|
90d918bd6b | ||
|
|
3b19580ae8 | ||
|
|
457c6878b3 | ||
|
|
17cda261a3 | ||
|
|
9515648d41 | ||
|
|
1eec780e50 | ||
|
|
9a8bc6d22a | ||
|
|
746d70b015 | ||
|
|
1469c4edc1 | ||
|
|
c65dfb436e | ||
|
|
80944aac7f | ||
|
|
8226f2c3a7 | ||
|
|
77241e86e1 | ||
|
|
0ee6a23fc5 | ||
|
|
2d0d2c8c6b | ||
|
|
0fb9f341c9 | ||
|
|
41994f2213 | ||
|
|
ef9b4cf0f0 | ||
|
|
2dd344d2e1 | ||
|
|
5b27e6c5c8 | ||
|
|
a93baeafb5 |
+3668
-11
File diff suppressed because it is too large
Load Diff
@@ -4,3 +4,4 @@
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.a
|
||||
/*.pc
|
||||
|
||||
Vendored
+34
-1
@@ -1,6 +1,39 @@
|
||||
language: c
|
||||
sudo: false
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
script: make && make check
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
before_script:
|
||||
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc6-dbg
|
||||
- libc6-dev
|
||||
- libc6:i386
|
||||
- libc6-dev-i386
|
||||
- libc6-dbg:i386
|
||||
- gcc-multilib
|
||||
- valgrind
|
||||
|
||||
env:
|
||||
- CFLAGS="-Werror"
|
||||
- PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
- TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
|
||||
- TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
|
||||
- os: osx
|
||||
env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
|
||||
|
||||
script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example
|
||||
|
||||
Vendored
+125
@@ -1,3 +1,128 @@
|
||||
### 1.0.0 (unreleased)
|
||||
|
||||
**Fixes**:
|
||||
|
||||
* Catch a buffer overflow when formatting the error message
|
||||
* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
|
||||
* Fix warnings, when compiled with -Wshadow
|
||||
* Make hiredis compile in Cygwin on Windows, now CI-tested
|
||||
|
||||
**BREAKING CHANGES**:
|
||||
|
||||
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
|
||||
|
||||
User code should compare this to `size_t` values as well.
|
||||
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
|
||||
|
||||
* Remove backwards compatibility macro's
|
||||
|
||||
This removes the following old function aliases, use the new name now:
|
||||
|
||||
| Old | New |
|
||||
| --------------------------- | ---------------------- |
|
||||
| redisReplyReaderCreate | redisReaderCreate |
|
||||
| redisReplyReaderCreate | redisReaderCreate |
|
||||
| redisReplyReaderFree | redisReaderFree |
|
||||
| redisReplyReaderFeed | redisReaderFeed |
|
||||
| redisReplyReaderGetReply | redisReaderGetReply |
|
||||
| redisReplyReaderSetPrivdata | redisReaderSetPrivdata |
|
||||
| redisReplyReaderGetObject | redisReaderGetObject |
|
||||
| redisReplyReaderGetError | redisReaderGetError |
|
||||
|
||||
* The `DEBUG` variable in the Makefile was renamed to `DEBUG_FLAGS`
|
||||
|
||||
Previously it broke some builds for people that had `DEBUG` set to some arbitrary value,
|
||||
due to debugging other software.
|
||||
By renaming we avoid unintentional name clashes.
|
||||
|
||||
Simply rename `DEBUG` to `DEBUG_FLAGS` in your environment to make it working again.
|
||||
|
||||
### 0.13.3 (2015-09-16)
|
||||
|
||||
* Revert "Clear `REDIS_CONNECTED` flag when connection is closed".
|
||||
* Make tests pass on FreeBSD (Thanks, Giacomo Olgeni)
|
||||
|
||||
|
||||
If the `REDIS_CONNECTED` flag is cleared,
|
||||
the async onDisconnect callback function will never be called.
|
||||
This causes problems as the disconnect is never reported back to the user.
|
||||
|
||||
### 0.13.2 (2015-08-25)
|
||||
|
||||
* Prevent crash on pending replies in async code (Thanks, @switch-st)
|
||||
* Clear `REDIS_CONNECTED` flag when connection is closed (Thanks, Jerry Jacobs)
|
||||
* Add MacOS X addapter (Thanks, @dizzus)
|
||||
* Add Qt adapter (Thanks, Pietro Cerutti)
|
||||
* Add Ivykis adapter (Thanks, Gergely Nagy)
|
||||
|
||||
All adapters are provided as is and are only tested where possible.
|
||||
|
||||
### 0.13.1 (2015-05-03)
|
||||
|
||||
This is a bug fix release.
|
||||
The new `reconnect` method introduced new struct members, which clashed with pre-defined names in pre-C99 code.
|
||||
Another commit forced C99 compilation just to make it work, but of course this is not desirable for outside projects.
|
||||
Other non-C99 code can now use hiredis as usual again.
|
||||
Sorry for the inconvenience.
|
||||
|
||||
* Fix memory leak in async reply handling (Salvatore Sanfilippo)
|
||||
* Rename struct member to avoid name clash with pre-c99 code (Alex Balashov, ncopa)
|
||||
|
||||
### 0.13.0 (2015-04-16)
|
||||
|
||||
This release adds a minimal Windows compatibility layer.
|
||||
The parser, standalone since v0.12.0, can now be compiled on Windows
|
||||
(and thus used in other client libraries as well)
|
||||
|
||||
* Windows compatibility layer for parser code (tzickel)
|
||||
* Properly escape data printed to PKGCONF file (Dan Skorupski)
|
||||
* Fix tests when assert() undefined (Keith Bennett, Matt Stancliff)
|
||||
* Implement a reconnect method for the client context, this changes the structure of `redisContext` (Aaron Bedra)
|
||||
|
||||
### 0.12.1 (2015-01-26)
|
||||
|
||||
* Fix `make install`: DESTDIR support, install all required files, install PKGCONF in proper location
|
||||
* Fix `make test` as 32 bit build on 64 bit platform
|
||||
|
||||
### 0.12.0 (2015-01-22)
|
||||
|
||||
* Add optional KeepAlive support
|
||||
|
||||
* Try again on EINTR errors
|
||||
|
||||
* Add libuv adapter
|
||||
|
||||
* Add IPv6 support
|
||||
|
||||
* Remove possiblity of multiple close on same fd
|
||||
|
||||
* Add ability to bind source address on connect
|
||||
|
||||
* Add redisConnectFd() and redisFreeKeepFd()
|
||||
|
||||
* Fix getaddrinfo() memory leak
|
||||
|
||||
* Free string if it is unused (fixes memory leak)
|
||||
|
||||
* Improve redisAppendCommandArgv performance 2.5x
|
||||
|
||||
* Add support for SO_REUSEADDR
|
||||
|
||||
* Fix redisvFormatCommand format parsing
|
||||
|
||||
* Add GLib 2.0 adapter
|
||||
|
||||
* Refactor reading code into read.c
|
||||
|
||||
* Fix errno error buffers to not clobber errors
|
||||
|
||||
* Generate pkgconf during build
|
||||
|
||||
* Silence _BSD_SOURCE warnings
|
||||
|
||||
* Improve digit counting for multibulk creation
|
||||
|
||||
|
||||
### 0.11.0
|
||||
|
||||
* Increase the maximum multi-bulk reply depth to 7.
|
||||
|
||||
Vendored
+79
-28
@@ -3,13 +3,25 @@
|
||||
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ=net.o hiredis.o sds.o async.o
|
||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev
|
||||
OBJ=net.o hiredis.o sds.o async.o read.o
|
||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
|
||||
TESTS=hiredis-test
|
||||
LIBNAME=libhiredis
|
||||
PKGCONFNAME=hiredis.pc
|
||||
|
||||
HIREDIS_MAJOR=0
|
||||
HIREDIS_MINOR=11
|
||||
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
|
||||
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
|
||||
HIREDIS_PATCH=$(shell grep HIREDIS_PATCH hiredis.h | awk '{print $$3}')
|
||||
HIREDIS_SONAME=$(shell grep HIREDIS_SONAME hiredis.h | awk '{print $$3}')
|
||||
|
||||
# Installation related variables and target
|
||||
PREFIX?=/usr/local
|
||||
INCLUDE_PATH?=include/hiredis
|
||||
LIBRARY_PATH?=lib
|
||||
PKGCONF_PATH?=pkgconfig
|
||||
INSTALL_INCLUDE_PATH= $(DESTDIR)$(PREFIX)/$(INCLUDE_PATH)
|
||||
INSTALL_LIBRARY_PATH= $(DESTDIR)$(PREFIX)/$(LIBRARY_PATH)
|
||||
INSTALL_PKGCONF_PATH= $(INSTALL_LIBRARY_PATH)/$(PKGCONF_PATH)
|
||||
|
||||
# redis-server configuration used for testing
|
||||
REDIS_PORT=56379
|
||||
@@ -25,15 +37,16 @@ export REDIS_TEST_CONFIG
|
||||
|
||||
# Fallback to gcc when $CC is not in $PATH.
|
||||
CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
|
||||
CXX:=$(shell sh -c 'type $(CXX) >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
|
||||
DEBUG?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG) $(ARCH)
|
||||
DEBUG_FLAGS?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(ARCH)
|
||||
REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
STLIBSUFFIX=a
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR).$(HIREDIS_MINOR)
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
@@ -49,19 +62,20 @@ ifeq ($(uname_S),SunOS)
|
||||
endif
|
||||
ifeq ($(uname_S),Darwin)
|
||||
DYLIBSUFFIX=dylib
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MAJOR_NAME=$(LIBNAME).$(HIREDIS_MAJOR).$(DYLIBSUFFIX)
|
||||
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
|
||||
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
|
||||
endif
|
||||
|
||||
all: $(DYLIBNAME)
|
||||
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
net.o: net.c fmacros.h net.h hiredis.h
|
||||
async.o: async.c async.h hiredis.h sds.h dict.c dict.h
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h net.h sds.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
|
||||
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
|
||||
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
|
||||
read.o: read.c fmacros.h read.h sds.h
|
||||
sds.o: sds.c sds.h
|
||||
test.o: test.c hiredis.h
|
||||
test.o: test.c fmacros.h hiredis.h read.h sds.h
|
||||
|
||||
$(DYLIBNAME): $(OBJ)
|
||||
$(DYLIB_MAKE_CMD) $(OBJ)
|
||||
@@ -79,6 +93,15 @@ hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLI
|
||||
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
|
||||
|
||||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
|
||||
|
||||
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
|
||||
|
||||
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
|
||||
|
||||
ifndef AE_DIR
|
||||
hiredis-example-ae:
|
||||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||
@@ -94,7 +117,20 @@ hiredis-example-libuv:
|
||||
@false
|
||||
else
|
||||
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME)
|
||||
endif
|
||||
|
||||
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
|
||||
hiredis-example-qt:
|
||||
@echo "Please specify QT_MOC, QT_INCLUDE_DIR AND QT_LIBRARY_DIR"
|
||||
@false
|
||||
else
|
||||
hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
|
||||
$(QT_MOC) adapters/qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
|
||||
$(CXX) -x c++ -o qt-adapter-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
|
||||
$(QT_MOC) examples/example-qt.h -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore | \
|
||||
$(CXX) -x c++ -o qt-example-moc.o -c - $(REAL_CFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore
|
||||
$(CXX) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(QT_INCLUDE_DIR) -I$(QT_INCLUDE_DIR)/QtCore -L$(QT_LIBRARY_DIR) qt-adapter-moc.o qt-example-moc.o $< -pthread $(STLIBNAME) -lQtCore
|
||||
endif
|
||||
|
||||
hiredis-example: examples/example.c $(STLIBNAME)
|
||||
@@ -103,14 +139,16 @@ hiredis-example: examples/example.c $(STLIBNAME)
|
||||
examples: $(EXAMPLES)
|
||||
|
||||
hiredis-test: test.o $(STLIBNAME)
|
||||
$(CC) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
|
||||
|
||||
hiredis-%: %.o $(STLIBNAME)
|
||||
$(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
|
||||
|
||||
test: hiredis-test
|
||||
./hiredis-test
|
||||
|
||||
check: hiredis-test
|
||||
@echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
|
||||
./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
|
||||
$(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`
|
||||
|
||||
@@ -118,29 +156,38 @@ check: hiredis-test
|
||||
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
|
||||
|
||||
clean:
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
|
||||
# Installation related variables and target
|
||||
PREFIX?=/usr/local
|
||||
INSTALL_INCLUDE_PATH= $(PREFIX)/include/hiredis
|
||||
INSTALL_LIBRARY_PATH= $(PREFIX)/lib
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
INSTALL?= cp -r
|
||||
endif
|
||||
|
||||
INSTALL?= cp -a
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME)
|
||||
$(PKGCONFNAME): hiredis.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@echo prefix=$(PREFIX) > $@
|
||||
@echo exec_prefix=\$${prefix} >> $@
|
||||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||
@echo >> $@
|
||||
@echo Name: hiredis >> $@
|
||||
@echo Description: Minimalistic C client library for Redis. >> $@
|
||||
@echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
|
||||
@echo Libs: -L\$${libdir} -lhiredis >> $@
|
||||
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h adapters $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIB_MAJOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MAJOR_NAME) $(DYLIBNAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
|
||||
mkdir -p $(INSTALL_PKGCONF_PATH)
|
||||
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
|
||||
|
||||
32bit:
|
||||
@echo ""
|
||||
@@ -148,6 +195,10 @@ install: $(DYLIBNAME) $(STLIBNAME)
|
||||
@echo ""
|
||||
$(MAKE) CFLAGS="-m32" LDFLAGS="-m32"
|
||||
|
||||
32bit-vars:
|
||||
$(eval CFLAGS=-m32)
|
||||
$(eval LDFLAGS=-m32)
|
||||
|
||||
gprof:
|
||||
$(MAKE) CFLAGS="-pg" LDFLAGS="-pg"
|
||||
|
||||
@@ -163,4 +214,4 @@ coverage: gcov
|
||||
noopt:
|
||||
$(MAKE) OPTIMIZATION=""
|
||||
|
||||
.PHONY: all test check clean dep install 32bit gprof gcov noopt
|
||||
.PHONY: all test check clean dep install 32bit 32bit-vars gprof gcov noopt
|
||||
|
||||
Vendored
+115
-88
@@ -1,11 +1,13 @@
|
||||
[](https://travis-ci.org/redis/hiredis)
|
||||
|
||||
**This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.**
|
||||
|
||||
# HIREDIS
|
||||
|
||||
Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database.
|
||||
|
||||
It is minimalistic because it just adds minimal support for the protocol, but
|
||||
at the same time it uses an high level printf-alike API in order to make it
|
||||
at the same time it uses a high level printf-alike API in order to make it
|
||||
much higher level than otherwise suggested by its minimal code base and the
|
||||
lack of explicit bindings for every Redis command.
|
||||
|
||||
@@ -20,7 +22,15 @@ Redis version >= 1.2.0.
|
||||
The library comes with multiple APIs. There is the
|
||||
*synchronous API*, the *asynchronous API* and the *reply parsing API*.
|
||||
|
||||
## UPGRADING
|
||||
## Upgrading to `1.0.0`
|
||||
|
||||
Version 1.0.0 marks a stable release of hiredis.
|
||||
It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory.
|
||||
It also bundles the updated `sds` library, to sync up with upstream and Redis.
|
||||
For most applications a recompile against the new hiredis should be enough.
|
||||
For code changes see the [Changelog](CHANGELOG.md).
|
||||
|
||||
## Upgrading from `<0.9.0`
|
||||
|
||||
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
|
||||
code using hiredis should not be a big pain. The key thing to keep in mind when
|
||||
@@ -31,51 +41,62 @@ the stateless 0.0.1 that only has a file descriptor to work with.
|
||||
|
||||
To consume the synchronous API, there are only a few function calls that need to be introduced:
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
void freeReplyObject(void *reply);
|
||||
```c
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
void freeReplyObject(void *reply);
|
||||
```
|
||||
|
||||
### Connecting
|
||||
|
||||
The function `redisConnect` is used to create a so-called `redisContext`. The
|
||||
context is where Hiredis holds state for a connection. The `redisContext`
|
||||
struct has an integer `err` field that is non-zero when an the connection is in
|
||||
struct has an integer `err` field that is non-zero when the connection is in
|
||||
an error state. The field `errstr` will contain a string with a description of
|
||||
the error. More information on errors can be found in the **Errors** section.
|
||||
After trying to connect to Redis using `redisConnect` you should
|
||||
check the `err` field to see if establishing the connection was successful:
|
||||
|
||||
redisContext *c = redisConnect("127.0.0.1", 6379);
|
||||
if (c != NULL && c->err) {
|
||||
```c
|
||||
redisContext *c = redisConnect("127.0.0.1", 6379);
|
||||
if (c == NULL || c->err) {
|
||||
if (c) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
} else {
|
||||
printf("Can't allocate redis context\n");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Note: A `redisContext` is not thread-safe.*
|
||||
|
||||
### Sending commands
|
||||
|
||||
There are several ways to issue commands to Redis. The first that will be introduced is
|
||||
`redisCommand`. This function takes a format similar to printf. In the simplest form,
|
||||
it is used like this:
|
||||
|
||||
reply = redisCommand(context, "SET foo bar");
|
||||
```c
|
||||
reply = redisCommand(context, "SET foo bar");
|
||||
```
|
||||
|
||||
The specifier `%s` interpolates a string in the command, and uses `strlen` to
|
||||
determine the length of the string:
|
||||
|
||||
reply = redisCommand(context, "SET foo %s", value);
|
||||
|
||||
```c
|
||||
reply = redisCommand(context, "SET foo %s", value);
|
||||
```
|
||||
When you need to pass binary safe strings in a command, the `%b` specifier can be
|
||||
used. Together with a pointer to the string, it requires a `size_t` length argument
|
||||
of the string:
|
||||
|
||||
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
|
||||
|
||||
```c
|
||||
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
|
||||
```
|
||||
Internally, Hiredis splits the command in different arguments and will
|
||||
convert it to the protocol used to communicate with Redis.
|
||||
One or more spaces separates arguments, so you can use the specifiers
|
||||
anywhere in an argument:
|
||||
|
||||
reply = redisCommand(context, "SET key:%s %s", myid, value);
|
||||
```c
|
||||
reply = redisCommand(context, "SET key:%s %s", myid, value);
|
||||
```
|
||||
|
||||
### Using replies
|
||||
|
||||
@@ -114,11 +135,11 @@ was received:
|
||||
Redis may reply with nested arrays but this is fully supported.
|
||||
|
||||
Replies should be freed using the `freeReplyObject()` function.
|
||||
Note that this function will take care of freeing sub-replies objects
|
||||
Note that this function will take care of freeing sub-reply objects
|
||||
contained in arrays and nested arrays, so there is no need for the user to
|
||||
free the sub replies (it is actually harmful and will corrupt the memory).
|
||||
|
||||
**Important:** the current version of hiredis (0.10.0) free's replies when the
|
||||
**Important:** the current version of hiredis (0.10.0) frees replies when the
|
||||
asynchronous API is used. This means you should not call `freeReplyObject` when
|
||||
you use this API. The reply is cleaned up by hiredis _after_ the callback
|
||||
returns. This behavior will probably change in future releases, so make sure to
|
||||
@@ -127,19 +148,19 @@ keep an eye on the changelog when upgrading (see issue #39).
|
||||
### Cleaning up
|
||||
|
||||
To disconnect and free the context the following function can be used:
|
||||
|
||||
void redisFree(redisContext *c);
|
||||
|
||||
This function immediately closes the socket and then free's the allocations done in
|
||||
```c
|
||||
void redisFree(redisContext *c);
|
||||
```
|
||||
This function immediately closes the socket and then frees the allocations done in
|
||||
creating the context.
|
||||
|
||||
### Sending commands (cont'd)
|
||||
|
||||
Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
|
||||
It has the following prototype:
|
||||
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
```c
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
```
|
||||
It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
|
||||
arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
|
||||
use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
|
||||
@@ -169,10 +190,10 @@ The function `redisGetReply` is exported as part of the Hiredis API and can be u
|
||||
is expected on the socket. To pipeline commands, the only things that needs to be done is
|
||||
filling up the output buffer. For this cause, two commands can be used that are identical
|
||||
to the `redisCommand` family, apart from not returning a reply:
|
||||
|
||||
void redisAppendCommand(redisContext *c, const char *format, ...);
|
||||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
```c
|
||||
void redisAppendCommand(redisContext *c, const char *format, ...);
|
||||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
```
|
||||
After calling either function one or more times, `redisGetReply` can be used to receive the
|
||||
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
|
||||
the latter means an error occurred while reading a reply. Just as with the other commands,
|
||||
@@ -180,24 +201,24 @@ the `err` field in the context can be used to find out what the cause of this er
|
||||
|
||||
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
|
||||
a single call to `read(2)`):
|
||||
|
||||
redisReply *reply;
|
||||
redisAppendCommand(context,"SET foo bar");
|
||||
redisAppendCommand(context,"GET foo");
|
||||
redisGetReply(context,&reply); // reply for SET
|
||||
freeReplyObject(reply);
|
||||
redisGetReply(context,&reply); // reply for GET
|
||||
freeReplyObject(reply);
|
||||
|
||||
```c
|
||||
redisReply *reply;
|
||||
redisAppendCommand(context,"SET foo bar");
|
||||
redisAppendCommand(context,"GET foo");
|
||||
redisGetReply(context,&reply); // reply for SET
|
||||
freeReplyObject(reply);
|
||||
redisGetReply(context,&reply); // reply for GET
|
||||
freeReplyObject(reply);
|
||||
```
|
||||
This API can also be used to implement a blocking subscriber:
|
||||
|
||||
reply = redisCommand(context,"SUBSCRIBE foo");
|
||||
```c
|
||||
reply = redisCommand(context,"SUBSCRIBE foo");
|
||||
freeReplyObject(reply);
|
||||
while(redisGetReply(context,&reply) == REDIS_OK) {
|
||||
// consume message
|
||||
freeReplyObject(reply);
|
||||
while(redisGetReply(context,&reply) == REDIS_OK) {
|
||||
// consume message
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
### Errors
|
||||
|
||||
When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
|
||||
@@ -237,58 +258,62 @@ should be checked after creation to see if there were errors creating the connec
|
||||
Because the connection that will be created is non-blocking, the kernel is not able to
|
||||
instantly return if the specified host and port is able to accept a connection.
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
}
|
||||
*Note: A `redisAsyncContext` is not thread-safe.*
|
||||
|
||||
```c
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
}
|
||||
```
|
||||
|
||||
The asynchronous context can hold a disconnect callback function that is called when the
|
||||
connection is disconnected (either because of an error or per user request). This function should
|
||||
have the following prototype:
|
||||
|
||||
void(const redisAsyncContext *c, int status);
|
||||
|
||||
```c
|
||||
void(const redisAsyncContext *c, int status);
|
||||
```
|
||||
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
|
||||
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
|
||||
field in the context can be accessed to find out the cause of the error.
|
||||
|
||||
The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
|
||||
The context object is always freed after the disconnect callback fired. When a reconnect is needed,
|
||||
the disconnect callback is a good point to do so.
|
||||
|
||||
Setting the disconnect callback can only be done once per context. For subsequent calls it will
|
||||
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
|
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
|
||||
```c
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
```
|
||||
### Sending commands and their callbacks
|
||||
|
||||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
||||
Therefore, unlike the synchronous API, there is only a single way to send commands.
|
||||
Because commands are sent to Redis asynchronously, issuing a command requires a callback function
|
||||
that is called when the reply is received. Reply callbacks should have the following prototype:
|
||||
|
||||
void(redisAsyncContext *c, void *reply, void *privdata);
|
||||
|
||||
```c
|
||||
void(redisAsyncContext *c, void *reply, void *privdata);
|
||||
```
|
||||
The `privdata` argument can be used to curry arbitrary data to the callback from the point where
|
||||
the command is initially queued for execution.
|
||||
|
||||
The functions that can be used to issue commands in an asynchronous context are:
|
||||
|
||||
int redisAsyncCommand(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
const char *format, ...);
|
||||
int redisAsyncCommandArgv(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
```c
|
||||
int redisAsyncCommand(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
const char *format, ...);
|
||||
int redisAsyncCommandArgv(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
int argc, const char **argv, const size_t *argvlen);
|
||||
```
|
||||
Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
|
||||
was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
|
||||
is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
|
||||
returned on calls to the `redisAsyncCommand` family.
|
||||
|
||||
If the reply for a command with a `NULL` callback is read, it is immediately free'd. When the callback
|
||||
for a command is non-`NULL`, the memory is free'd immediately following the callback: the reply is only
|
||||
If the reply for a command with a `NULL` callback is read, it is immediately freed. When the callback
|
||||
for a command is non-`NULL`, the memory is freed immediately following the callback: the reply is only
|
||||
valid for the duration of the callback.
|
||||
|
||||
All pending callbacks are called with a `NULL` reply when the context encountered an error.
|
||||
@@ -296,14 +321,14 @@ All pending callbacks are called with a `NULL` reply when the context encountere
|
||||
### Disconnecting
|
||||
|
||||
An asynchronous connection can be terminated using:
|
||||
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
|
||||
```c
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
```
|
||||
When this function is called, the connection is **not** immediately terminated. Instead, new
|
||||
commands are no longer accepted and the connection is only terminated when all pending commands
|
||||
have been written to the socket, their respective replies have been read and their respective
|
||||
callbacks have been executed. After this, the disconnection callback is executed with the
|
||||
`REDIS_OK` status and the context object is free'd.
|
||||
`REDIS_OK` status and the context object is freed.
|
||||
|
||||
### Hooking it up to event library *X*
|
||||
|
||||
@@ -316,12 +341,12 @@ Hiredis comes with a reply parsing API that makes it easy for writing higher
|
||||
level language bindings.
|
||||
|
||||
The reply parsing API consists of the following functions:
|
||||
|
||||
redisReader *redisReaderCreate(void);
|
||||
void redisReaderFree(redisReader *reader);
|
||||
int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
|
||||
int redisReaderGetReply(redisReader *reader, void **reply);
|
||||
|
||||
```c
|
||||
redisReader *redisReaderCreate(void);
|
||||
void redisReaderFree(redisReader *reader);
|
||||
int redisReaderFeed(redisReader *reader, const char *buf, size_t len);
|
||||
int redisReaderGetReply(redisReader *reader, void **reply);
|
||||
```
|
||||
The same set of functions are used internally by hiredis when creating a
|
||||
normal Redis context, the above API just exposes it to the user for a direct
|
||||
usage.
|
||||
@@ -361,7 +386,7 @@ Both when using the Reader API directly or when using it indirectly via a
|
||||
normal Redis context, the redisReader structure uses a buffer in order to
|
||||
accumulate data from the server.
|
||||
Usually this buffer is destroyed when it is empty and is larger than 16
|
||||
kb in order to avoid wasting memory in unused buffers
|
||||
KiB in order to avoid wasting memory in unused buffers
|
||||
|
||||
However when working with very big payloads destroying the buffer may slow
|
||||
down performances considerably, so it is possible to modify the max size of
|
||||
@@ -371,9 +396,9 @@ value for an idle buffer, so the buffer will never get freed.
|
||||
|
||||
For instance if you have a normal Redis context you can set the maximum idle
|
||||
buffer to zero (unlimited) just with:
|
||||
|
||||
context->reader->maxbuf = 0;
|
||||
|
||||
```c
|
||||
context->reader->maxbuf = 0;
|
||||
```
|
||||
This should be done only in order to maximize performances when working with
|
||||
large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again
|
||||
as soon as possible in order to prevent allocation of useless memory.
|
||||
@@ -381,4 +406,6 @@ as soon as possible in order to prevent allocation of useless memory.
|
||||
## AUTHORS
|
||||
|
||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
|
||||
Jan-Erik Rediger (janerik at fnordig dot com)
|
||||
|
||||
Vendored
+153
@@ -0,0 +1,153 @@
|
||||
#ifndef __HIREDIS_GLIB_H__
|
||||
#define __HIREDIS_GLIB_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSource source;
|
||||
redisAsyncContext *ac;
|
||||
GPollFD poll_fd;
|
||||
} RedisSource;
|
||||
|
||||
static void
|
||||
redis_source_add_read (gpointer data)
|
||||
{
|
||||
RedisSource *source = (RedisSource *)data;
|
||||
g_return_if_fail(source);
|
||||
source->poll_fd.events |= G_IO_IN;
|
||||
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||
}
|
||||
|
||||
static void
|
||||
redis_source_del_read (gpointer data)
|
||||
{
|
||||
RedisSource *source = (RedisSource *)data;
|
||||
g_return_if_fail(source);
|
||||
source->poll_fd.events &= ~G_IO_IN;
|
||||
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||
}
|
||||
|
||||
static void
|
||||
redis_source_add_write (gpointer data)
|
||||
{
|
||||
RedisSource *source = (RedisSource *)data;
|
||||
g_return_if_fail(source);
|
||||
source->poll_fd.events |= G_IO_OUT;
|
||||
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||
}
|
||||
|
||||
static void
|
||||
redis_source_del_write (gpointer data)
|
||||
{
|
||||
RedisSource *source = (RedisSource *)data;
|
||||
g_return_if_fail(source);
|
||||
source->poll_fd.events &= ~G_IO_OUT;
|
||||
g_main_context_wakeup(g_source_get_context((GSource *)data));
|
||||
}
|
||||
|
||||
static void
|
||||
redis_source_cleanup (gpointer data)
|
||||
{
|
||||
RedisSource *source = (RedisSource *)data;
|
||||
|
||||
g_return_if_fail(source);
|
||||
|
||||
redis_source_del_read(source);
|
||||
redis_source_del_write(source);
|
||||
/*
|
||||
* It is not our responsibility to remove ourself from the
|
||||
* current main loop. However, we will remove the GPollFD.
|
||||
*/
|
||||
if (source->poll_fd.fd >= 0) {
|
||||
g_source_remove_poll((GSource *)data, &source->poll_fd);
|
||||
source->poll_fd.fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
redis_source_prepare (GSource *source,
|
||||
gint *timeout_)
|
||||
{
|
||||
RedisSource *redis = (RedisSource *)source;
|
||||
*timeout_ = -1;
|
||||
return !!(redis->poll_fd.events & redis->poll_fd.revents);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
redis_source_check (GSource *source)
|
||||
{
|
||||
RedisSource *redis = (RedisSource *)source;
|
||||
return !!(redis->poll_fd.events & redis->poll_fd.revents);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
redis_source_dispatch (GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
RedisSource *redis = (RedisSource *)source;
|
||||
|
||||
if ((redis->poll_fd.revents & G_IO_OUT)) {
|
||||
redisAsyncHandleWrite(redis->ac);
|
||||
redis->poll_fd.revents &= ~G_IO_OUT;
|
||||
}
|
||||
|
||||
if ((redis->poll_fd.revents & G_IO_IN)) {
|
||||
redisAsyncHandleRead(redis->ac);
|
||||
redis->poll_fd.revents &= ~G_IO_IN;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
return callback(user_data);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
redis_source_finalize (GSource *source)
|
||||
{
|
||||
RedisSource *redis = (RedisSource *)source;
|
||||
|
||||
if (redis->poll_fd.fd >= 0) {
|
||||
g_source_remove_poll(source, &redis->poll_fd);
|
||||
redis->poll_fd.fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static GSource *
|
||||
redis_source_new (redisAsyncContext *ac)
|
||||
{
|
||||
static GSourceFuncs source_funcs = {
|
||||
.prepare = redis_source_prepare,
|
||||
.check = redis_source_check,
|
||||
.dispatch = redis_source_dispatch,
|
||||
.finalize = redis_source_finalize,
|
||||
};
|
||||
redisContext *c = &ac->c;
|
||||
RedisSource *source;
|
||||
|
||||
g_return_val_if_fail(ac != NULL, NULL);
|
||||
|
||||
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
|
||||
source->ac = ac;
|
||||
source->poll_fd.fd = c->fd;
|
||||
source->poll_fd.events = 0;
|
||||
source->poll_fd.revents = 0;
|
||||
g_source_add_poll((GSource *)source, &source->poll_fd);
|
||||
|
||||
ac->ev.addRead = redis_source_add_read;
|
||||
ac->ev.delRead = redis_source_del_read;
|
||||
ac->ev.addWrite = redis_source_add_write;
|
||||
ac->ev.delWrite = redis_source_del_write;
|
||||
ac->ev.cleanup = redis_source_cleanup;
|
||||
ac->ev.data = source;
|
||||
|
||||
return (GSource *)source;
|
||||
}
|
||||
|
||||
#endif /* __HIREDIS_GLIB_H__ */
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
#ifndef __HIREDIS_IVYKIS_H__
|
||||
#define __HIREDIS_IVYKIS_H__
|
||||
#include <iv.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisIvykisEvents {
|
||||
redisAsyncContext *context;
|
||||
struct iv_fd fd;
|
||||
} redisIvykisEvents;
|
||||
|
||||
static void redisIvykisReadEvent(void *arg) {
|
||||
redisAsyncContext *context = (redisAsyncContext *)arg;
|
||||
redisAsyncHandleRead(context);
|
||||
}
|
||||
|
||||
static void redisIvykisWriteEvent(void *arg) {
|
||||
redisAsyncContext *context = (redisAsyncContext *)arg;
|
||||
redisAsyncHandleWrite(context);
|
||||
}
|
||||
|
||||
static void redisIvykisAddRead(void *privdata) {
|
||||
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||
iv_fd_set_handler_in(&e->fd, redisIvykisReadEvent);
|
||||
}
|
||||
|
||||
static void redisIvykisDelRead(void *privdata) {
|
||||
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||
iv_fd_set_handler_in(&e->fd, NULL);
|
||||
}
|
||||
|
||||
static void redisIvykisAddWrite(void *privdata) {
|
||||
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||
iv_fd_set_handler_out(&e->fd, redisIvykisWriteEvent);
|
||||
}
|
||||
|
||||
static void redisIvykisDelWrite(void *privdata) {
|
||||
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||
iv_fd_set_handler_out(&e->fd, NULL);
|
||||
}
|
||||
|
||||
static void redisIvykisCleanup(void *privdata) {
|
||||
redisIvykisEvents *e = (redisIvykisEvents*)privdata;
|
||||
|
||||
iv_fd_unregister(&e->fd);
|
||||
free(e);
|
||||
}
|
||||
|
||||
static int redisIvykisAttach(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisIvykisEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->ev.data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = (redisIvykisEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->ev.addRead = redisIvykisAddRead;
|
||||
ac->ev.delRead = redisIvykisDelRead;
|
||||
ac->ev.addWrite = redisIvykisAddWrite;
|
||||
ac->ev.delWrite = redisIvykisDelWrite;
|
||||
ac->ev.cleanup = redisIvykisCleanup;
|
||||
ac->ev.data = e;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
IV_FD_INIT(&e->fd);
|
||||
e->fd.fd = c->fd;
|
||||
e->fd.handler_in = redisIvykisReadEvent;
|
||||
e->fd.handler_out = redisIvykisWriteEvent;
|
||||
e->fd.handler_err = NULL;
|
||||
e->fd.cookie = e->context;
|
||||
|
||||
iv_fd_register(&e->fd);
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif
|
||||
Vendored
+12
-12
@@ -30,13 +30,13 @@
|
||||
|
||||
#ifndef __HIREDIS_LIBEVENT_H__
|
||||
#define __HIREDIS_LIBEVENT_H__
|
||||
#include <event.h>
|
||||
#include <event2/event.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisLibeventEvents {
|
||||
redisAsyncContext *context;
|
||||
struct event rev, wev;
|
||||
struct event *rev, *wev;
|
||||
} redisLibeventEvents;
|
||||
|
||||
static void redisLibeventReadEvent(int fd, short event, void *arg) {
|
||||
@@ -53,28 +53,28 @@ static void redisLibeventWriteEvent(int fd, short event, void *arg) {
|
||||
|
||||
static void redisLibeventAddRead(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(&e->rev,NULL);
|
||||
event_add(e->rev,NULL);
|
||||
}
|
||||
|
||||
static void redisLibeventDelRead(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->rev);
|
||||
event_del(e->rev);
|
||||
}
|
||||
|
||||
static void redisLibeventAddWrite(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(&e->wev,NULL);
|
||||
event_add(e->wev,NULL);
|
||||
}
|
||||
|
||||
static void redisLibeventDelWrite(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->wev);
|
||||
event_del(e->wev);
|
||||
}
|
||||
|
||||
static void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->rev);
|
||||
event_del(&e->wev);
|
||||
event_del(e->rev);
|
||||
event_del(e->wev);
|
||||
free(e);
|
||||
}
|
||||
|
||||
@@ -99,10 +99,10 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
ac->ev.data = e;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
|
||||
event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e);
|
||||
event_base_set(base,&e->rev);
|
||||
event_base_set(base,&e->wev);
|
||||
e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
|
||||
e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
|
||||
event_add(e->rev, NULL);
|
||||
event_add(e->wev, NULL);
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
Vendored
+4
-3
@@ -1,5 +1,6 @@
|
||||
#ifndef __HIREDIS_LIBUV_H__
|
||||
#define __HIREDIS_LIBUV_H__
|
||||
#include <stdlib.h>
|
||||
#include <uv.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
@@ -11,7 +12,6 @@ typedef struct redisLibuvEvents {
|
||||
int events;
|
||||
} redisLibuvEvents;
|
||||
|
||||
int redisLibuvAttach(redisAsyncContext*, uv_loop_t*);
|
||||
|
||||
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
|
||||
@@ -20,10 +20,10 @@ static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (events & UV_READABLE) {
|
||||
if (p->context != NULL && (events & UV_READABLE)) {
|
||||
redisAsyncHandleRead(p->context);
|
||||
}
|
||||
if (events & UV_WRITABLE) {
|
||||
if (p->context != NULL && (events & UV_WRITABLE)) {
|
||||
redisAsyncHandleWrite(p->context);
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,7 @@ static void on_close(uv_handle_t* handle) {
|
||||
static void redisLibuvCleanup(void *privdata) {
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||
|
||||
p->context = NULL; // indicate that context might no longer exist
|
||||
uv_close((uv_handle_t*)&p->handle, on_close);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// Created by Дмитрий Бахвалов on 13.07.15.
|
||||
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef __HIREDIS_MACOSX_H__
|
||||
#define __HIREDIS_MACOSX_H__
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct {
|
||||
redisAsyncContext *context;
|
||||
CFSocketRef socketRef;
|
||||
CFRunLoopSourceRef sourceRef;
|
||||
} RedisRunLoop;
|
||||
|
||||
static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
|
||||
if( redisRunLoop != NULL ) {
|
||||
if( redisRunLoop->sourceRef != NULL ) {
|
||||
CFRunLoopSourceInvalidate(redisRunLoop->sourceRef);
|
||||
CFRelease(redisRunLoop->sourceRef);
|
||||
}
|
||||
if( redisRunLoop->socketRef != NULL ) {
|
||||
CFSocketInvalidate(redisRunLoop->socketRef);
|
||||
CFRelease(redisRunLoop->socketRef);
|
||||
}
|
||||
free(redisRunLoop);
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static void redisMacOSAddRead(void *privdata) {
|
||||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
|
||||
}
|
||||
|
||||
static void redisMacOSDelRead(void *privdata) {
|
||||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketReadCallBack);
|
||||
}
|
||||
|
||||
static void redisMacOSAddWrite(void *privdata) {
|
||||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||
CFSocketEnableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
|
||||
}
|
||||
|
||||
static void redisMacOSDelWrite(void *privdata) {
|
||||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||
CFSocketDisableCallBacks(redisRunLoop->socketRef, kCFSocketWriteCallBack);
|
||||
}
|
||||
|
||||
static void redisMacOSCleanup(void *privdata) {
|
||||
RedisRunLoop *redisRunLoop = (RedisRunLoop*)privdata;
|
||||
freeRedisRunLoop(redisRunLoop);
|
||||
}
|
||||
|
||||
static void redisMacOSAsyncCallback(CFSocketRef __unused s, CFSocketCallBackType callbackType, CFDataRef __unused address, const void __unused *data, void *info) {
|
||||
redisAsyncContext* context = (redisAsyncContext*) info;
|
||||
|
||||
switch (callbackType) {
|
||||
case kCFSocketReadCallBack:
|
||||
redisAsyncHandleRead(context);
|
||||
break;
|
||||
|
||||
case kCFSocketWriteCallBack:
|
||||
redisAsyncHandleWrite(context);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLoop) {
|
||||
redisContext *redisCtx = &(redisAsyncCtx->c);
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
|
||||
|
||||
RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop));
|
||||
if( !redisRunLoop ) return REDIS_ERR;
|
||||
|
||||
/* Setup redis stuff */
|
||||
redisRunLoop->context = redisAsyncCtx;
|
||||
|
||||
redisAsyncCtx->ev.addRead = redisMacOSAddRead;
|
||||
redisAsyncCtx->ev.delRead = redisMacOSDelRead;
|
||||
redisAsyncCtx->ev.addWrite = redisMacOSAddWrite;
|
||||
redisAsyncCtx->ev.delWrite = redisMacOSDelWrite;
|
||||
redisAsyncCtx->ev.cleanup = redisMacOSCleanup;
|
||||
redisAsyncCtx->ev.data = redisRunLoop;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
CFSocketContext socketCtx = { 0, redisAsyncCtx, NULL, NULL, NULL };
|
||||
|
||||
redisRunLoop->socketRef = CFSocketCreateWithNative(NULL, redisCtx->fd,
|
||||
kCFSocketReadCallBack | kCFSocketWriteCallBack,
|
||||
redisMacOSAsyncCallback,
|
||||
&socketCtx);
|
||||
if( !redisRunLoop->socketRef ) return freeRedisRunLoop(redisRunLoop);
|
||||
|
||||
redisRunLoop->sourceRef = CFSocketCreateRunLoopSource(NULL, redisRunLoop->socketRef, 0);
|
||||
if( !redisRunLoop->sourceRef ) return freeRedisRunLoop(redisRunLoop);
|
||||
|
||||
CFRunLoopAddSource(runLoop, redisRunLoop->sourceRef, kCFRunLoopDefaultMode);
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
/*-
|
||||
* Copyright (C) 2014 Pietro Cerutti <gahr@gahr.ch>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __HIREDIS_QT_H__
|
||||
#define __HIREDIS_QT_H__
|
||||
#include <QSocketNotifier>
|
||||
#include "../async.h"
|
||||
|
||||
static void RedisQtAddRead(void *);
|
||||
static void RedisQtDelRead(void *);
|
||||
static void RedisQtAddWrite(void *);
|
||||
static void RedisQtDelWrite(void *);
|
||||
static void RedisQtCleanup(void *);
|
||||
|
||||
class RedisQtAdapter : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
friend
|
||||
void RedisQtAddRead(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->addRead();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtDelRead(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->delRead();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtAddWrite(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->addWrite();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtDelWrite(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->delWrite();
|
||||
}
|
||||
|
||||
friend
|
||||
void RedisQtCleanup(void * adapter) {
|
||||
RedisQtAdapter * a = static_cast<RedisQtAdapter *>(adapter);
|
||||
a->cleanup();
|
||||
}
|
||||
|
||||
public:
|
||||
RedisQtAdapter(QObject * parent = 0)
|
||||
: QObject(parent), m_ctx(0), m_read(0), m_write(0) { }
|
||||
|
||||
~RedisQtAdapter() {
|
||||
if (m_ctx != 0) {
|
||||
m_ctx->ev.data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int setContext(redisAsyncContext * ac) {
|
||||
if (ac->ev.data != NULL) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
m_ctx = ac;
|
||||
m_ctx->ev.data = this;
|
||||
m_ctx->ev.addRead = RedisQtAddRead;
|
||||
m_ctx->ev.delRead = RedisQtDelRead;
|
||||
m_ctx->ev.addWrite = RedisQtAddWrite;
|
||||
m_ctx->ev.delWrite = RedisQtDelWrite;
|
||||
m_ctx->ev.cleanup = RedisQtCleanup;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
void addRead() {
|
||||
if (m_read) return;
|
||||
m_read = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Read, 0);
|
||||
connect(m_read, SIGNAL(activated(int)), this, SLOT(read()));
|
||||
}
|
||||
|
||||
void delRead() {
|
||||
if (!m_read) return;
|
||||
delete m_read;
|
||||
m_read = 0;
|
||||
}
|
||||
|
||||
void addWrite() {
|
||||
if (m_write) return;
|
||||
m_write = new QSocketNotifier(m_ctx->c.fd, QSocketNotifier::Write, 0);
|
||||
connect(m_write, SIGNAL(activated(int)), this, SLOT(write()));
|
||||
}
|
||||
|
||||
void delWrite() {
|
||||
if (!m_write) return;
|
||||
delete m_write;
|
||||
m_write = 0;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
delRead();
|
||||
delWrite();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void read() { redisAsyncHandleRead(m_ctx); }
|
||||
void write() { redisAsyncHandleWrite(m_ctx); }
|
||||
|
||||
private:
|
||||
redisAsyncContext * m_ctx;
|
||||
QSocketNotifier * m_read;
|
||||
QSocketNotifier * m_write;
|
||||
};
|
||||
|
||||
#endif /* !__HIREDIS_QT_H__ */
|
||||
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
|
||||
environment:
|
||||
matrix:
|
||||
- CYG_ROOT: C:\cygwin64
|
||||
CYG_SETUP: setup-x86_64.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin64\var\cache\setup
|
||||
CYG_BASH: C:\cygwin64\bin\bash
|
||||
CC: gcc
|
||||
- CYG_ROOT: C:\cygwin
|
||||
CYG_SETUP: setup-x86.exe
|
||||
CYG_MIRROR: http://cygwin.mirror.constant.com
|
||||
CYG_CACHE: C:\cygwin\var\cache\setup
|
||||
CYG_BASH: C:\cygwin\bin\bash
|
||||
CC: gcc
|
||||
TARGET: 32bit
|
||||
TARGET_VARS: 32bit-vars
|
||||
|
||||
# Cache Cygwin files to speed up build
|
||||
cache:
|
||||
- '%CYG_CACHE%'
|
||||
clone_depth: 1
|
||||
|
||||
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
# Install needed build dependencies
|
||||
install:
|
||||
- ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
|
||||
- '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
|
||||
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
|
||||
|
||||
build_script:
|
||||
- 'echo building...'
|
||||
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'
|
||||
Vendored
+38
-13
@@ -58,7 +58,7 @@
|
||||
} while(0);
|
||||
|
||||
/* Forward declaration of function in hiredis.c */
|
||||
void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
|
||||
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
|
||||
|
||||
/* Functions managing dictionary of callbacks for pub/sub. */
|
||||
static unsigned int callbackHash(const void *key) {
|
||||
@@ -142,6 +142,9 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
/* We want the error field to be accessible directly instead of requiring
|
||||
* an indirection to the redisContext struct. */
|
||||
static void __redisAsyncCopyError(redisAsyncContext *ac) {
|
||||
if (!ac)
|
||||
return;
|
||||
|
||||
redisContext *c = &(ac->c);
|
||||
ac->err = c->err;
|
||||
ac->errstr = c->errstr;
|
||||
@@ -173,6 +176,14 @@ redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
||||
redisContext *c;
|
||||
redisAsyncContext *ac;
|
||||
@@ -407,7 +418,8 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
if (reply == NULL) {
|
||||
/* When the connection is being disconnected and there are
|
||||
* no more replies, this is the cue to really disconnect. */
|
||||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
|
||||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0
|
||||
&& ac->replies.head == NULL) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
}
|
||||
@@ -477,7 +489,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
}
|
||||
|
||||
/* Internal helper function to detect socket status the first time a read or
|
||||
* write event fires. When connecting was not succesful, the connect callback
|
||||
* write event fires. When connecting was not successful, the connect callback
|
||||
* is called with a REDIS_ERR status and the context is free'd. */
|
||||
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
@@ -551,8 +563,8 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
|
||||
/* Sets a pointer to the first argument and its length starting at p. Returns
|
||||
* the number of bytes to skip to get to the following argument. */
|
||||
static char *nextArgument(char *start, char **str, size_t *len) {
|
||||
char *p = start;
|
||||
static const char *nextArgument(const char *start, const char **str, size_t *len) {
|
||||
const char *p = start;
|
||||
if (p[0] != '$') {
|
||||
p = strchr(p,'$');
|
||||
if (p == NULL) return NULL;
|
||||
@@ -568,14 +580,15 @@ static char *nextArgument(char *start, char **str, size_t *len) {
|
||||
/* Helper function for the redisAsyncCommand* family of functions. Writes a
|
||||
* formatted command to the output buffer and registers the provided callback
|
||||
* function with the context. */
|
||||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
|
||||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
int pvariant, hasnext;
|
||||
char *cstr, *astr;
|
||||
const char *cstr, *astr;
|
||||
size_t clen, alen;
|
||||
char *p;
|
||||
const char *p;
|
||||
sds sname;
|
||||
int ret;
|
||||
|
||||
/* Don't accept new commands when the connection is about to be closed. */
|
||||
if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
|
||||
@@ -599,9 +612,11 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
||||
sname = sdsnewlen(astr,alen);
|
||||
if (pvariant)
|
||||
dictReplace(ac->sub.patterns,sname,&cb);
|
||||
ret = dictReplace(ac->sub.patterns,sname,&cb);
|
||||
else
|
||||
dictReplace(ac->sub.channels,sname,&cb);
|
||||
ret = dictReplace(ac->sub.channels,sname,&cb);
|
||||
|
||||
if (ret == 0) sdsfree(sname);
|
||||
}
|
||||
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
|
||||
/* It is only useful to call (P)UNSUBSCRIBE when the context is
|
||||
@@ -637,6 +652,11 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
|
||||
int len;
|
||||
int status;
|
||||
len = redisvFormatCommand(&cmd,format,ap);
|
||||
|
||||
/* We don't want to pass -1 or -2 to future functions as a length. */
|
||||
if (len < 0)
|
||||
return REDIS_ERR;
|
||||
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
free(cmd);
|
||||
return status;
|
||||
@@ -652,11 +672,16 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata
|
||||
}
|
||||
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd;
|
||||
sds cmd;
|
||||
int len;
|
||||
int status;
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
|
||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
free(cmd);
|
||||
sdsfree(cmd);
|
||||
return status;
|
||||
}
|
||||
|
||||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
|
||||
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
return status;
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -103,6 +103,8 @@ typedef struct redisAsyncContext {
|
||||
/* Functions that proxy to hiredis */
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
|
||||
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
const char *source_addr);
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
@@ -118,6 +120,7 @@ void redisAsyncHandleWrite(redisAsyncContext *ac);
|
||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
|
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
|
||||
int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -161,7 +161,7 @@ static int dictReplace(dict *ht, void *key, void *val) {
|
||||
dictEntry *entry, auxentry;
|
||||
|
||||
/* Try to add the element. If the key
|
||||
* does not exists dictAdd will suceed. */
|
||||
* does not exists dictAdd will succeed. */
|
||||
if (dictAdd(ht, key, val) == DICT_OK)
|
||||
return 1;
|
||||
/* It already exists, get the entry */
|
||||
@@ -293,7 +293,7 @@ static void dictReleaseIterator(dictIterator *iter) {
|
||||
|
||||
/* Expand the hash table if needed */
|
||||
static int _dictExpandIfNeeded(dict *ht) {
|
||||
/* If the hash table is empty expand it to the intial size,
|
||||
/* If the hash table is empty expand it to the initial size,
|
||||
* if the table is "full" dobule its size. */
|
||||
if (ht->size == 0)
|
||||
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
|
||||
|
||||
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <async.h>
|
||||
#include <adapters/glib.h>
|
||||
|
||||
static GMainLoop *mainloop;
|
||||
|
||||
static void
|
||||
connect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
|
||||
int status)
|
||||
{
|
||||
if (status != REDIS_OK) {
|
||||
g_printerr("Failed to connect: %s\n", ac->errstr);
|
||||
g_main_loop_quit(mainloop);
|
||||
} else {
|
||||
g_printerr("Connected...\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
disconnect_cb (const redisAsyncContext *ac G_GNUC_UNUSED,
|
||||
int status)
|
||||
{
|
||||
if (status != REDIS_OK) {
|
||||
g_error("Failed to disconnect: %s", ac->errstr);
|
||||
} else {
|
||||
g_printerr("Disconnected...\n");
|
||||
g_main_loop_quit(mainloop);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
command_cb(redisAsyncContext *ac,
|
||||
gpointer r,
|
||||
gpointer user_data G_GNUC_UNUSED)
|
||||
{
|
||||
redisReply *reply = r;
|
||||
|
||||
if (reply) {
|
||||
g_print("REPLY: %s\n", reply->str);
|
||||
}
|
||||
|
||||
redisAsyncDisconnect(ac);
|
||||
}
|
||||
|
||||
gint
|
||||
main (gint argc G_GNUC_UNUSED,
|
||||
gchar *argv[] G_GNUC_UNUSED)
|
||||
{
|
||||
redisAsyncContext *ac;
|
||||
GMainContext *context = NULL;
|
||||
GSource *source;
|
||||
|
||||
ac = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (ac->err) {
|
||||
g_printerr("%s\n", ac->errstr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
source = redis_source_new(ac);
|
||||
mainloop = g_main_loop_new(context, FALSE);
|
||||
g_source_attach(source, context);
|
||||
|
||||
redisAsyncSetConnectCallback(ac, connect_cb);
|
||||
redisAsyncSetDisconnectCallback(ac, disconnect_cb);
|
||||
redisAsyncCommand(ac, command_cb, NULL, "SET key 1234");
|
||||
redisAsyncCommand(ac, command_cb, NULL, "GET key");
|
||||
|
||||
g_main_loop_run(mainloop);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <async.h>
|
||||
#include <adapters/ivykis.h>
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
iv_init();
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisIvykisAttach(c);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
|
||||
iv_main();
|
||||
|
||||
iv_deinit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Created by Дмитрий Бахвалов on 13.07.15.
|
||||
// Copyright (c) 2015 Dmitry Bakhvalov. All rights reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <async.h>
|
||||
#include <adapters/macosx.h>
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
printf("Disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
CFRunLoopRef loop = CFRunLoopGetCurrent();
|
||||
if( !loop ) {
|
||||
printf("Error: Cannot get current run loop\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisMacOSAttach(c, loop);
|
||||
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
|
||||
CFRunLoopRun();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QTimer>
|
||||
|
||||
#include "example-qt.h"
|
||||
|
||||
void getCallback(redisAsyncContext *, void * r, void * privdata) {
|
||||
|
||||
redisReply * reply = static_cast<redisReply *>(r);
|
||||
ExampleQt * ex = static_cast<ExampleQt *>(privdata);
|
||||
if (reply == nullptr || ex == nullptr) return;
|
||||
|
||||
cout << "key: " << reply->str << endl;
|
||||
|
||||
ex->finish();
|
||||
}
|
||||
|
||||
void ExampleQt::run() {
|
||||
|
||||
m_ctx = redisAsyncConnect("localhost", 6379);
|
||||
|
||||
if (m_ctx->err) {
|
||||
cerr << "Error: " << m_ctx->errstr << endl;
|
||||
redisAsyncFree(m_ctx);
|
||||
emit finished();
|
||||
}
|
||||
|
||||
m_adapter.setContext(m_ctx);
|
||||
|
||||
redisAsyncCommand(m_ctx, NULL, NULL, "SET key %s", m_value);
|
||||
redisAsyncCommand(m_ctx, getCallback, this, "GET key");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
ExampleQt example(argv[argc-1]);
|
||||
|
||||
QObject::connect(&example, SIGNAL(finished()), &app, SLOT(quit()));
|
||||
QTimer::singleShot(0, &example, SLOT(run()));
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
#ifndef __HIREDIS_EXAMPLE_QT_H
|
||||
#define __HIREDIS_EXAMPLE_QT_H
|
||||
|
||||
#include <adapters/qt.h>
|
||||
|
||||
class ExampleQt : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ExampleQt(const char * value, QObject * parent = 0)
|
||||
: QObject(parent), m_value(value) {}
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
|
||||
private:
|
||||
void finish() { emit finished(); }
|
||||
|
||||
private:
|
||||
const char * m_value;
|
||||
redisAsyncContext * m_ctx;
|
||||
RedisQtAdapter m_adapter;
|
||||
|
||||
friend
|
||||
void getCallback(redisAsyncContext *, void *, void *);
|
||||
};
|
||||
|
||||
#endif /* !__HIREDIS_EXAMPLE_QT_H */
|
||||
Vendored
+1
-1
@@ -57,7 +57,7 @@ int main(int argc, char **argv) {
|
||||
for (j = 0; j < 10; j++) {
|
||||
char buf[64];
|
||||
|
||||
snprintf(buf,64,"%d",j);
|
||||
snprintf(buf,64,"%u",j);
|
||||
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
Vendored
+8
-7
@@ -1,23 +1,24 @@
|
||||
#ifndef __HIREDIS_FMACRO_H
|
||||
#define __HIREDIS_FMACRO_H
|
||||
|
||||
#if !defined(_BSD_SOURCE)
|
||||
#if defined(__linux__)
|
||||
#define _BSD_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(_AIX)
|
||||
#define _ALL_SOURCE
|
||||
#if defined(__CYGWIN__)
|
||||
#include <sys/cdefs.h>
|
||||
#endif
|
||||
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
#define _XOPEN_SOURCE 600
|
||||
#else
|
||||
#define _XOPEN_SOURCE
|
||||
#if !(defined(__APPLE__) && defined(__MACH__))
|
||||
#define _XOPEN_SOURCE 600
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if __APPLE__ && __MACH__
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define _OSX
|
||||
#endif
|
||||
|
||||
|
||||
Vendored
+191
-528
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
|
||||
* Jan-Erik Rediger <janerik at fnordig dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -73,6 +75,9 @@ void freeReplyObject(void *reply) {
|
||||
redisReply *r = reply;
|
||||
size_t j;
|
||||
|
||||
if (r == NULL)
|
||||
return;
|
||||
|
||||
switch(r->type) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
break; /* Nothing to free */
|
||||
@@ -183,504 +188,23 @@ static void *createNilObject(const redisReadTask *task) {
|
||||
return r;
|
||||
}
|
||||
|
||||
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
size_t len;
|
||||
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||
r->fn->freeObject(r->reply);
|
||||
r->reply = NULL;
|
||||
}
|
||||
|
||||
/* Clear input buffer on errors. */
|
||||
if (r->buf != NULL) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
}
|
||||
|
||||
/* Reset task stack. */
|
||||
r->ridx = -1;
|
||||
|
||||
/* Set error. */
|
||||
r->err = type;
|
||||
len = strlen(str);
|
||||
len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
|
||||
memcpy(r->errstr,str,len);
|
||||
r->errstr[len] = '\0';
|
||||
}
|
||||
|
||||
static size_t chrtos(char *buf, size_t size, char byte) {
|
||||
size_t len = 0;
|
||||
|
||||
switch(byte) {
|
||||
case '\\':
|
||||
case '"':
|
||||
len = snprintf(buf,size,"\"\\%c\"",byte);
|
||||
break;
|
||||
case '\n': len = snprintf(buf,size,"\"\\n\""); break;
|
||||
case '\r': len = snprintf(buf,size,"\"\\r\""); break;
|
||||
case '\t': len = snprintf(buf,size,"\"\\t\""); break;
|
||||
case '\a': len = snprintf(buf,size,"\"\\a\""); break;
|
||||
case '\b': len = snprintf(buf,size,"\"\\b\""); break;
|
||||
default:
|
||||
if (isprint(byte))
|
||||
len = snprintf(buf,size,"\"%c\"",byte);
|
||||
else
|
||||
len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
|
||||
break;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
|
||||
char cbuf[8], sbuf[128];
|
||||
|
||||
chrtos(cbuf,sizeof(cbuf),byte);
|
||||
snprintf(sbuf,sizeof(sbuf),
|
||||
"Protocol error, got %s as reply type byte", cbuf);
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
|
||||
}
|
||||
|
||||
static void __redisReaderSetErrorOOM(redisReader *r) {
|
||||
__redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
|
||||
}
|
||||
|
||||
static char *readBytes(redisReader *r, unsigned int bytes) {
|
||||
char *p;
|
||||
if (r->len-r->pos >= bytes) {
|
||||
p = r->buf+r->pos;
|
||||
r->pos += bytes;
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find pointer to \r\n. */
|
||||
static char *seekNewline(char *s, size_t len) {
|
||||
int pos = 0;
|
||||
int _len = len-1;
|
||||
|
||||
/* Position should be < len-1 because the character at "pos" should be
|
||||
* followed by a \n. Note that strchr cannot be used because it doesn't
|
||||
* allow to search a limited length and the buffer that is being searched
|
||||
* might not have a trailing NULL character. */
|
||||
while (pos < _len) {
|
||||
while(pos < _len && s[pos] != '\r') pos++;
|
||||
if (s[pos] != '\r') {
|
||||
/* Not found. */
|
||||
return NULL;
|
||||
} else {
|
||||
if (s[pos+1] == '\n') {
|
||||
/* Found. */
|
||||
return s+pos;
|
||||
} else {
|
||||
/* Continue searching. */
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read a long long value starting at *s, under the assumption that it will be
|
||||
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
|
||||
static long long readLongLong(char *s) {
|
||||
long long v = 0;
|
||||
int dec, mult = 1;
|
||||
char c;
|
||||
|
||||
if (*s == '-') {
|
||||
mult = -1;
|
||||
s++;
|
||||
} else if (*s == '+') {
|
||||
mult = 1;
|
||||
s++;
|
||||
}
|
||||
|
||||
while ((c = *(s++)) != '\r') {
|
||||
dec = c - '0';
|
||||
if (dec >= 0 && dec < 10) {
|
||||
v *= 10;
|
||||
v += dec;
|
||||
} else {
|
||||
/* Should not happen... */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return mult*v;
|
||||
}
|
||||
|
||||
static char *readLine(redisReader *r, int *_len) {
|
||||
char *p, *s;
|
||||
int len;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p,(r->len-r->pos));
|
||||
if (s != NULL) {
|
||||
len = s-(r->buf+r->pos);
|
||||
r->pos += len+2; /* skip \r\n */
|
||||
if (_len) *_len = len;
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void moveToNextTask(redisReader *r) {
|
||||
redisReadTask *cur, *prv;
|
||||
while (r->ridx >= 0) {
|
||||
/* Return a.s.a.p. when the stack is now empty. */
|
||||
if (r->ridx == 0) {
|
||||
r->ridx--;
|
||||
return;
|
||||
}
|
||||
|
||||
cur = &(r->rstack[r->ridx]);
|
||||
prv = &(r->rstack[r->ridx-1]);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY);
|
||||
if (cur->idx == prv->elements-1) {
|
||||
r->ridx--;
|
||||
} else {
|
||||
/* Reset the type because the next item can be anything */
|
||||
assert(cur->idx < prv->elements);
|
||||
cur->type = -1;
|
||||
cur->elements = -1;
|
||||
cur->idx++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int processLineItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
int len;
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
if (r->fn && r->fn->createInteger)
|
||||
obj = r->fn->createInteger(cur,readLongLong(p));
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_INTEGER;
|
||||
} else {
|
||||
/* Type will be error or status. */
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,p,len);
|
||||
else
|
||||
obj = (void*)(size_t)(cur->type);
|
||||
}
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Set reply if this is the root object. */
|
||||
if (r->ridx == 0) r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj = NULL;
|
||||
char *p, *s;
|
||||
long len;
|
||||
unsigned long bytelen;
|
||||
int success = 0;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p,r->len-r->pos);
|
||||
if (s != NULL) {
|
||||
p = r->buf+r->pos;
|
||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||
len = readLongLong(p);
|
||||
|
||||
if (len < 0) {
|
||||
/* The nil object can always be created. */
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_NIL;
|
||||
success = 1;
|
||||
} else {
|
||||
/* Only continue when the buffer contains the entire bulk item. */
|
||||
bytelen += len+2; /* include \r\n */
|
||||
if (r->pos+bytelen <= r->len) {
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,s+2,len);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_STRING;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Proceed when obj was created. */
|
||||
if (success) {
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
r->pos += bytelen;
|
||||
|
||||
/* Set reply if this is the root object. */
|
||||
if (r->ridx == 0) r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processMultiBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
long elements;
|
||||
int root = 0;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 7 */
|
||||
if (r->ridx == 8) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"No support for nested multi bulk replies with depth > 7");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,NULL)) != NULL) {
|
||||
elements = readLongLong(p);
|
||||
root = (r->ridx == 0);
|
||||
|
||||
if (elements == -1) {
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_NIL;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
moveToNextTask(r);
|
||||
} else {
|
||||
if (r->fn && r->fn->createArray)
|
||||
obj = r->fn->createArray(cur,elements);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_ARRAY;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Modify task stack when there are more than 0 elements. */
|
||||
if (elements > 0) {
|
||||
cur->elements = elements;
|
||||
cur->obj = obj;
|
||||
r->ridx++;
|
||||
r->rstack[r->ridx].type = -1;
|
||||
r->rstack[r->ridx].elements = -1;
|
||||
r->rstack[r->ridx].idx = 0;
|
||||
r->rstack[r->ridx].obj = NULL;
|
||||
r->rstack[r->ridx].parent = cur;
|
||||
r->rstack[r->ridx].privdata = r->privdata;
|
||||
} else {
|
||||
moveToNextTask(r);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set reply if this is the root object. */
|
||||
if (root) r->reply = obj;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
char *p;
|
||||
|
||||
/* check if we need to read type */
|
||||
if (cur->type < 0) {
|
||||
if ((p = readBytes(r,1)) != NULL) {
|
||||
switch (p[0]) {
|
||||
case '-':
|
||||
cur->type = REDIS_REPLY_ERROR;
|
||||
break;
|
||||
case '+':
|
||||
cur->type = REDIS_REPLY_STATUS;
|
||||
break;
|
||||
case ':':
|
||||
cur->type = REDIS_REPLY_INTEGER;
|
||||
break;
|
||||
case '$':
|
||||
cur->type = REDIS_REPLY_STRING;
|
||||
break;
|
||||
case '*':
|
||||
cur->type = REDIS_REPLY_ARRAY;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else {
|
||||
/* could not consume 1 byte */
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* process typed item */
|
||||
switch(cur->type) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_INTEGER:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
return processMultiBulkItem(r);
|
||||
default:
|
||||
assert(NULL);
|
||||
return REDIS_ERR; /* Avoid warning. */
|
||||
}
|
||||
}
|
||||
|
||||
redisReader *redisReaderCreate(void) {
|
||||
redisReader *r;
|
||||
|
||||
r = calloc(sizeof(redisReader),1);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->err = 0;
|
||||
r->errstr[0] = '\0';
|
||||
r->fn = &defaultFunctions;
|
||||
r->buf = sdsempty();
|
||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||
if (r->buf == NULL) {
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->ridx = -1;
|
||||
return r;
|
||||
}
|
||||
|
||||
void redisReaderFree(redisReader *r) {
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||
r->fn->freeObject(r->reply);
|
||||
if (r->buf != NULL)
|
||||
sdsfree(r->buf);
|
||||
free(r);
|
||||
}
|
||||
|
||||
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
|
||||
sds newbuf;
|
||||
|
||||
/* Return early when this reader is in an erroneous state. */
|
||||
if (r->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Copy the provided buffer. */
|
||||
if (buf != NULL && len >= 1) {
|
||||
/* Destroy internal buffer when it is empty and is quite large. */
|
||||
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
r->pos = 0;
|
||||
|
||||
/* r->buf should not be NULL since we just free'd a larger one. */
|
||||
assert(r->buf != NULL);
|
||||
}
|
||||
|
||||
newbuf = sdscatlen(r->buf,buf,len);
|
||||
if (newbuf == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
r->buf = newbuf;
|
||||
r->len = sdslen(r->buf);
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisReaderGetReply(redisReader *r, void **reply) {
|
||||
/* Default target pointer to NULL. */
|
||||
if (reply != NULL)
|
||||
*reply = NULL;
|
||||
|
||||
/* Return early when this reader is in an erroneous state. */
|
||||
if (r->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* When the buffer is empty, there will never be a reply. */
|
||||
if (r->len == 0)
|
||||
return REDIS_OK;
|
||||
|
||||
/* Set first item to process when the stack is empty. */
|
||||
if (r->ridx == -1) {
|
||||
r->rstack[0].type = -1;
|
||||
r->rstack[0].elements = -1;
|
||||
r->rstack[0].idx = -1;
|
||||
r->rstack[0].obj = NULL;
|
||||
r->rstack[0].parent = NULL;
|
||||
r->rstack[0].privdata = r->privdata;
|
||||
r->ridx = 0;
|
||||
}
|
||||
|
||||
/* Process items in reply. */
|
||||
while (r->ridx >= 0)
|
||||
if (processItem(r) != REDIS_OK)
|
||||
break;
|
||||
|
||||
/* Return ASAP when an error occurred. */
|
||||
if (r->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Discard part of the buffer when we've consumed at least 1k, to avoid
|
||||
* doing unnecessary calls to memmove() in sds.c. */
|
||||
if (r->pos >= 1024) {
|
||||
sdsrange(r->buf,r->pos,-1);
|
||||
r->pos = 0;
|
||||
r->len = sdslen(r->buf);
|
||||
}
|
||||
|
||||
/* Emit a reply when there is one. */
|
||||
if (r->ridx == -1) {
|
||||
if (reply != NULL)
|
||||
*reply = r->reply;
|
||||
r->reply = NULL;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
/* Calculate the number of bytes needed to represent an integer as string. */
|
||||
static int intlen(int i) {
|
||||
int len = 0;
|
||||
if (i < 0) {
|
||||
len++;
|
||||
i = -i;
|
||||
}
|
||||
do {
|
||||
len++;
|
||||
i /= 10;
|
||||
} while(i);
|
||||
return len;
|
||||
/* Return the number of digits of 'v' when converted to string in radix 10.
|
||||
* Implementation borrowed from link in redis/src/util.c:string2ll(). */
|
||||
static uint32_t countDigits(uint64_t v) {
|
||||
uint32_t result = 1;
|
||||
for (;;) {
|
||||
if (v < 10) return result;
|
||||
if (v < 100) return result + 1;
|
||||
if (v < 1000) return result + 2;
|
||||
if (v < 10000) return result + 3;
|
||||
v /= 10000U;
|
||||
result += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper that calculates the bulk length given a certain string length. */
|
||||
static size_t bulklen(size_t len) {
|
||||
return 1+intlen(len)+2+len+2;
|
||||
return 1+countDigits(len)+2+len+2;
|
||||
}
|
||||
|
||||
int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
@@ -692,6 +216,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
char **curargv = NULL, **newargv = NULL;
|
||||
int argc = 0;
|
||||
int totlen = 0;
|
||||
int error_type = 0; /* 0 = no error; -1 = memory error; -2 = format error */
|
||||
int j;
|
||||
|
||||
/* Abort if there is not target to set */
|
||||
@@ -708,19 +233,19 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
if (*c == ' ') {
|
||||
if (touched) {
|
||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
||||
if (newargv == NULL) goto err;
|
||||
if (newargv == NULL) goto memory_err;
|
||||
curargv = newargv;
|
||||
curargv[argc++] = curarg;
|
||||
totlen += bulklen(sdslen(curarg));
|
||||
|
||||
/* curarg is put in argv so it can be overwritten. */
|
||||
curarg = sdsempty();
|
||||
if (curarg == NULL) goto err;
|
||||
if (curarg == NULL) goto memory_err;
|
||||
touched = 0;
|
||||
}
|
||||
} else {
|
||||
newarg = sdscatlen(curarg,c,1);
|
||||
if (newarg == NULL) goto err;
|
||||
if (newarg == NULL) goto memory_err;
|
||||
curarg = newarg;
|
||||
touched = 1;
|
||||
}
|
||||
@@ -751,17 +276,14 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
/* Try to detect printf format */
|
||||
{
|
||||
static const char intfmts[] = "diouxX";
|
||||
static const char flags[] = "#0-+ ";
|
||||
char _format[16];
|
||||
const char *_p = c+1;
|
||||
size_t _l = 0;
|
||||
va_list _cpy;
|
||||
|
||||
/* Flags */
|
||||
if (*_p != '\0' && *_p == '#') _p++;
|
||||
if (*_p != '\0' && *_p == '0') _p++;
|
||||
if (*_p != '\0' && *_p == '-') _p++;
|
||||
if (*_p != '\0' && *_p == ' ') _p++;
|
||||
if (*_p != '\0' && *_p == '+') _p++;
|
||||
while (*_p != '\0' && strchr(flags,*_p) != NULL) _p++;
|
||||
|
||||
/* Field width */
|
||||
while (*_p != '\0' && isdigit(*_p)) _p++;
|
||||
@@ -829,7 +351,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
|
||||
fmt_invalid:
|
||||
va_end(_cpy);
|
||||
goto err;
|
||||
goto format_err;
|
||||
|
||||
fmt_valid:
|
||||
_l = (_p+1)-c;
|
||||
@@ -848,7 +370,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
}
|
||||
}
|
||||
|
||||
if (newarg == NULL) goto err;
|
||||
if (newarg == NULL) goto memory_err;
|
||||
curarg = newarg;
|
||||
|
||||
touched = 1;
|
||||
@@ -860,7 +382,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
/* Add the last argument if needed */
|
||||
if (touched) {
|
||||
newargv = realloc(curargv,sizeof(char*)*(argc+1));
|
||||
if (newargv == NULL) goto err;
|
||||
if (newargv == NULL) goto memory_err;
|
||||
curargv = newargv;
|
||||
curargv[argc++] = curarg;
|
||||
totlen += bulklen(sdslen(curarg));
|
||||
@@ -872,11 +394,11 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
curarg = NULL;
|
||||
|
||||
/* Add bytes needed to hold multi bulk count */
|
||||
totlen += 1+intlen(argc)+2;
|
||||
totlen += 1+countDigits(argc)+2;
|
||||
|
||||
/* Build the command at protocol level */
|
||||
cmd = malloc(totlen+1);
|
||||
if (cmd == NULL) goto err;
|
||||
if (cmd == NULL) goto memory_err;
|
||||
|
||||
pos = sprintf(cmd,"*%d\r\n",argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
@@ -894,20 +416,29 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
*target = cmd;
|
||||
return totlen;
|
||||
|
||||
err:
|
||||
while(argc--)
|
||||
sdsfree(curargv[argc]);
|
||||
free(curargv);
|
||||
format_err:
|
||||
error_type = -2;
|
||||
goto cleanup;
|
||||
|
||||
if (curarg != NULL)
|
||||
sdsfree(curarg);
|
||||
memory_err:
|
||||
error_type = -1;
|
||||
goto cleanup;
|
||||
|
||||
cleanup:
|
||||
if (curargv) {
|
||||
while(argc--)
|
||||
sdsfree(curargv[argc]);
|
||||
free(curargv);
|
||||
}
|
||||
|
||||
sdsfree(curarg);
|
||||
|
||||
/* No need to check cmd since it is the last statement that can fail,
|
||||
* but do it anyway to be as defensive as possible. */
|
||||
if (cmd != NULL)
|
||||
free(cmd);
|
||||
|
||||
return -1;
|
||||
return error_type;
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol. This function
|
||||
@@ -928,9 +459,69 @@ int redisFormatCommand(char **target, const char *format, ...) {
|
||||
va_start(ap,format);
|
||||
len = redisvFormatCommand(target,format,ap);
|
||||
va_end(ap);
|
||||
|
||||
/* The API says "-1" means bad result, but we now also return "-2" in some
|
||||
* cases. Force the return value to always be -1. */
|
||||
if (len < 0)
|
||||
len = -1;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol using an sds string and
|
||||
* sdscatfmt for the processing of arguments. This function takes the
|
||||
* number of arguments, an array with arguments and an array with their
|
||||
* lengths. If the latter is set to NULL, strlen will be used to compute the
|
||||
* argument lengths.
|
||||
*/
|
||||
int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
|
||||
const size_t *argvlen)
|
||||
{
|
||||
sds cmd;
|
||||
unsigned long long totlen;
|
||||
int j;
|
||||
size_t len;
|
||||
|
||||
/* Abort on a NULL target */
|
||||
if (target == NULL)
|
||||
return -1;
|
||||
|
||||
/* Calculate our total size */
|
||||
totlen = 1+countDigits(argc)+2;
|
||||
for (j = 0; j < argc; j++) {
|
||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
||||
totlen += bulklen(len);
|
||||
}
|
||||
|
||||
/* Use an SDS string for command construction */
|
||||
cmd = sdsempty();
|
||||
if (cmd == NULL)
|
||||
return -1;
|
||||
|
||||
/* We already know how much storage we need */
|
||||
cmd = sdsMakeRoomFor(cmd, totlen);
|
||||
if (cmd == NULL)
|
||||
return -1;
|
||||
|
||||
/* Construct command */
|
||||
cmd = sdscatfmt(cmd, "*%i\r\n", argc);
|
||||
for (j=0; j < argc; j++) {
|
||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
||||
cmd = sdscatfmt(cmd, "$%u\r\n", len);
|
||||
cmd = sdscatlen(cmd, argv[j], len);
|
||||
cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
|
||||
}
|
||||
|
||||
assert(sdslen(cmd)==totlen);
|
||||
|
||||
*target = cmd;
|
||||
return totlen;
|
||||
}
|
||||
|
||||
void redisFreeSdsCommand(sds cmd) {
|
||||
sdsfree(cmd);
|
||||
}
|
||||
|
||||
/* Format a command according to the Redis protocol. This function takes the
|
||||
* number of arguments, an array with arguments and an array with their
|
||||
* lengths. If the latter is set to NULL, strlen will be used to compute the
|
||||
@@ -942,8 +533,12 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
|
||||
size_t len;
|
||||
int totlen, j;
|
||||
|
||||
/* Abort on a NULL target */
|
||||
if (target == NULL)
|
||||
return -1;
|
||||
|
||||
/* Calculate number of bytes needed for the command */
|
||||
totlen = 1+intlen(argc)+2;
|
||||
totlen = 1+countDigits(argc)+2;
|
||||
for (j = 0; j < argc; j++) {
|
||||
len = argvlen ? argvlen[j] : strlen(argv[j]);
|
||||
totlen += bulklen(len);
|
||||
@@ -970,6 +565,10 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
|
||||
return totlen;
|
||||
}
|
||||
|
||||
void redisFreeCommand(char *cmd) {
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
void __redisSetError(redisContext *c, int type, const char *str) {
|
||||
size_t len;
|
||||
|
||||
@@ -982,10 +581,14 @@ void __redisSetError(redisContext *c, int type, const char *str) {
|
||||
} else {
|
||||
/* Only REDIS_ERR_IO may lack a description! */
|
||||
assert(type == REDIS_ERR_IO);
|
||||
strerror_r(errno,c->errstr,sizeof(c->errstr));
|
||||
__redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
|
||||
}
|
||||
}
|
||||
|
||||
redisReader *redisReaderCreate(void) {
|
||||
return redisReaderCreateWithFunctions(&defaultFunctions);
|
||||
}
|
||||
|
||||
static redisContext *redisContextInit(void) {
|
||||
redisContext *c;
|
||||
|
||||
@@ -997,24 +600,72 @@ static redisContext *redisContextInit(void) {
|
||||
c->errstr[0] = '\0';
|
||||
c->obuf = sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
c->tcp.host = NULL;
|
||||
c->tcp.source_addr = NULL;
|
||||
c->unix_sock.path = NULL;
|
||||
c->timeout = NULL;
|
||||
|
||||
if (c->obuf == NULL || c->reader == NULL) {
|
||||
redisFree(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void redisFree(redisContext *c) {
|
||||
if (c == NULL)
|
||||
return;
|
||||
if (c->fd > 0)
|
||||
close(c->fd);
|
||||
if (c->obuf != NULL)
|
||||
sdsfree(c->obuf);
|
||||
if (c->reader != NULL)
|
||||
redisReaderFree(c->reader);
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
if (c->tcp.source_addr)
|
||||
free(c->tcp.source_addr);
|
||||
if (c->unix_sock.path)
|
||||
free(c->unix_sock.path);
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
free(c);
|
||||
}
|
||||
|
||||
int redisFreeKeepFd(redisContext *c) {
|
||||
int fd = c->fd;
|
||||
c->fd = -1;
|
||||
redisFree(c);
|
||||
return fd;
|
||||
int fd = c->fd;
|
||||
c->fd = -1;
|
||||
redisFree(c);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int redisReconnect(redisContext *c) {
|
||||
c->err = 0;
|
||||
memset(c->errstr, '\0', strlen(c->errstr));
|
||||
|
||||
if (c->fd > 0) {
|
||||
close(c->fd);
|
||||
}
|
||||
|
||||
sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
|
||||
c->obuf = sdsempty();
|
||||
c->reader = redisReaderCreate();
|
||||
|
||||
if (c->connection_type == REDIS_CONN_TCP) {
|
||||
return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
|
||||
c->timeout, c->tcp.source_addr);
|
||||
} else if (c->connection_type == REDIS_CONN_UNIX) {
|
||||
return redisContextConnectUnix(c, c->unix_sock.path, c->timeout);
|
||||
} else {
|
||||
/* Something bad happened here and shouldn't have. There isn't
|
||||
enough information in the context to reconnect. */
|
||||
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
|
||||
}
|
||||
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Connect to a Redis instance. On error the field error in the returned
|
||||
@@ -1064,6 +715,15 @@ redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
const char *source_addr) {
|
||||
redisContext *c = redisContextInit();
|
||||
c->flags &= ~REDIS_BLOCK;
|
||||
c->flags |= REDIS_REUSEADDR;
|
||||
redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
|
||||
return c;
|
||||
}
|
||||
|
||||
redisContext *redisConnectUnix(const char *path) {
|
||||
redisContext *c;
|
||||
|
||||
@@ -1162,10 +822,10 @@ int redisBufferRead(redisContext *c) {
|
||||
/* Write the output buffer to the socket.
|
||||
*
|
||||
* Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
|
||||
* succesfully written to the socket. When the buffer is empty after the
|
||||
* successfully written to the socket. When the buffer is empty after the
|
||||
* write operation, "done" is set to 1 (if given).
|
||||
*
|
||||
* Returns REDIS_ERR if an error occured trying to write and sets
|
||||
* Returns REDIS_ERR if an error occurred trying to write and sets
|
||||
* c->errstr to hold the appropriate error string.
|
||||
*/
|
||||
int redisBufferWrite(redisContext *c, int *done) {
|
||||
@@ -1274,6 +934,9 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
|
||||
if (len == -1) {
|
||||
__redisSetError(c,REDIS_ERR_OOM,"Out of memory");
|
||||
return REDIS_ERR;
|
||||
} else if (len == -2) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,"Invalid format string");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
|
||||
@@ -1296,21 +959,21 @@ int redisAppendCommand(redisContext *c, const char *format, ...) {
|
||||
}
|
||||
|
||||
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd;
|
||||
sds cmd;
|
||||
int len;
|
||||
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
|
||||
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
|
||||
if (len == -1) {
|
||||
__redisSetError(c,REDIS_ERR_OOM,"Out of memory");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
|
||||
free(cmd);
|
||||
sdsfree(cmd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
free(cmd);
|
||||
sdsfree(cmd);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -1321,7 +984,7 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s
|
||||
* context is non-blocking, the "reply" pointer will not be used and the
|
||||
* command is simply appended to the write buffer.
|
||||
*
|
||||
* Returns the reply when a reply was succesfully retrieved. Returns NULL
|
||||
* Returns the reply when a reply was successfully retrieved. Returns NULL
|
||||
* otherwise. When NULL is returned in a blocking context, the error field
|
||||
* in the context will be set.
|
||||
*/
|
||||
|
||||
Vendored
+78
-75
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
|
||||
* Jan-Erik Rediger <janerik at fnordig dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -31,26 +33,16 @@
|
||||
|
||||
#ifndef __HIREDIS_H
|
||||
#define __HIREDIS_H
|
||||
#include <stdio.h> /* for size_t */
|
||||
#include "read.h"
|
||||
#include <stdarg.h> /* for va_list */
|
||||
#include <sys/time.h> /* for struct timeval */
|
||||
#include <stdint.h> /* uintXX_t, etc */
|
||||
#include "sds.h" /* for sds */
|
||||
|
||||
#define HIREDIS_MAJOR 0
|
||||
#define HIREDIS_MINOR 11
|
||||
#define HIREDIS_PATCH 0
|
||||
|
||||
#define REDIS_ERR -1
|
||||
#define REDIS_OK 0
|
||||
|
||||
/* When an error occurs, the err flag in a context is set to hold the type of
|
||||
* error that occured. REDIS_ERR_IO means there was an I/O error and you
|
||||
* should use the "errno" variable to find out what is wrong.
|
||||
* For other values, the "errstr" field will hold a description. */
|
||||
#define REDIS_ERR_IO 1 /* Error in read or write */
|
||||
#define REDIS_ERR_EOF 3 /* End of file */
|
||||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
||||
#define REDIS_ERR_OOM 5 /* Out of memory */
|
||||
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
||||
#define HIREDIS_MINOR 13
|
||||
#define HIREDIS_PATCH 3
|
||||
#define HIREDIS_SONAME 0.13
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
@@ -79,17 +71,39 @@
|
||||
/* Flag that is set when monitor mode is active */
|
||||
#define REDIS_MONITORING 0x40
|
||||
|
||||
#define REDIS_REPLY_STRING 1
|
||||
#define REDIS_REPLY_ARRAY 2
|
||||
#define REDIS_REPLY_INTEGER 3
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
|
||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
|
||||
#define REDIS_REUSEADDR 0x80
|
||||
|
||||
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
|
||||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
||||
* SO_REUSEADDR is being used. */
|
||||
#define REDIS_CONNECT_RETRIES 10
|
||||
|
||||
/* strerror_r has two completely different prototypes and behaviors
|
||||
* depending on system issues, so we need to operate on the error buffer
|
||||
* differently depending on which strerror_r we're using. */
|
||||
#ifndef _GNU_SOURCE
|
||||
/* "regular" POSIX strerror_r that does the right thing. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
strerror_r((errno), (buf), (len)); \
|
||||
} while (0)
|
||||
#else
|
||||
/* "bad" GNU strerror_r we need to clean up after. */
|
||||
#define __redis_strerror_r(errno, buf, len) \
|
||||
do { \
|
||||
char *err_str = strerror_r((errno), (buf), (len)); \
|
||||
/* If return value _isn't_ the start of the buffer we passed in, \
|
||||
* then GNU strerror_r returned an internal static buffer and we \
|
||||
* need to copy the result into our private buffer. */ \
|
||||
if (err_str != (buf)) { \
|
||||
strncpy((buf), err_str, ((len) - 1)); \
|
||||
buf[(len)-1] = '\0'; \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -98,61 +112,13 @@ extern "C" {
|
||||
typedef struct redisReply {
|
||||
int type; /* REDIS_REPLY_* */
|
||||
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
|
||||
int len; /* Length of string */
|
||||
size_t len; /* Length of string */
|
||||
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
|
||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} redisReply;
|
||||
|
||||
typedef struct redisReadTask {
|
||||
int type;
|
||||
int elements; /* number of elements in multibulk container */
|
||||
int idx; /* index in parent (array) object */
|
||||
void *obj; /* holds user-generated value for a read task */
|
||||
struct redisReadTask *parent; /* parent task */
|
||||
void *privdata; /* user-settable arbitrary field */
|
||||
} redisReadTask;
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
void *(*createInteger)(const redisReadTask*, long long);
|
||||
void *(*createNil)(const redisReadTask*);
|
||||
void (*freeObject)(void*);
|
||||
} redisReplyObjectFunctions;
|
||||
|
||||
/* State for the protocol parser */
|
||||
typedef struct redisReader {
|
||||
int err; /* Error flags, 0 when there is no error */
|
||||
char errstr[128]; /* String representation of error when applicable */
|
||||
|
||||
char *buf; /* Read buffer */
|
||||
size_t pos; /* Buffer cursor */
|
||||
size_t len; /* Buffer length */
|
||||
size_t maxbuf; /* Max length of unused buffer */
|
||||
|
||||
redisReadTask rstack[9];
|
||||
int ridx; /* Index of current read task */
|
||||
void *reply; /* Temporary reply pointer */
|
||||
|
||||
redisReplyObjectFunctions *fn;
|
||||
void *privdata;
|
||||
} redisReader;
|
||||
|
||||
/* Public API for the protocol parser. */
|
||||
redisReader *redisReaderCreate(void);
|
||||
void redisReaderFree(redisReader *r);
|
||||
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
|
||||
int redisReaderGetReply(redisReader *r, void **reply);
|
||||
|
||||
/* Backwards compatibility, can be removed on big version bump. */
|
||||
#define redisReplyReaderCreate redisReaderCreate
|
||||
#define redisReplyReaderFree redisReaderFree
|
||||
#define redisReplyReaderFeed redisReaderFeed
|
||||
#define redisReplyReaderGetReply redisReaderGetReply
|
||||
#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
|
||||
#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
|
||||
#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
|
||||
|
||||
/* Function to free the reply objects hiredis returns by default. */
|
||||
void freeReplyObject(void *reply);
|
||||
@@ -161,6 +127,14 @@ void freeReplyObject(void *reply);
|
||||
int redisvFormatCommand(char **target, const char *format, va_list ap);
|
||||
int redisFormatCommand(char **target, const char *format, ...);
|
||||
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
|
||||
int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen);
|
||||
void redisFreeCommand(char *cmd);
|
||||
void redisFreeSdsCommand(sds cmd);
|
||||
|
||||
enum redisConnectionType {
|
||||
REDIS_CONN_TCP,
|
||||
REDIS_CONN_UNIX
|
||||
};
|
||||
|
||||
/* Context for a connection to Redis */
|
||||
typedef struct redisContext {
|
||||
@@ -170,16 +144,45 @@ typedef struct redisContext {
|
||||
int flags;
|
||||
char *obuf; /* Write buffer */
|
||||
redisReader *reader; /* Protocol reader */
|
||||
|
||||
enum redisConnectionType connection_type;
|
||||
struct timeval *timeout;
|
||||
|
||||
struct {
|
||||
char *host;
|
||||
char *source_addr;
|
||||
int port;
|
||||
} tcp;
|
||||
|
||||
struct {
|
||||
char *path;
|
||||
} unix_sock;
|
||||
|
||||
} redisContext;
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||
redisContext *redisConnectBindNonBlock(const char *ip, int port, const char *source_addr);
|
||||
redisContext *redisConnectBindNonBlock(const char *ip, int port,
|
||||
const char *source_addr);
|
||||
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
|
||||
const char *source_addr);
|
||||
redisContext *redisConnectUnix(const char *path);
|
||||
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
|
||||
redisContext *redisConnectUnixNonBlock(const char *path);
|
||||
redisContext *redisConnectFd(int fd);
|
||||
|
||||
/**
|
||||
* Reconnect the given context using the saved information.
|
||||
*
|
||||
* This re-uses the exact same connect options as in the initial connection.
|
||||
* host, ip (or path), timeout and bind address are reused,
|
||||
* flags are used unmodified from the existing context.
|
||||
*
|
||||
* Returns REDIS_OK on successful connect or REDIS_ERR otherwise.
|
||||
*/
|
||||
int redisReconnect(redisContext *c);
|
||||
|
||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||
int redisEnableKeepAlive(redisContext *c);
|
||||
void redisFree(redisContext *c);
|
||||
|
||||
Vendored
+113
-19
@@ -1,7 +1,9 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
*
|
||||
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
|
||||
* Jan-Erik Rediger <janerik at fnordig dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -47,6 +49,7 @@
|
||||
#include <stdio.h>
|
||||
#include <poll.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
@@ -67,7 +70,7 @@ static void __redisSetErrorFromErrno(redisContext *c, int type, const char *pref
|
||||
|
||||
if (prefix != NULL)
|
||||
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
|
||||
strerror_r(errno,buf+len,sizeof(buf)-len);
|
||||
__redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
|
||||
__redisSetError(c,type,buf);
|
||||
}
|
||||
|
||||
@@ -138,7 +141,7 @@ int redisKeepAlive(redisContext *c, int interval) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#else
|
||||
#ifndef __sun
|
||||
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
|
||||
val = interval;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
@@ -175,19 +178,15 @@ static int redisSetTcpNoDelay(redisContext *c) {
|
||||
|
||||
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
|
||||
|
||||
static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) {
|
||||
struct pollfd wfd[1];
|
||||
long msec;
|
||||
|
||||
msec = -1;
|
||||
wfd[0].fd = c->fd;
|
||||
wfd[0].events = POLLOUT;
|
||||
static int redisContextTimeoutMsec(redisContext *c, long *result)
|
||||
{
|
||||
const struct timeval *timeout = c->timeout;
|
||||
long msec = -1;
|
||||
|
||||
/* Only use timeout when not NULL. */
|
||||
if (timeout != NULL) {
|
||||
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
|
||||
__redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
|
||||
redisContextCloseFd(c);
|
||||
*result = msec;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -198,6 +197,16 @@ static int redisContextWaitReady(redisContext *c, const struct timeval *timeout)
|
||||
}
|
||||
}
|
||||
|
||||
*result = msec;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redisContextWaitReady(redisContext *c, long msec) {
|
||||
struct pollfd wfd[1];
|
||||
|
||||
wfd[0].fd = c->fd;
|
||||
wfd[0].events = POLLOUT;
|
||||
|
||||
if (errno == EINPROGRESS) {
|
||||
int res;
|
||||
|
||||
@@ -256,10 +265,57 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
const struct timeval *timeout,
|
||||
const char *source_addr) {
|
||||
int s, rv;
|
||||
int s, rv, n;
|
||||
char _port[6]; /* strlen("65535"); */
|
||||
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
int reuseaddr = (c->flags & REDIS_REUSEADDR);
|
||||
int reuses = 0;
|
||||
long timeout_msec = -1;
|
||||
|
||||
servinfo = NULL;
|
||||
c->connection_type = REDIS_CONN_TCP;
|
||||
c->tcp.port = port;
|
||||
|
||||
/* We need to take possession of the passed parameters
|
||||
* to make them reusable for a reconnect.
|
||||
* We also carefully check we don't free data we already own,
|
||||
* as in the case of the reconnect method.
|
||||
*
|
||||
* This is a bit ugly, but atleast it works and doesn't leak memory.
|
||||
**/
|
||||
if (c->tcp.host != addr) {
|
||||
if (c->tcp.host)
|
||||
free(c->tcp.host);
|
||||
|
||||
c->tcp.host = strdup(addr);
|
||||
}
|
||||
|
||||
if (timeout) {
|
||||
if (c->timeout != timeout) {
|
||||
if (c->timeout == NULL)
|
||||
c->timeout = malloc(sizeof(struct timeval));
|
||||
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
|
||||
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (source_addr == NULL) {
|
||||
free(c->tcp.source_addr);
|
||||
c->tcp.source_addr = NULL;
|
||||
} else if (c->tcp.source_addr != source_addr) {
|
||||
free(c->tcp.source_addr);
|
||||
c->tcp.source_addr = strdup(source_addr);
|
||||
}
|
||||
|
||||
snprintf(_port, 6, "%d", port);
|
||||
memset(&hints,0,sizeof(hints));
|
||||
@@ -271,7 +327,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
* as this would add latency to every connect. Otherwise a more sensible
|
||||
* route could be: Use IPv6 if both addresses are available and there is IPv6
|
||||
* connectivity. */
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
|
||||
hints.ai_family = AF_INET6;
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
|
||||
@@ -279,21 +335,31 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
}
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
addrretry:
|
||||
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
|
||||
continue;
|
||||
|
||||
c->fd = s;
|
||||
if (redisSetBlocking(c,0) != REDIS_OK)
|
||||
goto error;
|
||||
if (source_addr) {
|
||||
if (c->tcp.source_addr) {
|
||||
int bound = 0;
|
||||
/* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
|
||||
if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) {
|
||||
if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
|
||||
char buf[128];
|
||||
snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
|
||||
__redisSetError(c,REDIS_ERR_OTHER,buf);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (reuseaddr) {
|
||||
n = 1;
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
|
||||
sizeof(n)) < 0) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
for (b = bservinfo; b != NULL; b = b->ai_next) {
|
||||
if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
|
||||
bound = 1;
|
||||
@@ -314,8 +380,15 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
continue;
|
||||
} else if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
|
||||
if (++reuses >= REDIS_CONNECT_RETRIES) {
|
||||
goto error;
|
||||
} else {
|
||||
redisContextCloseFd(c);
|
||||
goto addrretry;
|
||||
}
|
||||
} else {
|
||||
if (redisContextWaitReady(c,timeout) != REDIS_OK)
|
||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
@@ -356,19 +429,40 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
|
||||
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
struct sockaddr_un sa;
|
||||
long timeout_msec = -1;
|
||||
|
||||
if (redisCreateSocket(c,AF_LOCAL) < 0)
|
||||
return REDIS_ERR;
|
||||
if (redisSetBlocking(c,0) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
c->connection_type = REDIS_CONN_UNIX;
|
||||
if (c->unix_sock.path != path)
|
||||
c->unix_sock.path = strdup(path);
|
||||
|
||||
if (timeout) {
|
||||
if (c->timeout != timeout) {
|
||||
if (c->timeout == NULL)
|
||||
c->timeout = malloc(sizeof(struct timeval));
|
||||
|
||||
memcpy(c->timeout, timeout, sizeof(struct timeval));
|
||||
}
|
||||
} else {
|
||||
if (c->timeout)
|
||||
free(c->timeout);
|
||||
c->timeout = NULL;
|
||||
}
|
||||
|
||||
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else {
|
||||
if (redisContextWaitReady(c,timeout) != REDIS_OK)
|
||||
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+5
-3
@@ -1,7 +1,9 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
*
|
||||
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
|
||||
* Jan-Erik Rediger <janerik at fnordig dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -35,7 +37,7 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
#if defined(__sun) || defined(_AIX)
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
|
||||
Vendored
+525
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#ifndef _MSC_VER
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "read.h"
|
||||
#include "sds.h"
|
||||
|
||||
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
|
||||
size_t len;
|
||||
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject) {
|
||||
r->fn->freeObject(r->reply);
|
||||
r->reply = NULL;
|
||||
}
|
||||
|
||||
/* Clear input buffer on errors. */
|
||||
if (r->buf != NULL) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = NULL;
|
||||
r->pos = r->len = 0;
|
||||
}
|
||||
|
||||
/* Reset task stack. */
|
||||
r->ridx = -1;
|
||||
|
||||
/* Set error. */
|
||||
r->err = type;
|
||||
len = strlen(str);
|
||||
len = len < (sizeof(r->errstr)-1) ? len : (sizeof(r->errstr)-1);
|
||||
memcpy(r->errstr,str,len);
|
||||
r->errstr[len] = '\0';
|
||||
}
|
||||
|
||||
static size_t chrtos(char *buf, size_t size, char byte) {
|
||||
size_t len = 0;
|
||||
|
||||
switch(byte) {
|
||||
case '\\':
|
||||
case '"':
|
||||
len = snprintf(buf,size,"\"\\%c\"",byte);
|
||||
break;
|
||||
case '\n': len = snprintf(buf,size,"\"\\n\""); break;
|
||||
case '\r': len = snprintf(buf,size,"\"\\r\""); break;
|
||||
case '\t': len = snprintf(buf,size,"\"\\t\""); break;
|
||||
case '\a': len = snprintf(buf,size,"\"\\a\""); break;
|
||||
case '\b': len = snprintf(buf,size,"\"\\b\""); break;
|
||||
default:
|
||||
if (isprint(byte))
|
||||
len = snprintf(buf,size,"\"%c\"",byte);
|
||||
else
|
||||
len = snprintf(buf,size,"\"\\x%02x\"",(unsigned char)byte);
|
||||
break;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static void __redisReaderSetErrorProtocolByte(redisReader *r, char byte) {
|
||||
char cbuf[8], sbuf[128];
|
||||
|
||||
chrtos(cbuf,sizeof(cbuf),byte);
|
||||
snprintf(sbuf,sizeof(sbuf),
|
||||
"Protocol error, got %s as reply type byte", cbuf);
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,sbuf);
|
||||
}
|
||||
|
||||
static void __redisReaderSetErrorOOM(redisReader *r) {
|
||||
__redisReaderSetError(r,REDIS_ERR_OOM,"Out of memory");
|
||||
}
|
||||
|
||||
static char *readBytes(redisReader *r, unsigned int bytes) {
|
||||
char *p;
|
||||
if (r->len-r->pos >= bytes) {
|
||||
p = r->buf+r->pos;
|
||||
r->pos += bytes;
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Find pointer to \r\n. */
|
||||
static char *seekNewline(char *s, size_t len) {
|
||||
int pos = 0;
|
||||
int _len = len-1;
|
||||
|
||||
/* Position should be < len-1 because the character at "pos" should be
|
||||
* followed by a \n. Note that strchr cannot be used because it doesn't
|
||||
* allow to search a limited length and the buffer that is being searched
|
||||
* might not have a trailing NULL character. */
|
||||
while (pos < _len) {
|
||||
while(pos < _len && s[pos] != '\r') pos++;
|
||||
if (pos==_len) {
|
||||
/* Not found. */
|
||||
return NULL;
|
||||
} else {
|
||||
if (s[pos+1] == '\n') {
|
||||
/* Found. */
|
||||
return s+pos;
|
||||
} else {
|
||||
/* Continue searching. */
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Read a long long value starting at *s, under the assumption that it will be
|
||||
* terminated by \r\n. Ambiguously returns -1 for unexpected input. */
|
||||
static long long readLongLong(char *s) {
|
||||
long long v = 0;
|
||||
int dec, mult = 1;
|
||||
char c;
|
||||
|
||||
if (*s == '-') {
|
||||
mult = -1;
|
||||
s++;
|
||||
} else if (*s == '+') {
|
||||
mult = 1;
|
||||
s++;
|
||||
}
|
||||
|
||||
while ((c = *(s++)) != '\r') {
|
||||
dec = c - '0';
|
||||
if (dec >= 0 && dec < 10) {
|
||||
v *= 10;
|
||||
v += dec;
|
||||
} else {
|
||||
/* Should not happen... */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return mult*v;
|
||||
}
|
||||
|
||||
static char *readLine(redisReader *r, int *_len) {
|
||||
char *p, *s;
|
||||
int len;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p,(r->len-r->pos));
|
||||
if (s != NULL) {
|
||||
len = s-(r->buf+r->pos);
|
||||
r->pos += len+2; /* skip \r\n */
|
||||
if (_len) *_len = len;
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void moveToNextTask(redisReader *r) {
|
||||
redisReadTask *cur, *prv;
|
||||
while (r->ridx >= 0) {
|
||||
/* Return a.s.a.p. when the stack is now empty. */
|
||||
if (r->ridx == 0) {
|
||||
r->ridx--;
|
||||
return;
|
||||
}
|
||||
|
||||
cur = &(r->rstack[r->ridx]);
|
||||
prv = &(r->rstack[r->ridx-1]);
|
||||
assert(prv->type == REDIS_REPLY_ARRAY);
|
||||
if (cur->idx == prv->elements-1) {
|
||||
r->ridx--;
|
||||
} else {
|
||||
/* Reset the type because the next item can be anything */
|
||||
assert(cur->idx < prv->elements);
|
||||
cur->type = -1;
|
||||
cur->elements = -1;
|
||||
cur->idx++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int processLineItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
int len;
|
||||
|
||||
if ((p = readLine(r,&len)) != NULL) {
|
||||
if (cur->type == REDIS_REPLY_INTEGER) {
|
||||
if (r->fn && r->fn->createInteger)
|
||||
obj = r->fn->createInteger(cur,readLongLong(p));
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_INTEGER;
|
||||
} else {
|
||||
/* Type will be error or status. */
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,p,len);
|
||||
else
|
||||
obj = (void*)(size_t)(cur->type);
|
||||
}
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Set reply if this is the root object. */
|
||||
if (r->ridx == 0) r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj = NULL;
|
||||
char *p, *s;
|
||||
long len;
|
||||
unsigned long bytelen;
|
||||
int success = 0;
|
||||
|
||||
p = r->buf+r->pos;
|
||||
s = seekNewline(p,r->len-r->pos);
|
||||
if (s != NULL) {
|
||||
p = r->buf+r->pos;
|
||||
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
|
||||
len = readLongLong(p);
|
||||
|
||||
if (len < 0) {
|
||||
/* The nil object can always be created. */
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_NIL;
|
||||
success = 1;
|
||||
} else {
|
||||
/* Only continue when the buffer contains the entire bulk item. */
|
||||
bytelen += len+2; /* include \r\n */
|
||||
if (r->pos+bytelen <= r->len) {
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,s+2,len);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_STRING;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Proceed when obj was created. */
|
||||
if (success) {
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
r->pos += bytelen;
|
||||
|
||||
/* Set reply if this is the root object. */
|
||||
if (r->ridx == 0) r->reply = obj;
|
||||
moveToNextTask(r);
|
||||
return REDIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processMultiBulkItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
void *obj;
|
||||
char *p;
|
||||
long elements;
|
||||
int root = 0;
|
||||
|
||||
/* Set error for nested multi bulks with depth > 7 */
|
||||
if (r->ridx == 8) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
"No support for nested multi bulk replies with depth > 7");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if ((p = readLine(r,NULL)) != NULL) {
|
||||
elements = readLongLong(p);
|
||||
root = (r->ridx == 0);
|
||||
|
||||
if (elements == -1) {
|
||||
if (r->fn && r->fn->createNil)
|
||||
obj = r->fn->createNil(cur);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_NIL;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
moveToNextTask(r);
|
||||
} else {
|
||||
if (r->fn && r->fn->createArray)
|
||||
obj = r->fn->createArray(cur,elements);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_ARRAY;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Modify task stack when there are more than 0 elements. */
|
||||
if (elements > 0) {
|
||||
cur->elements = elements;
|
||||
cur->obj = obj;
|
||||
r->ridx++;
|
||||
r->rstack[r->ridx].type = -1;
|
||||
r->rstack[r->ridx].elements = -1;
|
||||
r->rstack[r->ridx].idx = 0;
|
||||
r->rstack[r->ridx].obj = NULL;
|
||||
r->rstack[r->ridx].parent = cur;
|
||||
r->rstack[r->ridx].privdata = r->privdata;
|
||||
} else {
|
||||
moveToNextTask(r);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set reply if this is the root object. */
|
||||
if (root) r->reply = obj;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
static int processItem(redisReader *r) {
|
||||
redisReadTask *cur = &(r->rstack[r->ridx]);
|
||||
char *p;
|
||||
|
||||
/* check if we need to read type */
|
||||
if (cur->type < 0) {
|
||||
if ((p = readBytes(r,1)) != NULL) {
|
||||
switch (p[0]) {
|
||||
case '-':
|
||||
cur->type = REDIS_REPLY_ERROR;
|
||||
break;
|
||||
case '+':
|
||||
cur->type = REDIS_REPLY_STATUS;
|
||||
break;
|
||||
case ':':
|
||||
cur->type = REDIS_REPLY_INTEGER;
|
||||
break;
|
||||
case '$':
|
||||
cur->type = REDIS_REPLY_STRING;
|
||||
break;
|
||||
case '*':
|
||||
cur->type = REDIS_REPLY_ARRAY;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
} else {
|
||||
/* could not consume 1 byte */
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* process typed item */
|
||||
switch(cur->type) {
|
||||
case REDIS_REPLY_ERROR:
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_INTEGER:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
return processMultiBulkItem(r);
|
||||
default:
|
||||
assert(NULL);
|
||||
return REDIS_ERR; /* Avoid warning. */
|
||||
}
|
||||
}
|
||||
|
||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
|
||||
redisReader *r;
|
||||
|
||||
r = calloc(sizeof(redisReader),1);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
r->err = 0;
|
||||
r->errstr[0] = '\0';
|
||||
r->fn = fn;
|
||||
r->buf = sdsempty();
|
||||
r->maxbuf = REDIS_READER_MAX_BUF;
|
||||
if (r->buf == NULL) {
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->ridx = -1;
|
||||
return r;
|
||||
}
|
||||
|
||||
void redisReaderFree(redisReader *r) {
|
||||
if (r->reply != NULL && r->fn && r->fn->freeObject)
|
||||
r->fn->freeObject(r->reply);
|
||||
if (r->buf != NULL)
|
||||
sdsfree(r->buf);
|
||||
free(r);
|
||||
}
|
||||
|
||||
int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
|
||||
sds newbuf;
|
||||
|
||||
/* Return early when this reader is in an erroneous state. */
|
||||
if (r->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Copy the provided buffer. */
|
||||
if (buf != NULL && len >= 1) {
|
||||
/* Destroy internal buffer when it is empty and is quite large. */
|
||||
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) {
|
||||
sdsfree(r->buf);
|
||||
r->buf = sdsempty();
|
||||
r->pos = 0;
|
||||
|
||||
/* r->buf should not be NULL since we just free'd a larger one. */
|
||||
assert(r->buf != NULL);
|
||||
}
|
||||
|
||||
newbuf = sdscatlen(r->buf,buf,len);
|
||||
if (newbuf == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
r->buf = newbuf;
|
||||
r->len = sdslen(r->buf);
|
||||
}
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisReaderGetReply(redisReader *r, void **reply) {
|
||||
/* Default target pointer to NULL. */
|
||||
if (reply != NULL)
|
||||
*reply = NULL;
|
||||
|
||||
/* Return early when this reader is in an erroneous state. */
|
||||
if (r->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* When the buffer is empty, there will never be a reply. */
|
||||
if (r->len == 0)
|
||||
return REDIS_OK;
|
||||
|
||||
/* Set first item to process when the stack is empty. */
|
||||
if (r->ridx == -1) {
|
||||
r->rstack[0].type = -1;
|
||||
r->rstack[0].elements = -1;
|
||||
r->rstack[0].idx = -1;
|
||||
r->rstack[0].obj = NULL;
|
||||
r->rstack[0].parent = NULL;
|
||||
r->rstack[0].privdata = r->privdata;
|
||||
r->ridx = 0;
|
||||
}
|
||||
|
||||
/* Process items in reply. */
|
||||
while (r->ridx >= 0)
|
||||
if (processItem(r) != REDIS_OK)
|
||||
break;
|
||||
|
||||
/* Return ASAP when an error occurred. */
|
||||
if (r->err)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Discard part of the buffer when we've consumed at least 1k, to avoid
|
||||
* doing unnecessary calls to memmove() in sds.c. */
|
||||
if (r->pos >= 1024) {
|
||||
sdsrange(r->buf,r->pos,-1);
|
||||
r->pos = 0;
|
||||
r->len = sdslen(r->buf);
|
||||
}
|
||||
|
||||
/* Emit a reply when there is one. */
|
||||
if (r->ridx == -1) {
|
||||
if (reply != NULL)
|
||||
*reply = r->reply;
|
||||
r->reply = NULL;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __HIREDIS_READ_H
|
||||
#define __HIREDIS_READ_H
|
||||
#include <stdio.h> /* for size_t */
|
||||
|
||||
#define REDIS_ERR -1
|
||||
#define REDIS_OK 0
|
||||
|
||||
/* When an error occurs, the err flag in a context is set to hold the type of
|
||||
* error that occurred. REDIS_ERR_IO means there was an I/O error and you
|
||||
* should use the "errno" variable to find out what is wrong.
|
||||
* For other values, the "errstr" field will hold a description. */
|
||||
#define REDIS_ERR_IO 1 /* Error in read or write */
|
||||
#define REDIS_ERR_EOF 3 /* End of file */
|
||||
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
|
||||
#define REDIS_ERR_OOM 5 /* Out of memory */
|
||||
#define REDIS_ERR_OTHER 2 /* Everything else... */
|
||||
|
||||
#define REDIS_REPLY_STRING 1
|
||||
#define REDIS_REPLY_ARRAY 2
|
||||
#define REDIS_REPLY_INTEGER 3
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
|
||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct redisReadTask {
|
||||
int type;
|
||||
int elements; /* number of elements in multibulk container */
|
||||
int idx; /* index in parent (array) object */
|
||||
void *obj; /* holds user-generated value for a read task */
|
||||
struct redisReadTask *parent; /* parent task */
|
||||
void *privdata; /* user-settable arbitrary field */
|
||||
} redisReadTask;
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
void *(*createInteger)(const redisReadTask*, long long);
|
||||
void *(*createNil)(const redisReadTask*);
|
||||
void (*freeObject)(void*);
|
||||
} redisReplyObjectFunctions;
|
||||
|
||||
typedef struct redisReader {
|
||||
int err; /* Error flags, 0 when there is no error */
|
||||
char errstr[128]; /* String representation of error when applicable */
|
||||
|
||||
char *buf; /* Read buffer */
|
||||
size_t pos; /* Buffer cursor */
|
||||
size_t len; /* Buffer length */
|
||||
size_t maxbuf; /* Max length of unused buffer */
|
||||
|
||||
redisReadTask rstack[9];
|
||||
int ridx; /* Index of current read task */
|
||||
void *reply; /* Temporary reply pointer */
|
||||
|
||||
redisReplyObjectFunctions *fn;
|
||||
void *privdata;
|
||||
} redisReader;
|
||||
|
||||
/* Public API for the protocol parser. */
|
||||
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn);
|
||||
void redisReaderFree(redisReader *r);
|
||||
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
|
||||
int redisReaderGetReply(redisReader *r, void **reply);
|
||||
|
||||
#define redisReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
|
||||
#define redisReaderGetObject(_r) (((redisReader*)(_r))->reply)
|
||||
#define redisReaderGetError(_r) (((redisReader*)(_r))->errstr)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
+2
-4
@@ -89,9 +89,9 @@ sds sdsnewlen(const void *init, size_t initlen) {
|
||||
unsigned char *fp; /* flags pointer. */
|
||||
|
||||
sh = s_malloc(hdrlen+initlen+1);
|
||||
if (sh == NULL) return NULL;
|
||||
if (!init)
|
||||
memset(sh, 0, hdrlen+initlen+1);
|
||||
if (sh == NULL) return NULL;
|
||||
s = (char*)sh+hdrlen;
|
||||
fp = ((unsigned char*)s)-1;
|
||||
switch(type) {
|
||||
@@ -577,14 +577,12 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
* %% - Verbatim "%" character.
|
||||
*/
|
||||
sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
size_t initlen = sdslen(s);
|
||||
const char *f = fmt;
|
||||
int i;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
f = fmt; /* Next format specifier byte to process. */
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
i = sdslen(s); /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
size_t l;
|
||||
|
||||
Vendored
+1
-1
@@ -79,7 +79,7 @@ struct __attribute__ ((__packed__)) sdshdr64 {
|
||||
#define SDS_TYPE_64 4
|
||||
#define SDS_TYPE_MASK 7
|
||||
#define SDS_TYPE_BITS 3
|
||||
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
|
||||
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)));
|
||||
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
|
||||
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)
|
||||
|
||||
|
||||
Vendored
+4
-4
@@ -1,6 +1,7 @@
|
||||
/* SDSLib 2.0 -- A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2015, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2015, Oran Agra
|
||||
* Copyright (c) 2015, Redis Labs, Inc
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -36,7 +37,6 @@
|
||||
* the include of your alternate allocator if needed (not needed in order
|
||||
* to use the default libc allocator). */
|
||||
|
||||
#include "zmalloc.h"
|
||||
#define s_malloc zmalloc
|
||||
#define s_realloc zrealloc
|
||||
#define s_free zfree
|
||||
#define s_malloc malloc
|
||||
#define s_realloc realloc
|
||||
#define s_free free
|
||||
|
||||
Vendored
+105
-15
@@ -11,6 +11,7 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "net.h"
|
||||
|
||||
enum connection_type {
|
||||
CONN_TCP,
|
||||
@@ -29,7 +30,7 @@ struct config {
|
||||
|
||||
struct {
|
||||
const char *path;
|
||||
} unix;
|
||||
} unix_sock;
|
||||
};
|
||||
|
||||
/* The following lines make up our testing "framework" :) */
|
||||
@@ -43,6 +44,13 @@ static long long usec(void) {
|
||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
||||
}
|
||||
|
||||
/* The assert() calls below have side effects, so we need assert()
|
||||
* even if we are compiling without asserts (-DNDEBUG). */
|
||||
#ifdef NDEBUG
|
||||
#undef assert
|
||||
#define assert(e) (void)(e)
|
||||
#endif
|
||||
|
||||
static redisContext *select_database(redisContext *c) {
|
||||
redisReply *reply;
|
||||
|
||||
@@ -51,7 +59,7 @@ static redisContext *select_database(redisContext *c) {
|
||||
assert(reply != NULL);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Make sure the DB is empty */
|
||||
/* Make sure the DB is emtpy */
|
||||
reply = redisCommand(c,"DBSIZE");
|
||||
assert(reply != NULL);
|
||||
if (reply->type == REDIS_REPLY_INTEGER && reply->integer == 0) {
|
||||
@@ -89,10 +97,10 @@ static redisContext *connect(struct config config) {
|
||||
if (config.type == CONN_TCP) {
|
||||
c = redisConnect(config.tcp.host, config.tcp.port);
|
||||
} else if (config.type == CONN_UNIX) {
|
||||
c = redisConnectUnix(config.unix.path);
|
||||
c = redisConnectUnix(config.unix_sock.path);
|
||||
} else if (config.type == CONN_FD) {
|
||||
/* Create a dummy connection just to get an fd to inherit */
|
||||
redisContext *dummy_ctx = redisConnectUnix(config.unix.path);
|
||||
redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
|
||||
if (dummy_ctx) {
|
||||
int fd = disconnect(dummy_ctx, 1);
|
||||
printf("Connecting to inherited fd %d\n", fd);
|
||||
@@ -107,6 +115,7 @@ static redisContext *connect(struct config config) {
|
||||
exit(1);
|
||||
} else if (c->err) {
|
||||
printf("Connection error: %s\n", c->errstr);
|
||||
redisFree(c);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -215,6 +224,22 @@ static void test_format_commands(void) {
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
sds sds_cmd;
|
||||
|
||||
sds_cmd = sdsempty();
|
||||
test("Format command into sds by passing argc/argv without lengths: ");
|
||||
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
|
||||
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
sdsfree(sds_cmd);
|
||||
|
||||
sds_cmd = sdsempty();
|
||||
test("Format command into sds by passing argc/argv with lengths: ");
|
||||
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
|
||||
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||
sdsfree(sds_cmd);
|
||||
}
|
||||
|
||||
static void test_append_formatted_commands(struct config config) {
|
||||
@@ -318,16 +343,31 @@ static void test_reply_reader(void) {
|
||||
redisReaderFree(reader);
|
||||
}
|
||||
|
||||
static void test_free_null(void) {
|
||||
void *redisCtx = NULL;
|
||||
void *reply = NULL;
|
||||
|
||||
test("Don't fail when redisFree is passed a NULL value: ");
|
||||
redisFree(redisCtx);
|
||||
test_cond(redisCtx == NULL);
|
||||
|
||||
test("Don't fail when freeReplyObject is passed a NULL value: ");
|
||||
freeReplyObject(reply);
|
||||
test_cond(reply == NULL);
|
||||
}
|
||||
|
||||
static void test_blocking_connection_errors(void) {
|
||||
redisContext *c;
|
||||
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
c = redisConnect((char*)"idontexist.local", 6379);
|
||||
c = redisConnect((char*)"idontexist.test", 6379);
|
||||
test_cond(c->err == REDIS_ERR_OTHER &&
|
||||
(strcmp(c->errstr,"Name or service not known") == 0 ||
|
||||
strcmp(c->errstr,"Can't resolve: idontexist.local") == 0 ||
|
||||
strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
|
||||
strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"No address associated with hostname") == 0 ||
|
||||
strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
|
||||
strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr,"no address associated with name") == 0));
|
||||
redisFree(c);
|
||||
|
||||
@@ -337,7 +377,7 @@ static void test_blocking_connection_errors(void) {
|
||||
strcmp(c->errstr,"Connection refused") == 0);
|
||||
redisFree(c);
|
||||
|
||||
test("Returns error when the unix socket path doesn't accept connections: ");
|
||||
test("Returns error when the unix_sock socket path doesn't accept connections: ");
|
||||
c = redisConnectUnix((char*)"/tmp/idontexist.sock");
|
||||
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
|
||||
redisFree(c);
|
||||
@@ -421,6 +461,52 @@ static void test_blocking_connection(struct config config) {
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
static void test_blocking_connection_timeouts(struct config config) {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
ssize_t s;
|
||||
const char *cmd = "DEBUG SLEEP 3\r\n";
|
||||
struct timeval tv;
|
||||
|
||||
c = connect(config);
|
||||
test("Successfully completes a command when the timeout is not exceeded: ");
|
||||
reply = redisCommand(c,"SET foo fast");
|
||||
freeReplyObject(reply);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000;
|
||||
redisSetTimeout(c, tv);
|
||||
reply = redisCommand(c, "GET foo");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STRING && memcmp(reply->str, "fast", 4) == 0);
|
||||
freeReplyObject(reply);
|
||||
disconnect(c, 0);
|
||||
|
||||
c = connect(config);
|
||||
test("Does not return a reply when the command times out: ");
|
||||
s = write(c->fd, cmd, strlen(cmd));
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000;
|
||||
redisSetTimeout(c, tv);
|
||||
reply = redisCommand(c, "GET foo");
|
||||
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Reconnect properly reconnects after a timeout: ");
|
||||
redisReconnect(c);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Reconnect properly uses owned parameters: ");
|
||||
config.tcp.host = "foo";
|
||||
config.unix_sock.path = "foo";
|
||||
redisReconnect(c);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
static void test_blocking_io_errors(struct config config) {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
@@ -444,7 +530,7 @@ static void test_blocking_io_errors(struct config config) {
|
||||
|
||||
test("Returns I/O error when the connection is lost: ");
|
||||
reply = redisCommand(c,"QUIT");
|
||||
if (major >= 2 && minor > 0) {
|
||||
if (major > 2 || (major == 2 && minor > 0)) {
|
||||
/* > 2.0 returns OK on QUIT and read() should be issued once more
|
||||
* to know the descriptor is at EOF. */
|
||||
test_cond(strcasecmp(reply->str,"OK") == 0 &&
|
||||
@@ -482,7 +568,8 @@ static void test_invalid_timeout_errors(struct config config) {
|
||||
|
||||
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
|
||||
|
||||
test_cond(c->err == REDIS_ERR_IO);
|
||||
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
|
||||
redisFree(c);
|
||||
|
||||
test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: ");
|
||||
|
||||
@@ -491,8 +578,7 @@ static void test_invalid_timeout_errors(struct config config) {
|
||||
|
||||
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
|
||||
|
||||
test_cond(c->err == REDIS_ERR_IO);
|
||||
|
||||
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
|
||||
redisFree(c);
|
||||
}
|
||||
|
||||
@@ -666,7 +752,7 @@ int main(int argc, char **argv) {
|
||||
.host = "127.0.0.1",
|
||||
.port = 6379
|
||||
},
|
||||
.unix = {
|
||||
.unix_sock = {
|
||||
.path = "/tmp/redis.sock"
|
||||
}
|
||||
};
|
||||
@@ -687,7 +773,7 @@ int main(int argc, char **argv) {
|
||||
cfg.tcp.port = atoi(argv[0]);
|
||||
} else if (argc >= 2 && !strcmp(argv[0],"-s")) {
|
||||
argv++; argc--;
|
||||
cfg.unix.path = argv[0];
|
||||
cfg.unix_sock.path = argv[0];
|
||||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-throughput")) {
|
||||
throughput = 0;
|
||||
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
|
||||
@@ -702,27 +788,31 @@ int main(int argc, char **argv) {
|
||||
test_format_commands();
|
||||
test_reply_reader();
|
||||
test_blocking_connection_errors();
|
||||
test_free_null();
|
||||
|
||||
printf("\nTesting against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
|
||||
cfg.type = CONN_TCP;
|
||||
test_blocking_connection(cfg);
|
||||
test_blocking_connection_timeouts(cfg);
|
||||
test_blocking_io_errors(cfg);
|
||||
test_invalid_timeout_errors(cfg);
|
||||
test_append_formatted_commands(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
|
||||
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix.path);
|
||||
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path);
|
||||
cfg.type = CONN_UNIX;
|
||||
test_blocking_connection(cfg);
|
||||
test_blocking_connection_timeouts(cfg);
|
||||
test_blocking_io_errors(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
|
||||
if (test_inherit_fd) {
|
||||
printf("\nTesting against inherited fd (%s):\n", cfg.unix.path);
|
||||
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
|
||||
cfg.type = CONN_FD;
|
||||
test_blocking_connection(cfg);
|
||||
}
|
||||
|
||||
|
||||
if (fails) {
|
||||
printf("*** %d TESTS FAILED ***\n", fails);
|
||||
return 1;
|
||||
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
#ifndef _WIN32_HELPER_INCLUDE
|
||||
#define _WIN32_HELPER_INCLUDE
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#ifndef inline
|
||||
#define inline __inline
|
||||
#endif
|
||||
|
||||
#ifndef va_copy
|
||||
#define va_copy(d,s) ((d) = (s))
|
||||
#endif
|
||||
|
||||
#ifndef snprintf
|
||||
#define snprintf c99_snprintf
|
||||
|
||||
__inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)
|
||||
{
|
||||
int count = -1;
|
||||
|
||||
if (size != 0)
|
||||
count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
|
||||
if (count == -1)
|
||||
count = _vscprintf(format, ap);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
__inline int c99_snprintf(char* str, size_t size, const char* format, ...)
|
||||
{
|
||||
int count;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
count = c99_vsnprintf(str, size, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return count;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
Vendored
-13
@@ -1,13 +0,0 @@
|
||||
/* Drop in replacement for zmalloc.h in order to just use libc malloc without
|
||||
* any wrappering. */
|
||||
|
||||
#ifndef ZMALLOC_H
|
||||
#define ZMALLOC_H
|
||||
|
||||
#define zmalloc malloc
|
||||
#define zrealloc realloc
|
||||
#define zcalloc(x) calloc(x,1)
|
||||
#define zfree free
|
||||
#define zstrdup strdup
|
||||
|
||||
#endif
|
||||
@@ -100,3 +100,7 @@
|
||||
# define JEMALLOC_RESTRICT_RETURN
|
||||
# define JEMALLOC_ALLOCATOR
|
||||
#endif
|
||||
|
||||
/* This version of Jemalloc, modified for Redis, has the je_get_defrag_hint()
|
||||
* function. */
|
||||
#define JEMALLOC_FRAG_HINT
|
||||
|
||||
Vendored
+32
@@ -2591,3 +2591,35 @@ jemalloc_postfork_child(void)
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* Helps the application decide if a pointer is worth re-allocating in order to reduce fragmentation.
|
||||
* returns 0 if the allocation is in the currently active run,
|
||||
* or when it is not causing any frag issue (large or huge bin)
|
||||
* returns the bin utilization and run utilization both in fixed point 16:16.
|
||||
* If the application decides to re-allocate it should use MALLOCX_TCACHE_NONE when doing so. */
|
||||
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
|
||||
je_get_defrag_hint(void* ptr, int *bin_util, int *run_util) {
|
||||
int defrag = 0;
|
||||
arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
|
||||
if (likely(chunk != ptr)) { /* indication that this is not a HUGE alloc */
|
||||
size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
|
||||
size_t mapbits = arena_mapbits_get(chunk, pageind);
|
||||
if (likely((mapbits & CHUNK_MAP_LARGE) == 0)) { /* indication that this is not a LARGE alloc */
|
||||
arena_t *arena = extent_node_arena_get(&chunk->node);
|
||||
size_t rpages_ind = pageind - arena_mapbits_small_runind_get(chunk, pageind);
|
||||
arena_run_t *run = &arena_miscelm_get(chunk, rpages_ind)->run;
|
||||
arena_bin_t *bin = &arena->bins[run->binind];
|
||||
malloc_mutex_lock(&bin->lock);
|
||||
/* runs that are in the same chunk in as the current chunk, are likely to be the next currun */
|
||||
if (chunk != (arena_chunk_t *)CHUNK_ADDR2BASE(bin->runcur)) {
|
||||
arena_bin_info_t *bin_info = &arena_bin_info[run->binind];
|
||||
size_t availregs = bin_info->nregs * bin->stats.curruns;
|
||||
*bin_util = (bin->stats.curregs<<16) / availregs;
|
||||
*run_util = ((bin_info->nregs - run->nfree)<<16) / bin_info->nregs;
|
||||
defrag = 1;
|
||||
}
|
||||
malloc_mutex_unlock(&bin->lock);
|
||||
}
|
||||
}
|
||||
return defrag;
|
||||
}
|
||||
|
||||
+72
-1
@@ -185,6 +185,14 @@ logfile ""
|
||||
# dbid is a number between 0 and 'databases'-1
|
||||
databases 16
|
||||
|
||||
# By default Redis shows an ASCII art logo only when started to log to the
|
||||
# standard output and if the standard output is a TTY. Basically this means
|
||||
# that normally a logo is displayed only in interactive sessions.
|
||||
#
|
||||
# However it is possible to force the pre-4.0 behavior and always show a
|
||||
# ASCII art logo in startup logs by setting the following option to yes.
|
||||
always-show-logo yes
|
||||
|
||||
################################ SNAPSHOTTING ################################
|
||||
#
|
||||
# Save the DB on disk:
|
||||
@@ -822,7 +830,7 @@ lua-time-limit 5000
|
||||
# A slave of a failing master will avoid to start a failover if its data
|
||||
# looks too old.
|
||||
#
|
||||
# There is no simple way for a slave to actually have a exact measure of
|
||||
# There is no simple way for a slave to actually have an exact measure of
|
||||
# its "data age", so the following two checks are performed:
|
||||
#
|
||||
# 1) If there are multiple slaves able to failover, they exchange messages
|
||||
@@ -1220,3 +1228,66 @@ aof-rewrite-incremental-fsync yes
|
||||
#
|
||||
# lfu-log-factor 10
|
||||
# lfu-decay-time 1
|
||||
|
||||
########################### ACTIVE DEFRAGMENTATION #######################
|
||||
#
|
||||
# WARNING THIS FEATURE IS EXPERIMENTAL. However it was stress tested
|
||||
# even in production and manually tested by multiple engineers for some
|
||||
# time.
|
||||
#
|
||||
# What is active defragmentation?
|
||||
# -------------------------------
|
||||
#
|
||||
# Active (online) defragmentation allows a Redis server to compact the
|
||||
# spaces left between small allocations and deallocations of data in memory,
|
||||
# thus allowing to reclaim back memory.
|
||||
#
|
||||
# Fragmentation is a natural process that happens with every allocator (but
|
||||
# less so with Jemalloc, fortunately) and certain workloads. Normally a server
|
||||
# restart is needed in order to lower the fragmentation, or at least to flush
|
||||
# away all the data and create it again. However thanks to this feature
|
||||
# implemented by Oran Agra for Redis 4.0 this process can happen at runtime
|
||||
# in an "hot" way, while the server is running.
|
||||
#
|
||||
# Basically when the fragmentation is over a certain level (see the
|
||||
# configuration options below) Redis will start to create new copies of the
|
||||
# values in contiguous memory regions by exploiting certain specific Jemalloc
|
||||
# features (in order to understand if an allocation is causing fragmentation
|
||||
# and to allocate it in a better place), and at the same time, will release the
|
||||
# old copies of the data. This process, repeated incrementally for all the keys
|
||||
# will cause the fragmentation to drop back to normal values.
|
||||
#
|
||||
# Important things to understand:
|
||||
#
|
||||
# 1. This feature is disabled by default, and only works if you compiled Redis
|
||||
# to use the copy of Jemalloc we ship with the source code of Redis.
|
||||
# This is the default with Linux builds.
|
||||
#
|
||||
# 2. You never need to enable this feature if you don't have fragmentation
|
||||
# issues.
|
||||
#
|
||||
# 3. Once you experience fragmentation, you can enable this feature when
|
||||
# needed with the command "CONFIG SET activedefrag yes".
|
||||
#
|
||||
# The configuration parameters are able to fine tune the behavior of the
|
||||
# defragmentation process. If you are not sure about what they mean it is
|
||||
# a good idea to leave the defaults untouched.
|
||||
|
||||
# Enabled active defragmentation
|
||||
# activedefrag yes
|
||||
|
||||
# Minimum amount of fragmentation waste to start active defrag
|
||||
# active-defrag-ignore-bytes 100mb
|
||||
|
||||
# Minimum percentage of fragmentation to start active defrag
|
||||
# active-defrag-threshold-lower 10
|
||||
|
||||
# Maximum percentage of fragmentation at which we use maximum effort
|
||||
# active-defrag-threshold-upper 100
|
||||
|
||||
# Minimal effort for defrag in CPU percentage
|
||||
# active-defrag-cycle-min 25
|
||||
|
||||
# Maximal effort for defrag in CPU percentage
|
||||
# active-defrag-cycle-max 75
|
||||
|
||||
|
||||
+30
-12
@@ -14,6 +14,7 @@
|
||||
|
||||
release_hdr := $(shell sh -c './mkreleasehdr.sh')
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
NODEPS:=clean distclean
|
||||
@@ -27,11 +28,19 @@ PREFIX?=/usr/local
|
||||
INSTALL_BIN=$(PREFIX)/bin
|
||||
INSTALL=install
|
||||
|
||||
# Default allocator
|
||||
# Default allocator defaults to Jemalloc if it's not an ARM
|
||||
MALLOC=libc
|
||||
ifneq ($(uname_M),armv6l)
|
||||
ifneq ($(uname_M),armv7l)
|
||||
ifeq ($(uname_S),Linux)
|
||||
MALLOC=jemalloc
|
||||
else
|
||||
MALLOC=libc
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# To get ARM stack traces if Redis crashes we need a special C flag.
|
||||
ifneq (,$(findstring armv,$(uname_M)))
|
||||
CFLAGS+=-funwind-tables
|
||||
endif
|
||||
|
||||
# Backwards compatibility for selecting an allocator
|
||||
@@ -61,6 +70,13 @@ DEBUG=-g -ggdb
|
||||
|
||||
ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
ifneq ($(@@),32bit)
|
||||
CFLAGS+= -m64
|
||||
LDFLAGS+= -m64
|
||||
endif
|
||||
DEBUG=-g
|
||||
DEBUG_FLAGS=-g
|
||||
export CFLAGS LDFLAGS DEBUG DEBUG_FLAGS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread -lrt
|
||||
@@ -128,14 +144,13 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
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 geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.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
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o zmalloc.o redis-benchmark.o
|
||||
REDIS_CHECK_RDB_NAME=redis-check-rdb
|
||||
REDIS_CHECK_AOF_NAME=redis-check-aof
|
||||
REDIS_CHECK_AOF_OBJ=redis-check-aof.o
|
||||
|
||||
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@echo ""
|
||||
@@ -191,6 +206,10 @@ $(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
|
||||
$(REDIS_CHECK_RDB_NAME): $(REDIS_SERVER_NAME)
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_RDB_NAME)
|
||||
|
||||
# redis-check-aof
|
||||
$(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME)
|
||||
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
|
||||
# redis-cli
|
||||
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
|
||||
@@ -199,12 +218,8 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
|
||||
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
|
||||
|
||||
# redis-check-aof
|
||||
$(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
|
||||
|
||||
dict-benchmark: dict.c zmalloc.c sds.c
|
||||
$(REDIS_CC) $(FINAL_CFLAGS) dict.c zmalloc.c sds.c -D DICT_BENCHMARK_MAIN -o dict-benchmark
|
||||
dict-benchmark: dict.c zmalloc.c sds.c siphash.c
|
||||
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
|
||||
|
||||
# Because the jemalloc.h header is generated as a part of the jemalloc build,
|
||||
# building it should complete before building any other object. Instead of
|
||||
@@ -238,7 +253,7 @@ lcov:
|
||||
@genhtml --legend -o lcov-html redis.info
|
||||
|
||||
test-sds: sds.c sds.h
|
||||
$(REDIS_CC) sds.c zmalloc.c -DSDS_TEST_MAIN -o /tmp/sds_test
|
||||
$(REDIS_CC) sds.c zmalloc.c -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test
|
||||
/tmp/sds_test
|
||||
|
||||
.PHONY: lcov
|
||||
@@ -261,6 +276,9 @@ noopt:
|
||||
valgrind:
|
||||
$(MAKE) OPTIMIZATION="-O0" MALLOC="libc"
|
||||
|
||||
helgrind:
|
||||
$(MAKE) OPTIMIZATION="-O0" MALLOC="libc" CFLAGS="-D__ATOMIC_VAR_FORCE_SYNC_MACROS"
|
||||
|
||||
src/help.h:
|
||||
@../utils/generate-command-help.rb > help.h
|
||||
|
||||
|
||||
+31
-4
@@ -52,10 +52,8 @@ list *listCreate(void)
|
||||
return list;
|
||||
}
|
||||
|
||||
/* Free the whole list.
|
||||
*
|
||||
* This function can't fail. */
|
||||
void listRelease(list *list)
|
||||
/* Remove all the elements from the list without destroying the list itself. */
|
||||
void listEmpty(list *list)
|
||||
{
|
||||
unsigned long len;
|
||||
listNode *current, *next;
|
||||
@@ -68,6 +66,16 @@ void listRelease(list *list)
|
||||
zfree(current);
|
||||
current = next;
|
||||
}
|
||||
list->head = list->tail = NULL;
|
||||
list->len = 0;
|
||||
}
|
||||
|
||||
/* Free the whole list.
|
||||
*
|
||||
* This function can't fail. */
|
||||
void listRelease(list *list)
|
||||
{
|
||||
listEmpty(list);
|
||||
zfree(list);
|
||||
}
|
||||
|
||||
@@ -333,3 +341,22 @@ void listRotate(list *list) {
|
||||
tail->next = list->head;
|
||||
list->head = tail;
|
||||
}
|
||||
|
||||
/* Add all the elements of the list 'o' at the end of the
|
||||
* list 'l'. The list 'other' remains empty but otherwise valid. */
|
||||
void listJoin(list *l, list *o) {
|
||||
if (o->head)
|
||||
o->head->prev = l->tail;
|
||||
|
||||
if (l->tail)
|
||||
l->tail->next = o->head;
|
||||
else
|
||||
l->head = o->head;
|
||||
|
||||
l->tail = o->tail;
|
||||
l->len += o->len;
|
||||
|
||||
/* Setup other as an empty list. */
|
||||
o->head = o->tail = NULL;
|
||||
o->len = 0;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ typedef struct list {
|
||||
/* Prototypes */
|
||||
list *listCreate(void);
|
||||
void listRelease(list *list);
|
||||
void listEmpty(list *list);
|
||||
list *listAddNodeHead(list *list, void *value);
|
||||
list *listAddNodeTail(list *list, void *value);
|
||||
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
|
||||
@@ -85,6 +86,7 @@ listNode *listIndex(list *list, long index);
|
||||
void listRewind(list *list, listIter *li);
|
||||
void listRewindTail(list *list, listIter *li);
|
||||
void listRotate(list *list);
|
||||
void listJoin(list *l, list *o);
|
||||
|
||||
/* Directions for iterators */
|
||||
#define AL_START_HEAD 0
|
||||
|
||||
@@ -75,6 +75,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
||||
eventLoop->stop = 0;
|
||||
eventLoop->maxfd = -1;
|
||||
eventLoop->beforesleep = NULL;
|
||||
eventLoop->aftersleep = NULL;
|
||||
if (aeApiCreate(eventLoop) == -1) goto err;
|
||||
/* Events with mask == AE_NONE are not set. So let's initialize the
|
||||
* vector with it. */
|
||||
@@ -343,6 +344,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
||||
* if flags has AE_FILE_EVENTS set, file events are processed.
|
||||
* if flags has AE_TIME_EVENTS set, time events are processed.
|
||||
* if flags has AE_DONT_WAIT set the function returns ASAP until all
|
||||
* if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
|
||||
* the events that's possible to process without to wait are processed.
|
||||
*
|
||||
* The function returns the number of events processed. */
|
||||
@@ -397,7 +399,14 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
||||
}
|
||||
}
|
||||
|
||||
/* Call the multiplexing API, will return only on timeout or when
|
||||
* some event fires. */
|
||||
numevents = aeApiPoll(eventLoop, tvp);
|
||||
|
||||
/* After sleep callback. */
|
||||
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
|
||||
eventLoop->aftersleep(eventLoop);
|
||||
|
||||
for (j = 0; j < numevents; j++) {
|
||||
aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
|
||||
int mask = eventLoop->fired[j].mask;
|
||||
@@ -452,7 +461,7 @@ void aeMain(aeEventLoop *eventLoop) {
|
||||
while (!eventLoop->stop) {
|
||||
if (eventLoop->beforesleep != NULL)
|
||||
eventLoop->beforesleep(eventLoop);
|
||||
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
|
||||
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,3 +472,7 @@ char *aeGetApiName(void) {
|
||||
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) {
|
||||
eventLoop->beforesleep = beforesleep;
|
||||
}
|
||||
|
||||
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep) {
|
||||
eventLoop->aftersleep = aftersleep;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#define AE_TIME_EVENTS 2
|
||||
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
|
||||
#define AE_DONT_WAIT 4
|
||||
#define AE_CALL_AFTER_SLEEP 8
|
||||
|
||||
#define AE_NOMORE -1
|
||||
#define AE_DELETED_EVENT_ID -1
|
||||
@@ -98,6 +99,7 @@ typedef struct aeEventLoop {
|
||||
int stop;
|
||||
void *apidata; /* This is used for polling API specific data */
|
||||
aeBeforeSleepProc *beforesleep;
|
||||
aeBeforeSleepProc *aftersleep;
|
||||
} aeEventLoop;
|
||||
|
||||
/* Prototypes */
|
||||
@@ -117,6 +119,7 @@ int aeWait(int fd, int mask, long long milliseconds);
|
||||
void aeMain(aeEventLoop *eventLoop);
|
||||
char *aeGetApiName(void);
|
||||
void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep);
|
||||
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
|
||||
int aeGetSetSize(aeEventLoop *eventLoop);
|
||||
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
|
||||
|
||||
|
||||
+5
-2
@@ -380,8 +380,10 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (flags & ANET_CONNECT_NONBLOCK) {
|
||||
if (anetNonBlock(err,s) != ANET_OK)
|
||||
if (anetNonBlock(err,s) != ANET_OK) {
|
||||
close(s);
|
||||
return ANET_ERR;
|
||||
}
|
||||
}
|
||||
if (connect(s,(struct sockaddr*)&sa,sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS &&
|
||||
@@ -462,7 +464,7 @@ static int anetV6Only(char *err, int s) {
|
||||
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
|
||||
{
|
||||
int s, rv;
|
||||
int s = -1, rv;
|
||||
char _port[6]; /* strlen("65535") */
|
||||
struct addrinfo hints, *servinfo, *p;
|
||||
|
||||
@@ -491,6 +493,7 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
||||
}
|
||||
|
||||
error:
|
||||
if (s != -1) close(s);
|
||||
s = ANET_ERR;
|
||||
end:
|
||||
freeaddrinfo(servinfo);
|
||||
|
||||
@@ -115,6 +115,7 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (nwritten <= 0) return;
|
||||
memmove(block->buf,block->buf+nwritten,block->used-nwritten);
|
||||
block->used -= nwritten;
|
||||
block->free += nwritten;
|
||||
}
|
||||
if (block->used == 0) listDelNode(server.aof_rewrite_buf_blocks,ln);
|
||||
}
|
||||
@@ -535,6 +536,22 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
|
||||
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
|
||||
decrRefCount(tmpargv[0]);
|
||||
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
|
||||
} else if (cmd->proc == setCommand && argc > 3) {
|
||||
int i;
|
||||
robj *exarg = NULL, *pxarg = NULL;
|
||||
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
|
||||
buf = catAppendOnlyGenericCommand(buf,3,argv);
|
||||
for (i = 3; i < argc; i ++) {
|
||||
if (!strcasecmp(argv[i]->ptr, "ex")) exarg = argv[i+1];
|
||||
if (!strcasecmp(argv[i]->ptr, "px")) pxarg = argv[i+1];
|
||||
}
|
||||
serverAssert(!(exarg && pxarg));
|
||||
if (exarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.expireCommand,argv[1],
|
||||
exarg);
|
||||
if (pxarg)
|
||||
buf = catAppendOnlyExpireAtCommand(buf,server.pexpireCommand,argv[1],
|
||||
pxarg);
|
||||
} else {
|
||||
/* All the other commands don't need translation or need the
|
||||
* same translation already operated in the command vector
|
||||
@@ -1357,6 +1374,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
serverLog(LL_WARNING,
|
||||
"Can't rewrite append only file in background: fork: %s",
|
||||
strerror(errno));
|
||||
aofClosePipes();
|
||||
return C_ERR;
|
||||
}
|
||||
serverLog(LL_NOTICE,
|
||||
|
||||
+65
-26
@@ -3,18 +3,29 @@
|
||||
*
|
||||
* The exported interaface is composed of three macros:
|
||||
*
|
||||
* atomicIncr(var,count,mutex) -- Increment the atomic counter
|
||||
* atomicDecr(var,count,mutex) -- Decrement the atomic counter
|
||||
* atomicGet(var,dstvar,mutex) -- Fetch the atomic counter value
|
||||
* atomicIncr(var,count) -- Increment the atomic counter
|
||||
* atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter
|
||||
* atomicDecr(var,count) -- Decrement the atomic counter
|
||||
* atomicGet(var,dstvar) -- Fetch the atomic counter value
|
||||
* atomicSet(var,value) -- Set the atomic counter value
|
||||
*
|
||||
* The variable 'var' should also have a declared mutex with the same
|
||||
* name and the "_mutex" postfix, for instance:
|
||||
*
|
||||
* long myvar;
|
||||
* pthread_mutex_t myvar_mutex;
|
||||
* atomicSet(myvar,12345);
|
||||
*
|
||||
* If atomic primitives are availble (tested in config.h) the mutex
|
||||
* is not used.
|
||||
*
|
||||
* Never use return value from the macros. To update and get use instead:
|
||||
* Never use return value from the macros, instead use the AtomicGetIncr()
|
||||
* if you need to get the current value and increment it atomically, like
|
||||
* in the followign example:
|
||||
*
|
||||
* atomicIncr(mycounter,...);
|
||||
* atomicGet(mycounter,newvalue);
|
||||
* doSomethingWith(newvalue);
|
||||
* long oldvalue;
|
||||
* atomicGetIncr(myvar,oldvalue,1);
|
||||
* doSomethingWith(oldvalue);
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
@@ -51,44 +62,72 @@
|
||||
#ifndef __ATOMIC_VAR_H
|
||||
#define __ATOMIC_VAR_H
|
||||
|
||||
#if defined(__ATOMIC_RELAXED) && (!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057)
|
||||
/* To test Redis with Helgrind (a Valgrind tool) it is useful to define
|
||||
* the following macro, so that __sync macros are used: those can be detected
|
||||
* by Helgrind (even if they are less efficient) so that no false positive
|
||||
* is reported. */
|
||||
// #define __ATOMIC_VAR_FORCE_SYNC_MACROS
|
||||
|
||||
#if !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && defined(__ATOMIC_RELAXED) && !defined(__sun) && (!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057)
|
||||
/* Implementation using __atomic macros. */
|
||||
|
||||
#define atomicIncr(var,count,mutex) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||
#define atomicDecr(var,count,mutex) __atomic_sub_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||
#define atomicGet(var,dstvar,mutex) do { \
|
||||
#define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||
#define atomicGetIncr(var,oldvalue_var,count) do { \
|
||||
oldvalue_var = __atomic_fetch_add(&var,(count),__ATOMIC_RELAXED); \
|
||||
} while(0)
|
||||
#define atomicDecr(var,count) __atomic_sub_fetch(&var,(count),__ATOMIC_RELAXED)
|
||||
#define atomicGet(var,dstvar) do { \
|
||||
dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \
|
||||
} while(0)
|
||||
#define atomicSet(var,value) __atomic_store_n(&var,value,__ATOMIC_RELAXED)
|
||||
#define REDIS_ATOMIC_API "atomic-builtin"
|
||||
|
||||
#elif defined(HAVE_ATOMIC)
|
||||
/* Implementation using __sync macros. */
|
||||
|
||||
#define atomicIncr(var,count,mutex) __sync_add_and_fetch(&var,(count))
|
||||
#define atomicDecr(var,count,mutex) __sync_sub_and_fetch(&var,(count))
|
||||
#define atomicGet(var,dstvar,mutex) do { \
|
||||
#define atomicIncr(var,count) __sync_add_and_fetch(&var,(count))
|
||||
#define atomicGetIncr(var,oldvalue_var,count) do { \
|
||||
oldvalue_var = __sync_fetch_and_add(&var,(count)); \
|
||||
} while(0)
|
||||
#define atomicDecr(var,count) __sync_sub_and_fetch(&var,(count))
|
||||
#define atomicGet(var,dstvar) do { \
|
||||
dstvar = __sync_sub_and_fetch(&var,0); \
|
||||
} while(0)
|
||||
#define atomicSet(var,value) do { \
|
||||
while(!__sync_bool_compare_and_swap(&var,var,value)); \
|
||||
} while(0)
|
||||
#define REDIS_ATOMIC_API "sync-builtin"
|
||||
|
||||
#else
|
||||
/* Implementation using pthread mutex. */
|
||||
|
||||
#define atomicIncr(var,count,mutex) do { \
|
||||
pthread_mutex_lock(&mutex); \
|
||||
#define atomicIncr(var,count) do { \
|
||||
pthread_mutex_lock(&var ## _mutex); \
|
||||
var += (count); \
|
||||
pthread_mutex_unlock(&mutex); \
|
||||
pthread_mutex_unlock(&var ## _mutex); \
|
||||
} while(0)
|
||||
|
||||
#define atomicDecr(var,count,mutex) do { \
|
||||
pthread_mutex_lock(&mutex); \
|
||||
#define atomicGetIncr(var,oldvalue_var,count) do { \
|
||||
pthread_mutex_lock(&var ## _mutex); \
|
||||
oldvalue_var = var; \
|
||||
var += (count); \
|
||||
pthread_mutex_unlock(&var ## _mutex); \
|
||||
} while(0)
|
||||
#define atomicDecr(var,count) do { \
|
||||
pthread_mutex_lock(&var ## _mutex); \
|
||||
var -= (count); \
|
||||
pthread_mutex_unlock(&mutex); \
|
||||
pthread_mutex_unlock(&var ## _mutex); \
|
||||
} while(0)
|
||||
|
||||
#define atomicGet(var,dstvar,mutex) do { \
|
||||
pthread_mutex_lock(&mutex); \
|
||||
#define atomicGet(var,dstvar) do { \
|
||||
pthread_mutex_lock(&var ## _mutex); \
|
||||
dstvar = var; \
|
||||
pthread_mutex_unlock(&mutex); \
|
||||
pthread_mutex_unlock(&var ## _mutex); \
|
||||
} while(0)
|
||||
#endif
|
||||
#define atomicSet(var,value) do { \
|
||||
pthread_mutex_lock(&var ## _mutex); \
|
||||
var = value; \
|
||||
pthread_mutex_unlock(&var ## _mutex); \
|
||||
} while(0)
|
||||
#define REDIS_ATOMIC_API "pthread-mutex"
|
||||
|
||||
#endif
|
||||
#endif /* __ATOMIC_VAR_H */
|
||||
|
||||
+19
-8
@@ -104,6 +104,7 @@ long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
unsigned long j;
|
||||
int found;
|
||||
|
||||
/* Process whole words first, seeking for first word that is not
|
||||
* all ones or all zeros respectively if we are lookig for zeros
|
||||
@@ -117,21 +118,27 @@ long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
/* Skip initial bits not aligned to sizeof(unsigned long) byte by byte. */
|
||||
skipval = bit ? 0 : UCHAR_MAX;
|
||||
c = (unsigned char*) s;
|
||||
found = 0;
|
||||
while((unsigned long)c & (sizeof(*l)-1) && count) {
|
||||
if (*c != skipval) break;
|
||||
if (*c != skipval) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
c++;
|
||||
count--;
|
||||
pos += 8;
|
||||
}
|
||||
|
||||
/* Skip bits with full word step. */
|
||||
skipval = bit ? 0 : ULONG_MAX;
|
||||
l = (unsigned long*) c;
|
||||
while (count >= sizeof(*l)) {
|
||||
if (*l != skipval) break;
|
||||
l++;
|
||||
count -= sizeof(*l);
|
||||
pos += sizeof(*l)*8;
|
||||
if (!found) {
|
||||
skipval = bit ? 0 : ULONG_MAX;
|
||||
while (count >= sizeof(*l)) {
|
||||
if (*l != skipval) break;
|
||||
l++;
|
||||
count -= sizeof(*l);
|
||||
pos += sizeof(*l)*8;
|
||||
}
|
||||
}
|
||||
|
||||
/* Load bytes into "word" considering the first byte as the most significant
|
||||
@@ -654,8 +661,11 @@ void bitopCommand(client *c) {
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. */
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
j = 0;
|
||||
#ifndef USE_ALIGNED_ACCESS
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
@@ -716,6 +726,7 @@ void bitopCommand(client *c) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* j is set to the next byte to process by the previous loop. */
|
||||
for (; j < maxlen; j++) {
|
||||
|
||||
+187
-47
@@ -37,7 +37,6 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
#include <math.h>
|
||||
@@ -423,8 +422,11 @@ void clusterInit(void) {
|
||||
server.cluster->failover_auth_epoch = 0;
|
||||
server.cluster->cant_failover_reason = CLUSTER_CANT_FAILOVER_NONE;
|
||||
server.cluster->lastVoteEpoch = 0;
|
||||
server.cluster->stats_bus_messages_sent = 0;
|
||||
server.cluster->stats_bus_messages_received = 0;
|
||||
for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) {
|
||||
server.cluster->stats_bus_messages_sent[i] = 0;
|
||||
server.cluster->stats_bus_messages_received[i] = 0;
|
||||
}
|
||||
server.cluster->stats_pfail_nodes = 0;
|
||||
memset(server.cluster->slots,0, sizeof(server.cluster->slots));
|
||||
clusterCloseAllSlots();
|
||||
|
||||
@@ -476,8 +478,10 @@ void clusterInit(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* The slots -> keys map is a sorted set. Init it. */
|
||||
server.cluster->slots_to_keys = zslCreate();
|
||||
/* The slots -> keys map is a radix tree. Initialize it here. */
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
|
||||
/* Set myself->port / cport to my listening ports, we'll just need to
|
||||
* discover the IP address via MEET messages. */
|
||||
@@ -1350,6 +1354,28 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
|
||||
}
|
||||
}
|
||||
|
||||
/* If from our POV the node is up (no failure flags are set),
|
||||
* we have no pending ping for the node, nor we have failure
|
||||
* reports for this node, update the last pong time with the
|
||||
* one we see from the other nodes. */
|
||||
if (!(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) &&
|
||||
node->ping_sent == 0 &&
|
||||
clusterNodeFailureReportsCount(node) == 0)
|
||||
{
|
||||
mstime_t pongtime = ntohl(g->pong_received);
|
||||
pongtime *= 1000; /* Convert back to milliseconds. */
|
||||
|
||||
/* Replace the pong time with the received one only if
|
||||
* it's greater than our view but is not in the future
|
||||
* (with 500 milliseconds tolerance) from the POV of our
|
||||
* clock. */
|
||||
if (pongtime <= (server.mstime+500) &&
|
||||
pongtime > node->pong_received)
|
||||
{
|
||||
node->pong_received = pongtime;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we already know this node, but it is not reachable, and
|
||||
* we see a different address in the gossip section of a node that
|
||||
* can talk with this other node, update the address, disconnect
|
||||
@@ -1581,7 +1607,8 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
uint32_t totlen = ntohl(hdr->totlen);
|
||||
uint16_t type = ntohs(hdr->type);
|
||||
|
||||
server.cluster->stats_bus_messages_received++;
|
||||
if (type < CLUSTERMSG_TYPE_COUNT)
|
||||
server.cluster->stats_bus_messages_received[type]++;
|
||||
serverLog(LL_DEBUG,"--- Processing packet of type %d, %lu bytes",
|
||||
type, (unsigned long) totlen);
|
||||
|
||||
@@ -2128,7 +2155,12 @@ void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
|
||||
clusterWriteHandler,link);
|
||||
|
||||
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
|
||||
server.cluster->stats_bus_messages_sent++;
|
||||
|
||||
/* Populate sent messages stats. */
|
||||
clusterMsg *hdr = (clusterMsg*) msg;
|
||||
uint16_t type = ntohs(hdr->type);
|
||||
if (type < CLUSTERMSG_TYPE_COUNT)
|
||||
server.cluster->stats_bus_messages_sent[type]++;
|
||||
}
|
||||
|
||||
/* Send a message to all the nodes that are part of the cluster having
|
||||
@@ -2229,6 +2261,33 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
|
||||
/* For PING, PONG, and MEET, fixing the totlen field is up to the caller. */
|
||||
}
|
||||
|
||||
/* Return non zero if the node is already present in the gossip section of the
|
||||
* message pointed by 'hdr' and having 'count' gossip entries. Otherwise
|
||||
* zero is returned. Helper for clusterSendPing(). */
|
||||
int clusterNodeIsInGossipSection(clusterMsg *hdr, int count, clusterNode *n) {
|
||||
int j;
|
||||
for (j = 0; j < count; j++) {
|
||||
if (memcmp(hdr->data.ping.gossip[j].nodename,n->name,
|
||||
CLUSTER_NAMELEN) == 0) break;
|
||||
}
|
||||
return j != count;
|
||||
}
|
||||
|
||||
/* Set the i-th entry of the gossip section in the message pointed by 'hdr'
|
||||
* to the info of the specified node 'n'. */
|
||||
void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
|
||||
clusterMsgDataGossip *gossip;
|
||||
gossip = &(hdr->data.ping.gossip[i]);
|
||||
memcpy(gossip->nodename,n->name,CLUSTER_NAMELEN);
|
||||
gossip->ping_sent = htonl(n->ping_sent/1000);
|
||||
gossip->pong_received = htonl(n->pong_received/1000);
|
||||
memcpy(gossip->ip,n->ip,sizeof(n->ip));
|
||||
gossip->port = htons(n->port);
|
||||
gossip->cport = htons(n->cport);
|
||||
gossip->flags = htons(n->flags);
|
||||
gossip->notused1 = 0;
|
||||
}
|
||||
|
||||
/* Send a PING or PONG packet to the specified node, making sure to add enough
|
||||
* gossip informations. */
|
||||
void clusterSendPing(clusterLink *link, int type) {
|
||||
@@ -2273,11 +2332,15 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
if (wanted < 3) wanted = 3;
|
||||
if (wanted > freshnodes) wanted = freshnodes;
|
||||
|
||||
/* Include all the nodes in PFAIL state, so that failure reports are
|
||||
* faster to propagate to go from PFAIL to FAIL state. */
|
||||
int pfail_wanted = server.cluster->stats_pfail_nodes;
|
||||
|
||||
/* Compute the maxium totlen to allocate our buffer. We'll fix the totlen
|
||||
* later according to the number of gossip sections we really were able
|
||||
* to put inside the packet. */
|
||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
totlen += (sizeof(clusterMsgDataGossip)*wanted);
|
||||
totlen += (sizeof(clusterMsgDataGossip)*(wanted+pfail_wanted));
|
||||
/* Note: clusterBuildMessageHdr() expects the buffer to be always at least
|
||||
* sizeof(clusterMsg) or more. */
|
||||
if (totlen < (int)sizeof(clusterMsg)) totlen = sizeof(clusterMsg);
|
||||
@@ -2294,17 +2357,13 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
while(freshnodes > 0 && gossipcount < wanted && maxiterations--) {
|
||||
dictEntry *de = dictGetRandomKey(server.cluster->nodes);
|
||||
clusterNode *this = dictGetVal(de);
|
||||
clusterMsgDataGossip *gossip;
|
||||
int j;
|
||||
|
||||
/* Don't include this node: the whole packet header is about us
|
||||
* already, so we just gossip about other nodes. */
|
||||
if (this == myself) continue;
|
||||
|
||||
/* Give a bias to FAIL/PFAIL nodes. */
|
||||
if (maxiterations > wanted*2 &&
|
||||
!(this->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL)))
|
||||
continue;
|
||||
/* PFAIL nodes will be added later. */
|
||||
if (this->flags & CLUSTER_NODE_PFAIL) continue;
|
||||
|
||||
/* In the gossip section don't include:
|
||||
* 1) Nodes in HANDSHAKE state.
|
||||
@@ -2318,27 +2377,37 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check if we already added this node */
|
||||
for (j = 0; j < gossipcount; j++) {
|
||||
if (memcmp(hdr->data.ping.gossip[j].nodename,this->name,
|
||||
CLUSTER_NAMELEN) == 0) break;
|
||||
}
|
||||
if (j != gossipcount) continue;
|
||||
/* Do not add a node we already have. */
|
||||
if (clusterNodeIsInGossipSection(hdr,gossipcount,this)) continue;
|
||||
|
||||
/* Add it */
|
||||
clusterSetGossipEntry(hdr,gossipcount,this);
|
||||
freshnodes--;
|
||||
gossip = &(hdr->data.ping.gossip[gossipcount]);
|
||||
memcpy(gossip->nodename,this->name,CLUSTER_NAMELEN);
|
||||
gossip->ping_sent = htonl(this->ping_sent);
|
||||
gossip->pong_received = htonl(this->pong_received);
|
||||
memcpy(gossip->ip,this->ip,sizeof(this->ip));
|
||||
gossip->port = htons(this->port);
|
||||
gossip->cport = htons(this->cport);
|
||||
gossip->flags = htons(this->flags);
|
||||
gossip->notused1 = 0;
|
||||
gossipcount++;
|
||||
}
|
||||
|
||||
/* If there are PFAIL nodes, add them at the end. */
|
||||
if (pfail_wanted) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
di = dictGetSafeIterator(server.cluster->nodes);
|
||||
while((de = dictNext(di)) != NULL && pfail_wanted > 0) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
if (node->flags & CLUSTER_NODE_HANDSHAKE) continue;
|
||||
if (node->flags & CLUSTER_NODE_NOADDR) continue;
|
||||
if (!(node->flags & CLUSTER_NODE_PFAIL)) continue;
|
||||
clusterSetGossipEntry(hdr,gossipcount,node);
|
||||
freshnodes--;
|
||||
gossipcount++;
|
||||
/* We take the count of the slots we allocated, since the
|
||||
* PFAIL stats may not match perfectly with the current number
|
||||
* of PFAIL nodes. */
|
||||
pfail_wanted--;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* Ready to send... fix the totlen fiend and queue the message in the
|
||||
* output buffer. */
|
||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
@@ -2774,7 +2843,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
* and wait for replies), and the failover retry time (the time to wait
|
||||
* before trying to get voted again).
|
||||
*
|
||||
* Timeout is MIN(NODE_TIMEOUT*2,2000) milliseconds.
|
||||
* Timeout is MAX(NODE_TIMEOUT*2,2000) milliseconds.
|
||||
* Retry is two times the Timeout.
|
||||
*/
|
||||
auth_timeout = server.cluster_node_timeout*2;
|
||||
@@ -3164,13 +3233,21 @@ void clusterCron(void) {
|
||||
handshake_timeout = server.cluster_node_timeout;
|
||||
if (handshake_timeout < 1000) handshake_timeout = 1000;
|
||||
|
||||
/* Check if we have disconnected nodes and re-establish the connection. */
|
||||
/* Check if we have disconnected nodes and re-establish the connection.
|
||||
* Also update a few stats while we are here, that can be used to make
|
||||
* better decisions in other part of the code. */
|
||||
di = dictGetSafeIterator(server.cluster->nodes);
|
||||
server.cluster->stats_pfail_nodes = 0;
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
|
||||
/* Not interested in reconnecting the link with myself or nodes
|
||||
* for which we have no address. */
|
||||
if (node->flags & (CLUSTER_NODE_MYSELF|CLUSTER_NODE_NOADDR)) continue;
|
||||
|
||||
if (node->flags & CLUSTER_NODE_PFAIL)
|
||||
server.cluster->stats_pfail_nodes++;
|
||||
|
||||
/* A Node in HANDSHAKE state has a limited lifespan equal to the
|
||||
* configured node timeout. */
|
||||
if (nodeInHandshake(node) && now - node->ctime > handshake_timeout) {
|
||||
@@ -3875,6 +3952,21 @@ sds clusterGenNodesDescription(int filter) {
|
||||
* CLUSTER command
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
const char *clusterGetMessageTypeString(int type) {
|
||||
switch(type) {
|
||||
case CLUSTERMSG_TYPE_PING: return "ping";
|
||||
case CLUSTERMSG_TYPE_PONG: return "pong";
|
||||
case CLUSTERMSG_TYPE_MEET: return "meet";
|
||||
case CLUSTERMSG_TYPE_FAIL: return "fail";
|
||||
case CLUSTERMSG_TYPE_PUBLISH: return "publish";
|
||||
case CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST: return "auth-req";
|
||||
case CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK: return "auth-ack";
|
||||
case CLUSTERMSG_TYPE_UPDATE: return "update";
|
||||
case CLUSTERMSG_TYPE_MFSTART: return "mfstart";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
int getSlotOrReply(client *c, robj *o) {
|
||||
long long slot;
|
||||
|
||||
@@ -4206,8 +4298,6 @@ void clusterCommand(client *c) {
|
||||
"cluster_size:%d\r\n"
|
||||
"cluster_current_epoch:%llu\r\n"
|
||||
"cluster_my_epoch:%llu\r\n"
|
||||
"cluster_stats_messages_sent:%lld\r\n"
|
||||
"cluster_stats_messages_received:%lld\r\n"
|
||||
, statestr[server.cluster->state],
|
||||
slots_assigned,
|
||||
slots_ok,
|
||||
@@ -4216,10 +4306,36 @@ void clusterCommand(client *c) {
|
||||
dictSize(server.cluster->nodes),
|
||||
server.cluster->size,
|
||||
(unsigned long long) server.cluster->currentEpoch,
|
||||
(unsigned long long) myepoch,
|
||||
server.cluster->stats_bus_messages_sent,
|
||||
server.cluster->stats_bus_messages_received
|
||||
(unsigned long long) myepoch
|
||||
);
|
||||
|
||||
/* Show stats about messages sent and received. */
|
||||
long long tot_msg_sent = 0;
|
||||
long long tot_msg_received = 0;
|
||||
|
||||
for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) {
|
||||
if (server.cluster->stats_bus_messages_sent[i] == 0) continue;
|
||||
tot_msg_sent += server.cluster->stats_bus_messages_sent[i];
|
||||
info = sdscatprintf(info,
|
||||
"cluster_stats_messages_%s_sent:%lld\r\n",
|
||||
clusterGetMessageTypeString(i),
|
||||
server.cluster->stats_bus_messages_sent[i]);
|
||||
}
|
||||
info = sdscatprintf(info,
|
||||
"cluster_stats_messages_sent:%lld\r\n", tot_msg_sent);
|
||||
|
||||
for (int i = 0; i < CLUSTERMSG_TYPE_COUNT; i++) {
|
||||
if (server.cluster->stats_bus_messages_received[i] == 0) continue;
|
||||
tot_msg_received += server.cluster->stats_bus_messages_received[i];
|
||||
info = sdscatprintf(info,
|
||||
"cluster_stats_messages_%s_received:%lld\r\n",
|
||||
clusterGetMessageTypeString(i),
|
||||
server.cluster->stats_bus_messages_received[i]);
|
||||
}
|
||||
info = sdscatprintf(info,
|
||||
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
|
||||
|
||||
/* Produce the reply protocol. */
|
||||
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",
|
||||
(unsigned long)sdslen(info)));
|
||||
addReplySds(c,info);
|
||||
@@ -4264,6 +4380,11 @@ void clusterCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Avoid allocating more than needed in case of large COUNT argument
|
||||
* and smaller actual number of keys. */
|
||||
unsigned int keys_in_slot = countKeysInSlot(slot);
|
||||
if (maxkeys > keys_in_slot) maxkeys = keys_in_slot;
|
||||
|
||||
keys = zmalloc(sizeof(robj*)*maxkeys);
|
||||
numkeys = getKeysInSlot(slot, keys, maxkeys);
|
||||
addReplyMultiBulkLen(c,numkeys);
|
||||
@@ -4617,7 +4738,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
/* Create the key and set the TTL if any */
|
||||
dbAdd(c->db,c->argv[1],obj);
|
||||
if (ttl) setExpire(c->db,c->argv[1],mstime()+ttl);
|
||||
if (ttl) setExpire(c,c->db,c->argv[1],mstime()+ttl);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
addReply(c,shared.ok);
|
||||
server.dirty++;
|
||||
@@ -4750,13 +4871,13 @@ void migrateCommand(client *c) {
|
||||
int copy, replace, j;
|
||||
long timeout;
|
||||
long dbid;
|
||||
long long ttl, expireat;
|
||||
robj **ov = NULL; /* Objects to migrate. */
|
||||
robj **kv = NULL; /* Key names. */
|
||||
robj **newargv = NULL; /* Used to rewrite the command as DEL ... keys ... */
|
||||
rio cmd, payload;
|
||||
int may_retry = 1;
|
||||
int write_error = 0;
|
||||
int argv_rewritten = 0;
|
||||
|
||||
/* To support the KEYS option we need the following additional state. */
|
||||
int first_key = 3; /* Argument index of the first key. */
|
||||
@@ -4765,7 +4886,6 @@ void migrateCommand(client *c) {
|
||||
/* Initialization */
|
||||
copy = 0;
|
||||
replace = 0;
|
||||
ttl = 0;
|
||||
|
||||
/* Parse additional options */
|
||||
for (j = 6; j < c->argc; j++) {
|
||||
@@ -4841,7 +4961,9 @@ try_again:
|
||||
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
expireat = getExpire(c->db,kv[j]);
|
||||
long long ttl = 0;
|
||||
long long expireat = getExpire(c->db,kv[j]);
|
||||
|
||||
if (expireat != -1) {
|
||||
ttl = expireat-mstime();
|
||||
if (ttl < 1) ttl = 1;
|
||||
@@ -4939,12 +5061,20 @@ try_again:
|
||||
goto socket_err; /* A retry is guaranteed because of tested conditions.*/
|
||||
}
|
||||
|
||||
/* On socket errors, close the migration socket now that we still have
|
||||
* the original host/port in the ARGV. Later the original command may be
|
||||
* rewritten to DEL and will be too later. */
|
||||
if (socket_error) migrateCloseSocket(c->argv[1],c->argv[2]);
|
||||
|
||||
if (!copy) {
|
||||
/* Translate MIGRATE as DEL for replication/AOF. */
|
||||
/* Translate MIGRATE as DEL for replication/AOF. Note that we do
|
||||
* this only for the keys for which we received an acknowledgement
|
||||
* from the receiving Redis server, by using the del_idx index. */
|
||||
if (del_idx > 1) {
|
||||
newargv[0] = createStringObject("DEL",3);
|
||||
/* Note that the following call takes ownership of newargv. */
|
||||
replaceClientCommandVector(c,del_idx,newargv);
|
||||
argv_rewritten = 1;
|
||||
} else {
|
||||
/* No key transfer acknowledged, no need to rewrite as DEL. */
|
||||
zfree(newargv);
|
||||
@@ -4953,8 +5083,8 @@ try_again:
|
||||
}
|
||||
|
||||
/* If we are here and a socket error happened, we don't want to retry.
|
||||
* Just signal the problem to the client, but only do it if we don't
|
||||
* already queued a different error reported by the destination server. */
|
||||
* Just signal the problem to the client, but only do it if we did not
|
||||
* already queue a different error reported by the destination server. */
|
||||
if (!error_from_target && socket_error) {
|
||||
may_retry = 0;
|
||||
goto socket_err;
|
||||
@@ -4962,7 +5092,11 @@ try_again:
|
||||
|
||||
if (!error_from_target) {
|
||||
/* Success! Update the last_dbid in migrateCachedSocket, so that we can
|
||||
* avoid SELECT the next time if the target DB is the same. Reply +OK. */
|
||||
* avoid SELECT the next time if the target DB is the same. Reply +OK.
|
||||
*
|
||||
* Note: If we reached this point, even if socket_error is true
|
||||
* still the SELECT command succeeded (otherwise the code jumps to
|
||||
* socket_err label. */
|
||||
cs->last_dbid = dbid;
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
@@ -4972,7 +5106,6 @@ try_again:
|
||||
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
zfree(ov); zfree(kv); zfree(newargv);
|
||||
if (socket_error) migrateCloseSocket(c->argv[1],c->argv[2]);
|
||||
return;
|
||||
|
||||
/* On socket errors we try to close the cached socket and try again.
|
||||
@@ -4982,7 +5115,12 @@ socket_err:
|
||||
/* Cleanup we want to perform in both the retry and no retry case.
|
||||
* Note: Closing the migrate socket will also force SELECT next time. */
|
||||
sdsfree(cmd.io.buffer.ptr);
|
||||
migrateCloseSocket(c->argv[1],c->argv[2]);
|
||||
|
||||
/* If the command was rewritten as DEL and there was a socket error,
|
||||
* we already closed the socket earlier. While migrateCloseSocket()
|
||||
* is idempotent, the host/port arguments are now gone, so don't do it
|
||||
* again. */
|
||||
if (!argv_rewritten) migrateCloseSocket(c->argv[1],c->argv[2]);
|
||||
zfree(newargv);
|
||||
newargv = NULL; /* This will get reallocated on retry. */
|
||||
|
||||
@@ -5285,8 +5423,9 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* All keys must belong to the same slot, so check first key only. */
|
||||
di = dictGetIterator(c->bpop.keys);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
if ((de = dictNext(di)) != NULL) {
|
||||
robj *key = dictGetKey(de);
|
||||
int slot = keyHashSlot((char*)key->ptr, sdslen(key->ptr));
|
||||
clusterNode *node = server.cluster->slots[slot];
|
||||
@@ -5304,6 +5443,7 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
|
||||
clusterRedirectClient(c,node,slot,
|
||||
CLUSTER_REDIR_MOVED);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
+31
-24
@@ -73,6 +73,29 @@ typedef struct clusterLink {
|
||||
#define CLUSTER_CANT_FAILOVER_WAITING_VOTES 4
|
||||
#define CLUSTER_CANT_FAILOVER_RELOG_PERIOD (60*5) /* seconds. */
|
||||
|
||||
/* clusterState todo_before_sleep flags. */
|
||||
#define CLUSTER_TODO_HANDLE_FAILOVER (1<<0)
|
||||
#define CLUSTER_TODO_UPDATE_STATE (1<<1)
|
||||
#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
|
||||
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
||||
|
||||
/* Message types.
|
||||
*
|
||||
* Note that the PING, PONG and MEET messages are actually the same exact
|
||||
* kind of packet. PONG is the reply to ping, in the exact format as a PING,
|
||||
* while MEET is a special PING that forces the receiver to add the sender
|
||||
* as a node (if it is not already in the list). */
|
||||
#define CLUSTERMSG_TYPE_PING 0 /* Ping */
|
||||
#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
|
||||
#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
|
||||
#define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */
|
||||
#define CLUSTERMSG_TYPE_PUBLISH 4 /* Pub/Sub Publish propagation */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* May I failover? */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */
|
||||
#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
|
||||
#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
|
||||
#define CLUSTERMSG_TYPE_COUNT 9 /* Total number of message types. */
|
||||
|
||||
/* This structure represent elements of node->fail_reports. */
|
||||
typedef struct clusterNodeFailReport {
|
||||
struct clusterNode *node; /* Node reporting the failure condition. */
|
||||
@@ -116,7 +139,8 @@ typedef struct clusterState {
|
||||
clusterNode *migrating_slots_to[CLUSTER_SLOTS];
|
||||
clusterNode *importing_slots_from[CLUSTER_SLOTS];
|
||||
clusterNode *slots[CLUSTER_SLOTS];
|
||||
zskiplist *slots_to_keys;
|
||||
uint64_t slots_keys_count[CLUSTER_SLOTS];
|
||||
rax *slots_to_keys;
|
||||
/* The following fields are used to take the slave state on elections. */
|
||||
mstime_t failover_auth_time; /* Time of previous or next election. */
|
||||
int failover_auth_count; /* Number of votes received so far. */
|
||||
@@ -138,32 +162,15 @@ typedef struct clusterState {
|
||||
/* The followign fields are used by masters to take state on elections. */
|
||||
uint64_t lastVoteEpoch; /* Epoch of the last vote granted. */
|
||||
int todo_before_sleep; /* Things to do in clusterBeforeSleep(). */
|
||||
long long stats_bus_messages_sent; /* Num of msg sent via cluster bus. */
|
||||
long long stats_bus_messages_received; /* Num of msg rcvd via cluster bus.*/
|
||||
/* Messages received and sent by type. */
|
||||
long long stats_bus_messages_sent[CLUSTERMSG_TYPE_COUNT];
|
||||
long long stats_bus_messages_received[CLUSTERMSG_TYPE_COUNT];
|
||||
long long stats_pfail_nodes; /* Number of nodes in PFAIL status,
|
||||
excluding nodes without address. */
|
||||
} clusterState;
|
||||
|
||||
/* clusterState todo_before_sleep flags. */
|
||||
#define CLUSTER_TODO_HANDLE_FAILOVER (1<<0)
|
||||
#define CLUSTER_TODO_UPDATE_STATE (1<<1)
|
||||
#define CLUSTER_TODO_SAVE_CONFIG (1<<2)
|
||||
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
|
||||
|
||||
/* Redis cluster messages header */
|
||||
|
||||
/* Note that the PING, PONG and MEET messages are actually the same exact
|
||||
* kind of packet. PONG is the reply to ping, in the exact format as a PING,
|
||||
* while MEET is a special PING that forces the receiver to add the sender
|
||||
* as a node (if it is not already in the list). */
|
||||
#define CLUSTERMSG_TYPE_PING 0 /* Ping */
|
||||
#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
|
||||
#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
|
||||
#define CLUSTERMSG_TYPE_FAIL 3 /* Mark node xxx as failing */
|
||||
#define CLUSTERMSG_TYPE_PUBLISH 4 /* Pub/Sub Publish propagation */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* May I failover? */
|
||||
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */
|
||||
#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
|
||||
#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
|
||||
|
||||
/* Initially we don't know our "name", but we'll find it once we connect
|
||||
* to the first node, using the getsockname() function. Then we'll use this
|
||||
* address for all the next messages. */
|
||||
@@ -225,7 +232,7 @@ union clusterMsgData {
|
||||
typedef struct {
|
||||
char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 0. */
|
||||
uint16_t ver; /* Protocol version, currently set to 1. */
|
||||
uint16_t port; /* TCP base port number. */
|
||||
uint16_t type; /* Message type */
|
||||
uint16_t count; /* Only used for some kind of messages. */
|
||||
|
||||
+73
-1
@@ -283,6 +283,10 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
fclose(logfp);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) {
|
||||
if ((server.always_show_logo = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
|
||||
if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -419,6 +423,10 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"activedefrag") && argc == 2) {
|
||||
if ((server.active_defrag_enabled = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
|
||||
if ((server.daemonize = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -495,6 +503,36 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
zfree(server.rdb_filename);
|
||||
server.rdb_filename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-threshold-lower") && argc == 2) {
|
||||
server.active_defrag_threshold_lower = atoi(argv[1]);
|
||||
if (server.active_defrag_threshold_lower < 0) {
|
||||
err = "active-defrag-threshold-lower must be 0 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-threshold-upper") && argc == 2) {
|
||||
server.active_defrag_threshold_upper = atoi(argv[1]);
|
||||
if (server.active_defrag_threshold_upper < 0) {
|
||||
err = "active-defrag-threshold-upper must be 0 or greater";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-ignore-bytes") && argc == 2) {
|
||||
server.active_defrag_ignore_bytes = memtoll(argv[1], NULL);
|
||||
if (server.active_defrag_ignore_bytes <= 0) {
|
||||
err = "active-defrag-ignore-bytes must above 0";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-cycle-min") && argc == 2) {
|
||||
server.active_defrag_cycle_min = atoi(argv[1]);
|
||||
if (server.active_defrag_cycle_min < 1 || server.active_defrag_cycle_min > 99) {
|
||||
err = "active-defrag-cycle-min must be between 1 and 99";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"active-defrag-cycle-max") && argc == 2) {
|
||||
server.active_defrag_cycle_max = atoi(argv[1]);
|
||||
if (server.active_defrag_cycle_max < 1 || server.active_defrag_cycle_max > 99) {
|
||||
err = "active-defrag-cycle-max must be between 1 and 99";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
|
||||
server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
|
||||
@@ -967,6 +1005,18 @@ void configSetCommand(client *c) {
|
||||
"slave-read-only",server.repl_slave_ro) {
|
||||
} config_set_bool_field(
|
||||
"activerehashing",server.activerehashing) {
|
||||
} config_set_bool_field(
|
||||
"activedefrag",server.active_defrag_enabled) {
|
||||
#ifndef HAVE_DEFRAG
|
||||
if (server.active_defrag_enabled) {
|
||||
server.active_defrag_enabled = 0;
|
||||
addReplyError(c,
|
||||
"Active defragmentation cannot be enabled: it requires a "
|
||||
"Redis server compiled with a modified Jemalloc like the "
|
||||
"one shipped by default with the Redis source distribution");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
} config_set_bool_field(
|
||||
"protected-mode",server.protected_mode) {
|
||||
} config_set_bool_field(
|
||||
@@ -994,6 +1044,16 @@ void configSetCommand(client *c) {
|
||||
"lfu-decay-time",server.lfu_decay_time,0,LLONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"timeout",server.maxidletime,0,LONG_MAX) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-threshold-lower",server.active_defrag_threshold_lower,0,1000) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-threshold-upper",server.active_defrag_threshold_upper,0,1000) {
|
||||
} config_set_memory_field(
|
||||
"active-defrag-ignore-bytes",server.active_defrag_ignore_bytes) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-cycle-min",server.active_defrag_cycle_min,1,99) {
|
||||
} config_set_numerical_field(
|
||||
"active-defrag-cycle-max",server.active_defrag_cycle_max,1,99) {
|
||||
} config_set_numerical_field(
|
||||
"auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,LLONG_MAX){
|
||||
} config_set_numerical_field(
|
||||
@@ -1162,6 +1222,11 @@ void configGetCommand(client *c) {
|
||||
config_get_numerical_field("maxmemory",server.maxmemory);
|
||||
config_get_numerical_field("maxmemory-samples",server.maxmemory_samples);
|
||||
config_get_numerical_field("timeout",server.maxidletime);
|
||||
config_get_numerical_field("active-defrag-threshold-lower",server.active_defrag_threshold_lower);
|
||||
config_get_numerical_field("active-defrag-threshold-upper",server.active_defrag_threshold_upper);
|
||||
config_get_numerical_field("active-defrag-ignore-bytes",server.active_defrag_ignore_bytes);
|
||||
config_get_numerical_field("active-defrag-cycle-min",server.active_defrag_cycle_min);
|
||||
config_get_numerical_field("active-defrag-cycle-max",server.active_defrag_cycle_max);
|
||||
config_get_numerical_field("auto-aof-rewrite-percentage",
|
||||
server.aof_rewrite_perc);
|
||||
config_get_numerical_field("auto-aof-rewrite-min-size",
|
||||
@@ -1226,6 +1291,7 @@ void configGetCommand(client *c) {
|
||||
config_get_bool_field("rdbcompression", server.rdb_compression);
|
||||
config_get_bool_field("rdbchecksum", server.rdb_checksum);
|
||||
config_get_bool_field("activerehashing", server.activerehashing);
|
||||
config_get_bool_field("activedefrag", server.active_defrag_enabled);
|
||||
config_get_bool_field("protected-mode", server.protected_mode);
|
||||
config_get_bool_field("repl-disable-tcp-nodelay",
|
||||
server.repl_disable_tcp_nodelay);
|
||||
@@ -1357,7 +1423,7 @@ void configGetCommand(client *c) {
|
||||
/* We use the following dictionary type to store where a configuration
|
||||
* option is mentioned in the old configuration file, so it's
|
||||
* like "maxmemory" -> list of line numbers (first line is zero). */
|
||||
unsigned int dictSdsCaseHash(const void *key);
|
||||
uint64_t dictSdsCaseHash(const void *key);
|
||||
int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
|
||||
void dictSdsDestructor(void *privdata, void *val);
|
||||
void dictListDestructor(void *privdata, void *val);
|
||||
@@ -1926,6 +1992,11 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY);
|
||||
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum,CONFIG_DEFAULT_MAXMEMORY_POLICY);
|
||||
rewriteConfigNumericalOption(state,"maxmemory-samples",server.maxmemory_samples,CONFIG_DEFAULT_MAXMEMORY_SAMPLES);
|
||||
rewriteConfigNumericalOption(state,"active-defrag-threshold-lower",server.active_defrag_threshold_lower,CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER);
|
||||
rewriteConfigNumericalOption(state,"active-defrag-threshold-upper",server.active_defrag_threshold_upper,CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER);
|
||||
rewriteConfigBytesOption(state,"active-defrag-ignore-bytes",server.active_defrag_ignore_bytes,CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES);
|
||||
rewriteConfigNumericalOption(state,"active-defrag-cycle-min",server.active_defrag_cycle_min,CONFIG_DEFAULT_DEFRAG_CYCLE_MIN);
|
||||
rewriteConfigNumericalOption(state,"active-defrag-cycle-max",server.active_defrag_cycle_max,CONFIG_DEFAULT_DEFRAG_CYCLE_MAX);
|
||||
rewriteConfigYesNoOption(state,"appendonly",server.aof_state != AOF_OFF,0);
|
||||
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,CONFIG_DEFAULT_AOF_FILENAME);
|
||||
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC);
|
||||
@@ -1952,6 +2023,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
|
||||
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
|
||||
rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
|
||||
rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ);
|
||||
|
||||
@@ -206,4 +206,22 @@ void setproctitle(const char *fmt, ...);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Make sure we can test for ARM just checking for __arm__, since sometimes
|
||||
* __arm is defined but __arm__ is not. */
|
||||
#if defined(__arm) && !defined(__arm__)
|
||||
#define __arm__
|
||||
#endif
|
||||
#if defined (__aarch64__) && !defined(__arm64__)
|
||||
#define __arm64__
|
||||
#endif
|
||||
|
||||
/* Make sure we can test for SPARC just checking for __sparc__. */
|
||||
#if defined(__sparc) && !defined(__sparc__)
|
||||
#define __sparc__
|
||||
#endif
|
||||
|
||||
#if defined(__sparc__) || defined(__arm__)
|
||||
#define USE_ALIGNED_ACCESS
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -93,7 +93,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
|
||||
if (expireIfNeeded(db,key) == 1) {
|
||||
/* Key expired. If we are in the context of a master, expireIfNeeded()
|
||||
* returns 0 only when the key does not exist at all, so it's save
|
||||
* returns 0 only when the key does not exist at all, so it's safe
|
||||
* to return NULL ASAP. */
|
||||
if (server.masterhost == NULL) return NULL;
|
||||
|
||||
@@ -190,7 +190,9 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
*
|
||||
* 1) The ref count of the value object is incremented.
|
||||
* 2) clients WATCHing for the destination key notified.
|
||||
* 3) The expire time of the key is reset (the key is made persistent). */
|
||||
* 3) The expire time of the key is reset (the key is made persistent).
|
||||
*
|
||||
* All the new keys in the database should be craeted via this interface. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
@@ -330,6 +332,7 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
slotToKeyFlush();
|
||||
}
|
||||
}
|
||||
if (dbnum == -1) flushSlaveKeysWithExpireList();
|
||||
return removed;
|
||||
}
|
||||
|
||||
@@ -662,7 +665,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
privdata[0] = keys;
|
||||
privdata[1] = o;
|
||||
do {
|
||||
cursor = dictScan(ht, cursor, scanCallback, privdata);
|
||||
cursor = dictScan(ht, cursor, scanCallback, NULL, privdata);
|
||||
} while (cursor &&
|
||||
maxiterations-- &&
|
||||
listLength(keys) < (unsigned long)count);
|
||||
@@ -851,7 +854,7 @@ void renameGenericCommand(client *c, int nx) {
|
||||
dbDelete(c->db,c->argv[2]);
|
||||
}
|
||||
dbAdd(c->db,c->argv[2],o);
|
||||
if (expire != -1) setExpire(c->db,c->argv[2],expire);
|
||||
if (expire != -1) setExpire(c,c->db,c->argv[2],expire);
|
||||
dbDelete(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
signalModifiedKey(c->db,c->argv[2]);
|
||||
@@ -917,7 +920,7 @@ void moveCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
dbAdd(dst,c->argv[1],o);
|
||||
if (expire != -1) setExpire(dst,c->argv[1],expire);
|
||||
if (expire != -1) setExpire(c,dst,c->argv[1],expire);
|
||||
incrRefCount(o);
|
||||
|
||||
/* OK! key moved, free the entry in the source DB */
|
||||
@@ -1022,7 +1025,11 @@ int removeExpire(redisDb *db, robj *key) {
|
||||
return dictDelete(db->expires,key->ptr) == DICT_OK;
|
||||
}
|
||||
|
||||
void setExpire(redisDb *db, robj *key, long long when) {
|
||||
/* Set an expire to the specified key. If the expire is set in the context
|
||||
* of an user calling a command 'c' is the client, otherwise 'c' is set
|
||||
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
|
||||
* after which the key will no longer be considered valid. */
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when) {
|
||||
dictEntry *kde, *de;
|
||||
|
||||
/* Reuse the sds from the main dict in the expire dict */
|
||||
@@ -1030,6 +1037,10 @@ void setExpire(redisDb *db, robj *key, long long when) {
|
||||
serverAssertWithInfo(NULL,key,kde != NULL);
|
||||
de = dictAddOrFind(db->expires,dictGetKey(kde));
|
||||
dictSetSignedIntegerVal(de,when);
|
||||
|
||||
int writable_slave = server.masterhost && server.repl_slave_ro == 0;
|
||||
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
|
||||
rememberSlaveKeyWithExpire(db,key);
|
||||
}
|
||||
|
||||
/* Return the expire time of the specified key, or -1 if no expire
|
||||
@@ -1122,11 +1133,24 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
last = cmd->lastkey;
|
||||
if (last < 0) last = argc+last;
|
||||
keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1));
|
||||
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
|
||||
serverAssert(j < argc);
|
||||
if (j >= argc) {
|
||||
/* Modules command do not have dispatch time arity checks, so
|
||||
* we need to handle the case where the user passed an invalid
|
||||
* number of arguments here. In this case we return no keys
|
||||
* and expect the module command to report an arity error. */
|
||||
if (cmd->flags & CMD_MODULE) {
|
||||
zfree(keys);
|
||||
*numkeys = 0;
|
||||
return NULL;
|
||||
} else {
|
||||
serverPanic("Redis built-in command declared keys positions not matching the arity requirements.");
|
||||
}
|
||||
}
|
||||
keys[i++] = j;
|
||||
}
|
||||
*numkeys = i;
|
||||
@@ -1288,92 +1312,125 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from following commands:
|
||||
* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
|
||||
* [COUNT count] [STORE key] [STOREDIST key]
|
||||
* GEORADIUSBYMEMBER key member radius unit ... options ... */
|
||||
int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
int i, num, *keys;
|
||||
UNUSED(cmd);
|
||||
|
||||
/* Check for the presence of the stored key in the command */
|
||||
int stored_key = -1;
|
||||
for (i = 5; i < argc; i++) {
|
||||
char *arg = argv[i]->ptr;
|
||||
/* For the case when user specifies both "store" and "storedist" options, the
|
||||
* second key specified would override the first key. This behavior is kept
|
||||
* the same as in georadiusCommand method.
|
||||
*/
|
||||
if ((!strcasecmp(arg, "store") || !strcasecmp(arg, "storedist")) && ((i+1) < argc)) {
|
||||
stored_key = i+1;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
num = 1 + (stored_key == -1 ? 0 : 1);
|
||||
|
||||
/* Keys in the command come from two places:
|
||||
* argv[1] = key,
|
||||
* argv[5...n] = stored key if present
|
||||
*/
|
||||
keys = zmalloc(sizeof(int) * num);
|
||||
|
||||
/* Add all key positions to keys[] */
|
||||
keys[0] = 1;
|
||||
if(num > 1) {
|
||||
keys[1] = stored_key;
|
||||
}
|
||||
*numkeys = num;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Slot to Key API. This is used by Redis Cluster in order to obtain in
|
||||
* a fast way a key that belongs to a specified hash slot. This is useful
|
||||
* while rehashing the cluster. */
|
||||
void slotToKeyAdd(robj *key) {
|
||||
* while rehashing the cluster and in other conditions when we need to
|
||||
* understand if we have keys for a given hash slot. */
|
||||
void slotToKeyUpdateKey(robj *key, int add) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
unsigned char buf[64];
|
||||
unsigned char *indexed = buf;
|
||||
size_t keylen = sdslen(key->ptr);
|
||||
|
||||
sds sdskey = sdsdup(key->ptr);
|
||||
zslInsert(server.cluster->slots_to_keys,hashslot,sdskey);
|
||||
server.cluster->slots_keys_count[hashslot] += add ? 1 : -1;
|
||||
if (keylen+2 > 64) indexed = zmalloc(keylen+2);
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
memcpy(indexed+2,key->ptr,keylen);
|
||||
if (add) {
|
||||
raxInsert(server.cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
|
||||
} else {
|
||||
raxRemove(server.cluster->slots_to_keys,indexed,keylen+2,NULL);
|
||||
}
|
||||
if (indexed != buf) zfree(indexed);
|
||||
}
|
||||
|
||||
void slotToKeyAdd(robj *key) {
|
||||
slotToKeyUpdateKey(key,1);
|
||||
}
|
||||
|
||||
void slotToKeyDel(robj *key) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
zslDelete(server.cluster->slots_to_keys,hashslot,key->ptr,NULL);
|
||||
slotToKeyUpdateKey(key,0);
|
||||
}
|
||||
|
||||
void slotToKeyFlush(void) {
|
||||
zslFree(server.cluster->slots_to_keys);
|
||||
server.cluster->slots_to_keys = zslCreate();
|
||||
raxFree(server.cluster->slots_to_keys);
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
}
|
||||
|
||||
/* Pupulate the specified array of objects with keys in the specified slot.
|
||||
* New objects are returned to represent keys, it's up to the caller to
|
||||
* decrement the reference count to release the keys names. */
|
||||
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) {
|
||||
zskiplistNode *n;
|
||||
zrangespec range;
|
||||
raxIterator iter;
|
||||
int j = 0;
|
||||
unsigned char indexed[2];
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, &range);
|
||||
while(n && n->score == hashslot && count--) {
|
||||
keys[j++] = createStringObject(n->ele,sdslen(n->ele));
|
||||
n = n->level[0].forward;
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
raxStart(&iter,server.cluster->slots_to_keys);
|
||||
raxSeek(&iter,">=",indexed,2);
|
||||
while(count-- && raxNext(&iter)) {
|
||||
if (iter.key[0] != indexed[0] || iter.key[1] != indexed[1]) break;
|
||||
keys[j++] = createStringObject((char*)iter.key+2,iter.key_len-2);
|
||||
}
|
||||
raxStop(&iter);
|
||||
return j;
|
||||
}
|
||||
|
||||
/* Remove all the keys in the specified hash slot.
|
||||
* The number of removed items is returned. */
|
||||
unsigned int delKeysInSlot(unsigned int hashslot) {
|
||||
zskiplistNode *n;
|
||||
zrangespec range;
|
||||
raxIterator iter;
|
||||
int j = 0;
|
||||
unsigned char indexed[2];
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
raxStart(&iter,server.cluster->slots_to_keys);
|
||||
while(server.cluster->slots_keys_count[hashslot]) {
|
||||
raxSeek(&iter,">=",indexed,2);
|
||||
raxNext(&iter);
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, &range);
|
||||
while(n && n->score == hashslot) {
|
||||
sds sdskey = n->ele;
|
||||
robj *key = createStringObject(sdskey,sdslen(sdskey));
|
||||
n = n->level[0].forward; /* Go to the next item before freeing it. */
|
||||
robj *key = createStringObject((char*)iter.key+2,iter.key_len-2);
|
||||
dbDelete(&server.db[0],key);
|
||||
decrRefCount(key);
|
||||
j++;
|
||||
}
|
||||
raxStop(&iter);
|
||||
return j;
|
||||
}
|
||||
|
||||
unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
zskiplist *zsl = server.cluster->slots_to_keys;
|
||||
zskiplistNode *zn;
|
||||
zrangespec range;
|
||||
int rank, count = 0;
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
/* Find first element in range */
|
||||
zn = zslFirstInRange(zsl, &range);
|
||||
|
||||
/* Use rank of first element, if any, to determine preliminary count */
|
||||
if (zn != NULL) {
|
||||
rank = zslGetRank(zsl, zn->score, zn->ele);
|
||||
count = (zsl->length - (rank - 1));
|
||||
|
||||
/* Find last element in range */
|
||||
zn = zslLastInRange(zsl, &range);
|
||||
|
||||
/* Use rank of last element, if any, to determine the actual count */
|
||||
if (zn != NULL) {
|
||||
rank = zslGetRank(zsl, zn->score, zn->ele);
|
||||
count -= (zsl->length - rank);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
return server.cluster->slots_keys_count[hashslot];
|
||||
}
|
||||
|
||||
+49
-8
@@ -126,7 +126,7 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
redisDb *db = server.db+j;
|
||||
|
||||
if (dictSize(db->dict) == 0) continue;
|
||||
di = dictGetIterator(db->dict);
|
||||
di = dictGetSafeIterator(db->dict);
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different
|
||||
* DB will lead to a different digest */
|
||||
@@ -239,6 +239,15 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
xorDigest(digest,eledigest,20);
|
||||
}
|
||||
hashTypeReleaseIterator(hi);
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
RedisModuleDigest md;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitDigestContext(md);
|
||||
if (mt->digest) {
|
||||
mt->digest(&md,mv->value);
|
||||
xorDigest(digest,md.x,sizeof(md.x));
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
@@ -266,6 +275,8 @@ void debugCommand(client *c) {
|
||||
blen++; addReplyStatus(c,
|
||||
"segfault -- Crash the server with sigsegv.");
|
||||
blen++; addReplyStatus(c,
|
||||
"panic -- Crash the server simulating a panic.");
|
||||
blen++; addReplyStatus(c,
|
||||
"restart -- Graceful restart: save config, db, restart.");
|
||||
blen++; addReplyStatus(c,
|
||||
"crash-and-recovery <milliseconds> -- Hard crash and restart after <milliseconds> delay.");
|
||||
@@ -280,7 +291,9 @@ void debugCommand(client *c) {
|
||||
blen++; addReplyStatus(c,
|
||||
"sdslen <key> -- Show low level SDS string info representing key and value.");
|
||||
blen++; addReplyStatus(c,
|
||||
"populate <count> [prefix] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.");
|
||||
"ziplist <key> -- Show low level info about the ziplist encoding.");
|
||||
blen++; addReplyStatus(c,
|
||||
"populate <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.");
|
||||
blen++; addReplyStatus(c,
|
||||
"digest -- Outputs an hex signature representing the current DB content.");
|
||||
blen++; addReplyStatus(c,
|
||||
@@ -298,6 +311,8 @@ void debugCommand(client *c) {
|
||||
setDeferredMultiBulkLength(c,blenp,blen);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"segfault")) {
|
||||
*((char*)-1) = 'x';
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"panic")) {
|
||||
serverPanic("DEBUG PANIC called at Unix time %ld", time(NULL));
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"restart") ||
|
||||
!strcasecmp(c->argv[1]->ptr,"crash-and-recover"))
|
||||
{
|
||||
@@ -418,8 +433,20 @@ void debugCommand(client *c) {
|
||||
(long long) sdsavail(val->ptr),
|
||||
(long long) getStringObjectSdsUsedMemory(val));
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"ziplist") && c->argc == 3) {
|
||||
robj *o;
|
||||
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
|
||||
== NULL) return;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
} else {
|
||||
ziplistRepr(o->ptr);
|
||||
addReplyStatus(c,"Ziplist structure printed on stdout");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"populate") &&
|
||||
(c->argc == 3 || c->argc == 4)) {
|
||||
c->argc >= 3 && c->argc <= 5) {
|
||||
long keys, j;
|
||||
robj *key, *val;
|
||||
char buf[128];
|
||||
@@ -428,15 +455,25 @@ void debugCommand(client *c) {
|
||||
return;
|
||||
dictExpand(c->db->dict,keys);
|
||||
for (j = 0; j < keys; j++) {
|
||||
long valsize = 0;
|
||||
snprintf(buf,sizeof(buf),"%s:%lu",
|
||||
(c->argc == 3) ? "key" : (char*)c->argv[3]->ptr, j);
|
||||
key = createStringObject(buf,strlen(buf));
|
||||
if (c->argc == 5)
|
||||
if (getLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK)
|
||||
return;
|
||||
if (lookupKeyWrite(c->db,key) != NULL) {
|
||||
decrRefCount(key);
|
||||
continue;
|
||||
}
|
||||
snprintf(buf,sizeof(buf),"value:%lu",j);
|
||||
val = createStringObject(buf,strlen(buf));
|
||||
if (valsize==0)
|
||||
val = createStringObject(buf,strlen(buf));
|
||||
else {
|
||||
int buflen = strlen(buf);
|
||||
val = createStringObject(NULL,valsize);
|
||||
memcpy(val->ptr, buf, valsize<=buflen? valsize: buflen);
|
||||
}
|
||||
dbAdd(c->db,key,val);
|
||||
signalModifiedKey(c->db,key);
|
||||
decrRefCount(key);
|
||||
@@ -591,11 +628,17 @@ void _serverAssertWithInfo(const client *c, const robj *o, const char *estr, con
|
||||
_serverAssert(estr,file,line);
|
||||
}
|
||||
|
||||
void _serverPanic(const char *msg, const char *file, int line) {
|
||||
void _serverPanic(const char *file, int line, const char *msg, ...) {
|
||||
va_list ap;
|
||||
va_start(ap,msg);
|
||||
char fmtmsg[256];
|
||||
vsnprintf(fmtmsg,sizeof(fmtmsg),msg,ap);
|
||||
va_end(ap);
|
||||
|
||||
bugReportStart();
|
||||
serverLog(LL_WARNING,"------------------------------------------------");
|
||||
serverLog(LL_WARNING,"!!! Software Failure. Press left mouse button to continue");
|
||||
serverLog(LL_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line);
|
||||
serverLog(LL_WARNING,"Guru Meditation: %s #%s:%d",fmtmsg,file,line);
|
||||
#ifdef HAVE_BACKTRACE
|
||||
serverLog(LL_WARNING,"(forcing SIGSEGV in order to print the stack trace)");
|
||||
#endif
|
||||
@@ -995,8 +1038,6 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
/* Log INFO and CLIENT LIST */
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ INFO OUTPUT ------\n");
|
||||
infostring = genRedisInfoString("all");
|
||||
infostring = sdscatprintf(infostring, "hash_init_value: %u\n",
|
||||
dictGetHashFunctionSeed());
|
||||
serverLogRaw(LL_WARNING|LL_RAW, infostring);
|
||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ CLIENT LIST OUTPUT ------\n");
|
||||
clients = getAllClientsInfoString();
|
||||
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
/*
|
||||
* Active memory defragmentation
|
||||
* Try to find key / value allocations that need to be re-allocated in order
|
||||
* to reduce external fragmentation.
|
||||
* We do that by scanning the keyspace and for each pointer we have, we can try to
|
||||
* ask the allocator if moving it to a new address will help reduce fragmentation.
|
||||
*
|
||||
* Copyright (c) 2017, Oran Agra
|
||||
* Copyright (c) 2017, Redis Labs, Inc
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef HAVE_DEFRAG
|
||||
|
||||
/* this method was added to jemalloc in order to help us understand which
|
||||
* pointers are worthwhile moving and which aren't */
|
||||
int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
|
||||
|
||||
/* Defrag helper for generic allocations.
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
void* activeDefragAlloc(void *ptr) {
|
||||
int bin_util, run_util;
|
||||
size_t size;
|
||||
void *newptr;
|
||||
if(!je_get_defrag_hint(ptr, &bin_util, &run_util)) {
|
||||
server.stat_active_defrag_misses++;
|
||||
return NULL;
|
||||
}
|
||||
/* if this run is more utilized than the average utilization in this bin
|
||||
* (or it is full), skip it. This will eventually move all the allocations
|
||||
* from relatively empty runs into relatively full runs. */
|
||||
if (run_util > bin_util || run_util == 1<<16) {
|
||||
server.stat_active_defrag_misses++;
|
||||
return NULL;
|
||||
}
|
||||
/* move this allocation to a new allocation.
|
||||
* make sure not to use the thread cache. so that we don't get back the same
|
||||
* pointers we try to free */
|
||||
size = zmalloc_size(ptr);
|
||||
newptr = zmalloc_no_tcache(size);
|
||||
memcpy(newptr, ptr, size);
|
||||
zfree_no_tcache(ptr);
|
||||
return newptr;
|
||||
}
|
||||
|
||||
/*Defrag helper for sds strings
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
sds activeDefragSds(sds sdsptr) {
|
||||
void* ptr = sdsAllocPtr(sdsptr);
|
||||
void* newptr = activeDefragAlloc(ptr);
|
||||
if (newptr) {
|
||||
size_t offset = sdsptr - (char*)ptr;
|
||||
sdsptr = (char*)newptr + offset;
|
||||
return sdsptr;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Defrag helper for robj and/or string objects
|
||||
*
|
||||
* returns NULL in case the allocatoin wasn't moved.
|
||||
* when it returns a non-null value, the old pointer was already released
|
||||
* and should NOT be accessed. */
|
||||
robj *activeDefragStringOb(robj* ob, int *defragged) {
|
||||
robj *ret = NULL;
|
||||
if (ob->refcount!=1)
|
||||
return NULL;
|
||||
|
||||
/* try to defrag robj (only if not an EMBSTR type (handled below). */
|
||||
if (ob->type!=OBJ_STRING || ob->encoding!=OBJ_ENCODING_EMBSTR) {
|
||||
if ((ret = activeDefragAlloc(ob))) {
|
||||
ob = ret;
|
||||
(*defragged)++;
|
||||
}
|
||||
}
|
||||
|
||||
/* try to defrag string object */
|
||||
if (ob->type == OBJ_STRING) {
|
||||
if(ob->encoding==OBJ_ENCODING_RAW) {
|
||||
sds newsds = activeDefragSds((sds)ob->ptr);
|
||||
if (newsds) {
|
||||
ob->ptr = newsds;
|
||||
(*defragged)++;
|
||||
}
|
||||
} else if (ob->encoding==OBJ_ENCODING_EMBSTR) {
|
||||
/* The sds is embedded in the object allocation, calculate the
|
||||
* offset and update the pointer in the new allocation. */
|
||||
long ofs = (intptr_t)ob->ptr - (intptr_t)ob;
|
||||
if ((ret = activeDefragAlloc(ob))) {
|
||||
ret->ptr = (void*)((intptr_t)ret + ofs);
|
||||
(*defragged)++;
|
||||
}
|
||||
} else if (ob->encoding!=OBJ_ENCODING_INT) {
|
||||
serverPanic("Unknown string encoding");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Defrag helper for dictEntries to be used during dict iteration (called on
|
||||
* each step). Teturns a stat of how many pointers were moved. */
|
||||
int dictIterDefragEntry(dictIterator *iter) {
|
||||
/* This function is a little bit dirty since it messes with the internals
|
||||
* of the dict and it's iterator, but the benefit is that it is very easy
|
||||
* to use, and require no other chagnes in the dict. */
|
||||
int defragged = 0;
|
||||
dictht *ht;
|
||||
/* Handle the next entry (if there is one), and update the pointer in the
|
||||
* current entry. */
|
||||
if (iter->nextEntry) {
|
||||
dictEntry *newde = activeDefragAlloc(iter->nextEntry);
|
||||
if (newde) {
|
||||
defragged++;
|
||||
iter->nextEntry = newde;
|
||||
iter->entry->next = newde;
|
||||
}
|
||||
}
|
||||
/* handle the case of the first entry in the hash bucket. */
|
||||
ht = &iter->d->ht[iter->table];
|
||||
if (ht->table[iter->index] == iter->entry) {
|
||||
dictEntry *newde = activeDefragAlloc(iter->entry);
|
||||
if (newde) {
|
||||
iter->entry = newde;
|
||||
ht->table[iter->index] = newde;
|
||||
defragged++;
|
||||
}
|
||||
}
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Defrag helper for dict main allocations (dict struct, and hash tables).
|
||||
* receives a pointer to the dict* and implicitly updates it when the dict
|
||||
* struct itself was moved. Returns a stat of how many pointers were moved. */
|
||||
int dictDefragTables(dict** dictRef) {
|
||||
dict *d = *dictRef;
|
||||
dictEntry **newtable;
|
||||
int defragged = 0;
|
||||
/* handle the dict struct */
|
||||
dict *newd = activeDefragAlloc(d);
|
||||
if (newd)
|
||||
defragged++, *dictRef = d = newd;
|
||||
/* handle the first hash table */
|
||||
newtable = activeDefragAlloc(d->ht[0].table);
|
||||
if (newtable)
|
||||
defragged++, d->ht[0].table = newtable;
|
||||
/* handle the second hash table */
|
||||
if (d->ht[1].table) {
|
||||
newtable = activeDefragAlloc(d->ht[1].table);
|
||||
if (newtable)
|
||||
defragged++, d->ht[1].table = newtable;
|
||||
}
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Internal function used by zslDefrag */
|
||||
void zslUpdateNode(zskiplist *zsl, zskiplistNode *oldnode, zskiplistNode *newnode, zskiplistNode **update) {
|
||||
int i;
|
||||
for (i = 0; i < zsl->level; i++) {
|
||||
if (update[i]->level[i].forward == oldnode)
|
||||
update[i]->level[i].forward = newnode;
|
||||
}
|
||||
serverAssert(zsl->header!=oldnode);
|
||||
if (newnode->level[0].forward) {
|
||||
serverAssert(newnode->level[0].forward->backward==oldnode);
|
||||
newnode->level[0].forward->backward = newnode;
|
||||
} else {
|
||||
serverAssert(zsl->tail==oldnode);
|
||||
zsl->tail = newnode;
|
||||
}
|
||||
}
|
||||
|
||||
/* Defrag helper for sorted set.
|
||||
* Update the robj pointer, defrag the skiplist struct and return the new score
|
||||
* reference. We may not access oldele pointer (not even the pointer stored in
|
||||
* the skiplist), as it was already freed. Newele may be null, in which case we
|
||||
* only need to defrag the skiplist, but not update the obj pointer.
|
||||
* When return value is non-NULL, it is the score reference that must be updated
|
||||
* in the dict record. */
|
||||
double *zslDefrag(zskiplist *zsl, double score, sds oldele, sds newele) {
|
||||
zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x, *newx;
|
||||
int i;
|
||||
sds ele = newele? newele: oldele;
|
||||
|
||||
/* find the skiplist node referring to the object that was moved,
|
||||
* and all pointers that need to be updated if we'll end up moving the skiplist node. */
|
||||
x = zsl->header;
|
||||
for (i = zsl->level-1; i >= 0; i--) {
|
||||
while (x->level[i].forward &&
|
||||
x->level[i].forward->ele != oldele && /* make sure not to access the
|
||||
->obj pointer if it matches
|
||||
oldele */
|
||||
(x->level[i].forward->score < score ||
|
||||
(x->level[i].forward->score == score &&
|
||||
sdscmp(x->level[i].forward->ele,ele) < 0)))
|
||||
x = x->level[i].forward;
|
||||
update[i] = x;
|
||||
}
|
||||
|
||||
/* update the robj pointer inside the skip list record. */
|
||||
x = x->level[0].forward;
|
||||
serverAssert(x && score == x->score && x->ele==oldele);
|
||||
if (newele)
|
||||
x->ele = newele;
|
||||
|
||||
/* try to defrag the skiplist record itself */
|
||||
newx = activeDefragAlloc(x);
|
||||
if (newx) {
|
||||
zslUpdateNode(zsl, x, newx, update);
|
||||
return &newx->score;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Utility function that replaces an old key pointer in the dictionary with a
|
||||
* new pointer. Additionally, we try to defrag the dictEntry in that dict.
|
||||
* Oldkey mey be a dead pointer and should not be accessed (we get a
|
||||
* pre-calculated hash value). Newkey may be null if the key pointer wasn't
|
||||
* moved. Return value is the the dictEntry if found, or NULL if not found.
|
||||
* NOTE: this is very ugly code, but it let's us avoid the complication of
|
||||
* doing a scan on another dict. */
|
||||
dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, int *defragged) {
|
||||
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
|
||||
if (deref) {
|
||||
dictEntry *de = *deref;
|
||||
dictEntry *newde = activeDefragAlloc(de);
|
||||
if (newde) {
|
||||
de = *deref = newde;
|
||||
(*defragged)++;
|
||||
}
|
||||
if (newkey)
|
||||
de->key = newkey;
|
||||
return de;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* for each key we scan in the main dict, this function will attempt to defrag
|
||||
* all the various pointers it has. Returns a stat of how many pointers were
|
||||
* moved. */
|
||||
int defragKey(redisDb *db, dictEntry *de) {
|
||||
sds keysds = dictGetKey(de);
|
||||
robj *newob, *ob;
|
||||
unsigned char *newzl;
|
||||
dict *d;
|
||||
dictIterator *di;
|
||||
int defragged = 0;
|
||||
sds newsds;
|
||||
|
||||
/* Try to defrag the key name. */
|
||||
newsds = activeDefragSds(keysds);
|
||||
if (newsds)
|
||||
defragged++, de->key = newsds;
|
||||
if (dictSize(db->expires)) {
|
||||
/* Dirty code:
|
||||
* I can't search in db->expires for that key after i already released
|
||||
* the pointer it holds it won't be able to do the string compare */
|
||||
unsigned int hash = dictGetHash(db->dict, de->key);
|
||||
replaceSateliteDictKeyPtrAndOrDefragDictEntry(db->expires, keysds, newsds, hash, &defragged);
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
ob = dictGetVal(de);
|
||||
if ((newob = activeDefragStringOb(ob, &defragged))) {
|
||||
de->v.val = newob;
|
||||
ob = newob;
|
||||
}
|
||||
|
||||
if (ob->type == OBJ_STRING) {
|
||||
/* Already handled in activeDefragStringOb. */
|
||||
} else if (ob->type == OBJ_LIST) {
|
||||
if (ob->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
quicklist *ql = ob->ptr, *newql;
|
||||
quicklistNode *node = ql->head, *newnode;
|
||||
if ((newql = activeDefragAlloc(ql)))
|
||||
defragged++, ob->ptr = ql = newql;
|
||||
while (node) {
|
||||
if ((newnode = activeDefragAlloc(node))) {
|
||||
if (newnode->prev)
|
||||
newnode->prev->next = newnode;
|
||||
else
|
||||
ql->head = newnode;
|
||||
if (newnode->next)
|
||||
newnode->next->prev = newnode;
|
||||
else
|
||||
ql->tail = newnode;
|
||||
node = newnode;
|
||||
defragged++;
|
||||
}
|
||||
if ((newzl = activeDefragAlloc(node->zl)))
|
||||
defragged++, node->zl = newzl;
|
||||
node = node->next;
|
||||
}
|
||||
} else if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||
defragged++, ob->ptr = newzl;
|
||||
} else {
|
||||
serverPanic("Unknown list encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
if (ob->encoding == OBJ_ENCODING_HT) {
|
||||
d = ob->ptr;
|
||||
di = dictGetIterator(d);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->key = newsds;
|
||||
defragged += dictIterDefragEntry(di);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
dictDefragTables((dict**)&ob->ptr);
|
||||
} else if (ob->encoding == OBJ_ENCODING_INTSET) {
|
||||
intset *is = ob->ptr;
|
||||
intset *newis = activeDefragAlloc(is);
|
||||
if (newis)
|
||||
defragged++, ob->ptr = newis;
|
||||
} else {
|
||||
serverPanic("Unknown set encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||
defragged++, ob->ptr = newzl;
|
||||
} else if (ob->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = (zset*)ob->ptr;
|
||||
zset *newzs;
|
||||
zskiplist *newzsl;
|
||||
struct zskiplistNode *newheader;
|
||||
if ((newzs = activeDefragAlloc(zs)))
|
||||
defragged++, ob->ptr = zs = newzs;
|
||||
if ((newzsl = activeDefragAlloc(zs->zsl)))
|
||||
defragged++, zs->zsl = newzsl;
|
||||
if ((newheader = activeDefragAlloc(zs->zsl->header)))
|
||||
defragged++, zs->zsl->header = newheader;
|
||||
d = zs->dict;
|
||||
di = dictGetIterator(d);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
double* newscore;
|
||||
sds sdsele = dictGetKey(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->key = newsds;
|
||||
newscore = zslDefrag(zs->zsl, *(double*)dictGetVal(de), sdsele, newsds);
|
||||
if (newscore) {
|
||||
dictSetVal(d, de, newscore);
|
||||
defragged++;
|
||||
}
|
||||
defragged += dictIterDefragEntry(di);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
dictDefragTables(&zs->dict);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
|
||||
if ((newzl = activeDefragAlloc(ob->ptr)))
|
||||
defragged++, ob->ptr = newzl;
|
||||
} else if (ob->encoding == OBJ_ENCODING_HT) {
|
||||
d = ob->ptr;
|
||||
di = dictGetIterator(d);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds sdsele = dictGetKey(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->key = newsds;
|
||||
sdsele = dictGetVal(de);
|
||||
if ((newsds = activeDefragSds(sdsele)))
|
||||
defragged++, de->v.val = newsds;
|
||||
defragged += dictIterDefragEntry(di);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
dictDefragTables((dict**)&ob->ptr);
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
/* Currently defragmenting modules private data types
|
||||
* is not supported. */
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
return defragged;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for the main db dictionary. */
|
||||
void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
int defragged = defragKey((redisDb*)privdata, (dictEntry*)de);
|
||||
server.stat_active_defrag_hits += defragged;
|
||||
if(defragged)
|
||||
server.stat_active_defrag_key_hits++;
|
||||
else
|
||||
server.stat_active_defrag_key_misses++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for for each hash table bicket,
|
||||
* used in order to defrag the dictEntry allocations. */
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
UNUSED(privdata);
|
||||
while(*bucketref) {
|
||||
dictEntry *de = *bucketref, *newde;
|
||||
if ((newde = activeDefragAlloc(de))) {
|
||||
*bucketref = newde;
|
||||
}
|
||||
bucketref = &(*bucketref)->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility function to get the fragmentation ratio from jemalloc.
|
||||
* It is critical to do that by comparing only heap maps that belown to
|
||||
* jemalloc, and skip ones the jemalloc keeps as spare. Since we use this
|
||||
* fragmentation ratio in order to decide if a defrag action should be taken
|
||||
* or not, a false detection can cause the defragmenter to waste a lot of CPU
|
||||
* without the possibility of getting any results. */
|
||||
float getAllocatorFragmentation(size_t *out_frag_bytes) {
|
||||
size_t epoch = 1, allocated = 0, resident = 0, active = 0, sz = sizeof(size_t);
|
||||
/* Update the statistics cached by mallctl. */
|
||||
je_mallctl("epoch", &epoch, &sz, &epoch, sz);
|
||||
/* Unlike RSS, this does not include RSS from shared libraries and other non
|
||||
* heap mappings. */
|
||||
je_mallctl("stats.resident", &resident, &sz, NULL, 0);
|
||||
/* Unlike resident, this doesn't not include the pages jemalloc reserves
|
||||
* for re-use (purge will clean that). */
|
||||
je_mallctl("stats.active", &active, &sz, NULL, 0);
|
||||
/* Unlike zmalloc_used_memory, this matches the stats.resident by taking
|
||||
* into account all allocations done by this process (not only zmalloc). */
|
||||
je_mallctl("stats.allocated", &allocated, &sz, NULL, 0);
|
||||
float frag_pct = ((float)active / allocated)*100 - 100;
|
||||
size_t frag_bytes = active - allocated;
|
||||
float rss_pct = ((float)resident / allocated)*100 - 100;
|
||||
size_t rss_bytes = resident - allocated;
|
||||
if(out_frag_bytes)
|
||||
*out_frag_bytes = frag_bytes;
|
||||
serverLog(LL_DEBUG,
|
||||
"allocated=%zu, active=%zu, resident=%zu, frag=%.0f%% (%.0f%% rss), frag_bytes=%zu (%zu%% rss)",
|
||||
allocated, active, resident, frag_pct, rss_pct, frag_bytes, rss_bytes);
|
||||
return frag_pct;
|
||||
}
|
||||
|
||||
#define INTERPOLATE(x, x1, x2, y1, y2) ( (y1) + ((x)-(x1)) * ((y2)-(y1)) / ((x2)-(x1)) )
|
||||
#define LIMIT(y, min, max) ((y)<(min)? min: ((y)>(max)? max: (y)))
|
||||
|
||||
/* Perform incremental defragmentation work from the serverCron.
|
||||
* This works in a similar way to activeExpireCycle, in the sense that
|
||||
* we do incremental work across calls. */
|
||||
void activeDefragCycle(void) {
|
||||
static int current_db = -1;
|
||||
static unsigned long cursor = 0;
|
||||
static redisDb *db = NULL;
|
||||
static long long start_scan, start_stat;
|
||||
unsigned int iterations = 0;
|
||||
unsigned long long defragged = server.stat_active_defrag_hits;
|
||||
long long start, timelimit;
|
||||
|
||||
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1)
|
||||
return; /* Defragging memory while there's a fork will just do damage. */
|
||||
|
||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||
* or making it more aggressive. */
|
||||
run_with_period(1000) {
|
||||
size_t frag_bytes;
|
||||
float frag_pct = getAllocatorFragmentation(&frag_bytes);
|
||||
/* If we're not already running, and below the threshold, exit. */
|
||||
if (!server.active_defrag_running) {
|
||||
if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Calculate the adaptive aggressiveness of the defrag */
|
||||
int cpu_pct = INTERPOLATE(frag_pct,
|
||||
server.active_defrag_threshold_lower,
|
||||
server.active_defrag_threshold_upper,
|
||||
server.active_defrag_cycle_min,
|
||||
server.active_defrag_cycle_max);
|
||||
cpu_pct = LIMIT(cpu_pct,
|
||||
server.active_defrag_cycle_min,
|
||||
server.active_defrag_cycle_max);
|
||||
/* We allow increasing the aggressiveness during a scan, but don't
|
||||
* reduce it. */
|
||||
if (!server.active_defrag_running ||
|
||||
cpu_pct > server.active_defrag_running)
|
||||
{
|
||||
server.active_defrag_running = cpu_pct;
|
||||
serverLog(LL_VERBOSE,
|
||||
"Starting active defrag, frag=%.0f%%, frag_bytes=%zu, cpu=%d%%",
|
||||
frag_pct, frag_bytes, cpu_pct);
|
||||
}
|
||||
}
|
||||
if (!server.active_defrag_running)
|
||||
return;
|
||||
|
||||
/* See activeExpireCycle for how timelimit is handled. */
|
||||
start = ustime();
|
||||
timelimit = 1000000*server.active_defrag_running/server.hz/100;
|
||||
if (timelimit <= 0) timelimit = 1;
|
||||
|
||||
do {
|
||||
if (!cursor) {
|
||||
/* Move on to next database, and stop if we reached the last one. */
|
||||
if (++current_db >= server.dbnum) {
|
||||
long long now = ustime();
|
||||
size_t frag_bytes;
|
||||
float frag_pct = getAllocatorFragmentation(&frag_bytes);
|
||||
serverLog(LL_VERBOSE,
|
||||
"Active defrag done in %dms, reallocated=%d, frag=%.0f%%, frag_bytes=%zu",
|
||||
(int)((now - start_scan)/1000), (int)(server.stat_active_defrag_hits - start_stat), frag_pct, frag_bytes);
|
||||
|
||||
start_scan = now;
|
||||
current_db = -1;
|
||||
cursor = 0;
|
||||
db = NULL;
|
||||
server.active_defrag_running = 0;
|
||||
return;
|
||||
}
|
||||
else if (current_db==0) {
|
||||
/* Start a scan from the first database. */
|
||||
start_scan = ustime();
|
||||
start_stat = server.stat_active_defrag_hits;
|
||||
}
|
||||
|
||||
db = &server.db[current_db];
|
||||
cursor = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
cursor = dictScan(db->dict, cursor, defragScanCallback, defragDictBucketCallback, db);
|
||||
/* Once in 16 scan iterations, or 1000 pointer reallocations
|
||||
* (if we have a lot of pointers in one hash bucket), check if we
|
||||
* reached the tiem limit. */
|
||||
if (cursor && (++iterations > 16 || server.stat_active_defrag_hits - defragged > 1000)) {
|
||||
if ((ustime() - start) > timelimit) {
|
||||
return;
|
||||
}
|
||||
iterations = 0;
|
||||
defragged = server.stat_active_defrag_hits;
|
||||
}
|
||||
} while(cursor);
|
||||
} while(1);
|
||||
}
|
||||
|
||||
#else /* HAVE_DEFRAG */
|
||||
|
||||
void activeDefragCycle(void) {
|
||||
/* Not implemented yet. */
|
||||
}
|
||||
|
||||
#endif
|
||||
+47
-63
@@ -37,11 +37,11 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
#include <sys/time.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "dict.h"
|
||||
#include "zmalloc.h"
|
||||
@@ -71,77 +71,28 @@ static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
|
||||
|
||||
/* -------------------------- hash functions -------------------------------- */
|
||||
|
||||
static uint32_t dict_hash_function_seed = 5381;
|
||||
static uint8_t dict_hash_function_seed[16];
|
||||
|
||||
void dictSetHashFunctionSeed(uint32_t seed) {
|
||||
dict_hash_function_seed = seed;
|
||||
void dictSetHashFunctionSeed(uint8_t *seed) {
|
||||
memcpy(dict_hash_function_seed,seed,sizeof(dict_hash_function_seed));
|
||||
}
|
||||
|
||||
uint32_t dictGetHashFunctionSeed(void) {
|
||||
uint8_t *dictGetHashFunctionSeed(void) {
|
||||
return dict_hash_function_seed;
|
||||
}
|
||||
|
||||
/* MurmurHash2, by Austin Appleby
|
||||
* Note - This code makes a few assumptions about how your machine behaves -
|
||||
* 1. We can read a 4-byte value from any address without crashing
|
||||
* 2. sizeof(int) == 4
|
||||
*
|
||||
* And it has a few limitations -
|
||||
*
|
||||
* 1. It will not work incrementally.
|
||||
* 2. It will not produce the same results on little-endian and big-endian
|
||||
* machines.
|
||||
*/
|
||||
unsigned int dictGenHashFunction(const void *key, int len) {
|
||||
/* 'm' and 'r' are mixing constants generated offline.
|
||||
They're not really 'magic', they just happen to work well. */
|
||||
uint32_t seed = dict_hash_function_seed;
|
||||
const uint32_t m = 0x5bd1e995;
|
||||
const int r = 24;
|
||||
/* The default hashing function uses SipHash implementation
|
||||
* in siphash.c. */
|
||||
|
||||
/* Initialize the hash to a 'random' value */
|
||||
uint32_t h = seed ^ len;
|
||||
uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k);
|
||||
uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k);
|
||||
|
||||
/* Mix 4 bytes at a time into the hash */
|
||||
const unsigned char *data = (const unsigned char *)key;
|
||||
|
||||
while(len >= 4) {
|
||||
uint32_t k = *(uint32_t*)data;
|
||||
|
||||
k *= m;
|
||||
k ^= k >> r;
|
||||
k *= m;
|
||||
|
||||
h *= m;
|
||||
h ^= k;
|
||||
|
||||
data += 4;
|
||||
len -= 4;
|
||||
}
|
||||
|
||||
/* Handle the last few bytes of the input array */
|
||||
switch(len) {
|
||||
case 3: h ^= data[2] << 16;
|
||||
case 2: h ^= data[1] << 8;
|
||||
case 1: h ^= data[0]; h *= m;
|
||||
};
|
||||
|
||||
/* Do a few final mixes of the hash to ensure the last few
|
||||
* bytes are well-incorporated. */
|
||||
h ^= h >> 13;
|
||||
h *= m;
|
||||
h ^= h >> 15;
|
||||
|
||||
return (unsigned int)h;
|
||||
uint64_t dictGenHashFunction(const void *key, int len) {
|
||||
return siphash(key,len,dict_hash_function_seed);
|
||||
}
|
||||
|
||||
/* And a case insensitive hash function (based on djb hash) */
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len) {
|
||||
unsigned int hash = (unsigned int)dict_hash_function_seed;
|
||||
|
||||
while (len--)
|
||||
hash = ((hash << 5) + hash) + (tolower(*buf++)); /* hash * 33 + c */
|
||||
return hash;
|
||||
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len) {
|
||||
return siphash_nocase(buf,len,dict_hash_function_seed);
|
||||
}
|
||||
|
||||
/* ----------------------------- API implementation ------------------------- */
|
||||
@@ -885,6 +836,7 @@ static unsigned long rev(unsigned long v) {
|
||||
unsigned long dictScan(dict *d,
|
||||
unsigned long v,
|
||||
dictScanFunction *fn,
|
||||
dictScanBucketFunction* bucketfn,
|
||||
void *privdata)
|
||||
{
|
||||
dictht *t0, *t1;
|
||||
@@ -898,6 +850,7 @@ unsigned long dictScan(dict *d,
|
||||
m0 = t0->sizemask;
|
||||
|
||||
/* Emit entries at cursor */
|
||||
if (bucketfn) bucketfn(privdata, &t0->table[v & m0]);
|
||||
de = t0->table[v & m0];
|
||||
while (de) {
|
||||
next = de->next;
|
||||
@@ -919,6 +872,7 @@ unsigned long dictScan(dict *d,
|
||||
m1 = t1->sizemask;
|
||||
|
||||
/* Emit entries at cursor */
|
||||
if (bucketfn) bucketfn(privdata, &t0->table[v & m0]);
|
||||
de = t0->table[v & m0];
|
||||
while (de) {
|
||||
next = de->next;
|
||||
@@ -930,6 +884,7 @@ unsigned long dictScan(dict *d,
|
||||
* of the index pointed to by the cursor in the smaller table */
|
||||
do {
|
||||
/* Emit entries at cursor */
|
||||
if (bucketfn) bucketfn(privdata, &t1->table[v & m1]);
|
||||
de = t1->table[v & m1];
|
||||
while (de) {
|
||||
next = de->next;
|
||||
@@ -1040,6 +995,35 @@ void dictDisableResize(void) {
|
||||
dict_can_resize = 0;
|
||||
}
|
||||
|
||||
unsigned int dictGetHash(dict *d, const void *key) {
|
||||
return dictHashKey(d, key);
|
||||
}
|
||||
|
||||
/* Finds the dictEntry reference by using pointer and pre-calculated hash.
|
||||
* oldkey is a dead pointer and should not be accessed.
|
||||
* the hash value should be provided using dictGetHash.
|
||||
* no string / key comparison is performed.
|
||||
* return value is the reference to the dictEntry if found, or NULL if not found. */
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, unsigned int hash) {
|
||||
dictEntry *he, **heref;
|
||||
unsigned int idx, table;
|
||||
|
||||
if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
|
||||
for (table = 0; table <= 1; table++) {
|
||||
idx = hash & d->ht[table].sizemask;
|
||||
heref = &d->ht[table].table[idx];
|
||||
he = *heref;
|
||||
while(he) {
|
||||
if (oldptr==he->key)
|
||||
return heref;
|
||||
heref = &he->next;
|
||||
he = *heref;
|
||||
}
|
||||
if (!dictIsRehashing(d)) return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------- Debugging ---------------------------------*/
|
||||
|
||||
#define DICT_STATS_VECTLEN 50
|
||||
@@ -1125,7 +1109,7 @@ void dictGetStats(char *buf, size_t bufsize, dict *d) {
|
||||
|
||||
#include "sds.h"
|
||||
|
||||
unsigned int hashCallback(const void *key) {
|
||||
uint64_t hashCallback(const void *key) {
|
||||
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
|
||||
}
|
||||
|
||||
|
||||
+9
-6
@@ -56,7 +56,7 @@ typedef struct dictEntry {
|
||||
} dictEntry;
|
||||
|
||||
typedef struct dictType {
|
||||
unsigned int (*hashFunction)(const void *key);
|
||||
uint64_t (*hashFunction)(const void *key);
|
||||
void *(*keyDup)(void *privdata, const void *key);
|
||||
void *(*valDup)(void *privdata, const void *obj);
|
||||
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
|
||||
@@ -95,6 +95,7 @@ typedef struct dictIterator {
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
typedef void (dictScanBucketFunction)(void *privdata, dictEntry **bucketref);
|
||||
|
||||
/* This is the initial size of every hash table */
|
||||
#define DICT_HT_INITIAL_SIZE 4
|
||||
@@ -167,16 +168,18 @@ void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
unsigned int dictGenHashFunction(const void *key, int len);
|
||||
unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
uint64_t dictGenHashFunction(const void *key, int len);
|
||||
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
|
||||
void dictEmpty(dict *d, void(callback)(void*));
|
||||
void dictEnableResize(void);
|
||||
void dictDisableResize(void);
|
||||
int dictRehash(dict *d, int n);
|
||||
int dictRehashMilliseconds(dict *d, int ms);
|
||||
void dictSetHashFunctionSeed(unsigned int initval);
|
||||
unsigned int dictGetHashFunctionSeed(void);
|
||||
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, void *privdata);
|
||||
void dictSetHashFunctionSeed(uint8_t *seed);
|
||||
uint8_t *dictGetHashFunctionSeed(void);
|
||||
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
|
||||
unsigned int dictGetHash(dict *d, const void *key);
|
||||
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, unsigned int hash);
|
||||
|
||||
/* Hash table types */
|
||||
extern dictType dictTypeHeapStringCopyKey;
|
||||
|
||||
+62
-19
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "server.h"
|
||||
#include "bio.h"
|
||||
#include "atomicvar.h"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Data structures
|
||||
@@ -72,6 +73,20 @@ unsigned int getLRUClock(void) {
|
||||
return (mstime()/LRU_CLOCK_RESOLUTION) & LRU_CLOCK_MAX;
|
||||
}
|
||||
|
||||
/* This function is used to obtain the current LRU clock.
|
||||
* If the current resolution is lower than the frequency we refresh the
|
||||
* LRU clock (as it should be in production servers) we return the
|
||||
* precomputed value, otherwise we need to resort to a system call. */
|
||||
unsigned int LRU_CLOCK(void) {
|
||||
unsigned int lruclock;
|
||||
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
} else {
|
||||
lruclock = getLRUClock();
|
||||
}
|
||||
return lruclock;
|
||||
}
|
||||
|
||||
/* Given an object returns the min number of milliseconds the object was never
|
||||
* requested, using an approximated LRU algorithm. */
|
||||
unsigned long long estimateObjectIdleTime(robj *o) {
|
||||
@@ -336,11 +351,39 @@ unsigned long LFUDecrAndReturn(robj *o) {
|
||||
* server when there is data to add in order to make space if needed.
|
||||
* --------------------------------------------------------------------------*/
|
||||
|
||||
/* We don't want to count AOF buffers and slaves output buffers as
|
||||
* used memory: the eviction should use mostly data size. This function
|
||||
* returns the sum of AOF and slaves buffer. */
|
||||
size_t freeMemoryGetNotCountedMemory(void) {
|
||||
size_t overhead = 0;
|
||||
int slaves = listLength(server.slaves);
|
||||
|
||||
if (slaves) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(slave);
|
||||
}
|
||||
}
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
overhead += sdslen(server.aof_buf)+aofRewriteBufferSize();
|
||||
}
|
||||
return overhead;
|
||||
}
|
||||
|
||||
int freeMemoryIfNeeded(void) {
|
||||
size_t mem_reported, mem_used, mem_tofree, mem_freed;
|
||||
int slaves = listLength(server.slaves);
|
||||
mstime_t latency, eviction_latency;
|
||||
long long delta;
|
||||
int slaves = listLength(server.slaves);
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
* expires and evictions of keys not being performed. */
|
||||
if (clientsArePaused()) return C_OK;
|
||||
|
||||
/* Check if we are over the memory usage limit. If we are not, no need
|
||||
* to subtract the slaves output buffers. We can just return ASAP. */
|
||||
@@ -350,24 +393,8 @@ int freeMemoryIfNeeded(void) {
|
||||
/* Remove the size of slaves output buffers and AOF buffer from the
|
||||
* count of used memory. */
|
||||
mem_used = mem_reported;
|
||||
if (slaves) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = listNodeValue(ln);
|
||||
unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave);
|
||||
if (obuf_bytes > mem_used)
|
||||
mem_used = 0;
|
||||
else
|
||||
mem_used -= obuf_bytes;
|
||||
}
|
||||
}
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
mem_used -= sdslen(server.aof_buf);
|
||||
mem_used -= aofRewriteBufferSize();
|
||||
}
|
||||
size_t overhead = freeMemoryGetNotCountedMemory();
|
||||
mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
|
||||
|
||||
/* Check if we are still over the memory limit. */
|
||||
if (mem_used <= server.maxmemory) return C_OK;
|
||||
@@ -498,6 +525,22 @@ int freeMemoryIfNeeded(void) {
|
||||
* deliver data to the slaves fast enough, so we force the
|
||||
* transmission here inside the loop. */
|
||||
if (slaves) flushSlavesOutputBuffers();
|
||||
|
||||
/* Normally our stop condition is the ability to release
|
||||
* a fixed, pre-computed amount of memory. However when we
|
||||
* are deleting objects in another thread, it's better to
|
||||
* check, from time to time, if we already reached our target
|
||||
* memory, since the "mem_freed" amount is computed only
|
||||
* across the dbAsyncDelete() call, while the thread can
|
||||
* release the memory all the time. */
|
||||
if (server.lazyfree_lazy_eviction && !(keys_freed % 16)) {
|
||||
overhead = freeMemoryGetNotCountedMemory();
|
||||
mem_used = zmalloc_used_memory();
|
||||
mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
|
||||
if (mem_used <= server.maxmemory) {
|
||||
mem_freed = mem_tofree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keys_freed) {
|
||||
|
||||
+158
-8
@@ -92,7 +92,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
|
||||
*
|
||||
* If type is ACTIVE_EXPIRE_CYCLE_SLOW, that normal expire cycle is
|
||||
* executed, where the time limit is a percentage of the REDIS_HZ period
|
||||
* as specified by the REDIS_EXPIRELOOKUPS_TIME_PERC define. */
|
||||
* as specified by the ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC define. */
|
||||
|
||||
void activeExpireCycle(int type) {
|
||||
/* This function has some global state in order to continue the work
|
||||
@@ -105,6 +105,11 @@ void activeExpireCycle(int type) {
|
||||
int dbs_per_call = CRON_DBS_PER_CALL;
|
||||
long long start = ustime(), timelimit;
|
||||
|
||||
/* When clients are paused the dataset should be static not just from the
|
||||
* POV of clients not being able to write, but also from the POV of
|
||||
* expires and evictions of keys not being performed. */
|
||||
if (clientsArePaused()) return;
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||
/* Don't start a fast cycle if the previous cycle did not exited
|
||||
* for time limt. Also don't repeat a fast cycle for the same period
|
||||
@@ -216,6 +221,154 @@ void activeExpireCycle(int type) {
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Expires of keys created in writable slaves
|
||||
*
|
||||
* Normally slaves do not process expires: they wait the masters to synthesize
|
||||
* DEL operations in order to retain consistency. However writable slaves are
|
||||
* an exception: if a key is created in the slave and an expire is assigned
|
||||
* to it, we need a way to expire such a key, since the master does not know
|
||||
* anything about such a key.
|
||||
*
|
||||
* In order to do so, we track keys created in the slave side with an expire
|
||||
* set, and call the expireSlaveKeys() function from time to time in order to
|
||||
* reclaim the keys if they already expired.
|
||||
*
|
||||
* Note that the use case we are trying to cover here, is a popular one where
|
||||
* slaves are put in writable mode in order to compute slow operations in
|
||||
* the slave side that are mostly useful to actually read data in a more
|
||||
* processed way. Think at sets intersections in a tmp key, with an expire so
|
||||
* that it is also used as a cache to avoid intersecting every time.
|
||||
*
|
||||
* This implementation is currently not perfect but a lot better than leaking
|
||||
* the keys as implemented in 3.2.
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* The dictionary where we remember key names and database ID of keys we may
|
||||
* want to expire from the slave. Since this function is not often used we
|
||||
* don't even care to initialize the database at startup. We'll do it once
|
||||
* the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
|
||||
* is called.
|
||||
*
|
||||
* The dictionary has an SDS string representing the key as the hash table
|
||||
* key, while the value is a 64 bit unsigned integer with the bits corresponding
|
||||
* to the DB where the keys may exist set to 1. Currently the keys created
|
||||
* with a DB id > 63 are not expired, but a trivial fix is to set the bitmap
|
||||
* to the max 64 bit unsigned value when we know there is a key with a DB
|
||||
* ID greater than 63, and check all the configured DBs in such a case. */
|
||||
dict *slaveKeysWithExpire = NULL;
|
||||
|
||||
/* Check the set of keys created by the master with an expire set in order to
|
||||
* check if they should be evicted. */
|
||||
void expireSlaveKeys(void) {
|
||||
if (slaveKeysWithExpire == NULL ||
|
||||
dictSize(slaveKeysWithExpire) == 0) return;
|
||||
|
||||
int cycles = 0, noexpire = 0;
|
||||
mstime_t start = mstime();
|
||||
while(1) {
|
||||
dictEntry *de = dictGetRandomKey(slaveKeysWithExpire);
|
||||
sds keyname = dictGetKey(de);
|
||||
uint64_t dbids = dictGetUnsignedIntegerVal(de);
|
||||
uint64_t new_dbids = 0;
|
||||
|
||||
/* Check the key against every database corresponding to the
|
||||
* bits set in the value bitmap. */
|
||||
int dbid = 0;
|
||||
while(dbids && dbid < server.dbnum) {
|
||||
if ((dbids & 1) != 0) {
|
||||
redisDb *db = server.db+dbid;
|
||||
dictEntry *expire = dictFind(db->expires,keyname);
|
||||
int expired = 0;
|
||||
|
||||
if (expire &&
|
||||
activeExpireCycleTryExpire(server.db+dbid,expire,start))
|
||||
{
|
||||
expired = 1;
|
||||
}
|
||||
|
||||
/* If the key was not expired in this DB, we need to set the
|
||||
* corresponding bit in the new bitmap we set as value.
|
||||
* At the end of the loop if the bitmap is zero, it means we
|
||||
* no longer need to keep track of this key. */
|
||||
if (expire && !expired) {
|
||||
noexpire++;
|
||||
new_dbids |= (uint64_t)1 << dbid;
|
||||
}
|
||||
}
|
||||
dbid++;
|
||||
dbids >>= 1;
|
||||
}
|
||||
|
||||
/* Set the new bitmap as value of the key, in the dictionary
|
||||
* of keys with an expire set directly in the writable slave. Otherwise
|
||||
* if the bitmap is zero, we no longer need to keep track of it. */
|
||||
if (new_dbids)
|
||||
dictSetUnsignedIntegerVal(de,new_dbids);
|
||||
else
|
||||
dictDelete(slaveKeysWithExpire,keyname);
|
||||
|
||||
/* Stop conditions: found 3 keys we cna't expire in a row or
|
||||
* time limit was reached. */
|
||||
cycles++;
|
||||
if (noexpire > 3) break;
|
||||
if ((cycles % 64) == 0 && mstime()-start > 1) break;
|
||||
if (dictSize(slaveKeysWithExpire) == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Track keys that received an EXPIRE or similar command in the context
|
||||
* of a writable slave. */
|
||||
void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
|
||||
if (slaveKeysWithExpire == NULL) {
|
||||
static dictType dt = {
|
||||
dictSdsHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
slaveKeysWithExpire = dictCreate(&dt,NULL);
|
||||
}
|
||||
if (db->id > 63) return;
|
||||
|
||||
dictEntry *de = dictAddOrFind(slaveKeysWithExpire,key->ptr);
|
||||
/* If the entry was just created, set it to a copy of the SDS string
|
||||
* representing the key: we don't want to need to take those keys
|
||||
* in sync with the main DB. The keys will be removed by expireSlaveKeys()
|
||||
* as it scans to find keys to remove. */
|
||||
if (de->key == key->ptr) {
|
||||
de->key = sdsdup(key->ptr);
|
||||
dictSetUnsignedIntegerVal(de,0);
|
||||
}
|
||||
|
||||
uint64_t dbids = dictGetUnsignedIntegerVal(de);
|
||||
dbids |= (uint64_t)1 << db->id;
|
||||
dictSetUnsignedIntegerVal(de,dbids);
|
||||
}
|
||||
|
||||
/* Return the number of keys we are tracking. */
|
||||
size_t getSlaveKeyWithExpireCount(void) {
|
||||
if (slaveKeysWithExpire == NULL) return 0;
|
||||
return dictSize(slaveKeysWithExpire);
|
||||
}
|
||||
|
||||
/* Remove the keys in the hash table. We need to do that when data is
|
||||
* flushed from the server. We may receive new keys from the master with
|
||||
* the same name/db and it is no longer a good idea to expire them.
|
||||
*
|
||||
* Note: technically we should handle the case of a single DB being flushed
|
||||
* but it is not worth it since anyway race conditions using the same set
|
||||
* of key names in a wriatable slave and in its master will lead to
|
||||
* inconsistencies. This is just a best-effort thing we do. */
|
||||
void flushSlaveKeysWithExpireList(void) {
|
||||
if (slaveKeysWithExpire) {
|
||||
dictRelease(slaveKeysWithExpire);
|
||||
slaveKeysWithExpire = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Expires Commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -265,7 +418,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
addReply(c, shared.cone);
|
||||
return;
|
||||
} else {
|
||||
setExpire(c->db,key,when);
|
||||
setExpire(c,c->db,key,when);
|
||||
addReply(c,shared.cone);
|
||||
signalModifiedKey(c->db,key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
@@ -329,18 +482,15 @@ void pttlCommand(client *c) {
|
||||
|
||||
/* PERSIST key */
|
||||
void persistCommand(client *c) {
|
||||
dictEntry *de;
|
||||
|
||||
de = dictFind(c->db->dict,c->argv[1]->ptr);
|
||||
if (de == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
} else {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
server.dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ double extractDistanceOrReply(client *c, robj **argv,
|
||||
addReplyError(c,"radius cannot be negative");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
double to_meters = extractUnitOrReply(c,argv[1]);
|
||||
if (to_meters < 0) {
|
||||
return -1;
|
||||
@@ -326,6 +326,7 @@ int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon,
|
||||
int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
|
||||
GeoHashBits neighbors[9];
|
||||
unsigned int i, count = 0, last_processed = 0;
|
||||
int debugmsg = 0;
|
||||
|
||||
neighbors[0] = n.hash;
|
||||
neighbors[1] = n.neighbors.north;
|
||||
@@ -340,8 +341,26 @@ int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, d
|
||||
/* For each neighbor (*and* our own hashbox), get all the matching
|
||||
* members and add them to the potential result list. */
|
||||
for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) {
|
||||
if (HASHISZERO(neighbors[i]))
|
||||
if (HASHISZERO(neighbors[i])) {
|
||||
if (debugmsg) D("neighbors[%d] is zero",i);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Debugging info. */
|
||||
if (debugmsg) {
|
||||
GeoHashRange long_range, lat_range;
|
||||
geohashGetCoordRange(&long_range,&lat_range);
|
||||
GeoHashArea myarea = {{0}};
|
||||
geohashDecode(long_range, lat_range, neighbors[i], &myarea);
|
||||
|
||||
/* Dump center square. */
|
||||
D("neighbors[%d]:\n",i);
|
||||
D("area.longitude.min: %f\n", myarea.longitude.min);
|
||||
D("area.longitude.max: %f\n", myarea.longitude.max);
|
||||
D("area.latitude.min: %f\n", myarea.latitude.min);
|
||||
D("area.latitude.max: %f\n", myarea.latitude.max);
|
||||
D("\n");
|
||||
}
|
||||
|
||||
/* When a huge Radius (in the 5000 km range or more) is used,
|
||||
* adjacent neighbors can be the same, leading to duplicated
|
||||
@@ -350,7 +369,11 @@ int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, d
|
||||
if (last_processed &&
|
||||
neighbors[i].bits == neighbors[last_processed].bits &&
|
||||
neighbors[i].step == neighbors[last_processed].step)
|
||||
{
|
||||
if (debugmsg)
|
||||
D("Skipping processing of %d, same as previous\n",i);
|
||||
continue;
|
||||
}
|
||||
count += membersOfGeoHashBox(zobj, neighbors[i], ga, lon, lat, radius);
|
||||
last_processed = i;
|
||||
}
|
||||
@@ -429,13 +452,14 @@ void geoaddCommand(client *c) {
|
||||
#define SORT_ASC 1
|
||||
#define SORT_DESC 2
|
||||
|
||||
#define RADIUS_COORDS 1
|
||||
#define RADIUS_MEMBER 2
|
||||
#define RADIUS_COORDS (1<<0) /* Search around coordinates. */
|
||||
#define RADIUS_MEMBER (1<<1) /* Search around member. */
|
||||
#define RADIUS_NOSTORE (1<<2) /* Do not acceot STORE/STOREDIST option. */
|
||||
|
||||
/* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
|
||||
* [COUNT count] [STORE key] [STOREDIST key]
|
||||
* GEORADIUSBYMEMBER key member radius unit ... options ... */
|
||||
void georadiusGeneric(client *c, int type) {
|
||||
void georadiusGeneric(client *c, int flags) {
|
||||
robj *key = c->argv[1];
|
||||
robj *storekey = NULL;
|
||||
int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
|
||||
@@ -450,11 +474,11 @@ void georadiusGeneric(client *c, int type) {
|
||||
/* Find long/lat to use for radius search based on inquiry type */
|
||||
int base_args;
|
||||
double xy[2] = { 0 };
|
||||
if (type == RADIUS_COORDS) {
|
||||
if (flags & RADIUS_COORDS) {
|
||||
base_args = 6;
|
||||
if (extractLongLatOrReply(c, c->argv + 2, xy) == C_ERR)
|
||||
return;
|
||||
} else if (type == RADIUS_MEMBER) {
|
||||
} else if (flags & RADIUS_MEMBER) {
|
||||
base_args = 5;
|
||||
robj *member = c->argv[2];
|
||||
if (longLatFromMember(zobj, member, xy) == C_ERR) {
|
||||
@@ -462,7 +486,7 @@ void georadiusGeneric(client *c, int type) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
addReplyError(c, "unknown georadius search type");
|
||||
addReplyError(c, "Unknown georadius search type");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -499,11 +523,17 @@ void georadiusGeneric(client *c, int type) {
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
} else if (!strcasecmp(arg, "store") && (i+1) < remaining) {
|
||||
} else if (!strcasecmp(arg, "store") &&
|
||||
(i+1) < remaining &&
|
||||
!(flags & RADIUS_NOSTORE))
|
||||
{
|
||||
storekey = c->argv[base_args+i+1];
|
||||
storedist = 0;
|
||||
i++;
|
||||
} else if (!strcasecmp(arg, "storedist") && (i+1) < remaining) {
|
||||
} else if (!strcasecmp(arg, "storedist") &&
|
||||
(i+1) < remaining &&
|
||||
!(flags & RADIUS_NOSTORE))
|
||||
{
|
||||
storekey = c->argv[base_args+i+1];
|
||||
storedist = 1;
|
||||
i++;
|
||||
@@ -648,10 +678,20 @@ void georadiusCommand(client *c) {
|
||||
}
|
||||
|
||||
/* GEORADIUSBYMEMBER wrapper function. */
|
||||
void georadiusByMemberCommand(client *c) {
|
||||
void georadiusbymemberCommand(client *c) {
|
||||
georadiusGeneric(c, RADIUS_MEMBER);
|
||||
}
|
||||
|
||||
/* GEORADIUS_RO wrapper function. */
|
||||
void georadiusroCommand(client *c) {
|
||||
georadiusGeneric(c, RADIUS_COORDS|RADIUS_NOSTORE);
|
||||
}
|
||||
|
||||
/* GEORADIUSBYMEMBER_RO wrapper function. */
|
||||
void georadiusbymemberroCommand(client *c) {
|
||||
georadiusGeneric(c, RADIUS_MEMBER|RADIUS_NOSTORE);
|
||||
}
|
||||
|
||||
/* GEOHASH key ele1 ele2 ... eleN
|
||||
*
|
||||
* Returns an array with an 11 characters geohash representation of the
|
||||
@@ -661,16 +701,15 @@ void geohashCommand(client *c) {
|
||||
int j;
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
|
||||
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
robj *zobj = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (zobj && checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Geohash elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReply(c,shared.nullbulk);
|
||||
} else {
|
||||
/* The internal format we use for geocoding is a bit different
|
||||
@@ -715,16 +754,15 @@ void geoposCommand(client *c) {
|
||||
int j;
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
|
||||
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
robj *zobj = lookupKeyRead(c->db, c->argv[1]);
|
||||
if (zobj && checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Report elements one after the other, using a null bulk reply for
|
||||
* missing elements. */
|
||||
addReplyMultiBulkLen(c,c->argc-2);
|
||||
for (j = 2; j < c->argc; j++) {
|
||||
double score;
|
||||
if (zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
|
||||
addReply(c,shared.nullmultibulk);
|
||||
} else {
|
||||
/* Decode... */
|
||||
@@ -759,7 +797,7 @@ void geodistCommand(client *c) {
|
||||
|
||||
/* Look up the requested zset */
|
||||
robj *zobj = NULL;
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptybulk))
|
||||
if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.nullbulk))
|
||||
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
|
||||
|
||||
/* Get the scores. We need both otherwise NULL is returned. */
|
||||
|
||||
+44
-62
@@ -82,30 +82,32 @@ uint8_t geohashEstimateStepsByRadius(double range_meters, double lat) {
|
||||
return step;
|
||||
}
|
||||
|
||||
/* Return the bounding box of the search area centered at latitude,longitude
|
||||
* having a radius of radius_meter. bounds[0] - bounds[2] is the minimum
|
||||
* and maxium longitude, while bounds[1] - bounds[3] is the minimum and
|
||||
* maximum latitude.
|
||||
*
|
||||
* This function does not behave correctly with very large radius values, for
|
||||
* instance for the coordinates 81.634948934258375 30.561509253718668 and a
|
||||
* radius of 7083 kilometers, it reports as bounding boxes:
|
||||
*
|
||||
* min_lon 7.680495, min_lat -33.119473, max_lon 155.589402, max_lat 94.242491
|
||||
*
|
||||
* However, for instance, a min_lon of 7.680495 is not correct, because the
|
||||
* point -1.27579540014266968 61.33421815228281559 is at less than 7000
|
||||
* kilometers away.
|
||||
*
|
||||
* Since this function is currently only used as an optimization, the
|
||||
* optimization is not used for very big radiuses, however the function
|
||||
* should be fixed. */
|
||||
int geohashBoundingBox(double longitude, double latitude, double radius_meters,
|
||||
double *bounds) {
|
||||
if (!bounds) return 0;
|
||||
|
||||
double lonr, latr;
|
||||
lonr = deg_rad(longitude);
|
||||
latr = deg_rad(latitude);
|
||||
|
||||
if (radius_meters > EARTH_RADIUS_IN_METERS)
|
||||
radius_meters = EARTH_RADIUS_IN_METERS;
|
||||
double distance = radius_meters / EARTH_RADIUS_IN_METERS;
|
||||
double min_latitude = latr - distance;
|
||||
double max_latitude = latr + distance;
|
||||
|
||||
/* Note: we're being lazy and not accounting for coordinates near poles */
|
||||
double min_longitude, max_longitude;
|
||||
double difference_longitude = asin(sin(distance) / cos(latr));
|
||||
min_longitude = lonr - difference_longitude;
|
||||
max_longitude = lonr + difference_longitude;
|
||||
|
||||
bounds[0] = rad_deg(min_longitude);
|
||||
bounds[1] = rad_deg(min_latitude);
|
||||
bounds[2] = rad_deg(max_longitude);
|
||||
bounds[3] = rad_deg(max_latitude);
|
||||
bounds[0] = longitude - rad_deg(radius_meters/EARTH_RADIUS_IN_METERS/cos(deg_rad(latitude)));
|
||||
bounds[2] = longitude + rad_deg(radius_meters/EARTH_RADIUS_IN_METERS/cos(deg_rad(latitude)));
|
||||
bounds[1] = latitude - rad_deg(radius_meters/EARTH_RADIUS_IN_METERS);
|
||||
bounds[3] = latitude + rad_deg(radius_meters/EARTH_RADIUS_IN_METERS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -158,55 +160,35 @@ GeoHashRadius geohashGetAreasByRadius(double longitude, double latitude, double
|
||||
< radius_meters) decrease_step = 1;
|
||||
}
|
||||
|
||||
if (decrease_step) {
|
||||
if (steps > 1 && decrease_step) {
|
||||
steps--;
|
||||
geohashEncode(&long_range,&lat_range,longitude,latitude,steps,&hash);
|
||||
geohashNeighbors(&hash,&neighbors);
|
||||
geohashDecode(long_range,lat_range,hash,&area);
|
||||
}
|
||||
|
||||
/* Example debug info. This turns to be very useful every time there is
|
||||
* to investigate radius search potential bugs. So better to leave it
|
||||
* here. */
|
||||
if (0) {
|
||||
GeoHashArea myarea = {{0}};
|
||||
geohashDecode(long_range, lat_range, neighbors.west, &myarea);
|
||||
|
||||
/* Dump West. */
|
||||
D("Neighbors");
|
||||
D("area.longitude.min: %f\n", myarea.longitude.min);
|
||||
D("area.longitude.max: %f\n", myarea.longitude.max);
|
||||
D("area.latitude.min: %f\n", myarea.latitude.min);
|
||||
D("area.latitude.max: %f\n", myarea.latitude.max);
|
||||
|
||||
/* Dump center square. */
|
||||
D("Area");
|
||||
D("area.longitude.min: %f\n", area.longitude.min);
|
||||
D("area.longitude.max: %f\n", area.longitude.max);
|
||||
D("area.latitude.min: %f\n", area.latitude.min);
|
||||
D("area.latitude.max: %f\n", area.latitude.max);
|
||||
}
|
||||
|
||||
/* Exclude the search areas that are useless. */
|
||||
if (area.latitude.min < min_lat) {
|
||||
GZERO(neighbors.south);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.south_east);
|
||||
}
|
||||
if (area.latitude.max > max_lat) {
|
||||
GZERO(neighbors.north);
|
||||
GZERO(neighbors.north_east);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.min < min_lon) {
|
||||
GZERO(neighbors.west);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.max > max_lon) {
|
||||
GZERO(neighbors.east);
|
||||
GZERO(neighbors.south_east);
|
||||
GZERO(neighbors.north_east);
|
||||
if (steps >= 2) {
|
||||
if (area.latitude.min < min_lat) {
|
||||
GZERO(neighbors.south);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.south_east);
|
||||
}
|
||||
if (area.latitude.max > max_lat) {
|
||||
GZERO(neighbors.north);
|
||||
GZERO(neighbors.north_east);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.min < min_lon) {
|
||||
GZERO(neighbors.west);
|
||||
GZERO(neighbors.south_west);
|
||||
GZERO(neighbors.north_west);
|
||||
}
|
||||
if (area.longitude.max > max_lon) {
|
||||
GZERO(neighbors.east);
|
||||
GZERO(neighbors.south_east);
|
||||
GZERO(neighbors.north_east);
|
||||
}
|
||||
}
|
||||
radius.hash = hash;
|
||||
radius.neighbors = neighbors;
|
||||
|
||||
+19
-25
@@ -401,7 +401,11 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
|
||||
uint64_t k;
|
||||
|
||||
#if (BYTE_ORDER == LITTLE_ENDIAN)
|
||||
#ifdef USE_ALIGNED_ACCESS
|
||||
memcpy(&k,data,sizeof(uint64_t));
|
||||
#else
|
||||
k = *((uint64_t*)data);
|
||||
#endif
|
||||
#else
|
||||
k = (uint64_t) data[0];
|
||||
k |= (uint64_t) data[1] << 8;
|
||||
@@ -994,32 +998,21 @@ uint64_t hllCount(struct hllhdr *hdr, int *invalid) {
|
||||
serverPanic("Unknown HyperLogLog encoding in hllCount()");
|
||||
}
|
||||
|
||||
/* Muliply the inverse of E for alpha_m * m^2 to have the raw estimate. */
|
||||
E = (1/E)*alpha*m*m;
|
||||
/* Apply loglog-beta to the raw estimate. See:
|
||||
* "LogLog-Beta and More: A New Algorithm for Cardinality Estimation
|
||||
* Based on LogLog Counting" Jason Qin, Denys Kim, Yumei Tung
|
||||
* arXiv:1612.02284 */
|
||||
double zl = log(ez + 1);
|
||||
double beta = -0.370393911*ez +
|
||||
0.070471823*zl +
|
||||
0.17393686*pow(zl,2) +
|
||||
0.16339839*pow(zl,3) +
|
||||
-0.09237745*pow(zl,4) +
|
||||
0.03738027*pow(zl,5) +
|
||||
-0.005384159*pow(zl,6) +
|
||||
0.00042419*pow(zl,7);
|
||||
|
||||
/* Use the LINEARCOUNTING algorithm for small cardinalities.
|
||||
* For larger values but up to 72000 HyperLogLog raw approximation is
|
||||
* used since linear counting error starts to increase. However HyperLogLog
|
||||
* shows a strong bias in the range 2.5*16384 - 72000, so we try to
|
||||
* compensate for it. */
|
||||
if (E < m*2.5 && ez != 0) {
|
||||
E = m*log(m/ez); /* LINEARCOUNTING() */
|
||||
} else if (m == 16384 && E < 72000) {
|
||||
/* We did polynomial regression of the bias for this range, this
|
||||
* way we can compute the bias for a given cardinality and correct
|
||||
* according to it. Only apply the correction for P=14 that's what
|
||||
* we use and the value the correction was verified with. */
|
||||
double bias = 5.9119*1.0e-18*(E*E*E*E)
|
||||
-1.4253*1.0e-12*(E*E*E)+
|
||||
1.2940*1.0e-7*(E*E)
|
||||
-5.2921*1.0e-3*E+
|
||||
83.3216;
|
||||
E -= E*(bias/100);
|
||||
}
|
||||
/* We don't apply the correction for E > 1/30 of 2^32 since we use
|
||||
* a 64 bit function and 6 bit counters. To apply the correction for
|
||||
* 1/30 of 2^64 is not needed since it would require a huge set
|
||||
* to approach such a value. */
|
||||
E = llroundl(alpha*m*(m-ez)*(1/(E+beta)));
|
||||
return (uint64_t) E;
|
||||
}
|
||||
|
||||
@@ -1128,6 +1121,7 @@ int isHLLObjectOrReply(client *c, robj *o) {
|
||||
if (checkType(c,o,OBJ_STRING))
|
||||
return C_ERR; /* Error already sent. */
|
||||
|
||||
if (!sdsEncodedObject(o)) goto invalid;
|
||||
if (stringObjectLen(o) < sizeof(*hdr)) goto invalid;
|
||||
hdr = o->ptr;
|
||||
|
||||
|
||||
+1
-1
@@ -261,7 +261,7 @@ int64_t intsetRandom(intset *is) {
|
||||
return _intsetGet(is,rand()%intrev32ifbe(is->length));
|
||||
}
|
||||
|
||||
/* Sets the value to the value at the given position. When this position is
|
||||
/* Get the value at the given position. When this position is
|
||||
* out of range the function returns 0, when in range it returns 1. */
|
||||
uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
|
||||
if (pos < intrev32ifbe(is->length)) {
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ int dictStringKeyCompare(void *privdata, const void *key1, const void *key2) {
|
||||
return strcmp(key1,key2) == 0;
|
||||
}
|
||||
|
||||
unsigned int dictStringHash(const void *key) {
|
||||
uint64_t dictStringHash(const void *key) {
|
||||
return dictGenHashFunction(key, strlen(key));
|
||||
}
|
||||
|
||||
|
||||
+18
-15
@@ -8,7 +8,9 @@ pthread_mutex_t lazyfree_objects_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* Return the number of currently pending objects to free. */
|
||||
size_t lazyfreeGetPendingObjectsCount(void) {
|
||||
return lazyfree_objects;
|
||||
size_t aux;
|
||||
atomicGet(lazyfree_objects,aux);
|
||||
return aux;
|
||||
}
|
||||
|
||||
/* Return the amount of work needed in order to free an object.
|
||||
@@ -65,7 +67,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
/* If releasing the object is too much work, let's put it into the
|
||||
* lazy free list. */
|
||||
if (free_effort > LAZYFREE_THRESHOLD) {
|
||||
atomicIncr(lazyfree_objects,1,lazyfree_objects_mutex);
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
||||
dictSetVal(db->dict,de,NULL);
|
||||
}
|
||||
@@ -89,26 +91,27 @@ void emptyDbAsync(redisDb *db) {
|
||||
dict *oldht1 = db->dict, *oldht2 = db->expires;
|
||||
db->dict = dictCreate(&dbDictType,NULL);
|
||||
db->expires = dictCreate(&keyptrDictType,NULL);
|
||||
atomicIncr(lazyfree_objects,dictSize(oldht1),
|
||||
lazyfree_objects_mutex);
|
||||
atomicIncr(lazyfree_objects,dictSize(oldht1));
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);
|
||||
}
|
||||
|
||||
/* Empty the slots-keys map of Redis CLuster by creating a new empty one
|
||||
* and scheduiling the old for lazy freeing. */
|
||||
void slotToKeyFlushAsync(void) {
|
||||
zskiplist *oldsl = server.cluster->slots_to_keys;
|
||||
server.cluster->slots_to_keys = zslCreate();
|
||||
atomicIncr(lazyfree_objects,oldsl->length,
|
||||
lazyfree_objects_mutex);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,oldsl);
|
||||
rax *old = server.cluster->slots_to_keys;
|
||||
|
||||
server.cluster->slots_to_keys = raxNew();
|
||||
memset(server.cluster->slots_keys_count,0,
|
||||
sizeof(server.cluster->slots_keys_count));
|
||||
atomicIncr(lazyfree_objects,old->numele);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,NULL,old);
|
||||
}
|
||||
|
||||
/* Release objects from the lazyfree thread. It's just decrRefCount()
|
||||
* updating the count of objects to release. */
|
||||
void lazyfreeFreeObjectFromBioThread(robj *o) {
|
||||
decrRefCount(o);
|
||||
atomicDecr(lazyfree_objects,1,lazyfree_objects_mutex);
|
||||
atomicDecr(lazyfree_objects,1);
|
||||
}
|
||||
|
||||
/* Release a database from the lazyfree thread. The 'db' pointer is the
|
||||
@@ -120,13 +123,13 @@ void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) {
|
||||
size_t numkeys = dictSize(ht1);
|
||||
dictRelease(ht1);
|
||||
dictRelease(ht2);
|
||||
atomicDecr(lazyfree_objects,numkeys,lazyfree_objects_mutex);
|
||||
atomicDecr(lazyfree_objects,numkeys);
|
||||
}
|
||||
|
||||
/* Release the skiplist mapping Redis Cluster keys to slots in the
|
||||
* lazyfree thread. */
|
||||
void lazyfreeFreeSlotsMapFromBioThread(zskiplist *sl) {
|
||||
size_t len = sl->length;
|
||||
zslFree(sl);
|
||||
atomicDecr(lazyfree_objects,len,lazyfree_objects_mutex);
|
||||
void lazyfreeFreeSlotsMapFromBioThread(rax *rt) {
|
||||
size_t len = rt->numele;
|
||||
raxFree(rt);
|
||||
atomicDecr(lazyfree_objects,len);
|
||||
}
|
||||
|
||||
+451
-122
@@ -95,17 +95,22 @@ typedef struct RedisModulePoolAllocBlock {
|
||||
*
|
||||
* Note that not all the context structure is always filled with actual values
|
||||
* but only the fields needed in a given context. */
|
||||
|
||||
struct RedisModuleBlockedClient;
|
||||
|
||||
struct RedisModuleCtx {
|
||||
void *getapifuncptr; /* NOTE: Must be the first field. */
|
||||
struct RedisModule *module; /* Module reference. */
|
||||
client *client; /* Client calling a command. */
|
||||
struct RedisModuleBlockedClient *blocked_client; /* Blocked client for
|
||||
thread safe context. */
|
||||
struct AutoMemEntry *amqueue; /* Auto memory queue of objects to free. */
|
||||
int amqueue_len; /* Number of slots in amqueue. */
|
||||
int amqueue_used; /* Number of used slots in amqueue. */
|
||||
int flags; /* REDISMODULE_CTX_... flags. */
|
||||
void **postponed_arrays; /* To set with RM_ReplySetArrayLength(). */
|
||||
int postponed_arrays_count; /* Number of entries in postponed_arrays. */
|
||||
void *blocked_privdata; /* Privdata set when unblocking a clinet. */
|
||||
void *blocked_privdata; /* Privdata set when unblocking a client. */
|
||||
|
||||
/* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST flag set. */
|
||||
int *keys_pos;
|
||||
@@ -115,12 +120,13 @@ struct RedisModuleCtx {
|
||||
};
|
||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
|
||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
|
||||
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
|
||||
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
|
||||
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
|
||||
#define REDISMODULE_CTX_BLOCKED_REPLY (1<<3)
|
||||
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
|
||||
#define REDISMODULE_CTX_THREAD_SAFE (1<<5)
|
||||
|
||||
/* This represents a Redis key opened with RM_OpenKey(). */
|
||||
struct RedisModuleKey {
|
||||
@@ -198,11 +204,18 @@ typedef struct RedisModuleBlockedClient {
|
||||
void *privdata; /* Module private data that may be used by the reply
|
||||
or timeout callback. It is set via the
|
||||
RedisModule_UnblockClient() API. */
|
||||
client *reply_client; /* Fake client used to accumulate replies
|
||||
in thread safe contexts. */
|
||||
int dbid; /* Database number selected by the original client. */
|
||||
} RedisModuleBlockedClient;
|
||||
|
||||
static pthread_mutex_t moduleUnblockedClientsMutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static list *moduleUnblockedClients;
|
||||
|
||||
/* We need a mutex that is unlocked / relocked in beforeSleep() in order to
|
||||
* allow thread safe contexts to execute commands at a safe moment. */
|
||||
static pthread_mutex_t moduleGIL = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Prototypes
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -421,6 +434,7 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
"calls.",
|
||||
ctx->module->name);
|
||||
}
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
|
||||
}
|
||||
|
||||
/* Helper function for when a command callback is called, in order to handle
|
||||
@@ -492,10 +506,10 @@ int RM_IsKeysPositionRequest(RedisModuleCtx *ctx) {
|
||||
* RedisModule_IsKeysPositionRequest() API and uses this function in
|
||||
* order to report keys, like in the following example:
|
||||
*
|
||||
* if (RedisModule_IsKeysPositionRequest(ctx)) {
|
||||
* RedisModule_KeyAtPos(ctx,1);
|
||||
* RedisModule_KeyAtPos(ctx,2);
|
||||
* }
|
||||
* if (RedisModule_IsKeysPositionRequest(ctx)) {
|
||||
* RedisModule_KeyAtPos(ctx,1);
|
||||
* RedisModule_KeyAtPos(ctx,2);
|
||||
* }
|
||||
*
|
||||
* Note: in the example below the get keys API would not be needed since
|
||||
* keys are at fixed positions. This interface is only used for commands
|
||||
@@ -923,8 +937,8 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha
|
||||
* Most functions always return REDISMODULE_OK so you can use it with
|
||||
* 'return' in order to return from the command implementation with:
|
||||
*
|
||||
* if (... some condition ...)
|
||||
* return RM_ReplyWithLongLong(ctx,mycount);
|
||||
* if (... some condition ...)
|
||||
* return RM_ReplyWithLongLong(ctx,mycount);
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Send an error about the number of arguments given to the command,
|
||||
@@ -932,7 +946,7 @@ int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const cha
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||
* if (argc != 3) return RedisModule_WrongArity(ctx);
|
||||
*/
|
||||
int RM_WrongArity(RedisModuleCtx *ctx) {
|
||||
addReplyErrorFormat(ctx->client,
|
||||
@@ -941,10 +955,33 @@ int RM_WrongArity(RedisModuleCtx *ctx) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Return the client object the `RM_Reply*` functions should target.
|
||||
* Normally this is just `ctx->client`, that is the client that called
|
||||
* the module command, however in the case of thread safe contexts there
|
||||
* is no directly associated client (since it would not be safe to access
|
||||
* the client from a thread), so instead the blocked client object referenced
|
||||
* in the thread safe context, has a fake client that we just use to accumulate
|
||||
* the replies. Later, when the client is unblocked, the accumulated replies
|
||||
* are appended to the actual client.
|
||||
*
|
||||
* The function returns the client pointer depending on the context, or
|
||||
* NULL if there is no potential client. This happens when we are in the
|
||||
* context of a thread safe context that was not initialized with a blocked
|
||||
* client object. */
|
||||
client *moduleGetReplyClient(RedisModuleCtx *ctx) {
|
||||
if (!(ctx->flags & REDISMODULE_CTX_THREAD_SAFE) && ctx->client)
|
||||
return ctx->client;
|
||||
if (ctx->blocked_client)
|
||||
return ctx->blocked_client->reply_client;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send an integer reply to the client, with the specified long long value.
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
|
||||
addReplyLongLong(ctx->client,ll);
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReplyLongLong(c,ll);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -952,10 +989,12 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
|
||||
* ReplyWithSimpleString() and ReplyWithError().
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
sds strmsg = sdsnewlen(prefix,1);
|
||||
strmsg = sdscat(strmsg,msg);
|
||||
strmsg = sdscatlen(strmsg,"\r\n",2);
|
||||
addReplySds(ctx->client,strmsg);
|
||||
addReplySds(c,strmsg);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -965,11 +1004,11 @@ int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
|
||||
* the initial error code. The function only provides the initial "-", so
|
||||
* the usage is, for example:
|
||||
*
|
||||
* RM_ReplyWithError(ctx,"ERR Wrong Type");
|
||||
* RedisModule_ReplyWithError(ctx,"ERR Wrong Type");
|
||||
*
|
||||
* and not just:
|
||||
*
|
||||
* RM_ReplyWithError(ctx,"Wrong Type");
|
||||
* RedisModule_ReplyWithError(ctx,"Wrong Type");
|
||||
*
|
||||
* The function always returns REDISMODULE_OK.
|
||||
*/
|
||||
@@ -998,14 +1037,16 @@ int RM_ReplyWithSimpleString(RedisModuleCtx *ctx, const char *msg) {
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
if (len == REDISMODULE_POSTPONED_ARRAY_LEN) {
|
||||
ctx->postponed_arrays = zrealloc(ctx->postponed_arrays,sizeof(void*)*
|
||||
(ctx->postponed_arrays_count+1));
|
||||
ctx->postponed_arrays[ctx->postponed_arrays_count] =
|
||||
addDeferredMultiBulkLength(ctx->client);
|
||||
addDeferredMultiBulkLength(c);
|
||||
ctx->postponed_arrays_count++;
|
||||
} else {
|
||||
addReplyMultiBulkLen(ctx->client,len);
|
||||
addReplyMultiBulkLen(c,len);
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -1022,14 +1063,14 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
|
||||
* For example in order to output an array like [1,[10,20,30]] we
|
||||
* could write:
|
||||
*
|
||||
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
* RedisModule_ReplyWithLongLong(ctx,1);
|
||||
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
* RedisModule_ReplyWithLongLong(ctx,10);
|
||||
* RedisModule_ReplyWithLongLong(ctx,20);
|
||||
* RedisModule_ReplyWithLongLong(ctx,30);
|
||||
* RedisModule_ReplySetArrayLength(ctx,3); // Set len of 10,20,30 array.
|
||||
* RedisModule_ReplySetArrayLength(ctx,2); // Set len of top array
|
||||
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
* RedisModule_ReplyWithLongLong(ctx,1);
|
||||
* RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
* RedisModule_ReplyWithLongLong(ctx,10);
|
||||
* RedisModule_ReplyWithLongLong(ctx,20);
|
||||
* RedisModule_ReplyWithLongLong(ctx,30);
|
||||
* RedisModule_ReplySetArrayLength(ctx,3); // Set len of 10,20,30 array.
|
||||
* RedisModule_ReplySetArrayLength(ctx,2); // Set len of top array
|
||||
*
|
||||
* Note that in the above example there is no reason to postpone the array
|
||||
* length, since we produce a fixed number of elements, but in the practice
|
||||
@@ -1037,6 +1078,8 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
|
||||
* that is not easy to calculate in advance the number of elements.
|
||||
*/
|
||||
void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return;
|
||||
if (ctx->postponed_arrays_count == 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"API misuse detected in module %s: "
|
||||
@@ -1046,7 +1089,7 @@ void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
|
||||
return;
|
||||
}
|
||||
ctx->postponed_arrays_count--;
|
||||
setDeferredMultiBulkLength(ctx->client,
|
||||
setDeferredMultiBulkLength(c,
|
||||
ctx->postponed_arrays[ctx->postponed_arrays_count],
|
||||
len);
|
||||
if (ctx->postponed_arrays_count == 0) {
|
||||
@@ -1059,7 +1102,9 @@ void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
|
||||
addReplyBulkCBuffer(ctx->client,(char*)buf,len);
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReplyBulkCBuffer(c,(char*)buf,len);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1067,7 +1112,9 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) {
|
||||
addReplyBulk(ctx->client,str);
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReplyBulk(c,str);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1076,7 +1123,9 @@ int RM_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) {
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithNull(RedisModuleCtx *ctx) {
|
||||
addReply(ctx->client,shared.nullbulk);
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReply(c,shared.nullbulk);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1087,8 +1136,10 @@ int RM_ReplyWithNull(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
sds proto = sdsnewlen(reply->proto, reply->protolen);
|
||||
addReplySds(ctx->client,proto);
|
||||
addReplySds(c,proto);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1099,7 +1150,9 @@ int RM_ReplyWithCallReply(RedisModuleCtx *ctx, RedisModuleCallReply *reply) {
|
||||
*
|
||||
* The function always returns REDISMODULE_OK. */
|
||||
int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) {
|
||||
addReplyDouble(ctx->client,d);
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
addReplyDouble(c,d);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -1111,7 +1164,12 @@ int RM_ReplyWithDouble(RedisModuleCtx *ctx, double d) {
|
||||
* in the context of a command execution. EXEC will be handled by the
|
||||
* RedisModuleCommandDispatcher() function. */
|
||||
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
/* If we already emitted MULTI return ASAP. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||
/* If this is a thread safe context, we do not want to wrap commands
|
||||
* executed into MUTLI/EXEC, they are executed as single commands
|
||||
* from an external client in essence. */
|
||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
|
||||
execCommandPropagateMulti(ctx->client);
|
||||
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
|
||||
}
|
||||
@@ -1342,7 +1400,7 @@ int RM_SetExpire(RedisModuleKey *key, mstime_t expire) {
|
||||
return REDISMODULE_ERR;
|
||||
if (expire != REDISMODULE_NO_EXPIRE) {
|
||||
expire += mstime();
|
||||
setExpire(key->db,key->key,expire);
|
||||
setExpire(key->ctx->client,key->db,key->key,expire);
|
||||
} else {
|
||||
removeExpire(key->db,key->key);
|
||||
}
|
||||
@@ -1371,8 +1429,8 @@ int RM_StringSet(RedisModuleKey *key, RedisModuleString *str) {
|
||||
*
|
||||
* The 'mode' is composed by bitwise OR-ing the following flags:
|
||||
*
|
||||
* REDISMODULE_READ -- Read access
|
||||
* REDISMODULE_WRITE -- Write access
|
||||
* REDISMODULE_READ -- Read access
|
||||
* REDISMODULE_WRITE -- Write access
|
||||
*
|
||||
* If the DMA is not requested for writing, the pointer returned should
|
||||
* only be accessed in a read-only fashion.
|
||||
@@ -1530,14 +1588,14 @@ int RM_ZsetAddFlagsFromCoreFlags(int flags) {
|
||||
*
|
||||
* The input flags are:
|
||||
*
|
||||
* REDISMODULE_ZADD_XX: Element must already exist. Do nothing otherwise.
|
||||
* REDISMODULE_ZADD_NX: Element must not exist. Do nothing otherwise.
|
||||
* REDISMODULE_ZADD_XX: Element must already exist. Do nothing otherwise.
|
||||
* REDISMODULE_ZADD_NX: Element must not exist. Do nothing otherwise.
|
||||
*
|
||||
* The output flags are:
|
||||
*
|
||||
* REDISMODULE_ZADD_ADDED: The new element was added to the sorted set.
|
||||
* REDISMODULE_ZADD_UPDATED: The score of the element was updated.
|
||||
* REDISMODULE_ZADD_NOP: No operation was performed because XX or NX flags.
|
||||
* REDISMODULE_ZADD_ADDED: The new element was added to the sorted set.
|
||||
* REDISMODULE_ZADD_UPDATED: The score of the element was updated.
|
||||
* REDISMODULE_ZADD_NOP: No operation was performed because XX or NX flags.
|
||||
*
|
||||
* On success the function returns REDISMODULE_OK. On the following errors
|
||||
* REDISMODULE_ERR is returned:
|
||||
@@ -1858,12 +1916,12 @@ int RM_ZsetRangeNext(RedisModuleKey *key) {
|
||||
} else {
|
||||
/* Are we still within the range? */
|
||||
if (key->ztype == REDISMODULE_ZSET_RANGE_SCORE &&
|
||||
!zslValueLteMax(ln->score,&key->zrs))
|
||||
!zslValueLteMax(next->score,&key->zrs))
|
||||
{
|
||||
key->zer = 1;
|
||||
return 0;
|
||||
} else if (key->ztype == REDISMODULE_ZSET_RANGE_LEX) {
|
||||
if (!zslLexValueLteMax(ln->ele,&key->zlrs)) {
|
||||
if (!zslLexValueLteMax(next->ele,&key->zlrs)) {
|
||||
key->zer = 1;
|
||||
return 0;
|
||||
}
|
||||
@@ -1921,7 +1979,7 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
|
||||
} else {
|
||||
/* Are we still within the range? */
|
||||
if (key->ztype == REDISMODULE_ZSET_RANGE_SCORE &&
|
||||
!zslValueGteMin(ln->score,&key->zrs))
|
||||
!zslValueGteMin(prev->score,&key->zrs))
|
||||
{
|
||||
key->zer = 1;
|
||||
return 0;
|
||||
@@ -1953,25 +2011,25 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
|
||||
*
|
||||
* Example to set the hash argv[1] to the value argv[2]:
|
||||
*
|
||||
* RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[1],argv[2],NULL);
|
||||
* RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[1],argv[2],NULL);
|
||||
*
|
||||
* The function can also be used in order to delete fields (if they exist)
|
||||
* by setting them to the specified value of REDISMODULE_HASH_DELETE:
|
||||
*
|
||||
* RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[1],
|
||||
* REDISMODULE_HASH_DELETE,NULL);
|
||||
* RedisModule_HashSet(key,REDISMODULE_HASH_NONE,argv[1],
|
||||
* REDISMODULE_HASH_DELETE,NULL);
|
||||
*
|
||||
* The behavior of the command changes with the specified flags, that can be
|
||||
* set to REDISMODULE_HASH_NONE if no special behavior is needed.
|
||||
*
|
||||
* REDISMODULE_HASH_NX: The operation is performed only if the field was not
|
||||
* already existing in the hash.
|
||||
* REDISMODULE_HASH_XX: The operation is performed only if the field was
|
||||
* already existing, so that a new value could be
|
||||
* associated to an existing filed, but no new fields
|
||||
* are created.
|
||||
* REDISMODULE_HASH_CFIELDS: The field names passed are null terminated C
|
||||
* strings instead of RedisModuleString objects.
|
||||
* REDISMODULE_HASH_NX: The operation is performed only if the field was not
|
||||
* already existing in the hash.
|
||||
* REDISMODULE_HASH_XX: The operation is performed only if the field was
|
||||
* already existing, so that a new value could be
|
||||
* associated to an existing filed, but no new fields
|
||||
* are created.
|
||||
* REDISMODULE_HASH_CFIELDS: The field names passed are null terminated C
|
||||
* strings instead of RedisModuleString objects.
|
||||
*
|
||||
* Unless NX is specified, the command overwrites the old field value with
|
||||
* the new one.
|
||||
@@ -1980,8 +2038,8 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
|
||||
* normal C strings, so for example to delete the field "foo" the following
|
||||
* code can be used:
|
||||
*
|
||||
* RedisModule_HashSet(key,REDISMODULE_HASH_CFIELDS,"foo",
|
||||
* REDISMODULE_HASH_DELETE,NULL);
|
||||
* RedisModule_HashSet(key,REDISMODULE_HASH_CFIELDS,"foo",
|
||||
* REDISMODULE_HASH_DELETE,NULL);
|
||||
*
|
||||
* Return value:
|
||||
*
|
||||
@@ -2286,8 +2344,10 @@ void RM_FreeCallReply_Rec(RedisModuleCallReply *reply, int freenested){
|
||||
* to have the first level function to return on nested replies, but only
|
||||
* if called by the module API. */
|
||||
void RM_FreeCallReply(RedisModuleCallReply *reply) {
|
||||
|
||||
RedisModuleCtx *ctx = reply->ctx;
|
||||
RM_FreeCallReply_Rec(reply,0);
|
||||
autoMemoryFreed(reply->ctx,REDISMODULE_AM_REPLY,reply);
|
||||
autoMemoryFreed(ctx,REDISMODULE_AM_REPLY,reply);
|
||||
}
|
||||
|
||||
/* Return the reply type. */
|
||||
@@ -2358,7 +2418,7 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
|
||||
* The integer pointed by 'flags' is populated with flags according
|
||||
* to special modifiers in "fmt". For now only one exists:
|
||||
*
|
||||
* "!" -> REDISMODULE_ARGV_REPLICATE
|
||||
* "!" -> REDISMODULE_ARGV_REPLICATE
|
||||
*
|
||||
* On error (format specifier error) NULL is returned and nothing is
|
||||
* allocated. On success the argument vector is returned. */
|
||||
@@ -2394,7 +2454,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
argv[argc++] = createStringObject(buf,len);
|
||||
} else if (*p == 'l') {
|
||||
long ll = va_arg(ap,long long);
|
||||
argv[argc++] = createStringObjectFromLongLong(ll);
|
||||
argv[argc++] = createObject(OBJ_STRING,sdsfromlonglong(ll));
|
||||
} else if (*p == 'v') {
|
||||
/* A vector of strings */
|
||||
robj **v = va_arg(ap, void*);
|
||||
@@ -2458,6 +2518,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
|
||||
/* Setup our fake client for command execution. */
|
||||
c->flags |= CLIENT_MODULE;
|
||||
c->db = ctx->client->db;
|
||||
c->argv = argv;
|
||||
c->argc = argc;
|
||||
c->cmd = c->lastcmd = cmd;
|
||||
@@ -2547,7 +2608,7 @@ const char *RM_CallReplyProto(RedisModuleCallReply *reply, size_t *len) {
|
||||
*
|
||||
* The resulting 64 bit integer is composed as follows:
|
||||
*
|
||||
* (high order bits) 6|6|6|6|6|6|6|6|6|10 (low order bits)
|
||||
* (high order bits) 6|6|6|6|6|6|6|6|6|10 (low order bits)
|
||||
*
|
||||
* The first 6 bits value is the first character, name[0], while the last
|
||||
* 6 bits value, immediately before the 10 bits integer, is name[8].
|
||||
@@ -2651,11 +2712,13 @@ moduleType *moduleTypeLookupModuleByID(uint64_t id) {
|
||||
}
|
||||
|
||||
/* Turn an (unresolved) module ID into a type name, to show the user an
|
||||
* error when RDB files contain module data we can't load. */
|
||||
* error when RDB files contain module data we can't load.
|
||||
* The buffer pointed by 'name' must be 10 bytes at least. The function will
|
||||
* fill it with a null terminated module name. */
|
||||
void moduleTypeNameByID(char *name, uint64_t moduleid) {
|
||||
const char *cset = ModuleTypeNameCharSet;
|
||||
|
||||
name[0] = '\0';
|
||||
name[9] = '\0';
|
||||
char *p = name+8;
|
||||
moduleid >>= 10;
|
||||
for (int j = 0; j < 9; j++) {
|
||||
@@ -2741,8 +2804,8 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
|
||||
moduleTypeLoadFunc rdb_load;
|
||||
moduleTypeSaveFunc rdb_save;
|
||||
moduleTypeRewriteFunc aof_rewrite;
|
||||
moduleTypeDigestFunc digest;
|
||||
moduleTypeMemUsageFunc mem_usage;
|
||||
moduleTypeDigestFunc digest;
|
||||
moduleTypeFreeFunc free;
|
||||
} *tms = (struct typemethods*) typemethods_ptr;
|
||||
|
||||
@@ -2823,25 +2886,36 @@ void moduleRDBLoadError(RedisModuleIO *io) {
|
||||
* data types. */
|
||||
void RM_SaveUnsigned(RedisModuleIO *io, uint64_t value) {
|
||||
if (io->error) return;
|
||||
int retval = rdbSaveLen(io->rio, value);
|
||||
if (retval == -1) {
|
||||
io->error = 1;
|
||||
} else {
|
||||
io->bytes += retval;
|
||||
}
|
||||
/* Save opcode. */
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_UINT);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
retval = rdbSaveLen(io->rio, value);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
return;
|
||||
|
||||
saveerr:
|
||||
io->error = 1;
|
||||
}
|
||||
|
||||
/* Load an unsigned 64 bit value from the RDB file. This function should only
|
||||
* be called in the context of the rdb_load method of modules implementing
|
||||
* new data types. */
|
||||
uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr;
|
||||
}
|
||||
uint64_t value;
|
||||
int retval = rdbLoadLenByRef(io->rio, NULL, &value);
|
||||
if (retval == -1) {
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
if (retval == -1) goto loaderr;
|
||||
return value;
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
|
||||
/* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */
|
||||
@@ -2866,35 +2940,52 @@ int64_t RM_LoadSigned(RedisModuleIO *io) {
|
||||
* the RDB file. */
|
||||
void RM_SaveString(RedisModuleIO *io, RedisModuleString *s) {
|
||||
if (io->error) return;
|
||||
int retval = rdbSaveStringObject(io->rio,s);
|
||||
if (retval == -1) {
|
||||
io->error = 1;
|
||||
} else {
|
||||
io->bytes += retval;
|
||||
}
|
||||
/* Save opcode. */
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
retval = rdbSaveStringObject(io->rio, s);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
return;
|
||||
|
||||
saveerr:
|
||||
io->error = 1;
|
||||
}
|
||||
|
||||
/* Like RedisModule_SaveString() but takes a raw C pointer and length
|
||||
* as input. */
|
||||
void RM_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len) {
|
||||
if (io->error) return;
|
||||
int retval = rdbSaveRawString(io->rio,(unsigned char*)str,len);
|
||||
if (retval == -1) {
|
||||
io->error = 1;
|
||||
} else {
|
||||
io->bytes += retval;
|
||||
}
|
||||
/* Save opcode. */
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_STRING);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
retval = rdbSaveRawString(io->rio, (unsigned char*)str,len);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
return;
|
||||
|
||||
saveerr:
|
||||
io->error = 1;
|
||||
}
|
||||
|
||||
/* Implements RM_LoadString() and RM_LoadStringBuffer() */
|
||||
void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr;
|
||||
}
|
||||
void *s = rdbGenericLoadStringObject(io->rio,
|
||||
plain ? RDB_LOAD_PLAIN : RDB_LOAD_NONE, lenptr);
|
||||
if (s == NULL) {
|
||||
moduleRDBLoadError(io);
|
||||
return NULL; /* Never reached. */
|
||||
}
|
||||
if (s == NULL) goto loaderr;
|
||||
return s;
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return NULL; /* Never reached. */
|
||||
}
|
||||
|
||||
/* In the context of the rdb_load method of a module data type, loads a string
|
||||
@@ -2926,49 +3017,131 @@ char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr) {
|
||||
* It is possible to load back the value with RedisModule_LoadDouble(). */
|
||||
void RM_SaveDouble(RedisModuleIO *io, double value) {
|
||||
if (io->error) return;
|
||||
int retval = rdbSaveBinaryDoubleValue(io->rio, value);
|
||||
if (retval == -1) {
|
||||
io->error = 1;
|
||||
} else {
|
||||
io->bytes += retval;
|
||||
}
|
||||
/* Save opcode. */
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_DOUBLE);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
retval = rdbSaveBinaryDoubleValue(io->rio, value);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
return;
|
||||
|
||||
saveerr:
|
||||
io->error = 1;
|
||||
}
|
||||
|
||||
/* In the context of the rdb_save method of a module data type, loads back the
|
||||
* double value saved by RedisModule_SaveDouble(). */
|
||||
double RM_LoadDouble(RedisModuleIO *io) {
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr;
|
||||
}
|
||||
double value;
|
||||
int retval = rdbLoadBinaryDoubleValue(io->rio, &value);
|
||||
if (retval == -1) {
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
if (retval == -1) goto loaderr;
|
||||
return value;
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
|
||||
/* In the context of the rdb_save method of a module data type, saves a float
|
||||
/* In the context of the rdb_save method of a module data type, saves a float
|
||||
* value to the RDB file. The float can be a valid number, a NaN or infinity.
|
||||
* It is possible to load back the value with RedisModule_LoadFloat(). */
|
||||
void RM_SaveFloat(RedisModuleIO *io, float value) {
|
||||
if (io->error) return;
|
||||
int retval = rdbSaveBinaryFloatValue(io->rio, value);
|
||||
if (retval == -1) {
|
||||
io->error = 1;
|
||||
} else {
|
||||
io->bytes += retval;
|
||||
}
|
||||
/* Save opcode. */
|
||||
int retval = rdbSaveLen(io->rio, RDB_MODULE_OPCODE_FLOAT);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
/* Save value. */
|
||||
retval = rdbSaveBinaryFloatValue(io->rio, value);
|
||||
if (retval == -1) goto saveerr;
|
||||
io->bytes += retval;
|
||||
return;
|
||||
|
||||
saveerr:
|
||||
io->error = 1;
|
||||
}
|
||||
|
||||
/* In the context of the rdb_save method of a module data type, loads back the
|
||||
* float value saved by RedisModule_SaveFloat(). */
|
||||
float RM_LoadFloat(RedisModuleIO *io) {
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr;
|
||||
}
|
||||
float value;
|
||||
int retval = rdbLoadBinaryFloatValue(io->rio, &value);
|
||||
if (retval == -1) {
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
if (retval == -1) goto loaderr;
|
||||
return value;
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Key digest API (DEBUG DIGEST interface for modules types)
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Add a new element to the digest. This function can be called multiple times
|
||||
* one element after the other, for all the elements that constitute a given
|
||||
* data structure. The function call must be followed by the call to
|
||||
* `RedisModule_DigestEndSequence` eventually, when all the elements that are
|
||||
* always in a given order are added. See the Redis Modules data types
|
||||
* documentation for more info. However this is a quick example that uses Redis
|
||||
* data types as an example.
|
||||
*
|
||||
* To add a sequence of unordered elements (for example in the case of a Redis
|
||||
* Set), the pattern to use is:
|
||||
*
|
||||
* foreach element {
|
||||
* AddElement(element);
|
||||
* EndSequence();
|
||||
* }
|
||||
*
|
||||
* Because Sets are not ordered, so every element added has a position that
|
||||
* does not depend from the other. However if instead our elements are
|
||||
* ordered in pairs, like field-value pairs of an Hash, then one should
|
||||
* use:
|
||||
*
|
||||
* foreach key,value {
|
||||
* AddElement(key);
|
||||
* AddElement(value);
|
||||
* EndSquence();
|
||||
* }
|
||||
*
|
||||
* Because the key and value will be always in the above order, while instead
|
||||
* the single key-value pairs, can appear in any position into a Redis hash.
|
||||
*
|
||||
* A list of ordered elements would be implemented with:
|
||||
*
|
||||
* foreach element {
|
||||
* AddElement(element);
|
||||
* }
|
||||
* EndSequence();
|
||||
*
|
||||
*/
|
||||
void RM_DigestAddStringBuffer(RedisModuleDigest *md, unsigned char *ele, size_t len) {
|
||||
mixDigest(md->o,ele,len);
|
||||
}
|
||||
|
||||
/* Like `RedisModule_DigestAddStringBuffer()` but takes a long long as input
|
||||
* that gets converted into a string before adding it to the digest. */
|
||||
void RM_DigestAddLongLong(RedisModuleDigest *md, long long ll) {
|
||||
char buf[LONG_STR_SIZE];
|
||||
size_t len = ll2string(buf,sizeof(buf),ll);
|
||||
mixDigest(md->o,buf,len);
|
||||
}
|
||||
|
||||
/* See the doucmnetation for `RedisModule_DigestAddElement()`. */
|
||||
void RM_DigestEndSequence(RedisModuleDigest *md) {
|
||||
xorDigest(md->x,md->o,sizeof(md->o));
|
||||
memset(md->o,0,sizeof(md->o));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
@@ -3046,8 +3219,8 @@ RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) {
|
||||
|
||||
/* This is the low level function implementing both:
|
||||
*
|
||||
* RM_Log()
|
||||
* RM_LogIOError()
|
||||
* RM_Log()
|
||||
* RM_LogIOError()
|
||||
*
|
||||
*/
|
||||
void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_list ap) {
|
||||
@@ -3066,8 +3239,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
|
||||
serverLogRaw(level,msg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Produces a log message to the standard Redis log, the format accepts
|
||||
/* Produces a log message to the standard Redis log, the format accepts
|
||||
* printf-alike specifiers, while level is a string describing the log
|
||||
* level to use when emitting the log, and must be one of the following:
|
||||
*
|
||||
@@ -3106,6 +3278,17 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
|
||||
* Blocking clients from modules
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Readable handler for the awake pipe. We do nothing here, the awake bytes
|
||||
* will be actually read in a more appropriate place in the
|
||||
* moduleHandleBlockedClients() function that is where clients are actually
|
||||
* served. */
|
||||
void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
UNUSED(el);
|
||||
UNUSED(fd);
|
||||
UNUSED(mask);
|
||||
UNUSED(privdata);
|
||||
}
|
||||
|
||||
/* This is called from blocked.c in order to unblock a client: may be called
|
||||
* for multiple reasons while the client is in the middle of being blocked
|
||||
* because the client is terminated, but is also called for cleanup when a
|
||||
@@ -3121,6 +3304,11 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
|
||||
void unblockClientFromModule(client *c) {
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
bc->client = NULL;
|
||||
/* Reset the client for a new query since, for blocking commands implemented
|
||||
* into modules, we do not it immediately after the command returns (and
|
||||
* the client blocks) in order to be still able to access the argument
|
||||
* vector from callbacks. */
|
||||
resetClient(c);
|
||||
}
|
||||
|
||||
/* Block a client in the context of a blocking command, returning an handle
|
||||
@@ -3130,12 +3318,14 @@ void unblockClientFromModule(client *c) {
|
||||
*
|
||||
* The callbacks are called in the following contexts:
|
||||
*
|
||||
* reply_callback: called after a successful RedisModule_UnblockClient() call
|
||||
* in order to reply to the client and unblock it.
|
||||
* reply_timeout: called when the timeout is reached in order to send an
|
||||
* error to the client.
|
||||
* free_privdata: called in order to free the privata data that is passed
|
||||
* by RedisModule_UnblockClient() call.
|
||||
* reply_callback: called after a successful RedisModule_UnblockClient()
|
||||
* call in order to reply to the client and unblock it.
|
||||
*
|
||||
* reply_timeout: called when the timeout is reached in order to send an
|
||||
* error to the client.
|
||||
*
|
||||
* free_privdata: called in order to free the privata data that is passed
|
||||
* by RedisModule_UnblockClient() call.
|
||||
*/
|
||||
RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms) {
|
||||
client *c = ctx->client;
|
||||
@@ -3148,6 +3338,9 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
|
||||
bc->timeout_callback = timeout_callback;
|
||||
bc->free_privdata = free_privdata;
|
||||
bc->privdata = NULL;
|
||||
bc->reply_client = createClient(-1);
|
||||
bc->reply_client->flags |= CLIENT_MODULE;
|
||||
bc->dbid = c->db->id;
|
||||
c->bpop.timeout = timeout_ms ? (mstime()+timeout_ms) : 0;
|
||||
|
||||
blockClient(c,BLOCKED_MODULE);
|
||||
@@ -3169,6 +3362,9 @@ int RM_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) {
|
||||
pthread_mutex_lock(&moduleUnblockedClientsMutex);
|
||||
bc->privdata = privdata;
|
||||
listAddNodeTail(moduleUnblockedClients,bc);
|
||||
if (write(server.module_blocked_pipe[1],"A",1) != 1) {
|
||||
/* Ignore the error, this is best-effort. */
|
||||
}
|
||||
pthread_mutex_unlock(&moduleUnblockedClientsMutex);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -3193,6 +3389,10 @@ void moduleHandleBlockedClients(void) {
|
||||
RedisModuleBlockedClient *bc;
|
||||
|
||||
pthread_mutex_lock(&moduleUnblockedClientsMutex);
|
||||
/* Here we unblock all the pending clients blocked in modules operations
|
||||
* so we can read every pending "awake byte" in the pipe. */
|
||||
char buf[1];
|
||||
while (read(server.module_blocked_pipe[0],buf,1) == 1);
|
||||
while (listLength(moduleUnblockedClients)) {
|
||||
ln = listFirst(moduleUnblockedClients);
|
||||
bc = ln->value;
|
||||
@@ -3203,7 +3403,9 @@ void moduleHandleBlockedClients(void) {
|
||||
/* Release the lock during the loop, as long as we don't
|
||||
* touch the shared list. */
|
||||
|
||||
if (c != NULL && bc->reply_callback != NULL) {
|
||||
/* Call the reply callback if the client is valid and we have
|
||||
* any callback. */
|
||||
if (c && bc->reply_callback) {
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.flags |= REDISMODULE_CTX_BLOCKED_REPLY;
|
||||
ctx.blocked_privdata = bc->privdata;
|
||||
@@ -3213,10 +3415,42 @@ void moduleHandleBlockedClients(void) {
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
}
|
||||
|
||||
/* Free privdata if any. */
|
||||
if (bc->privdata && bc->free_privdata)
|
||||
bc->free_privdata(bc->privdata);
|
||||
|
||||
/* It is possible that this blocked client object accumulated
|
||||
* replies to send to the client in a thread safe context.
|
||||
* We need to glue such replies to the client output buffer and
|
||||
* free the temporary client we just used for the replies. */
|
||||
if (c) {
|
||||
if (bc->reply_client->bufpos)
|
||||
addReplyString(c,bc->reply_client->buf,
|
||||
bc->reply_client->bufpos);
|
||||
if (listLength(bc->reply_client->reply))
|
||||
listJoin(c->reply,bc->reply_client->reply);
|
||||
c->reply_bytes += bc->reply_client->reply_bytes;
|
||||
}
|
||||
freeClient(bc->reply_client);
|
||||
|
||||
if (c != NULL) {
|
||||
unblockClient(c);
|
||||
/* Put the client in the list of clients that need to write
|
||||
* if there are pending replies here. This is needed since
|
||||
* during a non blocking command the client may receive output. */
|
||||
if (clientHasPendingReplies(c) &&
|
||||
!(c->flags & CLIENT_PENDING_WRITE))
|
||||
{
|
||||
c->flags |= CLIENT_PENDING_WRITE;
|
||||
listAddNodeHead(server.clients_pending_write,c);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free 'bc' only after unblocking the client, since it is
|
||||
* referenced in the client blocking context, and must be valid
|
||||
* when calling unblockClient(). */
|
||||
zfree(bc);
|
||||
if (c != NULL) unblockClient(c);
|
||||
|
||||
/* Lock again before to iterate the loop. */
|
||||
pthread_mutex_lock(&moduleUnblockedClientsMutex);
|
||||
@@ -3255,6 +3489,75 @@ void *RM_GetBlockedClientPrivateData(RedisModuleCtx *ctx) {
|
||||
return ctx->blocked_privdata;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Thread Safe Contexts
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Return a context which can be used inside threads to make Redis context
|
||||
* calls with certain modules APIs. If 'bc' is not NULL then the module will
|
||||
* be bound to a blocked client, and it will be possible to use the
|
||||
* `RedisModule_Reply*` family of functions to accumulate a reply for when the
|
||||
* client will be unblocked. Otherwise the thread safe context will be
|
||||
* detached by a specific client.
|
||||
*
|
||||
* To call non-reply APIs, the thread safe context must be prepared with:
|
||||
*
|
||||
* RedisModule_ThreadSafeCallStart(ctx);
|
||||
* ... make your call here ...
|
||||
* RedisModule_ThreadSafeCallStop(ctx);
|
||||
*
|
||||
* This is not needed when using `RedisModule_Reply*` functions, assuming
|
||||
* that a blocked client was used when the context was created, otherwise
|
||||
* no RedisModule_Reply* call should be made at all.
|
||||
*
|
||||
* TODO: thread safe contexts do not inherit the blocked client
|
||||
* selected database. */
|
||||
RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
|
||||
RedisModuleCtx *ctx = zmalloc(sizeof(*ctx));
|
||||
RedisModuleCtx empty = REDISMODULE_CTX_INIT;
|
||||
memcpy(ctx,&empty,sizeof(empty));
|
||||
if (bc) {
|
||||
ctx->blocked_client = bc;
|
||||
ctx->module = bc->module;
|
||||
}
|
||||
ctx->flags |= REDISMODULE_CTX_THREAD_SAFE;
|
||||
/* Even when the context is associated with a blocked client, we can't
|
||||
* access it safely from another thread, so we create a fake client here
|
||||
* in order to keep things like the currently selected database and similar
|
||||
* things. */
|
||||
ctx->client = createClient(-1);
|
||||
if (bc) selectDb(ctx->client,bc->dbid);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/* Release a thread safe context. */
|
||||
void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) {
|
||||
moduleFreeContext(ctx);
|
||||
zfree(ctx);
|
||||
}
|
||||
|
||||
/* Acquire the server lock before executing a thread safe API call.
|
||||
* This is not needed for `RedisModule_Reply*` calls when there is
|
||||
* a blocked client connected to the thread safe context. */
|
||||
void RM_ThreadSafeContextLock(RedisModuleCtx *ctx) {
|
||||
DICT_NOTUSED(ctx);
|
||||
moduleAcquireGIL();
|
||||
}
|
||||
|
||||
/* Release the server lock after a thread safe API call was executed. */
|
||||
void RM_ThreadSafeContextUnlock(RedisModuleCtx *ctx) {
|
||||
DICT_NOTUSED(ctx);
|
||||
moduleReleaseGIL();
|
||||
}
|
||||
|
||||
void moduleAcquireGIL(void) {
|
||||
pthread_mutex_lock(&moduleGIL);
|
||||
}
|
||||
|
||||
void moduleReleaseGIL(void) {
|
||||
pthread_mutex_unlock(&moduleGIL);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API internals
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -3262,7 +3565,7 @@ void *RM_GetBlockedClientPrivateData(RedisModuleCtx *ctx) {
|
||||
/* server.moduleapi dictionary type. Only uses plain C strings since
|
||||
* this gets queries from modules. */
|
||||
|
||||
unsigned int dictCStringKeyHash(const void *key) {
|
||||
uint64_t dictCStringKeyHash(const void *key) {
|
||||
return dictGenHashFunction((unsigned char*)key, strlen((char*)key));
|
||||
}
|
||||
|
||||
@@ -3296,6 +3599,20 @@ void moduleInitModulesSystem(void) {
|
||||
server.loadmodule_queue = listCreate();
|
||||
modules = dictCreate(&modulesDictType,NULL);
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
serverLog(LL_WARNING,
|
||||
"Can't create the pipe for module blocking commands: %s",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
/* Make the pipe non blocking. This is just a best effort aware mechanism
|
||||
* and we do not want to block not in the read nor in the write half. */
|
||||
anetNonBlock(NULL,server.module_blocked_pipe[0]);
|
||||
anetNonBlock(NULL,server.module_blocked_pipe[1]);
|
||||
|
||||
/* Our thread-safe contexts GIL must start with already locked:
|
||||
* it is just unlocked when it's safe. */
|
||||
pthread_mutex_lock(&moduleGIL);
|
||||
}
|
||||
|
||||
/* Load all the modules in the server.loadmodule_queue list, which is
|
||||
@@ -3370,8 +3687,8 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
|
||||
* C_OK is returned, otherwise C_ERR is returned and errno is set
|
||||
* to the following values depending on the type of error:
|
||||
*
|
||||
* ENONET: No such module having the specified name.
|
||||
* EBUSY: The module exports a new data type and can only be reloaded. */
|
||||
* * ENONET: No such module having the specified name.
|
||||
* * EBUSY: The module exports a new data type and can only be reloaded. */
|
||||
int moduleUnload(sds name) {
|
||||
struct RedisModule *module = dictFetchValue(modules,name);
|
||||
|
||||
@@ -3482,6 +3799,11 @@ void moduleCommand(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the number of registered modules. */
|
||||
size_t moduleCount(void) {
|
||||
return dictSize(modules);
|
||||
}
|
||||
|
||||
/* Register all the APIs we export. Keep this function at the end of the
|
||||
* file so that's easy to seek it to add new entries. */
|
||||
void moduleRegisterCoreAPI(void) {
|
||||
@@ -3587,4 +3909,11 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(GetBlockedClientPrivateData);
|
||||
REGISTER_API(AbortBlock);
|
||||
REGISTER_API(Milliseconds);
|
||||
REGISTER_API(GetThreadSafeContext);
|
||||
REGISTER_API(FreeThreadSafeContext);
|
||||
REGISTER_API(ThreadSafeContextLock);
|
||||
REGISTER_API(ThreadSafeContextUnlock);
|
||||
REGISTER_API(DigestAddStringBuffer);
|
||||
REGISTER_API(DigestAddLongLong);
|
||||
REGISTER_API(DigestEndSequence);
|
||||
}
|
||||
|
||||
-1145
File diff suppressed because it is too large
Load Diff
@@ -1,265 +0,0 @@
|
||||
Blocking commands in Redis modules
|
||||
===
|
||||
|
||||
Redis has a few blocking commands among the built-in set of commands.
|
||||
One of the most used is `BLPOP` (or the symmetric `BRPOP`) which blocks
|
||||
waiting for elements arriving in a list.
|
||||
|
||||
The interesting fact about blocking commands is that they do not block
|
||||
the whole server, but just the client calling them. Usually the reason to
|
||||
block is that we expect some external event to happen: this can be
|
||||
some change in the Redis data structures like in the `BLPOP` case, a
|
||||
long computation happening in a thread, to receive some data from the
|
||||
network, and so forth.
|
||||
|
||||
Redis modules have the ability to implement blocking commands as well,
|
||||
this documentation shows how the API works and describes a few patterns
|
||||
that can be used in order to model blocking commands.
|
||||
|
||||
How blocking and resuming works.
|
||||
---
|
||||
|
||||
_Note: You may want to check the `helloblock.c` example in the Redis source tree
|
||||
inside the `src/modules` directory, for a simple to understand example
|
||||
on how the blocking API is applied._
|
||||
|
||||
In Redis modules, commands are implemented by callback functions that
|
||||
are invoked by the Redis core when the specific command is called
|
||||
by the user. Normally the callback terminates its execution sending
|
||||
some reply to the client. Using the following function instead, the
|
||||
function implementing the module command may request that the client
|
||||
is put into the blocked state:
|
||||
|
||||
RedisModuleBlockedClient *RedisModule_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms);
|
||||
|
||||
The function returns a `RedisModuleBlockedClient` object, which is later
|
||||
used in order to unblock the client. The arguments have the following
|
||||
meaning:
|
||||
|
||||
* `ctx` is the command execution context as usually in the rest of the API.
|
||||
* `reply_callback` is the callback, having the same prototype of a normal command function, that is called when the client is unblocked in order to return a reply to the client.
|
||||
* `timeout_callback` is the callback, having the same prototype of a normal command function that is called when the client reached the `ms` timeout.
|
||||
* `free_privdata` is the callback that is called in order to free the private data. Private data is a pointer to some data that is passed between the API used to unblock the client, to the callback that will send the reply to the client. We'll see how this mechanism works later in this document.
|
||||
* `ms` is the timeout in milliseconds. When the timeout is reached, the timeout callback is called and the client is automatically aborted.
|
||||
|
||||
Once a client is blocked, it can be unblocked with the following API:
|
||||
|
||||
int RedisModule_UnblockClient(RedisModuleBlockedClient *bc, void *privdata);
|
||||
|
||||
The function takes as argument the blocked client object returned by
|
||||
the previous call to `RedisModule_BlockClient()`, and unblock the client.
|
||||
Immediately before the client gets unblocked, the `reply_callback` function
|
||||
specified when the client was blocked is called: this function will
|
||||
have access to the `privdata` pointer used here.
|
||||
|
||||
IMPORTANT: The above function is thread safe, and can be called from within
|
||||
a thread doing some work in order to implement the command that blocked
|
||||
the client.
|
||||
|
||||
The `privdata` data will be freed automatically using the `free_privdata`
|
||||
callback when the client is unblocked. This is useful **since the reply
|
||||
callback may never be called** in case the client timeouts or disconnects
|
||||
from the server, so it's important that it's up to an external function
|
||||
to have the responsibility to free the data passed if needed.
|
||||
|
||||
To better understand how the API works, we can imagine writing a command
|
||||
that blocks a client for one second, and then send as reply "Hello!".
|
||||
|
||||
Note: arity checks and other non important things are not implemented
|
||||
int his command, in order to take the example simple.
|
||||
|
||||
int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
int argc)
|
||||
{
|
||||
RedisModuleBlockedClient *bc =
|
||||
RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);
|
||||
|
||||
pthread_t tid;
|
||||
pthread_create(&tid,NULL,threadmain,bc);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void *threadmain(void *arg) {
|
||||
RedisModuleBlockedClient *bc = arg;
|
||||
|
||||
sleep(1); /* Wait one second and unblock. */
|
||||
RedisModule_UnblockClient(bc,NULL);
|
||||
}
|
||||
|
||||
The above command blocks the client ASAP, spawining a thread that will
|
||||
wait a second and will unblock the client. Let's check the reply and
|
||||
timeout callbacks, which are in our case very similar, since they
|
||||
just reply the client with a different reply type.
|
||||
|
||||
int reply_func(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
int argc)
|
||||
{
|
||||
return RedisModule_ReplyWithSimpleString(ctx,"Hello!");
|
||||
}
|
||||
|
||||
int timeout_func(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
int argc)
|
||||
{
|
||||
return RedisModule_ReplyWithNull(ctx);
|
||||
}
|
||||
|
||||
The reply callback just sends the "Hello!" string to the client.
|
||||
The important bit here is that the reply callback is called when the
|
||||
client is unblocked from the thread.
|
||||
|
||||
The timeout command returns `NULL`, as it often happens with actual
|
||||
Redis blocking commands timing out.
|
||||
|
||||
Passing reply data when unblocking
|
||||
---
|
||||
|
||||
The above example is simple to understand but lacks an important
|
||||
real world aspect of an actual blocking command implementation: often
|
||||
the reply function will need to know what to reply to the client,
|
||||
and this information is often provided as the client is unblocked.
|
||||
|
||||
We could modify the above example so that the thread generates a
|
||||
random number after waiting one second. You can think at it as an
|
||||
actually expansive operation of some kind. Then this random number
|
||||
can be passed to the reply function so that we return it to the command
|
||||
caller. In order to make this working, we modify the functions as follow:
|
||||
|
||||
void *threadmain(void *arg) {
|
||||
RedisModuleBlockedClient *bc = arg;
|
||||
|
||||
sleep(1); /* Wait one second and unblock. */
|
||||
|
||||
long *mynumber = RedisModule_Alloc(sizeof(long));
|
||||
*mynumber = rand();
|
||||
RedisModule_UnblockClient(bc,mynumber);
|
||||
}
|
||||
|
||||
As you can see, now the unblocking call is passing some private data,
|
||||
that is the `mynumber` pointer, to the reply callback. In order to
|
||||
obtain this private data, the reply callback will use the following
|
||||
fnuction:
|
||||
|
||||
void *RedisModule_GetBlockedClientPrivateData(RedisModuleCtx *ctx);
|
||||
|
||||
So our reply callback is modified like that:
|
||||
|
||||
int reply_func(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
int argc)
|
||||
{
|
||||
long *mynumber = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
/* IMPORTANT: don't free mynumber here, but in the
|
||||
* free privdata callback. */
|
||||
return RedisModule_ReplyWithLongLong(ctx,mynumber);
|
||||
}
|
||||
|
||||
Note that we also need to pass a `free_privdata` function when blocking
|
||||
the client with `RedisModule_BlockClient()`, since the allocated
|
||||
long value must be freed. Our callback will look like the following:
|
||||
|
||||
void free_privdata(void *privdata) {
|
||||
RedisModule_Free(privdata);
|
||||
}
|
||||
|
||||
NOTE: It is important to stress that the private data is best freed in the
|
||||
`free_privdata` callback becaues the reply function may not be called
|
||||
if the client disconnects or timeout.
|
||||
|
||||
Also note that the private data is also accessible from the timeout
|
||||
callback, always using the `GetBlockedClientPrivateData()` API.
|
||||
|
||||
Aborting the blocking of a client
|
||||
---
|
||||
|
||||
One problem that sometimes arises is that we need to allocate resources
|
||||
in order to implement the non blocking command. So we block the client,
|
||||
then, for example, try to create a thread, but the thread creation function
|
||||
returns an error. What to do in such a condition in order to recover? We
|
||||
don't want to take the client blocked, nor we want to call `UnblockClient()`
|
||||
because this will trigger the reply callback to be called.
|
||||
|
||||
In this case the best thing to do is to use the following function:
|
||||
|
||||
int RedisModule_AbortBlock(RedisModuleBlockedClient *bc);
|
||||
|
||||
Practically this is how to use it:
|
||||
|
||||
int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
int argc)
|
||||
{
|
||||
RedisModuleBlockedClient *bc =
|
||||
RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);
|
||||
|
||||
pthread_t tid;
|
||||
if (pthread_create(&tid,NULL,threadmain,bc) != 0) {
|
||||
RedisModule_AbortBlock(bc);
|
||||
RedisModule_ReplyWithError(ctx,"Sorry can't create a thread");
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
The client will be unblocked but the reply callback will not be called.
|
||||
|
||||
Implementing the command, reply and timeout callback using a single function
|
||||
---
|
||||
|
||||
The following functions can be used in order to implement the reply and
|
||||
callback with the same function that implements the primary command
|
||||
function:
|
||||
|
||||
int RedisModule_IsBlockedReplyRequest(RedisModuleCtx *ctx);
|
||||
int RedisModule_IsBlockedTimeoutRequest(RedisModuleCtx *ctx);
|
||||
|
||||
So I could rewrite the example command without using a separated
|
||||
reply and timeout callback:
|
||||
|
||||
int Example_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
|
||||
int argc)
|
||||
{
|
||||
if (RedisModule_IsBlockedReplyRequest(ctx)) {
|
||||
long *mynumber = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
return RedisModule_ReplyWithLongLong(ctx,mynumber);
|
||||
} else if (RedisModule_IsBlockedTimeoutRequest) {
|
||||
return RedisModule_ReplyWithNull(ctx);
|
||||
}
|
||||
|
||||
RedisModuleBlockedClient *bc =
|
||||
RedisModule_BlockClient(ctx,reply_func,timeout_func,NULL,0);
|
||||
|
||||
pthread_t tid;
|
||||
if (pthread_create(&tid,NULL,threadmain,bc) != 0) {
|
||||
RedisModule_AbortBlock(bc);
|
||||
RedisModule_ReplyWithError(ctx,"Sorry can't create a thread");
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
Functionally is the same but there are people that will prefer the less
|
||||
verbose implementation that concentrates most of the command logic in a
|
||||
single function.
|
||||
|
||||
Working on copies of data inside a thread
|
||||
---
|
||||
|
||||
An interesting pattern in order to work with threads implementing the
|
||||
slow part of a command, is to work with a copy of the data, so that
|
||||
while some operation is performed in a key, the user continues to see
|
||||
the old version. However when the thread terminated its work, the
|
||||
representations are swapped and the new, processed version, is used.
|
||||
|
||||
An example of this approach is the
|
||||
[Neural Redis module](https://github.com/antirez/neural-redis)
|
||||
where neural networks are trained in different threads while the
|
||||
user can still execute and inspect their older versions.
|
||||
|
||||
Future work
|
||||
---
|
||||
|
||||
An API is work in progress right now in order to allow Redis modules APIs
|
||||
to be called in a safe way from threads, so that the threaded command
|
||||
can access the data space and do incremental operations.
|
||||
|
||||
There is no ETA for this feature but it may appear in the course of the
|
||||
Redis 4.0 release at some point.
|
||||
@@ -1,857 +0,0 @@
|
||||
Redis Modules: an introduction to the API
|
||||
===
|
||||
|
||||
The modules documentation is composed of the following files:
|
||||
|
||||
* `INTRO.md` (this file). An overview about Redis Modules system and API. It's a good idea to start your reading here.
|
||||
* `API.md` is generated from module.c top comments of RedisMoule functions. It is a good reference in order to understand how each function works.
|
||||
* `TYPES.md` covers the implementation of native data types into modules.
|
||||
* `BLOCK.md` shows how to write blocking commands that will not reply immediately, but will block the client, without blocking the Redis server, and will provide a reply whenever will be possible.
|
||||
|
||||
Redis modules make possible to extend Redis functionality using external
|
||||
modules, implementing new Redis commands at a speed and with features
|
||||
similar to what can be done inside the core itself.
|
||||
|
||||
Redis modules are dynamic libraries, that can be loaded into Redis at
|
||||
startup or using the `MODULE LOAD` command. Redis exports a C API, in the
|
||||
form of a single C header file called `redismodule.h`. Modules are meant
|
||||
to be written in C, however it will be possible to use C++ or other languages
|
||||
that have C binding functionalities.
|
||||
|
||||
Modules are designed in order to be loaded into different versions of Redis,
|
||||
so a given module does not need to be designed, or recompiled, in order to
|
||||
run with a specific version of Redis. For this reason, the module will
|
||||
register to the Redis core using a specific API version. The current API
|
||||
version is "1".
|
||||
|
||||
This document is about an alpha version of Redis modules. API, functionalities
|
||||
and other details may change in the future.
|
||||
|
||||
# Loading modules
|
||||
|
||||
In order to test the module you are developing, you can load the module
|
||||
using the following `redis.conf` configuration directive:
|
||||
|
||||
loadmodule /path/to/mymodule.so
|
||||
|
||||
It is also possible to load a module at runtime using the following command:
|
||||
|
||||
MODULE LOAD /path/to/mymodule.so
|
||||
|
||||
In order to list all loaded modules, use:
|
||||
|
||||
MODULE LIST
|
||||
|
||||
Finally, you can unload (and later reload if you wish) a module using the
|
||||
following command:
|
||||
|
||||
MODULE UNLOAD mymodule
|
||||
|
||||
Note that `mymodule` above is not the filename without the `.so` suffix, but
|
||||
instead, the name the module used to register itself into the Redis core.
|
||||
The name can be obtained using `MODULE LIST`. However it is good practice
|
||||
that the filename of the dynamic library is the same as the name the module
|
||||
uses to register itself into the Redis core.
|
||||
|
||||
# The simplest module you can write
|
||||
|
||||
In order to show the different parts of a module, here we'll show a very
|
||||
simple module that implements a command that outputs a random number.
|
||||
|
||||
#include "redismodule.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
RedisModule_ReplyWithLongLong(ctx,rand());
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
|
||||
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"helloworld.rand",
|
||||
HelloworldRand_RedisCommand) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
The example module has two functions. One implements a command called
|
||||
HELLOWORLD.RAND. This function is specific of that module. However the
|
||||
other function called `RedisModule_OnLoad()` must be present in each
|
||||
Redis module. It is the entry point for the module to be initialized,
|
||||
register its commands, and potentially other private data structures
|
||||
it uses.
|
||||
|
||||
Note that it is a good idea for modules to call commands with the
|
||||
name of the module followed by a dot, and finally the command name,
|
||||
like in the case of `HELLOWORLD.RAND`. This way it is less likely to
|
||||
have collisions.
|
||||
|
||||
Note that if different modules have colliding commands, they'll not be
|
||||
able to work in Redis at the same time, since the function
|
||||
`RedisModule_CreateCommand` will fail in one of the modules, so the module
|
||||
loading will abort returning an error condition.
|
||||
|
||||
# Module initialization
|
||||
|
||||
The above example shows the usage of the function `RedisModule_Init()`.
|
||||
It should be the first function called by the module `OnLoad` function.
|
||||
The following is the function prototype:
|
||||
|
||||
int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename,
|
||||
int module_version, int api_version);
|
||||
|
||||
The `Init` function announces the Redis core that the module has a given
|
||||
name, its version (that is reported by `MODULE LIST`), and that is willing
|
||||
to use a specific version of the API.
|
||||
|
||||
If the API version is wrong, the name is already taken, or there are other
|
||||
similar errors, the function will return `REDISMODULE_ERR`, and the module
|
||||
`OnLoad` function should return ASAP with an error.
|
||||
|
||||
Before the `Init` function is called, no other API function can be called,
|
||||
otherwise the module will segfault and the Redis instance will crash.
|
||||
|
||||
The second function called, `RedisModule_CreateCommand`, is used in order
|
||||
to register commands into the Redis core. The following is the prototype:
|
||||
|
||||
int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *cmdname,
|
||||
RedisModuleCmdFunc cmdfunc);
|
||||
|
||||
As you can see, most Redis modules API calls all take as first argument
|
||||
the `context` of the module, so that they have a reference to the module
|
||||
calling it, to the command and client executing a given command, and so forth.
|
||||
|
||||
To create a new command, the above function needs the context, the command
|
||||
name, and the function pointer of the function implementing the command,
|
||||
which must have the following prototype:
|
||||
|
||||
|
||||
int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
|
||||
The command function arguments are just the context, that will be passed
|
||||
to all the other API calls, the command argument vector, and total number
|
||||
of arguments, as passed by the user.
|
||||
|
||||
As you can see, the arguments are provided as pointers to a specific data
|
||||
type, the `RedisModuleString`. This is an opaque data type you have API
|
||||
functions to access and use, direct access to its fields is never needed.
|
||||
|
||||
Zooming into the example command implementation, we can find another call:
|
||||
|
||||
int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long integer);
|
||||
|
||||
This function returns an integer to the client that invoked the command,
|
||||
exactly like other Redis commands do, like for example `INCR` or `SCARD`.
|
||||
|
||||
# Setup and dependencies of a Redis module
|
||||
|
||||
Redis modules don't depend on Redis or some other library, nor they
|
||||
need to be compiled with a specific `redismodule.h` file. In order
|
||||
to create a new module, just copy a recent version of `redismodule.h`
|
||||
in your source tree, link all the libraries you want, and create
|
||||
a dynamic library having the `RedisModule_OnLoad()` function symbol
|
||||
exported.
|
||||
|
||||
The module will be able to load into different versions of Redis.
|
||||
|
||||
# Passing configuration parameters to Redis modules
|
||||
|
||||
When the module is loaded with the `MODULE LOAD` command, or using the
|
||||
`loadmodule` directive in the `redis.conf` file, the user is able to pass
|
||||
configuration parameters to the module by adding arguments after the module
|
||||
file name:
|
||||
|
||||
loadmodule mymodule.so foo bar 1234
|
||||
|
||||
In the above example the strings `foo`, `bar` and `123` will be passed
|
||||
to the module `OnLoad()` function in the `argv` argument as an array
|
||||
of RedisModuleString pointers. The number of arguments passed is into `argc`.
|
||||
|
||||
The way you can access those strings will be explained in the rest of this
|
||||
document. Normally the module will store the module configuration parameters
|
||||
in some `static` global variable that can be accessed module wide, so that
|
||||
the configuration can change the behavior of different commands.
|
||||
|
||||
# Working with RedisModuleString objects
|
||||
|
||||
The command argument vector `argv` passed to module commands, and the
|
||||
return value of other module APIs functions, are of type `RedisModuleString`.
|
||||
|
||||
Usually you directly pass module strings to other API calls, however sometimes
|
||||
you may need to directly access the string object.
|
||||
|
||||
There are a few functions in order to work with string objects:
|
||||
|
||||
const char *RedisModule_StringPtrLen(RedisModuleString *string, size_t *len);
|
||||
|
||||
The above function accesses a string by returning its pointer and setting its
|
||||
length in `len`.
|
||||
You should never write to a string object pointer, as you can see from the
|
||||
`const` pointer qualifier.
|
||||
|
||||
However, if you want, you can create new string objects using the following
|
||||
API:
|
||||
|
||||
RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len);
|
||||
|
||||
The string returned by the above command must be freed using a corresponding
|
||||
call to `RedisModule_FreeString()`:
|
||||
|
||||
void RedisModule_FreeString(RedisModuleString *str);
|
||||
|
||||
However if you want to avoid having to free strings, the automatic memory
|
||||
management, covered later in this document, can be a good alternative, by
|
||||
doing it for you.
|
||||
|
||||
Note that the strings provided via the argument vector `argv` never need
|
||||
to be freed. You only need to free new strings you create, or new strings
|
||||
returned by other APIs, where it is specified that the returned string must
|
||||
be freed.
|
||||
|
||||
## Creating strings from numbers or parsing strings as numbers
|
||||
|
||||
Creating a new string from an integer is a very common operation, so there
|
||||
is a function to do this:
|
||||
|
||||
RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx,10);
|
||||
|
||||
Similarly in order to parse a string as a number:
|
||||
|
||||
long long myval;
|
||||
if (RedisModule_StringToLongLong(ctx,argv[1],&myval) == REDISMODULE_OK) {
|
||||
/* Do something with 'myval' */
|
||||
}
|
||||
|
||||
## Accessing Redis keys from modules
|
||||
|
||||
Most Redis modules, in order to be useful, have to interact with the Redis
|
||||
data space (this is not always true, for example an ID generator may
|
||||
never touch Redis keys). Redis modules have two different APIs in order to
|
||||
access the Redis data space, one is a low level API that provides very
|
||||
fast access and a set of functions to manipulate Redis data structures.
|
||||
The other API is more high level, and allows to call Redis commands and
|
||||
fetch the result, similarly to how Lua scripts access Redis.
|
||||
|
||||
The high level API is also useful in order to access Redis functionalities
|
||||
that are not available as APIs.
|
||||
|
||||
In general modules developers should prefer the low level API, because commands
|
||||
implemented using the low level API run at a speed comparable to the speed
|
||||
of native Redis commands. However there are definitely use cases for the
|
||||
higher level API. For example often the bottleneck could be processing the
|
||||
data and not accessing it.
|
||||
|
||||
Also note that sometimes using the low level API is not harder compared to
|
||||
the higher level one.
|
||||
|
||||
# Calling Redis commands
|
||||
|
||||
The high level API to access Redis is the sum of the `RedisModule_Call()`
|
||||
function, together with the functions needed in order to access the
|
||||
reply object returned by `Call()`.
|
||||
|
||||
`RedisModule_Call` uses a special calling convention, with a format specifier
|
||||
that is used to specify what kind of objects you are passing as arguments
|
||||
to the function.
|
||||
|
||||
Redis commands are invoked just using a command name and a list of arguments.
|
||||
However when calling commands, the arguments may originate from different
|
||||
kind of strings: null-terminated C strings, RedisModuleString objects as
|
||||
received from the `argv` parameter in the command implementation, binary
|
||||
safe C buffers with a pointer and a length, and so forth.
|
||||
|
||||
For example if I want to call `INCRBY` using a first argument (the key)
|
||||
a string received in the argument vector `argv`, which is an array
|
||||
of RedisModuleString object pointers, and a C string representing the
|
||||
number "10" as second argument (the increment), I'll use the following
|
||||
function call:
|
||||
|
||||
RedisModuleCallReply *reply;
|
||||
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
|
||||
|
||||
The first argument is the context, and the second is always a null terminated
|
||||
C string with the command name. The third argument is the format specifier
|
||||
where each character corresponds to the type of the arguments that will follow.
|
||||
In the above case `"sc"` means a RedisModuleString object, and a null
|
||||
terminated C string. The other arguments are just the two arguments as
|
||||
specified. In fact `argv[1]` is a RedisModuleString and `"10"` is a null
|
||||
terminated C string.
|
||||
|
||||
This is the full list of format specifiers:
|
||||
|
||||
* **c** -- Null terminated C string pointer.
|
||||
* **b** -- C buffer, two arguments needed: C string pointer and `size_t` length.
|
||||
* **s** -- RedisModuleString as received in `argv` or by other Redis module APIs returning a RedisModuleString object.
|
||||
* **l** -- Long long integer.
|
||||
* **v** -- Array of RedisModuleString objects.
|
||||
* **!** -- This modifier just tells the function to replicate the command to slaves and AOF. It is ignored from the point of view of arguments parsing.
|
||||
|
||||
The function returns a `RedisModuleCallReply` object on success, on
|
||||
error NULL is returned.
|
||||
|
||||
NULL is returned when the command name is invalid, the format specifier uses
|
||||
characters that are not recognized, or when the command is called with the
|
||||
wrong number of arguments. In the above cases the `errno` var is set to `EINVAL`. NULL is also returned when, in an instance with Cluster enabled, the target
|
||||
keys are about non local hash slots. In this case `errno` is set to `EPERM`.
|
||||
|
||||
## Working with RedisModuleCallReply objects.
|
||||
|
||||
`RedisModuleCall` returns reply objects that can be accessed using the
|
||||
`RedisModule_CallReply*` family of functions.
|
||||
|
||||
In order to obtain the type or reply (corresponding to one of the data types
|
||||
supported by the Redis protocol), the function `RedisModule_CallReplyType()`
|
||||
is used:
|
||||
|
||||
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
|
||||
if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
|
||||
long long myval = RedisModule_CallReplyInteger(reply);
|
||||
/* Do something with myval. */
|
||||
}
|
||||
|
||||
Valid reply types are:
|
||||
|
||||
* `REDISMODULE_REPLY_STRING` Bulk string or status replies.
|
||||
* `REDISMODULE_REPLY_ERROR` Errors.
|
||||
* `REDISMODULE_REPLY_INTEGER` Signed 64 bit integers.
|
||||
* `REDISMODULE_REPLY_ARRAY` Array of replies.
|
||||
* `REDISMODULE_REPLY_NULL` NULL reply.
|
||||
|
||||
Strings, errors and arrays have an associated length. For strings and errors
|
||||
the length corresponds to the length of the string. For arrays the length
|
||||
is the number of elements. To obtain the reply length the following function
|
||||
is used:
|
||||
|
||||
size_t reply_len = RedisModule_CallReplyLength(reply);
|
||||
|
||||
In order to obtain the value of an integer reply, the following function is used, as already shown in the example above:
|
||||
|
||||
long long reply_integer_val = RedisModule_CallReplyInteger(reply);
|
||||
|
||||
Called with a reply object of the wrong type, the above function always
|
||||
returns `LLONG_MIN`.
|
||||
|
||||
Sub elements of array replies are accessed this way:
|
||||
|
||||
RedisModuleCallReply *subreply;
|
||||
subreply = RedisModule_CallReplyArrayElement(reply,idx);
|
||||
|
||||
The above function returns NULL if you try to access out of range elements.
|
||||
|
||||
Strings and errors (which are like strings but with a different type) can
|
||||
be accessed using in the following way, making sure to never write to
|
||||
the resulting pointer (that is returned as as `const` pointer so that
|
||||
misusing must be pretty explicit):
|
||||
|
||||
size_t len;
|
||||
char *ptr = RedisModule_CallReplyStringPtr(reply,&len);
|
||||
|
||||
If the reply type is not a string or an error, NULL is returned.
|
||||
|
||||
RedisCallReply objects are not the same as module string objects
|
||||
(RedisModuleString types). However sometimes you may need to pass replies
|
||||
of type string or integer, to API functions expecting a module string.
|
||||
|
||||
When this is the case, you may want to evaluate if using the low level
|
||||
API could be a simpler way to implement your command, or you can use
|
||||
the following function in order to create a new string object from a
|
||||
call reply of type string, error or integer:
|
||||
|
||||
RedisModuleString *mystr = RedisModule_CreateStringFromCallReply(myreply);
|
||||
|
||||
If the reply is not of the right type, NULL is returned.
|
||||
The returned string object should be released with `RedisModule_FreeString()`
|
||||
as usually, or by enabling automatic memory management (see corresponding
|
||||
section).
|
||||
|
||||
# Releasing call reply objects
|
||||
|
||||
Reply objects must be freed using `RedisModule_FreeCallReply`. For arrays,
|
||||
you need to free only the top level reply, not the nested replies.
|
||||
Currently the module implementation provides a protection in order to avoid
|
||||
crashing if you free a nested reply object for error, however this feature
|
||||
is not guaranteed to be here forever, so should not be considered part
|
||||
of the API.
|
||||
|
||||
If you use automatic memory management (explained later in this document)
|
||||
you don't need to free replies (but you still could if you wish to release
|
||||
memory ASAP).
|
||||
|
||||
## Returning values from Redis commands
|
||||
|
||||
Like normal Redis commands, new commands implemented via modules must be
|
||||
able to return values to the caller. The API exports a set of functions for
|
||||
this goal, in order to return the usual types of the Redis protocol, and
|
||||
arrays of such types as elemented. Also errors can be returned with any
|
||||
error string and code (the error code is the initial uppercase letters in
|
||||
the error message, like the "BUSY" string in the "BUSY the sever is busy" error
|
||||
message).
|
||||
|
||||
All the functions to send a reply to the client are called
|
||||
`RedisModule_ReplyWith<something>`.
|
||||
|
||||
To return an error, use:
|
||||
|
||||
RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err);
|
||||
|
||||
There is a predefined error string for key of wrong type errors:
|
||||
|
||||
REDISMODULE_ERRORMSG_WRONGTYPE
|
||||
|
||||
Example usage:
|
||||
|
||||
RedisModule_ReplyWithError(ctx,"ERR invalid arguments");
|
||||
|
||||
We already saw how to reply with a long long in the examples above:
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx,12345);
|
||||
|
||||
To reply with a simple string, that can't contain binary values or newlines,
|
||||
(so it's suitable to send small words, like "OK") we use:
|
||||
|
||||
RedisModule_ReplyWithSimpleString(ctx,"OK");
|
||||
|
||||
It's possible to reply with "bulk strings" that are binary safe, using
|
||||
two different functions:
|
||||
|
||||
int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len);
|
||||
|
||||
int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
|
||||
The first function gets a C pointer and length. The second a RedisMoudleString
|
||||
object. Use one or the other depending on the source type you have at hand.
|
||||
|
||||
In order to reply with an array, you just need to use a function to emit the
|
||||
array length, followed by as many calls to the above functions as the number
|
||||
of elements of the array are:
|
||||
|
||||
RedisModule_ReplyWithArray(ctx,2);
|
||||
RedisModule_ReplyWithStringBuffer(ctx,"age",3);
|
||||
RedisModule_ReplyWithLongLong(ctx,22);
|
||||
|
||||
To return nested arrays is easy, your nested array element just uses another
|
||||
call to `RedisModule_ReplyWithArray()` followed by the calls to emit the
|
||||
sub array elements.
|
||||
|
||||
## Returning arrays with dynamic length
|
||||
|
||||
Sometimes it is not possible to know beforehand the number of items of
|
||||
an array. As an example, think of a Redis module implementing a FACTOR
|
||||
command that given a number outputs the prime factors. Instead of
|
||||
factorializing the number, storing the prime factors into an array, and
|
||||
later produce the command reply, a better solution is to start an array
|
||||
reply where the length is not known, and set it later. This is accomplished
|
||||
with a special argument to `RedisModule_ReplyWithArray()`:
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
|
||||
The above call starts an array reply so we can use other `ReplyWith` calls
|
||||
in order to produce the array items. Finally in order to set the length
|
||||
se use the following call:
|
||||
|
||||
RedisModule_ReplySetArrayLength(ctx, number_of_items);
|
||||
|
||||
In the case of the FACTOR command, this translates to some code similar
|
||||
to this:
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
number_of_factors = 0;
|
||||
while(still_factors) {
|
||||
RedisModule_ReplyWithLongLong(ctx, some_factor);
|
||||
number_of_factors++;
|
||||
}
|
||||
RedisModule_ReplySetArrayLength(ctx, number_of_factors);
|
||||
|
||||
Another common use case for this feature is iterating over the arrays of
|
||||
some collection and only returning the ones passing some kind of filtering.
|
||||
|
||||
It is possible to have multiple nested arrays with postponed reply.
|
||||
Each call to `SetArray()` will set the length of the latest corresponding
|
||||
call to `ReplyWithArray()`:
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
... generate 100 elements ...
|
||||
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
... generate 10 elements ...
|
||||
RedisModule_ReplySetArrayLength(ctx, 10);
|
||||
RedisModule_ReplySetArrayLength(ctx, 100);
|
||||
|
||||
This creates a 100 items array having as last element a 10 items array.
|
||||
|
||||
# Arity and type checks
|
||||
|
||||
Often commands need to check that the number of arguments and type of the key
|
||||
is correct. In order to report a wrong arity, there is a specific function
|
||||
called `RedisModule_WrongArity()`. The usage is trivial:
|
||||
|
||||
if (argc != 2) return RedisModule_WrongArity(ctx);
|
||||
|
||||
Checking for the wrong type involves opening the key and checking the type:
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
|
||||
int keytype = RedisModule_KeyType(key);
|
||||
if (keytype != REDISMODULE_KEYTYPE_STRING &&
|
||||
keytype != REDISMODULE_KEYTYPE_EMPTY)
|
||||
{
|
||||
RedisModule_CloseKey(key);
|
||||
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
}
|
||||
|
||||
Note that you often want to proceed with a command both if the key
|
||||
is of the expected type, or if it's empty.
|
||||
|
||||
## Low level access to keys
|
||||
|
||||
Low level access to keys allow to perform operations on value objects associated
|
||||
to keys directly, with a speed similar to what Redis uses internally to
|
||||
implement the built-in commands.
|
||||
|
||||
Once a key is opened, a key pointer is returned that will be used with all the
|
||||
other low level API calls in order to perform operations on the key or its
|
||||
associated value.
|
||||
|
||||
Because the API is meant to be very fast, it cannot do too many run-time
|
||||
checks, so the user must be aware of certain rules to follow:
|
||||
|
||||
* Opening the same key multiple times where at least one instance is opened for writing, is undefined and may lead to crashes.
|
||||
* While a key is open, it should only be accessed via the low level key API. For example opening a key, then calling DEL on the same key using the `RedisModule_Call()` API will result into a crash. However it is safe to open a key, perform some operation with the low level API, closing it, then using other APIs to manage the same key, and later opening it again to do some more work.
|
||||
|
||||
In order to open a key the `RedisModule_OpenKey` function is used. It returns
|
||||
a key pointer, that we'll use with all the next calls to access and modify
|
||||
the value:
|
||||
|
||||
RedisModuleKey *key;
|
||||
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);
|
||||
|
||||
The second argument is the key name, that must be a `RedisModuleString` object.
|
||||
The third argument is the mode: `REDISMODULE_READ` or `REDISMODULE_WRITE`.
|
||||
It is possible to use `|` to bitwise OR the two modes to open the key in
|
||||
both modes. Currently a key opened for writing can also be accessed for reading
|
||||
but this is to be considered an implementation detail. The right mode should
|
||||
be used in sane modules.
|
||||
|
||||
You can open non exisitng keys for writing, since the keys will be created
|
||||
when an attempt to write to the key is performed. However when opening keys
|
||||
just for reading, `RedisModule_OpenKey` will return NULL if the key does not
|
||||
exist.
|
||||
|
||||
Once you are done using a key, you can close it with:
|
||||
|
||||
RedisModule_CloseKey(key);
|
||||
|
||||
Note that if automatic memory management is enabled, you are not forced to
|
||||
close keys. When the module function returns, Redis will take care to close
|
||||
all the keys which are still open.
|
||||
|
||||
## Getting the key type
|
||||
|
||||
In order to obtain the value of a key, use the `RedisModule_KeyType()` function:
|
||||
|
||||
int keytype = RedisModule_KeyType(key);
|
||||
|
||||
It returns one of the following values:
|
||||
|
||||
REDISMODULE_KEYTYPE_EMPTY
|
||||
REDISMODULE_KEYTYPE_STRING
|
||||
REDISMODULE_KEYTYPE_LIST
|
||||
REDISMODULE_KEYTYPE_HASH
|
||||
REDISMODULE_KEYTYPE_SET
|
||||
REDISMODULE_KEYTYPE_ZSET
|
||||
|
||||
The above are just the usual Redis key types, with the addition of an empty
|
||||
type, that signals the key pointer is associated with an empty key that
|
||||
does not yet exists.
|
||||
|
||||
## Creating new keys
|
||||
|
||||
To create a new key, open it for writing and then write to it using one
|
||||
of the key writing functions. Example:
|
||||
|
||||
RedisModuleKey *key;
|
||||
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);
|
||||
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
|
||||
RedisModule_StringSet(key,argv[2]);
|
||||
}
|
||||
|
||||
## Deleting keys
|
||||
|
||||
Just use:
|
||||
|
||||
RedisModule_DeleteKey(key);
|
||||
|
||||
The function returns `REDISMODULE_ERR` if the key is not open for writing.
|
||||
Note that after a key gets deleted, it is setup in order to be targeted
|
||||
by new key commands. For example `RedisModule_KeyType()` will return it is
|
||||
an empty key, and writing to it will create a new key, possibly of another
|
||||
type (depending on the API used).
|
||||
|
||||
## Managing key expires (TTLs)
|
||||
|
||||
To control key expires two functions are provided, that are able to set,
|
||||
modify, get, and unset the time to live associated with a key.
|
||||
|
||||
One function is used in order to query the current expire of an open key:
|
||||
|
||||
mstime_t RedisModule_GetExpire(RedisModuleKey *key);
|
||||
|
||||
The function returns the time to live of the key in milliseconds, or
|
||||
`REDISMODULE_NO_EXPIRE` as a special value to signal the key has no associated
|
||||
expire or does not exist at all (you can differentiate the two cases checking
|
||||
if the key type is `REDISMODULE_KEYTYPE_EMPTY`).
|
||||
|
||||
In order to change the expire of a key the following function is used instead:
|
||||
|
||||
int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire);
|
||||
|
||||
When called on a non existing key, `REDISMODULE_ERR` is returned, because
|
||||
the function can only associate expires to existing open keys (non existing
|
||||
open keys are only useful in order to create new values with data type
|
||||
specific write operations).
|
||||
|
||||
Again the `expire` time is specified in milliseconds. If the key has currently
|
||||
no expire, a new expire is set. If the key already have an expire, it is
|
||||
replaced with the new value.
|
||||
|
||||
If the key has an expire, and the special value `REDISMODULE_NO_EXPIRE` is
|
||||
used as a new expire, the expire is removed, similarly to the Redis
|
||||
`PERSIST` command. In case the key was already persistent, no operation is
|
||||
performed.
|
||||
|
||||
## Obtaining the length of values
|
||||
|
||||
There is a single function in order to retrieve the length of the value
|
||||
associated to an open key. The returned length is value-specific, and is
|
||||
the string length for strings, and the number of elements for the aggregated
|
||||
data types (how many elements there is in a list, set, sorted set, hash).
|
||||
|
||||
size_t len = RedisModule_ValueLength(key);
|
||||
|
||||
If the key does not exist, 0 is returned by the function:
|
||||
|
||||
## String type API
|
||||
|
||||
Setting a new string value, like the Redis `SET` command does, is performed
|
||||
using:
|
||||
|
||||
int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str);
|
||||
|
||||
The function works exactly like the Redis `SET` command itself, that is, if
|
||||
there is a prior value (of any type) it will be deleted.
|
||||
|
||||
Accessing existing string values is performed using DMA (direct memory
|
||||
access) for speed. The API will return a pointer and a length, so that's
|
||||
possible to access and, if needed, modify the string directly.
|
||||
|
||||
size_t len, j;
|
||||
char *myptr = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
|
||||
for (j = 0; j < len; j++) myptr[j] = 'A';
|
||||
|
||||
In the above example we write directly on the string. Note that if you want
|
||||
to write, you must be sure to ask for `WRITE` mode.
|
||||
|
||||
DMA pointers are only valid if no other operations are performed with the key
|
||||
before using the pointer, after the DMA call.
|
||||
|
||||
Sometimes when we want to manipulate strings directly, we need to change
|
||||
their size as well. For this scope, the `RedisModule_StringTruncate` function
|
||||
is used. Example:
|
||||
|
||||
RedisModule_StringTruncate(mykey,1024);
|
||||
|
||||
The function truncates, or enlarges the string as needed, padding it with
|
||||
zero bytes if the previos length is smaller than the new length we request.
|
||||
If the string does not exist since `key` is associated to an open empty key,
|
||||
a string value is created and associated to the key.
|
||||
|
||||
Note that every time `StringTruncate()` is called, we need to re-obtain
|
||||
the DMA pointer again, since the old may be invalid.
|
||||
|
||||
## List type API
|
||||
|
||||
It's possible to push and pop values from list values:
|
||||
|
||||
int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele);
|
||||
RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where);
|
||||
|
||||
In both the APIs the `where` argument specifies if to push or pop from tail
|
||||
or head, using the following macros:
|
||||
|
||||
REDISMODULE_LIST_HEAD
|
||||
REDISMODULE_LIST_TAIL
|
||||
|
||||
Elements returned by `RedisModule_ListPop()` are like strings craeted with
|
||||
`RedisModule_CreateString()`, they must be released with
|
||||
`RedisModule_FreeString()` or by enabling automatic memory management.
|
||||
|
||||
## Set type API
|
||||
|
||||
Work in progress.
|
||||
|
||||
## Sorted set type API
|
||||
|
||||
Documentation missing, please refer to the top comments inside `module.c`
|
||||
for the following functions:
|
||||
|
||||
* `RedisModule_ZsetAdd`
|
||||
* `RedisModule_ZsetIncrby`
|
||||
* `RedisModule_ZsetScore`
|
||||
* `RedisModule_ZsetRem`
|
||||
|
||||
And for the sorted set iterator:
|
||||
|
||||
* `RedisModule_ZsetRangeStop`
|
||||
* `RedisModule_ZsetFirstInScoreRange`
|
||||
* `RedisModule_ZsetLastInScoreRange`
|
||||
* `RedisModule_ZsetFirstInLexRange`
|
||||
* `RedisModule_ZsetLastInLexRange`
|
||||
* `RedisModule_ZsetRangeCurrentElement`
|
||||
* `RedisModule_ZsetRangeNext`
|
||||
* `RedisModule_ZsetRangePrev`
|
||||
* `RedisModule_ZsetRangeEndReached`
|
||||
|
||||
## Hash type API
|
||||
|
||||
Documentation missing, please refer to the top comments inside `module.c`
|
||||
for the following functions:
|
||||
|
||||
* `RedisModule_HashSet`
|
||||
* `RedisModule_HashGet`
|
||||
|
||||
## Iterating aggregated values
|
||||
|
||||
Work in progress.
|
||||
|
||||
# Replicating commands
|
||||
|
||||
If you want to use module commands exactly like normal Redis commands, in the
|
||||
context of replicated Redis instances, or using the AOF file for persistence,
|
||||
it is important for module commands to handle their replication in a consistent
|
||||
way.
|
||||
|
||||
When using the higher level APIs to invoke commands, replication happens
|
||||
automatically if you use the "!" modifier in the format string of
|
||||
`RedisModule_Call()` as in the following example:
|
||||
|
||||
reply = RedisModule_Call(ctx,"INCR","!sc",argv[1],"10");
|
||||
|
||||
As you can see the format specifier is `"!sc"`. The bang is not parsed as a
|
||||
format specifier, but it internally flags the command as "must replicate".
|
||||
|
||||
If you use the above programming style, there are no problems.
|
||||
However sometimes things are more complex than that, and you use the low level
|
||||
API. In this case, if there are no side effects in the command execution, and
|
||||
it consistently always performs the same work, what is possible to do is to
|
||||
replicate the command verbatim as the user executed it. To do that, you just
|
||||
need to call the following function:
|
||||
|
||||
RedisModule_ReplicateVerbatim(ctx);
|
||||
|
||||
When you use the above API, you should not use any other replication function
|
||||
since they are not guaranteed to mix well.
|
||||
|
||||
However this is not the only option. It's also possible to exactly tell
|
||||
Redis what commands to replicate as the effect of the command execution, using
|
||||
an API similar to `RedisModule_Call()` but that instead of calling the command
|
||||
sends it to the AOF / slaves stream. Example:
|
||||
|
||||
RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment);
|
||||
|
||||
It's possible to call `RedisModule_Replicate` multiple times, and each
|
||||
will emit a command. All the sequence emitted is wrapped between a
|
||||
`MULTI/EXEC` transaction, so that the AOF and replication effects are the
|
||||
same as executing a single command.
|
||||
|
||||
Note that `Call()` replication and `Replicate()` replication have a rule,
|
||||
in case you want to mix both forms of replication (not necessarily a good
|
||||
idea if there are simpler approaches). Commands replicated with `Call()`
|
||||
are always the first emitted in the final `MULTI/EXEC` block, while all
|
||||
the commands emitted with `Replicate()` will follow.
|
||||
|
||||
# Automatic memory management
|
||||
|
||||
Normally when writing programs in the C language, programmers need to manage
|
||||
memory manually. This is why the Redis modules API has functions to release
|
||||
strings, close open keys, free replies, and so forth.
|
||||
|
||||
However given that commands are executed in a contained environment and
|
||||
with a set of strict APIs, Redis is able to provide automatic memory management
|
||||
to modules, at the cost of some performance (most of the time, a very low
|
||||
cost).
|
||||
|
||||
When automatic memory management is enabled:
|
||||
|
||||
1. You don't need to close open keys.
|
||||
2. You don't need to free replies.
|
||||
3. You don't need to free RedisModuleString objects.
|
||||
|
||||
However you can still do it, if you want. For example, automatic memory
|
||||
management may be active, but inside a loop allocating a lot of strings,
|
||||
you may still want to free strings no longer used.
|
||||
|
||||
In order to enable automatic memory management, just call the following
|
||||
function at the start of the command implementation:
|
||||
|
||||
RedisModule_AutoMemory(ctx);
|
||||
|
||||
Automatic memory management is usually the way to go, however experienced
|
||||
C programmers may not use it in order to gain some speed and memory usage
|
||||
benefit.
|
||||
|
||||
# Allocating memory into modules
|
||||
|
||||
Normal C programs use `malloc()` and `free()` in order to allocate and
|
||||
release memory dynamically. While in Redis modules the use of malloc is
|
||||
not technically forbidden, it is a lot better to use the Redis Modules
|
||||
specific functions, that are exact replacements for `malloc`, `free`,
|
||||
`realloc` and `strdup`. These functions are:
|
||||
|
||||
void *RedisModule_Alloc(size_t bytes);
|
||||
void* RedisModule_Realloc(void *ptr, size_t bytes);
|
||||
void RedisModule_Free(void *ptr);
|
||||
void RedisModule_Calloc(size_t nmemb, size_t size);
|
||||
char *RedisModule_Strdup(const char *str);
|
||||
|
||||
They work exactly like their `libc` equivalent calls, however they use
|
||||
the same allocator Redis uses, and the memory allocated using these
|
||||
functions is reported by the `INFO` command in the memory section, is
|
||||
accounted when enforcing the `maxmemory` policy, and in general is
|
||||
a first citizen of the Redis executable. On the contrar, the method
|
||||
allocated inside modules with libc `malloc()` is transparent to Redis.
|
||||
|
||||
Another reason to use the modules functions in order to allocate memory
|
||||
is that, when creating native data types inside modules, the RDB loading
|
||||
functions can return deserialized strings (from the RDB file) directly
|
||||
as `RedisModule_Alloc()` allocations, so they can be used directly to
|
||||
populate data structures after loading, instead of having to copy them
|
||||
to the data structure.
|
||||
|
||||
## Pool allocator
|
||||
|
||||
Sometimes in commands implementations, it is required to perform many
|
||||
small allocations that will be not retained at the end of the command
|
||||
execution, but are just functional to execute the command itself.
|
||||
|
||||
This work can be more easily accomplished using the Redis pool allocator:
|
||||
|
||||
void *RedisModule_PoolAlloc(RedisModuleCtx *ctx, size_t bytes);
|
||||
|
||||
It works similarly to `malloc()`, and returns memory aligned to the
|
||||
next power of two of greater or equal to `bytes` (for a maximum alignment
|
||||
of 8 bytes). However it allocates memory in blocks, so it the overhead
|
||||
of the allocations is small, and more important, the memory allocated
|
||||
is automatically released when the command returns.
|
||||
|
||||
So in general short living allocations are a good candidates for the pool
|
||||
allocator.
|
||||
|
||||
# Writing commands compatible with Redis Cluster
|
||||
|
||||
Documentation missing, please check the following functions inside `module.c`:
|
||||
|
||||
RedisModule_IsKeysPositionRequest(ctx);
|
||||
RedisModule_KeyAtPos(ctx,pos);
|
||||
@@ -1,371 +0,0 @@
|
||||
Native types in Redis modules
|
||||
===
|
||||
|
||||
Redis modules can access Redis built-in data structures both at high level,
|
||||
by calling Redis commands, and at low level, by manipulating the data structures
|
||||
directly.
|
||||
|
||||
By using these capabilities in order to build new abstractions on top of existing
|
||||
Redis data structures, or by using strings DMA in order to encode modules
|
||||
data structures into Redis strings, it is possible to create modules that
|
||||
*feel like* they are exporting new data types. However, for more complex
|
||||
problems, this is not enough, and the implementation of new data structures
|
||||
inside the module is needed.
|
||||
|
||||
We call the ability of Redis modules to implement new data structures that
|
||||
feel like native Redis ones **native types support**. This document describes
|
||||
the API exported by the Redis modules system in order to create new data
|
||||
structures and handle the serialization in RDB files, the rewriting process
|
||||
in AOF, the type reporting via the `TYPE` command, and so forth.
|
||||
|
||||
Overview of native types
|
||||
---
|
||||
|
||||
A module exporting a native type is composed of the following main parts:
|
||||
|
||||
* The implementation of some kind of new data structure and of commands operating on the new data structure.
|
||||
* A set of callbacks that handle: RDB saving, RDB loading, AOF rewriting, releasing of a value associated with a key, calculation of a value digest (hash) to be used with the `DEBUG DIGEST` command.
|
||||
* A 9 characters name that is unique to each module native data type.
|
||||
* An encoding version, used to persist into RDB files a module-specific data version, so that a module will be able to load older representations from RDB files.
|
||||
|
||||
While to handle RDB loading, saving and AOF rewriting may look complex as a first glance, the modules API provide very high level function for handling all this, without requiring the user to handle read/write errors, so in practical terms, writing a new data structure for Redis is a simple task.
|
||||
|
||||
A **very easy** to understand but complete example of native type implementation
|
||||
is available inside the Redis distribution in the `/modules/hellotype.c` file.
|
||||
The reader is encouraged to read the documentation by looking at this example
|
||||
implementation to see how things are applied in the practice.
|
||||
|
||||
Registering a new data type
|
||||
===
|
||||
|
||||
In order to register a new native type into the Redis core, the module needs
|
||||
to declare a global variable that will hold a reference to the data type.
|
||||
The API to register the data type will return a data type reference that will
|
||||
be stored in the global variable.
|
||||
|
||||
static RedisModuleType *MyType;
|
||||
#define MYTYPE_ENCODING_VERSION 0
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx) {
|
||||
MyType = RedisModule_CreateDataType("MyType-AZ", MYTYPE_ENCODING_VERSION,
|
||||
MyTypeRDBLoad, MyTypeRDBSave, MyTypeAOFRewrite, MyTypeDigest,
|
||||
MyTypeFree);
|
||||
if (MyType == NULL) return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
As you can see from the example above, a single API call is needed in order to
|
||||
register the new type. However a number of function pointers are passed as
|
||||
arguments. The prototype of `RedisModule_CreateDataType` is the following:
|
||||
|
||||
moduleType *RedisModule_CreateDataType(RedisModuleCtx *ctx,
|
||||
const char *name, int encver,
|
||||
moduleTypeLoadFunc rdb_load,
|
||||
moduleTypeSaveFunc rdb_save,
|
||||
moduleTypeRewriteFunc aof_rewrite,
|
||||
moduleTypeDigestFunc digest,
|
||||
moduleTypeFreeFunc free);
|
||||
|
||||
The `ctx` argument is the context that we receive in the `OnLoad` function.
|
||||
The type `name` is a 9 character name in the character set that includes
|
||||
from `A-Z`, `a-z`, `0-9`, plus the underscore `_` and minus `-` characters.
|
||||
|
||||
Note that **this name must be unique** for each data type in the Redis
|
||||
ecosystem, so be creative, use both lower-case and upper case if it makes
|
||||
sense, and try to use the convention of mixing the type name with the name
|
||||
of the author of the module, to create a 9 character unique name.
|
||||
|
||||
For example if I'm building a *b-tree* data structure and my name is *antirez*
|
||||
I'll call my type **btree1-az**. The name, converted to a 64 bit integer,
|
||||
is stored inside the RDB file when saving the type, and will be used when the
|
||||
RDB data is loaded in order to resolve what module can load the data. If Redis
|
||||
finds no matching module, the integer is converted back to a name in order to
|
||||
provide some clue to the user about what module is missing in order to load
|
||||
the data.
|
||||
|
||||
The type name is also used as a reply for the `TYPE` command when called
|
||||
with a key holding the registered type.
|
||||
|
||||
The `encver` argument is the encoding version used by the module to store data
|
||||
inside the RDB file. For example I can start with an encoding version of 0,
|
||||
but later when I release version 2.0 of my module, I can switch encoding to
|
||||
something better. The new module will register with an encoding version of 1,
|
||||
so when it saves new RDB files, the new version will be stored on disk. However
|
||||
when loading RDB files, the module `rdb_load` method will be called even if
|
||||
there is data found for a different encoding version (and the encoding version
|
||||
is passed as argument to `rdb_load`), so that the module can still load old
|
||||
RDB files.
|
||||
|
||||
The remaining arguments `rdb_load`, `rdb_save`, `aof_rewrite`, `digest` and
|
||||
`free` are all callbacks with the following prototypes and uses:
|
||||
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
|
||||
* `rdb_load` is called when loading data from the RDB file. It loads data in the same format as `rdb_save` produces.
|
||||
* `rdb_save` is called when saving data to the RDB file.
|
||||
* `aof_rewrite` is called when the AOF is being rewritten, and the module needs to tell Redis what is the sequence of commands to recreate the content of a given key.
|
||||
* `digest` is called when `DEBUG DIGEST` is executed and a key holding this module type is found. Currently this is not yet implemented so the function ca be left empty.
|
||||
* `free` is called when a key with the module native type is deleted via `DEL` or in any other mean, in order to let the module reclaim the memory associated with such a value.
|
||||
|
||||
Ok, but *why* modules types require a 9 characters name?
|
||||
---
|
||||
|
||||
Oh, I understand you need to understand this, so here is a very specific
|
||||
explanation.
|
||||
|
||||
When Redis persists to RDB files, modules specific data types require to
|
||||
be persisted as well. Now RDB files are sequences of key-value pairs
|
||||
like the following:
|
||||
|
||||
[1 byte type] [key] [a type specific value]
|
||||
|
||||
The 1 byte type identifies strings, lists, sets, and so forth. In the case
|
||||
of modules data, it is set to a special value of `module data`, but of
|
||||
course this is not enough, we need the information needed to link a specific
|
||||
value with a specific module type that is able to load and handle it.
|
||||
|
||||
So when we save a `type specific value` about a module, we prefix it with
|
||||
a 64 bit integer. 64 bits is large enough to store the informations needed
|
||||
in order to lookup the module that can handle that specific type, but is
|
||||
short enough that we can prefix each module value we store inside the RDB
|
||||
without making the final RDB file too big. At the same time, this solution
|
||||
of prefixing the value with a 64 bit *signature* does not require to do
|
||||
strange things like defining in the RDB header a list of modules specific
|
||||
types. Everything is pretty simple.
|
||||
|
||||
So, what you can store in 64 bits in order to identify a given module in
|
||||
a reliable way? Well if you build a character set of 64 symbols, you can
|
||||
easily store 9 characters of 6 bits, and you are left with 10 bits, that
|
||||
are used in order to store the *encoding version* of the type, so that
|
||||
the same type can evolve in the future and provide a different and more
|
||||
efficient or updated serialization format for RDB files.
|
||||
|
||||
So the 64 bit prefix stored before each module value is like the following:
|
||||
|
||||
6|6|6|6|6|6|6|6|6|10
|
||||
|
||||
The first 9 elements are 6-bits characters, the final 10 bits is the
|
||||
encoding version.
|
||||
|
||||
When the RDB file is loaded back, it reads the 64 bit value, masks the final
|
||||
10 bits, and searches for a matching module in the modules types cache.
|
||||
When a matching one is found, the method to load the RDB file value is called
|
||||
with the 10 bits encoding version as argument, so that the module knows
|
||||
what version of the data layout to load, if it can support multiple versions.
|
||||
|
||||
Now the interesting thing about all this is that, if instead the module type
|
||||
cannot be resolved, since there is no loaded module having this signature,
|
||||
we can convert back the 64 bit value into a 9 characters name, and print
|
||||
an error to the user that includes the module type name! So that she or he
|
||||
immediately realizes what's wrong.
|
||||
|
||||
Setting and getting keys
|
||||
---
|
||||
|
||||
After registering our new data type in the `RedisModule_OnLoad()` function,
|
||||
we also need to be able to set Redis keys having as value our native type.
|
||||
|
||||
This normally happens in the context of commands that write data to a key.
|
||||
The native types API allow to set and get keys to module native data types,
|
||||
and to test if a given key is already associated to a value of a specific data
|
||||
type.
|
||||
|
||||
The API uses the normal modules `RedisModule_OpenKey()` low level key access
|
||||
interface in order to deal with this. This is an eaxmple of setting a
|
||||
native type private data structure to a Redis key:
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx,keyname,REDISMODULE_WRITE);
|
||||
struct some_private_struct *data = createMyDataStructure();
|
||||
RedisModule_ModuleTypeSetValue(key,MyType,data);
|
||||
|
||||
The function `RedisModule_ModuleTypeSetValue()` is used with a key handle open
|
||||
for writing, and gets three arguments: the key handle, the reference to the
|
||||
native type, as obtained during the type registration, and finally a `void*`
|
||||
pointer that contains the private data implementing the module native type.
|
||||
|
||||
Note that Redis has no clues at all about what your data contains. It will
|
||||
just call the callbacks you provided during the method registration in order
|
||||
to perform operations on the type.
|
||||
|
||||
Similarly we can retrieve the private data from a key using this function:
|
||||
|
||||
struct some_private_struct *data;
|
||||
data = RedisModule_ModuleTypeGetValue(key);
|
||||
|
||||
We can also test for a key to have our native type as value:
|
||||
|
||||
if (RedisModule_ModuleTypeGetType(key) == MyType) {
|
||||
/* ... do something ... */
|
||||
}
|
||||
|
||||
However for the calls to do the right thing, we need to check if the key
|
||||
is empty, if it contains a value of the right kind, and so forth. So
|
||||
the idiomatic code to implement a command writing to our native type
|
||||
is along these lines:
|
||||
|
||||
RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
|
||||
REDISMODULE_READ|REDISMODULE_WRITE);
|
||||
int type = RedisModule_KeyType(key);
|
||||
if (type != REDISMODULE_KEYTYPE_EMPTY &&
|
||||
RedisModule_ModuleTypeGetType(key) != MyType)
|
||||
{
|
||||
return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
|
||||
}
|
||||
|
||||
Then if we successfully verified the key is not of the wrong type, and
|
||||
we are going to write to it, we usually want to create a new data structure if
|
||||
the key is empty, or retrieve the reference to the value associated to the
|
||||
key if there is already one:
|
||||
|
||||
/* Create an empty value object if the key is currently empty. */
|
||||
struct some_private_struct *data;
|
||||
if (type == REDISMODULE_KEYTYPE_EMPTY) {
|
||||
data = createMyDataStructure();
|
||||
RedisModule_ModuleTypeSetValue(key,MyTyke,data);
|
||||
} else {
|
||||
data = RedisModule_ModuleTypeGetValue(key);
|
||||
}
|
||||
/* Do something with 'data'... */
|
||||
|
||||
Free method
|
||||
---
|
||||
|
||||
As already mentioned, when Redis needs to free a key holding a native type
|
||||
value, it needs help from the module in order to release the memory. This
|
||||
is the reason why we pass a `free` callback during the type registration:
|
||||
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
|
||||
A trivial implementation of the free method can be something like this,
|
||||
assuming our data structure is composed of a single allocation:
|
||||
|
||||
void MyTypeFreeCallback(void *value) {
|
||||
RedisModule_Free(value);
|
||||
}
|
||||
|
||||
However a more real world one will call some function that performs a more
|
||||
complex memory reclaiming, by casting the void pointer to some structure
|
||||
and freeing all the resources composing the value.
|
||||
|
||||
RDB load and save methods
|
||||
---
|
||||
|
||||
The RDB saving and loading callbacks need to create (and load back) a
|
||||
representation of the data type on disk. Redis offers an high level API
|
||||
that can automatically store inside the RDB file the following types:
|
||||
|
||||
* Unsigned 64 bit integers.
|
||||
* Signed 64 bit integers.
|
||||
* Doubles.
|
||||
* Strings.
|
||||
|
||||
It is up to the module to find a viable representation using the above base
|
||||
types. However note that while the integer and double values are stored
|
||||
and loaded in an architecture and *endianess* agnostic way, if you use
|
||||
the raw string saving API to, for example, save a structure on disk, you
|
||||
have to care those details yourself.
|
||||
|
||||
This is the list of functions performing RDB saving and loading:
|
||||
|
||||
void RedisModule_SaveUnsigned(RedisModuleIO *io, uint64_t value);
|
||||
uint64_t RedisModule_LoadUnsigned(RedisModuleIO *io);
|
||||
void RedisModule_SaveSigned(RedisModuleIO *io, int64_t value);
|
||||
int64_t RedisModule_LoadSigned(RedisModuleIO *io);
|
||||
void RedisModule_SaveString(RedisModuleIO *io, RedisModuleString *s);
|
||||
void RedisModule_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len);
|
||||
RedisModuleString *RedisModule_LoadString(RedisModuleIO *io);
|
||||
char *RedisModule_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr);
|
||||
void RedisModule_SaveDouble(RedisModuleIO *io, double value);
|
||||
double RedisModule_LoadDouble(RedisModuleIO *io);
|
||||
|
||||
The functions don't require any error checking from the module, that can
|
||||
always assume calls succeed.
|
||||
|
||||
As an example, imagine I've a native type that implements an array of
|
||||
double values, with the following structure:
|
||||
|
||||
struct double_array {
|
||||
size_t count;
|
||||
double *values;
|
||||
};
|
||||
|
||||
My `rdb_save` method may look like the following:
|
||||
|
||||
void DoubleArrayRDBSave(RedisModuleIO *io, void *ptr) {
|
||||
struct dobule_array *da = ptr;
|
||||
RedisModule_SaveUnsigned(io,da->count);
|
||||
for (size_t j = 0; j < da->count; j++)
|
||||
RedisModule_SaveDouble(io,da->values[j]);
|
||||
}
|
||||
|
||||
What we did was to store the number of elements followed by each double
|
||||
value. So when later we'll have to load the structure in the `rdb_load`
|
||||
method we'll do something like this:
|
||||
|
||||
void *DoubleArrayRDBLoad(RedisModuleIO *io, int encver) {
|
||||
if (encver != DOUBLE_ARRAY_ENC_VER) {
|
||||
/* We should actually log an error here, or try to implement
|
||||
the ability to load older versions of our data structure. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct double_array *da;
|
||||
da = RedisModule_Alloc(sizeof(*da));
|
||||
da->count = RedisModule_LoadUnsigned(io);
|
||||
da->values = RedisModule_Alloc(da->count * sizeof(double));
|
||||
for (size_t j = 0; j < da->count; j++)
|
||||
da->values = RedisModule_LoadDouble(io);
|
||||
return da;
|
||||
}
|
||||
|
||||
The load callback just reconstruct back the data structure from the data
|
||||
we stored in the RDB file.
|
||||
|
||||
Note that while there is no error handling on the API that writes and reads
|
||||
from disk, still the load callback can return NULL on errors in case what
|
||||
it reads does not look correct. Redis will just panic in that case.
|
||||
|
||||
AOF rewriting
|
||||
---
|
||||
|
||||
void RedisModule_EmitAOF(RedisModuleIO *io, const char *cmdname, const char *fmt, ...);
|
||||
|
||||
Handling multiple encodings
|
||||
---
|
||||
|
||||
WORK IN PROGRESS
|
||||
|
||||
Allocating memory
|
||||
---
|
||||
|
||||
Modules data types should try to use `RedisModule_Alloc()` functions family
|
||||
in order to allocate, reallocate and release heap memory used to implement the native data structures (see the other Redis Modules documentation for detailed information).
|
||||
|
||||
This is not just useful in order for Redis to be able to account for the memory used by the module, but there are also more advantages:
|
||||
|
||||
* Redis uses the `jemalloc` allcator, that often prevents fragmentation problems that could be caused by using the libc allocator.
|
||||
* When loading strings from the RDB file, the native types API is able to return strings allocated directly with `RedisModule_Alloc()`, so that the module can directly link this memory into the data structure representation, avoiding an useless copy of the data.
|
||||
|
||||
Even if you are using external libraries implementing your data structures, the
|
||||
allocation functions provided by the module API is exactly compatible with
|
||||
`malloc()`, `realloc()`, `free()` and `strdup()`, so converting the libraries
|
||||
in order to use these functions should be trivial.
|
||||
|
||||
In case you have an external library that uses libc `malloc()`, and you want
|
||||
to avoid replacing manually all the calls with the Redis Modules API calls,
|
||||
an approach could be to use simple macros in order to replace the libc calls
|
||||
with the Redis API calls. Something like this could work:
|
||||
|
||||
#define malloc RedisModule_Alloc
|
||||
#define realloc RedisModule_Realloc
|
||||
#define free RedisModule_Free
|
||||
#define strdup RedisModule_Strdup
|
||||
|
||||
However take in mind that mixing libc calls with Redis API calls will result
|
||||
into troubles and crashes, so if you replace calls using macros, you need to
|
||||
make sure that all the calls are correctly replaced, and that the code with
|
||||
the substituted calls will never, for example, attempt to call
|
||||
`RedisModule_Free()` with a pointer allocated using libc `malloc()`.
|
||||
+15
-7
@@ -6,21 +6,29 @@ def markdown(s)
|
||||
s = s.gsub(/\*\/$/,"")
|
||||
s = s.gsub(/^ \* {0,1}/,"")
|
||||
s = s.gsub(/^\/\* /,"")
|
||||
if s[0] != ' '
|
||||
s = s.gsub(/RM_[A-z()]+/){|x| "`#{x}`"}
|
||||
s = s.gsub(/RedisModule_[A-z()]+/){|x| "`#{x}`"}
|
||||
s = s.gsub(/REDISMODULE_[A-z]+/){|x| "`#{x}`"}
|
||||
end
|
||||
s.chop! while s[-1] == "\n" || s[-1] == " "
|
||||
return s
|
||||
lines = s.split("\n")
|
||||
newlines = []
|
||||
lines.each{|l|
|
||||
if l[0] != ' '
|
||||
l = l.gsub(/RM_[A-z()]+/){|x| "`#{x}`"}
|
||||
l = l.gsub(/RedisModule_[A-z()]+/){|x| "`#{x}`"}
|
||||
l = l.gsub(/REDISMODULE_[A-z]+/){|x| "`#{x}`"}
|
||||
end
|
||||
newlines << l
|
||||
}
|
||||
return newlines.join("\n")
|
||||
end
|
||||
|
||||
# Given the source code array and the index at which an exported symbol was
|
||||
# detected, extracts and outputs the documentation.
|
||||
def docufy(src,i)
|
||||
m = /RM_[A-z0-9]+/.match(src[i])
|
||||
name = m[0]
|
||||
name = name.sub("RM_","RedisModule_")
|
||||
proto = src[i].sub("{","").strip+";\n"
|
||||
puts "## `#{m[0]}`\n\n"
|
||||
proto = proto.sub("RM_","RedisModule_")
|
||||
puts "## `#{name}`\n\n"
|
||||
puts " #{proto}\n"
|
||||
comment = ""
|
||||
while true
|
||||
|
||||
@@ -105,6 +105,76 @@ int HelloBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int a
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* The thread entry point that actually executes the blocking part
|
||||
* of the command HELLO.KEYS.
|
||||
*
|
||||
* Note: this implementation is very simple on purpose, so no duplicated
|
||||
* keys (returned by SCAN) are filtered. However adding such a functionality
|
||||
* would be trivial just using any data structure implementing a dictionary
|
||||
* in order to filter the duplicated items. */
|
||||
void *HelloKeys_ThreadMain(void *arg) {
|
||||
RedisModuleBlockedClient *bc = arg;
|
||||
RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(bc);
|
||||
long long cursor = 0;
|
||||
size_t replylen = 0;
|
||||
|
||||
RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
|
||||
do {
|
||||
RedisModule_ThreadSafeContextLock(ctx);
|
||||
RedisModuleCallReply *reply = RedisModule_Call(ctx,
|
||||
"SCAN","l",(long long)cursor);
|
||||
RedisModule_ThreadSafeContextUnlock(ctx);
|
||||
|
||||
RedisModuleCallReply *cr_cursor =
|
||||
RedisModule_CallReplyArrayElement(reply,0);
|
||||
RedisModuleCallReply *cr_keys =
|
||||
RedisModule_CallReplyArrayElement(reply,1);
|
||||
|
||||
RedisModuleString *s = RedisModule_CreateStringFromCallReply(cr_cursor);
|
||||
RedisModule_StringToLongLong(s,&cursor);
|
||||
RedisModule_FreeString(ctx,s);
|
||||
|
||||
size_t items = RedisModule_CallReplyLength(cr_keys);
|
||||
for (size_t j = 0; j < items; j++) {
|
||||
RedisModuleCallReply *ele =
|
||||
RedisModule_CallReplyArrayElement(cr_keys,j);
|
||||
RedisModule_ReplyWithCallReply(ctx,ele);
|
||||
replylen++;
|
||||
}
|
||||
RedisModule_FreeCallReply(reply);
|
||||
} while (cursor != 0);
|
||||
RedisModule_ReplySetArrayLength(ctx,replylen);
|
||||
|
||||
RedisModule_FreeThreadSafeContext(ctx);
|
||||
RedisModule_UnblockClient(bc,NULL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* HELLO.KEYS -- Return all the keys in the current database without blocking
|
||||
* the server. The keys do not represent a point-in-time state so only the keys
|
||||
* that were in the database from the start to the end are guaranteed to be
|
||||
* there. */
|
||||
int HelloKeys_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
if (argc != 1) return RedisModule_WrongArity(ctx);
|
||||
|
||||
pthread_t tid;
|
||||
|
||||
/* Note that when blocking the client we do not set any callback: no
|
||||
* timeout is possible since we passed '0', nor we need a reply callback
|
||||
* because we'll use the thread safe context to accumulate a reply. */
|
||||
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,NULL,NULL,NULL,0);
|
||||
|
||||
/* Now that we setup a blocking client, we need to pass the control
|
||||
* to the thread. However we need to pass arguments to the thread:
|
||||
* the reference to the blocked client handle. */
|
||||
if (pthread_create(&tid,NULL,HelloKeys_ThreadMain,bc) != 0) {
|
||||
RedisModule_AbortBlock(bc);
|
||||
return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
@@ -117,6 +187,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"hello.block",
|
||||
HelloBlock_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
if (RedisModule_CreateCommand(ctx,"hello.keys",
|
||||
HelloKeys_RedisCommand,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
+19
-5
@@ -226,16 +226,28 @@ void HelloTypeAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value
|
||||
}
|
||||
}
|
||||
|
||||
void HelloTypeDigest(RedisModuleDigest *digest, void *value) {
|
||||
REDISMODULE_NOT_USED(digest);
|
||||
REDISMODULE_NOT_USED(value);
|
||||
/* TODO: The DIGEST module interface is yet not implemented. */
|
||||
/* The goal of this function is to return the amount of memory used by
|
||||
* the HelloType value. */
|
||||
size_t HelloTypeMemUsage(const void *value) {
|
||||
const struct HelloTypeObject *hto = value;
|
||||
struct HelloTypeNode *node = hto->head;
|
||||
return sizeof(*hto) + sizeof(*node)*hto->len;
|
||||
}
|
||||
|
||||
void HelloTypeFree(void *value) {
|
||||
HelloTypeReleaseObject(value);
|
||||
}
|
||||
|
||||
void HelloTypeDigest(RedisModuleDigest *md, void *value) {
|
||||
struct HelloTypeObject *hto = value;
|
||||
struct HelloTypeNode *node = hto->head;
|
||||
while(node) {
|
||||
RedisModule_DigestAddLongLong(md,node->value);
|
||||
node = node->next;
|
||||
}
|
||||
RedisModule_DigestEndSequence(md);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
@@ -250,7 +262,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
.rdb_load = HelloTypeRdbLoad,
|
||||
.rdb_save = HelloTypeRdbSave,
|
||||
.aof_rewrite = HelloTypeAofRewrite,
|
||||
.free = HelloTypeFree
|
||||
.mem_usage = HelloTypeMemUsage,
|
||||
.free = HelloTypeFree,
|
||||
.digest = HelloTypeDigest
|
||||
};
|
||||
|
||||
HelloType = RedisModule_CreateDataType(ctx,"hellotype",0,&tm);
|
||||
|
||||
@@ -241,7 +241,6 @@ int HelloRepl1_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int a
|
||||
{
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleCallReply *reply;
|
||||
RedisModule_AutoMemory(ctx);
|
||||
|
||||
/* This will be replicated *after* the two INCR statements, since
|
||||
@@ -258,8 +257,8 @@ int HelloRepl1_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int a
|
||||
|
||||
/* Using the "!" modifier we replicate the command if it
|
||||
* modified the dataset in some way. */
|
||||
reply = RedisModule_Call(ctx,"INCR","c!","foo");
|
||||
reply = RedisModule_Call(ctx,"INCR","c!","bar");
|
||||
RedisModule_Call(ctx,"INCR","c!","foo");
|
||||
RedisModule_Call(ctx,"INCR","c!","bar");
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx,0);
|
||||
|
||||
|
||||
+18
-3
@@ -117,6 +117,7 @@ void execCommand(client *c) {
|
||||
int orig_argc;
|
||||
struct redisCommand *orig_cmd;
|
||||
int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */
|
||||
int was_master = server.masterhost == NULL;
|
||||
|
||||
if (!(c->flags & CLIENT_MULTI)) {
|
||||
addReplyError(c,"EXEC without MULTI");
|
||||
@@ -147,11 +148,12 @@ void execCommand(client *c) {
|
||||
c->argv = c->mstate.commands[j].argv;
|
||||
c->cmd = c->mstate.commands[j].cmd;
|
||||
|
||||
/* Propagate a MULTI request once we encounter the first write op.
|
||||
/* Propagate a MULTI request once we encounter the first command which
|
||||
* is not readonly nor an administrative one.
|
||||
* This way we'll deliver the MULTI/..../EXEC block as a whole and
|
||||
* both the AOF and the replication link will have the same consistency
|
||||
* and atomicity guarantees. */
|
||||
if (!must_propagate && !(c->cmd->flags & CMD_READONLY)) {
|
||||
if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
|
||||
execCommandPropagateMulti(c);
|
||||
must_propagate = 1;
|
||||
}
|
||||
@@ -167,9 +169,22 @@ void execCommand(client *c) {
|
||||
c->argc = orig_argc;
|
||||
c->cmd = orig_cmd;
|
||||
discardTransaction(c);
|
||||
|
||||
/* Make sure the EXEC command will be propagated as well if MULTI
|
||||
* was already propagated. */
|
||||
if (must_propagate) server.dirty++;
|
||||
if (must_propagate) {
|
||||
int is_master = server.masterhost == NULL;
|
||||
server.dirty++;
|
||||
/* If inside the MULTI/EXEC block this instance was suddenly
|
||||
* switched from master to slave (using the SLAVEOF command), the
|
||||
* initial MULTI was propagated into the replication backlog, but the
|
||||
* rest was not. We need to make sure to at least terminate the
|
||||
* backlog with the final EXEC. */
|
||||
if (server.repl_backlog && was_master && !is_master) {
|
||||
char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
|
||||
feedReplicationBacklog(execcmd,strlen(execcmd));
|
||||
}
|
||||
}
|
||||
|
||||
handle_monitor:
|
||||
/* Send EXEC to clients waiting data from MONITOR. We do it here
|
||||
|
||||
+78
-15
@@ -28,6 +28,7 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "atomicvar.h"
|
||||
#include <sys/uio.h>
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
@@ -88,11 +89,14 @@ client *createClient(int fd) {
|
||||
}
|
||||
|
||||
selectDb(c,0);
|
||||
c->id = server.next_client_id++;
|
||||
uint64_t client_id;
|
||||
atomicGetIncr(server.next_client_id,client_id,1);
|
||||
c->id = client_id;
|
||||
c->fd = fd;
|
||||
c->name = NULL;
|
||||
c->bufpos = 0;
|
||||
c->querybuf = sdsempty();
|
||||
c->pending_querybuf = sdsempty();
|
||||
c->querybuf_peak = 0;
|
||||
c->reqtype = 0;
|
||||
c->argc = 0;
|
||||
@@ -107,6 +111,7 @@ client *createClient(int fd) {
|
||||
c->replstate = REPL_STATE_NONE;
|
||||
c->repl_put_online_on_ack = 0;
|
||||
c->reploff = 0;
|
||||
c->read_reploff = 0;
|
||||
c->repl_ack_off = 0;
|
||||
c->repl_ack_time = 0;
|
||||
c->slave_listening_port = 0;
|
||||
@@ -556,8 +561,7 @@ void addReplyBulkCBuffer(client *c, const void *p, size_t len) {
|
||||
|
||||
/* Add sds to reply (takes ownership of sds and frees it) */
|
||||
void addReplyBulkSds(client *c, sds s) {
|
||||
addReplySds(c,sdscatfmt(sdsempty(),"$%u\r\n",
|
||||
(unsigned long)sdslen(s)));
|
||||
addReplyLongLongWithPrefix(c,sdslen(s),'$');
|
||||
addReplySds(c,s);
|
||||
addReply(c,shared.crlf);
|
||||
}
|
||||
@@ -796,6 +800,7 @@ void freeClient(client *c) {
|
||||
|
||||
/* Free the query buffer */
|
||||
sdsfree(c->querybuf);
|
||||
sdsfree(c->pending_querybuf);
|
||||
c->querybuf = NULL;
|
||||
|
||||
/* Deallocate structures used to block on blocking ops. */
|
||||
@@ -921,6 +926,10 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
c->sentlen = 0;
|
||||
c->reply_bytes -= objlen;
|
||||
/* If there are no longer objects in the list, we expect
|
||||
* the count of reply bytes to be exactly zero. */
|
||||
if (listLength(c->reply) == 0)
|
||||
serverAssert(c->reply_bytes == 0);
|
||||
}
|
||||
}
|
||||
/* Note that we avoid to send more than NET_MAX_WRITES_PER_EVENT
|
||||
@@ -931,11 +940,11 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
*
|
||||
* However if we are over the maxmemory limit we ignore that and
|
||||
* just deliver as much data as it is possible to deliver. */
|
||||
server.stat_net_output_bytes += totwritten;
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(server.maxmemory == 0 ||
|
||||
zmalloc_used_memory() < server.maxmemory)) break;
|
||||
}
|
||||
server.stat_net_output_bytes += totwritten;
|
||||
if (nwritten == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
nwritten = 0;
|
||||
@@ -1027,6 +1036,13 @@ void resetClient(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Like processMultibulkBuffer(), but for the inline protocol instead of RESP,
|
||||
* this function consumes the client query buffer and creates a command ready
|
||||
* to be executed inside the client structure. Returns C_OK if the command
|
||||
* is ready to be executed, or C_ERR if there is still protocol to read to
|
||||
* have a well formed command. The function also returns C_ERR when there is
|
||||
* a protocol error: in such a case the client structure is setup to reply
|
||||
* with the error and close the connection. */
|
||||
int processInlineBuffer(client *c) {
|
||||
char *newline;
|
||||
int argc, j;
|
||||
@@ -1119,6 +1135,17 @@ static void setProtocolError(const char *errstr, client *c, int pos) {
|
||||
sdsrange(c->querybuf,pos,-1);
|
||||
}
|
||||
|
||||
/* Process the query buffer for client 'c', setting up the client argument
|
||||
* vector for command execution. Returns C_OK if after running the function
|
||||
* the client has a well-formed ready to be processed command, otherwise
|
||||
* C_ERR if there is still to read more buffer to get the full command.
|
||||
* The function also returns C_ERR when there is a protocol error: in such a
|
||||
* case the client structure is setup to reply with the error and close
|
||||
* the connection.
|
||||
*
|
||||
* This function is called if processInputBuffer() detects that the next
|
||||
* command is in RESP format, so the first byte in the command is found
|
||||
* to be '*'. Otherwise for inline commands processInlineBuffer() is called. */
|
||||
int processMultibulkBuffer(client *c) {
|
||||
char *newline = NULL;
|
||||
int pos = 0, ok;
|
||||
@@ -1253,10 +1280,14 @@ int processMultibulkBuffer(client *c) {
|
||||
/* We're done when c->multibulk == 0 */
|
||||
if (c->multibulklen == 0) return C_OK;
|
||||
|
||||
/* Still not read to process the command */
|
||||
/* Still not ready to process the command */
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* This function is called every time, in the client structure 'c', there is
|
||||
* more query buffer to process, because we read more data from the socket
|
||||
* or because a client was blocked and later reactivated, so there could be
|
||||
* pending query buffer, already representing a full command, to process. */
|
||||
void processInputBuffer(client *c) {
|
||||
server.current_client = c;
|
||||
/* Keep processing while there is something in the input buffer */
|
||||
@@ -1296,10 +1327,22 @@ void processInputBuffer(client *c) {
|
||||
resetClient(c);
|
||||
} else {
|
||||
/* Only reset the client when the command was executed. */
|
||||
if (processCommand(c) == C_OK)
|
||||
resetClient(c);
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (processCommand(c) == C_OK) {
|
||||
if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
|
||||
/* Update the applied replication offset of our master. */
|
||||
c->reploff = c->read_reploff - sdslen(c->querybuf);
|
||||
}
|
||||
|
||||
/* Don't reset the client structure for clients blocked in a
|
||||
* module blocking command, so that the reply callback will
|
||||
* still be able to access the client argv and argc field.
|
||||
* The client will be reset in unblockClientFromModule(). */
|
||||
if (!(c->flags & CLIENT_BLOCKED) || c->btype != BLOCKED_MODULE)
|
||||
resetClient(c);
|
||||
}
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may
|
||||
* result into a slave, that may be the active client, to be
|
||||
* freed. */
|
||||
if (server.current_client == NULL) break;
|
||||
}
|
||||
}
|
||||
@@ -1344,15 +1387,17 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
serverLog(LL_VERBOSE, "Client closed connection");
|
||||
freeClient(c);
|
||||
return;
|
||||
} else if (c->flags & CLIENT_MASTER) {
|
||||
/* Append the query buffer to the pending (not applied) buffer
|
||||
* of the master. We'll use this buffer later in order to have a
|
||||
* copy of the string applied by the last command executed. */
|
||||
c->pending_querybuf = sdscatlen(c->pending_querybuf,
|
||||
c->querybuf+qblen,nread);
|
||||
}
|
||||
|
||||
sdsIncrLen(c->querybuf,nread);
|
||||
c->lastinteraction = server.unixtime;
|
||||
if (c->flags & CLIENT_MASTER) {
|
||||
c->reploff += nread;
|
||||
replicationFeedSlavesFromMasterStream(server.slaves,
|
||||
c->querybuf+qblen,nread);
|
||||
}
|
||||
if (c->flags & CLIENT_MASTER) c->read_reploff += nread;
|
||||
server.stat_net_input_bytes += nread;
|
||||
if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
|
||||
sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
|
||||
@@ -1364,7 +1409,25 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
freeClient(c);
|
||||
return;
|
||||
}
|
||||
processInputBuffer(c);
|
||||
|
||||
/* Time to process the buffer. If the client is a master we need to
|
||||
* compute the difference between the applied offset before and after
|
||||
* processing the buffer, to understand how much of the replication stream
|
||||
* was actually applied to the master state: this quantity, and its
|
||||
* corresponding part of the replication stream, will be propagated to
|
||||
* the sub-slaves and to the replication backlog. */
|
||||
if (!(c->flags & CLIENT_MASTER)) {
|
||||
processInputBuffer(c);
|
||||
} else {
|
||||
size_t prev_offset = c->reploff;
|
||||
processInputBuffer(c);
|
||||
size_t applied = c->reploff - prev_offset;
|
||||
if (applied) {
|
||||
replicationFeedSlavesFromMasterStream(server.slaves,
|
||||
c->pending_querybuf, applied);
|
||||
sdsrange(c->pending_querybuf,applied,-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
|
||||
+13
-7
@@ -246,11 +246,9 @@ void freeStringObject(robj *o) {
|
||||
}
|
||||
|
||||
void freeListObject(robj *o) {
|
||||
switch (o->encoding) {
|
||||
case OBJ_ENCODING_QUICKLIST:
|
||||
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
quicklistRelease(o->ptr);
|
||||
break;
|
||||
default:
|
||||
} else {
|
||||
serverPanic("Unknown list encoding type");
|
||||
}
|
||||
}
|
||||
@@ -786,6 +784,14 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
} else {
|
||||
serverPanic("Unknown hash encoding");
|
||||
}
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
if (mt->mem_usage != NULL) {
|
||||
asize = mt->mem_usage(mv->value);
|
||||
} else {
|
||||
asize = 0;
|
||||
}
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
@@ -945,7 +951,7 @@ sds getMemoryDoctorReport(void) {
|
||||
}
|
||||
|
||||
/* Slaves using more than 10 MB each? */
|
||||
if (mh->clients_slaves / numslaves > (1024*1024*10)) {
|
||||
if (numslaves > 0 && mh->clients_slaves / numslaves > (1024*1024*10)) {
|
||||
big_slave_buf = 1;
|
||||
num_reports++;
|
||||
}
|
||||
@@ -955,14 +961,14 @@ sds getMemoryDoctorReport(void) {
|
||||
if (num_reports == 0) {
|
||||
s = sdsnew(
|
||||
"Hi Sam, I can't find any memory issue in your instance. "
|
||||
"I can only account for what occurs on this base.");
|
||||
"I can only account for what occurs on this base.\n");
|
||||
} else if (empty == 1) {
|
||||
s = sdsnew(
|
||||
"Hi Sam, this instance is empty or is using very little memory, "
|
||||
"my issues detector can't be used in these conditions. "
|
||||
"Please, leave for your mission on Earth and fill it with some data. "
|
||||
"The new Sam and I will be back to our programming as soon as I "
|
||||
"finished rebooting.");
|
||||
"finished rebooting.\n");
|
||||
} else {
|
||||
s = sdsnew("Sam, I detected a few issues in this Redis instance memory implants:\n\n");
|
||||
if (big_peak) {
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
#ifndef RAX_H
|
||||
#define RAX_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Representation of a radix tree as implemented in this file, that contains
|
||||
* the strings "foo", "foobar" and "footer" after the insertion of each
|
||||
* word. When the node represents a key inside the radix tree, we write it
|
||||
* between [], otherwise it is written between ().
|
||||
*
|
||||
* This is the vanilla representation:
|
||||
*
|
||||
* (f) ""
|
||||
* \
|
||||
* (o) "f"
|
||||
* \
|
||||
* (o) "fo"
|
||||
* \
|
||||
* [t b] "foo"
|
||||
* / \
|
||||
* "foot" (e) (a) "foob"
|
||||
* / \
|
||||
* "foote" (r) (r) "fooba"
|
||||
* / \
|
||||
* "footer" [] [] "foobar"
|
||||
*
|
||||
* However, this implementation implements a very common optimization where
|
||||
* successive nodes having a single child are "compressed" into the node
|
||||
* itself as a string of characters, each representing a next-level child,
|
||||
* and only the link to the node representing the last character node is
|
||||
* provided inside the representation. So the above representation is turend
|
||||
* into:
|
||||
*
|
||||
* ["foo"] ""
|
||||
* |
|
||||
* [t b] "foo"
|
||||
* / \
|
||||
* "foot" ("er") ("ar") "foob"
|
||||
* / \
|
||||
* "footer" [] [] "foobar"
|
||||
*
|
||||
* However this optimization makes the implementation a bit more complex.
|
||||
* For instance if a key "first" is added in the above radix tree, a
|
||||
* "node splitting" operation is needed, since the "foo" prefix is no longer
|
||||
* composed of nodes having a single child one after the other. This is the
|
||||
* above tree and the resulting node splitting after this event happens:
|
||||
*
|
||||
*
|
||||
* (f) ""
|
||||
* /
|
||||
* (i o) "f"
|
||||
* / \
|
||||
* "firs" ("rst") (o) "fo"
|
||||
* / \
|
||||
* "first" [] [t b] "foo"
|
||||
* / \
|
||||
* "foot" ("er") ("ar") "foob"
|
||||
* / \
|
||||
* "footer" [] [] "foobar"
|
||||
*
|
||||
* Similarly after deletion, if a new chain of nodes having a single child
|
||||
* is created (the chain must also not include nodes that represent keys),
|
||||
* it must be compressed back into a single node.
|
||||
*
|
||||
*/
|
||||
|
||||
#define RAX_NODE_MAX_SIZE ((1<<29)-1)
|
||||
typedef struct raxNode {
|
||||
uint32_t iskey:1; /* Does this node contain a key? */
|
||||
uint32_t isnull:1; /* Associated value is NULL (don't store it). */
|
||||
uint32_t iscompr:1; /* Node is compressed. */
|
||||
uint32_t size:29; /* Number of children, or compressed string len. */
|
||||
/* Data layout is as follows:
|
||||
*
|
||||
* If node is not compressed we have 'size' bytes, one for each children
|
||||
* character, and 'size' raxNode pointers, point to each child node.
|
||||
* Note how the character is not stored in the children but in the
|
||||
* edge of the parents:
|
||||
*
|
||||
* [header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
|
||||
*
|
||||
* if node is compressed (strlen != 0) the node has 1 children.
|
||||
* In that case the 'size' bytes of the string stored immediately at
|
||||
* the start of the data section, represent a sequence of successive
|
||||
* nodes linked one after the other, for which only the last one in
|
||||
* the sequence is actually represented as a node, and pointed to by
|
||||
* the current compressed node.
|
||||
*
|
||||
* [header strlen=3][xyz][z-ptr](value-ptr?)
|
||||
*
|
||||
* Both compressed and not compressed nodes can represent a key
|
||||
* with associated data in the radix tree at any level (not just terminal
|
||||
* nodes).
|
||||
*
|
||||
* If the node has an associated key (iskey=1) and is not NULL
|
||||
* (isnull=0), then after the raxNode pointers poiting to the
|
||||
* childen, an additional value pointer is present (as you can see
|
||||
* in the representation above as "value-ptr" field).
|
||||
*/
|
||||
unsigned char data[];
|
||||
} raxNode;
|
||||
|
||||
typedef struct rax {
|
||||
raxNode *head;
|
||||
uint64_t numele;
|
||||
uint64_t numnodes;
|
||||
} rax;
|
||||
|
||||
/* Stack data structure used by raxLowWalk() in order to, optionally, return
|
||||
* a list of parent nodes to the caller. The nodes do not have a "parent"
|
||||
* field for space concerns, so we use the auxiliary stack when needed. */
|
||||
#define RAX_STACK_STATIC_ITEMS 32
|
||||
typedef struct raxStack {
|
||||
void **stack; /* Points to static_items or an heap allocated array. */
|
||||
size_t items, maxitems; /* Number of items contained and total space. */
|
||||
/* Up to RAXSTACK_STACK_ITEMS items we avoid to allocate on the heap
|
||||
* and use this static array of pointers instead. */
|
||||
void *static_items[RAX_STACK_STATIC_ITEMS];
|
||||
int oom; /* True if pushing into this stack failed for OOM at some point. */
|
||||
} raxStack;
|
||||
|
||||
/* Radix tree iterator state is encapsulated into this data structure. */
|
||||
#define RAX_ITER_STATIC_LEN 128
|
||||
#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current
|
||||
element for the first iteration and
|
||||
clear the flag. */
|
||||
#define RAX_ITER_EOF (1<<1) /* End of iteration reached. */
|
||||
#define RAX_ITER_SAFE (1<<2) /* Safe iterator, allows operations while
|
||||
iterating. But it is slower. */
|
||||
typedef struct raxIterator {
|
||||
int flags;
|
||||
rax *rt; /* Radix tree we are iterating. */
|
||||
unsigned char *key; /* The current string. */
|
||||
void *data; /* Data associated to this key. */
|
||||
size_t key_len; /* Current key length. */
|
||||
size_t key_max; /* Max key len the current key buffer can hold. */
|
||||
unsigned char key_static_string[RAX_ITER_STATIC_LEN];
|
||||
raxNode *node; /* Current node. Only for unsafe iteration. */
|
||||
raxStack stack; /* Stack used for unsafe iteration. */
|
||||
} raxIterator;
|
||||
|
||||
/* A special pointer returned for not found items. */
|
||||
extern void *raxNotFound;
|
||||
|
||||
/* Exported API. */
|
||||
rax *raxNew(void);
|
||||
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
|
||||
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
|
||||
void *raxFind(rax *rax, unsigned char *s, size_t len);
|
||||
void raxFree(rax *rax);
|
||||
void raxStart(raxIterator *it, rax *rt);
|
||||
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
|
||||
int raxNext(raxIterator *it);
|
||||
int raxPrev(raxIterator *it);
|
||||
int raxRandomWalk(raxIterator *it, size_t steps);
|
||||
int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len);
|
||||
void raxStop(raxIterator *it);
|
||||
void raxShow(rax *rax);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Rax -- A radix tree implementation.
|
||||
*
|
||||
* Copyright (c) 2017, 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.
|
||||
*/
|
||||
|
||||
/* Allocator selection.
|
||||
*
|
||||
* This file is used in order to change the Rax allocator at compile time.
|
||||
* Just define the following defines to what you want to use. Also add
|
||||
* the include of your alternate allocator if needed (not needed in order
|
||||
* to use the default libc allocator). */
|
||||
|
||||
#ifndef RAX_ALLOC_H
|
||||
#define RAX_ALLOC_H
|
||||
#include "zmalloc.h"
|
||||
#define rax_malloc zmalloc
|
||||
#define rax_realloc zrealloc
|
||||
#define rax_free zfree
|
||||
#endif
|
||||
@@ -61,7 +61,7 @@ void rdbCheckThenExit(int linenum, char *reason, ...) {
|
||||
if (!rdbCheckMode) {
|
||||
serverLog(LL_WARNING, "%s", msg);
|
||||
char *argv[2] = {"",server.rdb_filename};
|
||||
redis_check_rdb_main(2,argv);
|
||||
redis_check_rdb_main(2,argv,NULL);
|
||||
} else {
|
||||
rdbCheckError("%s",msg);
|
||||
}
|
||||
@@ -623,7 +623,7 @@ int rdbSaveObjectType(rio *rdb, robj *o) {
|
||||
else
|
||||
serverPanic("Unknown hash encoding");
|
||||
case OBJ_MODULE:
|
||||
return rdbSaveType(rdb,RDB_TYPE_MODULE);
|
||||
return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
|
||||
default:
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
@@ -704,23 +704,30 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
nwritten += n;
|
||||
} else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
dictIterator *di = dictGetIterator(zs->dict);
|
||||
dictEntry *de;
|
||||
zskiplist *zsl = zs->zsl;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,dictSize(zs->dict))) == -1) return -1;
|
||||
if ((n = rdbSaveLen(rdb,zsl->length)) == -1) return -1;
|
||||
nwritten += n;
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds ele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
|
||||
if ((n = rdbSaveRawString(rdb,(unsigned char*)ele,sdslen(ele)))
|
||||
== -1) return -1;
|
||||
/* We save the skiplist elements from the greatest to the smallest
|
||||
* (that's trivial since the elements are already ordered in the
|
||||
* skiplist): this improves the load process, since the next loaded
|
||||
* element will always be the smaller, so adding to the skiplist
|
||||
* will always immediately stop at the head, making the insertion
|
||||
* O(1) instead of O(log(N)). */
|
||||
zskiplistNode *zn = zsl->tail;
|
||||
while (zn != NULL) {
|
||||
if ((n = rdbSaveRawString(rdb,
|
||||
(unsigned char*)zn->ele,sdslen(zn->ele))) == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveBinaryDoubleValue(rdb,*score)) == -1) return -1;
|
||||
if ((n = rdbSaveBinaryDoubleValue(rdb,zn->score)) == -1)
|
||||
return -1;
|
||||
nwritten += n;
|
||||
zn = zn->backward;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else {
|
||||
serverPanic("Unknown sorted set encoding");
|
||||
}
|
||||
@@ -768,8 +775,12 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
/* Then write the module-specific representation. */
|
||||
/* Then write the module-specific representation + EOF marker. */
|
||||
mt->rdb_save(&io,mv->value);
|
||||
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
|
||||
if (retval == -1) return -1;
|
||||
io.bytes += retval;
|
||||
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
@@ -1095,6 +1106,45 @@ void rdbRemoveTempFile(pid_t childpid) {
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* This function is called by rdbLoadObject() when the code is in RDB-check
|
||||
* mode and we find a module value of type 2 that can be parsed without
|
||||
* the need of the actual module. The value is parsed for errors, finally
|
||||
* a dummy redis object is returned just to conform to the API. */
|
||||
robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
uint64_t opcode;
|
||||
while((opcode = rdbLoadLen(rdb,NULL)) != RDB_MODULE_OPCODE_EOF) {
|
||||
if (opcode == RDB_MODULE_OPCODE_SINT ||
|
||||
opcode == RDB_MODULE_OPCODE_UINT)
|
||||
{
|
||||
uint64_t len;
|
||||
if (rdbLoadLenByRef(rdb,NULL,&len) == -1) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Error reading integer from module %s value", modulename);
|
||||
}
|
||||
} else if (opcode == RDB_MODULE_OPCODE_STRING) {
|
||||
robj *o = rdbGenericLoadStringObject(rdb,RDB_LOAD_NONE,NULL);
|
||||
if (o == NULL) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Error reading string from module %s value", modulename);
|
||||
}
|
||||
decrRefCount(o);
|
||||
} else if (opcode == RDB_MODULE_OPCODE_FLOAT) {
|
||||
float val;
|
||||
if (rdbLoadBinaryFloatValue(rdb,&val) == -1) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Error reading float from module %s value", modulename);
|
||||
}
|
||||
} else if (opcode == RDB_MODULE_OPCODE_DOUBLE) {
|
||||
double val;
|
||||
if (rdbLoadBinaryDoubleValue(rdb,&val) == -1) {
|
||||
rdbExitReportCorruptRDB(
|
||||
"Error reading double from module %s value", modulename);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createStringObject("module-dummy-value",18);
|
||||
}
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
@@ -1346,11 +1396,14 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
|
||||
break;
|
||||
}
|
||||
} else if (rdbtype == RDB_TYPE_MODULE) {
|
||||
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
|
||||
uint64_t moduleid = rdbLoadLen(rdb,NULL);
|
||||
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
|
||||
char name[10];
|
||||
|
||||
if (rdbCheckMode && rdbtype == RDB_TYPE_MODULE_2)
|
||||
return rdbLoadCheckModuleValue(rdb,name);
|
||||
|
||||
if (mt == NULL) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module data I can't load: no matching module '%s'", name);
|
||||
@@ -1358,9 +1411,24 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
void *ptr = mt->rdb_load(&io,moduleid&1023);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
zfree(io.ctx);
|
||||
}
|
||||
|
||||
/* Module v2 serialization has an EOF mark at the end. */
|
||||
if (io.ver == 2) {
|
||||
uint64_t eof = rdbLoadLen(rdb,NULL);
|
||||
if (eof != RDB_MODULE_OPCODE_EOF) {
|
||||
serverLog(LL_WARNING,"The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == NULL) {
|
||||
moduleTypeNameByID(name,moduleid);
|
||||
serverLog(LL_WARNING,"The RDB file contains module data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
|
||||
@@ -1552,7 +1620,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
|
||||
dbAdd(db,key,val);
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(db,key,expiretime);
|
||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||
|
||||
decrRefCount(key);
|
||||
}
|
||||
@@ -1863,9 +1931,6 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||
} 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) {
|
||||
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
@@ -1889,6 +1954,10 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
close(pipefds[1]);
|
||||
closeChildInfoPipe();
|
||||
} 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",
|
||||
childpid);
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
@@ -1939,7 +2008,7 @@ void bgsaveCommand(client *c) {
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"An AOF log rewriting in progress: can't BGSAVE right now. "
|
||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenver "
|
||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||
"possible.");
|
||||
}
|
||||
} else if (rdbSaveBackground(server.rdb_filename,NULL) == C_OK) {
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
#define RDB_TYPE_HASH 4
|
||||
#define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */
|
||||
#define RDB_TYPE_MODULE 6
|
||||
#define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without
|
||||
the generating module being loaded. */
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Object types for encoded objects. */
|
||||
@@ -90,7 +92,7 @@
|
||||
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */
|
||||
|
||||
/* Test if a type is an object type. */
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 6) || (t >= 9 && t <= 14))
|
||||
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 14))
|
||||
|
||||
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
|
||||
#define RDB_OPCODE_AUX 250
|
||||
@@ -100,6 +102,14 @@
|
||||
#define RDB_OPCODE_SELECTDB 254
|
||||
#define RDB_OPCODE_EOF 255
|
||||
|
||||
/* Module serialized values sub opcodes */
|
||||
#define RDB_MODULE_OPCODE_EOF 0 /* End of module value. */
|
||||
#define RDB_MODULE_OPCODE_SINT 1 /* Signed integer. */
|
||||
#define RDB_MODULE_OPCODE_UINT 2 /* Unsigned integer. */
|
||||
#define RDB_MODULE_OPCODE_FLOAT 3 /* Float. */
|
||||
#define RDB_MODULE_OPCODE_DOUBLE 4 /* Double. */
|
||||
#define RDB_MODULE_OPCODE_STRING 5 /* String. */
|
||||
|
||||
/* rdbLoad...() functions flags. */
|
||||
#define RDB_LOAD_NONE 0
|
||||
#define RDB_LOAD_ENC (1<<0)
|
||||
|
||||
@@ -565,7 +565,7 @@ invalid:
|
||||
|
||||
usage:
|
||||
printf(
|
||||
"Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n"
|
||||
"Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>] [-k <boolean>]\n\n"
|
||||
" -h <hostname> Server hostname (default 127.0.0.1)\n"
|
||||
" -p <port> Server port (default 6379)\n"
|
||||
" -s <socket> Server socket (overrides host and port)\n"
|
||||
@@ -779,6 +779,13 @@ int main(int argc, const char **argv) {
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("hset")) {
|
||||
len = redisFormatCommand(&cmd,
|
||||
"HSET myset:__rand_int__ element:__rand_int__ %s",data);
|
||||
benchmark("HSET",cmd,len);
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
if (test_is_selected("spop")) {
|
||||
len = redisFormatCommand(&cmd,"SPOP myset");
|
||||
benchmark("SPOP",cmd,len);
|
||||
|
||||
+28
-13
@@ -28,13 +28,8 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "server.h"
|
||||
#include <sys/stat.h>
|
||||
#include "config.h"
|
||||
|
||||
#define ERROR(...) { \
|
||||
char __buf[1024]; \
|
||||
@@ -60,7 +55,7 @@ int readLong(FILE *fp, char prefix, long *target) {
|
||||
return 0;
|
||||
}
|
||||
if (buf[0] != prefix) {
|
||||
ERROR("Expected prefix '%c', got: '%c'",buf[0],prefix);
|
||||
ERROR("Expected prefix '%c', got: '%c'",prefix,buf[0]);
|
||||
return 0;
|
||||
}
|
||||
*target = strtol(buf+1,&eptr,10);
|
||||
@@ -87,7 +82,7 @@ int readString(FILE *fp, char** target) {
|
||||
|
||||
/* Increase length to also consume \r\n */
|
||||
len += 2;
|
||||
*target = (char*)malloc(len);
|
||||
*target = (char*)zmalloc(len);
|
||||
if (!readBytes(fp,*target,len)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -127,12 +122,12 @@ off_t process(FILE *fp) {
|
||||
}
|
||||
}
|
||||
}
|
||||
free(str);
|
||||
zfree(str);
|
||||
}
|
||||
|
||||
/* Stop if the loop did not finish */
|
||||
if (i < argc) {
|
||||
if (str) free(str);
|
||||
if (str) zfree(str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -146,7 +141,7 @@ off_t process(FILE *fp) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int redis_check_aof_main(int argc, char **argv) {
|
||||
char *filename;
|
||||
int fix = 0;
|
||||
|
||||
@@ -185,6 +180,25 @@ int main(int argc, char **argv) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* This AOF file may have an RDB preamble. Check this to start, and if this
|
||||
* is the case, start processing the RDB part. */
|
||||
if (size >= 8) { /* There must be at least room for the RDB header. */
|
||||
char sig[5];
|
||||
int has_preamble = fread(sig,sizeof(sig),1,fp) == 1 &&
|
||||
memcmp(sig,"REDIS",sizeof(sig)) == 0;
|
||||
rewind(fp);
|
||||
if (has_preamble) {
|
||||
printf("The AOF appears to start with an RDB preamble.\n"
|
||||
"Checking the RDB preamble to start:\n");
|
||||
if (redis_check_rdb_main(argc,argv,fp) == C_ERR) {
|
||||
printf("RDB preamble of AOF file is not sane, aborting.\n");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("RDB preamble is OK, proceding with AOF tail...\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
off_t pos = process(fp);
|
||||
off_t diff = size-pos;
|
||||
printf("AOF analyzed: size=%lld, ok_up_to=%lld, diff=%lld\n",
|
||||
@@ -206,7 +220,8 @@ int main(int argc, char **argv) {
|
||||
printf("Successfully truncated AOF\n");
|
||||
}
|
||||
} else {
|
||||
printf("AOF is not valid\n");
|
||||
printf("AOF is not valid. "
|
||||
"Use the --fix option to try fixing it.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
@@ -214,5 +229,5 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
return 0;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
+21
-11
@@ -173,16 +173,18 @@ void rdbCheckSetupSignals(void) {
|
||||
}
|
||||
|
||||
/* Check the specified RDB file. Return 0 if the RDB looks sane, otherwise
|
||||
* 1 is returned. */
|
||||
int redis_check_rdb(char *rdbfilename) {
|
||||
* 1 is returned.
|
||||
* The file is specified as a filename in 'rdbfilename' if 'fp' is not NULL,
|
||||
* otherwise the already open file 'fp' is checked. */
|
||||
int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
uint64_t dbid;
|
||||
int type, rdbver;
|
||||
char buf[1024];
|
||||
long long expiretime, now = mstime();
|
||||
FILE *fp;
|
||||
static rio rdb; /* Pointed by global struct riostate. */
|
||||
|
||||
if ((fp = fopen(rdbfilename,"r")) == NULL) return 1;
|
||||
int closefile = (fp == NULL);
|
||||
if (fp == NULL && (fp = fopen(rdbfilename,"r")) == NULL) return 1;
|
||||
|
||||
rioInitWithFile(&rdb,fp);
|
||||
rdbstate.rio = &rdb;
|
||||
@@ -310,7 +312,7 @@ int redis_check_rdb(char *rdbfilename) {
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
if (closefile) fclose(fp);
|
||||
return 0;
|
||||
|
||||
eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
@@ -323,12 +325,19 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
}
|
||||
|
||||
/* RDB check main: called form redis.c when Redis is executed with the
|
||||
* redis-check-rdb alias.
|
||||
* redis-check-rdb alias, on during RDB loading errors.
|
||||
*
|
||||
* The function never returns, but exits with the status code according
|
||||
* to success (RDB is sane) or error (RDB is corrupted). */
|
||||
int redis_check_rdb_main(int argc, char **argv) {
|
||||
if (argc != 2) {
|
||||
* The function works in two ways: can be called with argc/argv as a
|
||||
* standalone executable, or called with a non NULL 'fp' argument if we
|
||||
* already have an open file to check. This happens when the function
|
||||
* is used to check an RDB preamble inside an AOF file.
|
||||
*
|
||||
* When called with fp = NULL, the function never returns, but exits with the
|
||||
* status code according to success (RDB is sane) or error (RDB is corrupted).
|
||||
* Otherwise if called with a non NULL fp, the function returns C_OK or
|
||||
* C_ERR depending on the success or failure. */
|
||||
int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
|
||||
if (argc != 2 && fp == NULL) {
|
||||
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
@@ -341,10 +350,11 @@ int redis_check_rdb_main(int argc, char **argv) {
|
||||
rdbCheckMode = 1;
|
||||
rdbCheckInfo("Checking RDB file %s", argv[1]);
|
||||
rdbCheckSetupSignals();
|
||||
int retval = redis_check_rdb(argv[1]);
|
||||
int retval = redis_check_rdb(argv[1],fp);
|
||||
if (retval == 0) {
|
||||
rdbCheckInfo("\\o/ RDB looks OK! \\o/");
|
||||
rdbShowGenericInfo();
|
||||
}
|
||||
if (fp) return (retval == 0) ? C_OK : C_ERR;
|
||||
exit(retval);
|
||||
}
|
||||
|
||||
+58
-11
@@ -275,6 +275,10 @@ static void cliIntegrateHelp(void) {
|
||||
* don't already match what we have. */
|
||||
for (size_t j = 0; j < reply->elements; j++) {
|
||||
redisReply *entry = reply->element[j];
|
||||
if (entry->type != REDIS_REPLY_ARRAY || entry->elements < 4 ||
|
||||
entry->element[0]->type != REDIS_REPLY_STRING ||
|
||||
entry->element[1]->type != REDIS_REPLY_INTEGER ||
|
||||
entry->element[3]->type != REDIS_REPLY_INTEGER) return;
|
||||
char *cmdname = entry->element[0]->str;
|
||||
int i;
|
||||
|
||||
@@ -1119,6 +1123,12 @@ static void usage(void) {
|
||||
" --csv Output in CSV format.\n"
|
||||
" --stat Print rolling stats about server: mem, clients, ...\n"
|
||||
" --latency Enter a special mode continuously sampling latency.\n"
|
||||
" If you use this mode in an interactive session it runs\n"
|
||||
" forever displaying real-time stats. Otherwise if --raw or\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"
|
||||
" -i to change the interval), then produces a single output\n"
|
||||
" and exits.\n"
|
||||
" --latency-history Like --latency but tracking latency changes over time.\n"
|
||||
" Default time interval is 15 sec. Change it using -i.\n"
|
||||
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
|
||||
@@ -1269,6 +1279,11 @@ static void repl(void) {
|
||||
int argc;
|
||||
sds *argv;
|
||||
|
||||
/* Initialize the help and, if possible, use the COMMAND command in order
|
||||
* to retrieve missing entries. */
|
||||
cliInitHelp();
|
||||
cliIntegrateHelp();
|
||||
|
||||
config.interactive = 1;
|
||||
linenoiseSetMultiLine(1);
|
||||
linenoiseSetCompletionCallback(completionCallback);
|
||||
@@ -1344,7 +1359,9 @@ static void repl(void) {
|
||||
}
|
||||
|
||||
elapsed = mstime()-start_time;
|
||||
if (elapsed >= 500) {
|
||||
if (elapsed >= 500 &&
|
||||
config.output == OUTPUT_STANDARD)
|
||||
{
|
||||
printf("(%.2fs)\n",(double)elapsed/1000);
|
||||
}
|
||||
}
|
||||
@@ -1461,6 +1478,18 @@ static int evalMode(int argc, char **argv) {
|
||||
* Latency and latency history modes
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void latencyModePrint(long long min, long long max, double avg, long long count) {
|
||||
if (config.output == OUTPUT_STANDARD) {
|
||||
printf("min: %lld, max: %lld, avg: %.2f (%lld samples)",
|
||||
min, max, avg, count);
|
||||
fflush(stdout);
|
||||
} else if (config.output == OUTPUT_CSV) {
|
||||
printf("%lld,%lld,%.2f,%lld\n", min, max, avg, count);
|
||||
} else if (config.output == OUTPUT_RAW) {
|
||||
printf("%lld %lld %.2f %lld\n", min, max, avg, count);
|
||||
}
|
||||
}
|
||||
|
||||
#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
|
||||
#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
|
||||
static void latencyMode(void) {
|
||||
@@ -1472,6 +1501,14 @@ static void latencyMode(void) {
|
||||
double avg;
|
||||
long long history_start = mstime();
|
||||
|
||||
/* Set a default for the interval in case of --latency option
|
||||
* with --raw, --csv or when it is redirected to non tty. */
|
||||
if (config.interval == 0) {
|
||||
config.interval = 1000;
|
||||
} else {
|
||||
config.interval /= 1000; /* We need to convert to milliseconds. */
|
||||
}
|
||||
|
||||
if (!context) exit(1);
|
||||
while(1) {
|
||||
start = mstime();
|
||||
@@ -1492,9 +1529,19 @@ static void latencyMode(void) {
|
||||
tot += latency;
|
||||
avg = (double) tot/count;
|
||||
}
|
||||
printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)",
|
||||
min, max, avg, count);
|
||||
fflush(stdout);
|
||||
|
||||
if (config.output == OUTPUT_STANDARD) {
|
||||
printf("\x1b[0G\x1b[2K"); /* Clear the line. */
|
||||
latencyModePrint(min,max,avg,count);
|
||||
} else {
|
||||
if (config.latency_history) {
|
||||
latencyModePrint(min,max,avg,count);
|
||||
} else if (mstime()-history_start > config.interval) {
|
||||
latencyModePrint(min,max,avg,count);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.latency_history && mstime()-history_start > history_interval)
|
||||
{
|
||||
printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
|
||||
@@ -2015,8 +2062,13 @@ static void getKeyTypes(redisReply *keys, int *types) {
|
||||
keys->element[i]->str, context->err, context->errstr);
|
||||
exit(1);
|
||||
} else if(reply->type != REDIS_REPLY_STATUS) {
|
||||
fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
|
||||
reply->type, keys->element[i]->str);
|
||||
if(reply->type == REDIS_REPLY_ERROR) {
|
||||
fprintf(stderr, "TYPE returned an error: %s\n", reply->str);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"Invalid reply type (%d) for TYPE on key '%s'!\n",
|
||||
reply->type, keys->element[i]->str);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -2602,11 +2654,6 @@ int main(int argc, char **argv) {
|
||||
argc -= firstarg;
|
||||
argv += firstarg;
|
||||
|
||||
/* Initialize the help and, if possible, use the COMMAND command in order
|
||||
* to retrieve missing entries. */
|
||||
cliInitHelp();
|
||||
cliIntegrateHelp();
|
||||
|
||||
/* Latency mode */
|
||||
if (config.latency_mode) {
|
||||
if (cliConnect(0) == REDIS_ERR) exit(1);
|
||||
|
||||
@@ -41,7 +41,9 @@
|
||||
#include <unistd.h> /* for _exit() */
|
||||
|
||||
#define assert(_e) ((_e)?(void)0 : (_serverAssert(#_e,__FILE__,__LINE__),_exit(1)))
|
||||
#define panic(...) _serverPanic(__FILE__,__LINE__,__VA_ARGS__),_exit(1)
|
||||
|
||||
void _serverAssert(char *estr, char *file, int line);
|
||||
void _serverPanic(const char *file, int line, const char *msg, ...);
|
||||
|
||||
#endif
|
||||
|
||||
+16
-2
@@ -91,7 +91,7 @@ typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv
|
||||
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
|
||||
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
|
||||
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
|
||||
typedef size_t (*RedisModuleTypeMemUsageFunc)(void *value);
|
||||
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
|
||||
typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
||||
|
||||
@@ -102,7 +102,7 @@ typedef struct RedisModuleTypeMethods {
|
||||
RedisModuleTypeSaveFunc rdb_save;
|
||||
RedisModuleTypeRewriteFunc aof_rewrite;
|
||||
RedisModuleTypeMemUsageFunc mem_usage;
|
||||
RedisModuleTypeRewriteFunc digest;
|
||||
RedisModuleTypeDigestFunc digest;
|
||||
RedisModuleTypeFreeFunc free;
|
||||
} RedisModuleTypeMethods;
|
||||
|
||||
@@ -214,6 +214,13 @@ int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ct
|
||||
void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx);
|
||||
int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc);
|
||||
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
|
||||
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc);
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
|
||||
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
|
||||
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_DigestEndSequence)(RedisModuleDigest *md);
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
||||
@@ -322,6 +329,13 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(GetBlockedClientPrivateData);
|
||||
REDISMODULE_GET_API(AbortBlock);
|
||||
REDISMODULE_GET_API(Milliseconds);
|
||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||
REDISMODULE_GET_API(FreeThreadSafeContext);
|
||||
REDISMODULE_GET_API(ThreadSafeContextLock);
|
||||
REDISMODULE_GET_API(ThreadSafeContextUnlock);
|
||||
REDISMODULE_GET_API(DigestAddStringBuffer);
|
||||
REDISMODULE_GET_API(DigestAddLongLong);
|
||||
REDISMODULE_GET_API(DigestEndSequence);
|
||||
|
||||
RedisModule_SetModuleAttribs(ctx,name,ver,apiver);
|
||||
return REDISMODULE_OK;
|
||||
|
||||
+37
-16
@@ -122,7 +122,7 @@ void freeReplicationBacklog(void) {
|
||||
/* Add data to the replication backlog.
|
||||
* This function also increments the global replication offset stored at
|
||||
* server.master_repl_offset, because there is no case where we want to feed
|
||||
* the backlog without incrementing the buffer. */
|
||||
* the backlog without incrementing the offset. */
|
||||
void feedReplicationBacklog(void *ptr, size_t len) {
|
||||
unsigned char *p = ptr;
|
||||
|
||||
@@ -1078,6 +1078,7 @@ void replicationCreateMasterClient(int fd, int dbid) {
|
||||
server.master->flags |= CLIENT_MASTER;
|
||||
server.master->authenticated = 1;
|
||||
server.master->reploff = server.master_initial_offset;
|
||||
server.master->read_reploff = server.master->reploff;
|
||||
memcpy(server.master->replid, server.master_replid,
|
||||
sizeof(server.master_replid));
|
||||
/* If master offset is set to -1, this master is old and is not
|
||||
@@ -1087,6 +1088,18 @@ void replicationCreateMasterClient(int fd, int dbid) {
|
||||
if (dbid != -1) selectDb(server.master,dbid);
|
||||
}
|
||||
|
||||
void restartAOF() {
|
||||
int retry = 10;
|
||||
while (retry-- && startAppendOnly() == C_ERR) {
|
||||
serverLog(LL_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (!retry) {
|
||||
serverLog(LL_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Asynchronously read the SYNC payload we receive from a master */
|
||||
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
|
||||
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
@@ -1228,12 +1241,17 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
|
||||
if (eof_reached) {
|
||||
int aof_is_enabled = server.aof_state != AOF_OFF;
|
||||
|
||||
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
|
||||
serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno));
|
||||
cancelReplicationHandshake();
|
||||
return;
|
||||
}
|
||||
serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
|
||||
/* We need to stop any AOFRW fork before flusing and parsing
|
||||
* RDB, otherwise we'll create a copy-on-write disaster. */
|
||||
if(aof_is_enabled) stopAppendOnly();
|
||||
signalFlushedDb(-1);
|
||||
emptyDb(
|
||||
-1,
|
||||
@@ -1249,6 +1267,9 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
if (rdbLoad(server.rdb_filename,&rsi) != C_OK) {
|
||||
serverLog(LL_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
|
||||
cancelReplicationHandshake();
|
||||
/* Re-enable the AOF if we disabled it earlier, in order to restore
|
||||
* the original configuration. */
|
||||
if (aof_is_enabled) restartAOF();
|
||||
return;
|
||||
}
|
||||
/* Final setup of the connected slave <- master link */
|
||||
@@ -1272,21 +1293,8 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
/* Restart the AOF subsystem now that we finished the sync. This
|
||||
* will trigger an AOF rewrite, and when done will start appending
|
||||
* to the new file. */
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
int retry = 10;
|
||||
|
||||
stopAppendOnly();
|
||||
while (retry-- && startAppendOnly() == C_ERR) {
|
||||
serverLog(LL_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
|
||||
sleep(1);
|
||||
}
|
||||
if (!retry) {
|
||||
serverLog(LL_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (aof_is_enabled) restartAOF();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
@@ -1561,7 +1569,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
|
||||
* establish a connection with the master. */
|
||||
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
char tmpfile[256], *err = NULL;
|
||||
int dfd, maxtries = 5;
|
||||
int dfd = -1, maxtries = 5;
|
||||
int sockerr = 0, psync_result;
|
||||
socklen_t errlen = sizeof(sockerr);
|
||||
UNUSED(el);
|
||||
@@ -1825,6 +1833,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
|
||||
error:
|
||||
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
|
||||
if (dfd != -1) close(dfd);
|
||||
close(fd);
|
||||
server.repl_transfer_s = -1;
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
@@ -2110,6 +2119,18 @@ void replicationCacheMaster(client *c) {
|
||||
/* Unlink the client from the server structures. */
|
||||
unlinkClient(c);
|
||||
|
||||
/* Reset the master client so that's ready to accept new commands:
|
||||
* we want to discard te non processed query buffers and non processed
|
||||
* offsets, including pending transactions, already populated arguments,
|
||||
* pending outputs to the master. */
|
||||
sdsclear(server.master->querybuf);
|
||||
sdsclear(server.master->pending_querybuf);
|
||||
server.master->read_reploff = server.master->reploff;
|
||||
if (c->flags & CLIENT_MULTI) discardTransaction(c);
|
||||
listEmpty(c->reply);
|
||||
c->bufpos = 0;
|
||||
resetClient(c);
|
||||
|
||||
/* Save the master. Server.master will be set to null later by
|
||||
* replicationHandleMasterDisconnection(). */
|
||||
server.cached_master = server.master;
|
||||
|
||||
+1
-2
@@ -900,7 +900,6 @@ void scriptingInit(int setup) {
|
||||
server.lua_caller = NULL;
|
||||
server.lua_timedout = 0;
|
||||
server.lua_always_replicate_commands = 0; /* Only DEBUG can change it.*/
|
||||
server.lua_time_limit = LUA_SCRIPT_TIME_LIMIT;
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
@@ -2272,7 +2271,7 @@ ldbLog(sdsnew("[e]eval <code> Execute some Lua code (in a different callfr
|
||||
ldbLog(sdsnew("[r]edis <cmd> Execute a Redis command."));
|
||||
ldbLog(sdsnew("[m]axlen [len] Trim logged Redis replies and Lua var dumps to len."));
|
||||
ldbLog(sdsnew(" Specifying zero as <len> means unlimited."));
|
||||
ldbLog(sdsnew("[a]abort Stop the execution of the script. In sync"));
|
||||
ldbLog(sdsnew("[a]bort Stop the execution of the script. In sync"));
|
||||
ldbLog(sdsnew(" mode dataset changes will be retained."));
|
||||
ldbLog(sdsnew(""));
|
||||
ldbLog(sdsnew("Debugger functions you can call from Lua scripts:"));
|
||||
|
||||
+1
-1
@@ -379,7 +379,7 @@ void sentinelSimFailureCrash(void);
|
||||
|
||||
/* ========================= Dictionary types =============================== */
|
||||
|
||||
unsigned int dictSdsHash(const void *key);
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
void releaseSentinelRedisInstance(sentinelRedisInstance *ri);
|
||||
|
||||
|
||||
+145
-40
@@ -32,6 +32,7 @@
|
||||
#include "slowlog.h"
|
||||
#include "bio.h"
|
||||
#include "latency.h"
|
||||
#include "atomicvar.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
@@ -68,7 +69,8 @@ double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
/*================================= Globals ================================= */
|
||||
|
||||
/* Global vars */
|
||||
struct redisServer server; /* server global state */
|
||||
struct redisServer server; /* Server global state */
|
||||
volatile unsigned long lru_clock; /* Server global current LRU time. */
|
||||
|
||||
/* Our command table.
|
||||
*
|
||||
@@ -142,7 +144,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"incr",incrCommand,2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"mget",mgetCommand,-2,"r",0,NULL,1,-1,1,0,0},
|
||||
{"mget",mgetCommand,-2,"rF",0,NULL,1,-1,1,0,0},
|
||||
{"rpush",rpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"lpush",lpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"rpushx",rpushxCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
@@ -150,7 +152,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0},
|
||||
{"rpop",rpopCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"lpop",lpopCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"brpop",brpopCommand,-3,"ws",0,NULL,1,1,1,0,0},
|
||||
{"brpop",brpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
|
||||
{"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0},
|
||||
{"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
|
||||
{"llen",llenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
@@ -196,11 +198,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"hset",hsetCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"hmset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hmget",hmgetCommand,-3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
@@ -288,8 +290,10 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"wait",waitCommand,3,"s",0,NULL,0,0,0,0,0},
|
||||
{"command",commandCommand,0,"lt",0,NULL,0,0,0,0,0},
|
||||
{"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0},
|
||||
{"georadius",georadiusCommand,-6,"w",0,NULL,1,1,1,0,0},
|
||||
{"georadiusbymember",georadiusByMemberCommand,-5,"w",0,NULL,1,1,1,0,0},
|
||||
{"georadius",georadiusCommand,-6,"w",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadius_ro",georadiusroCommand,-6,"r",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadiusbymember",georadiusbymemberCommand,-5,"w",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"georadiusbymember_ro",georadiusbymemberroCommand,-5,"r",0,georadiusGetKeys,1,1,1,0,0},
|
||||
{"geohash",geohashCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"geopos",geoposCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"geodist",geodistCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
@@ -482,16 +486,16 @@ int dictObjKeyCompare(void *privdata, const void *key1,
|
||||
return dictSdsKeyCompare(privdata,o1->ptr,o2->ptr);
|
||||
}
|
||||
|
||||
unsigned int dictObjHash(const void *key) {
|
||||
uint64_t dictObjHash(const void *key) {
|
||||
const robj *o = key;
|
||||
return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr));
|
||||
}
|
||||
|
||||
unsigned int dictSdsHash(const void *key) {
|
||||
uint64_t dictSdsHash(const void *key) {
|
||||
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
|
||||
}
|
||||
|
||||
unsigned int dictSdsCaseHash(const void *key) {
|
||||
uint64_t dictSdsCaseHash(const void *key) {
|
||||
return dictGenCaseHashFunction((unsigned char*)key, sdslen((char*)key));
|
||||
}
|
||||
|
||||
@@ -513,7 +517,7 @@ int dictEncObjKeyCompare(void *privdata, const void *key1,
|
||||
return cmp;
|
||||
}
|
||||
|
||||
unsigned int dictEncObjHash(const void *key) {
|
||||
uint64_t dictEncObjHash(const void *key) {
|
||||
robj *o = (robj*) key;
|
||||
|
||||
if (sdsEncodedObject(o)) {
|
||||
@@ -526,7 +530,7 @@ unsigned int dictEncObjHash(const void *key) {
|
||||
len = ll2string(buf,32,(long)o->ptr);
|
||||
return dictGenHashFunction((unsigned char*)buf, len);
|
||||
} else {
|
||||
unsigned int hash;
|
||||
uint64_t hash;
|
||||
|
||||
o = getDecodedObject(o);
|
||||
hash = dictGenHashFunction(o->ptr, sdslen((sds)o->ptr));
|
||||
@@ -870,8 +874,15 @@ void clientsCron(void) {
|
||||
void databasesCron(void) {
|
||||
/* Expire keys by random sampling. Not required for slaves
|
||||
* as master will synthesize DELs for us. */
|
||||
if (server.active_expire_enabled && server.masterhost == NULL)
|
||||
if (server.active_expire_enabled && server.masterhost == NULL) {
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
|
||||
} else if (server.masterhost != NULL) {
|
||||
expireSlaveKeys();
|
||||
}
|
||||
|
||||
/* Defrag keys gradually. */
|
||||
if (server.active_defrag_enabled)
|
||||
activeDefragCycle();
|
||||
|
||||
/* Perform hash tables rehashing if needed, but only if there are no
|
||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||
@@ -914,7 +925,8 @@ void databasesCron(void) {
|
||||
* every object access, and accuracy is not needed. To access a global var is
|
||||
* a lot faster than calling time(NULL) */
|
||||
void updateCachedTime(void) {
|
||||
server.unixtime = time(NULL);
|
||||
time_t unixtime = time(NULL);
|
||||
atomicSet(server.unixtime,unixtime);
|
||||
server.mstime = mstime();
|
||||
}
|
||||
|
||||
@@ -969,7 +981,8 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
*
|
||||
* Note that you can change the resolution altering the
|
||||
* LRU_CLOCK_RESOLUTION define. */
|
||||
server.lruclock = getLRUClock();
|
||||
unsigned long lruclock = getLRUClock();
|
||||
atomicSet(server.lruclock,lruclock);
|
||||
|
||||
/* Record the max memory used since the server was started. */
|
||||
if (zmalloc_used_memory() > server.stat_peak_memory)
|
||||
@@ -1209,6 +1222,19 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
|
||||
/* Handle writes with pending output buffers. */
|
||||
handleClientsWithPendingWrites();
|
||||
|
||||
/* Before we are going to sleep, let the threads access the dataset by
|
||||
* releasing the GIL. Redis main thread will not touch anything at this
|
||||
* time. */
|
||||
if (moduleCount()) moduleReleaseGIL();
|
||||
}
|
||||
|
||||
/* This function is called immadiately after the event loop multiplexing
|
||||
* API returned, and the control is going to soon return to Redis by invoking
|
||||
* the different events callbacks. */
|
||||
void afterSleep(struct aeEventLoop *eventLoop) {
|
||||
UNUSED(eventLoop);
|
||||
if (moduleCount()) moduleAcquireGIL();
|
||||
}
|
||||
|
||||
/* =========================== Server initialization ======================== */
|
||||
@@ -1248,7 +1274,7 @@ void createSharedObjects(void) {
|
||||
shared.masterdownerr = createObject(OBJ_STRING,sdsnew(
|
||||
"-MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\r\n"));
|
||||
shared.bgsaveerr = createObject(OBJ_STRING,sdsnew(
|
||||
"-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.\r\n"));
|
||||
"-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.\r\n"));
|
||||
shared.roslaveerr = createObject(OBJ_STRING,sdsnew(
|
||||
"-READONLY You can't write against a read only slave.\r\n"));
|
||||
shared.noautherr = createObject(OBJ_STRING,sdsnew(
|
||||
@@ -1308,6 +1334,10 @@ void createSharedObjects(void) {
|
||||
void initServerConfig(void) {
|
||||
int j;
|
||||
|
||||
pthread_mutex_init(&server.next_client_id_mutex,NULL);
|
||||
pthread_mutex_init(&server.lruclock_mutex,NULL);
|
||||
pthread_mutex_init(&server.unixtime_mutex,NULL);
|
||||
|
||||
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
|
||||
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
|
||||
changeReplicationId();
|
||||
@@ -1329,6 +1359,12 @@ void initServerConfig(void) {
|
||||
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
||||
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
||||
server.active_expire_enabled = 1;
|
||||
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
||||
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
||||
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
|
||||
server.active_defrag_threshold_upper = CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER;
|
||||
server.active_defrag_cycle_min = CONFIG_DEFAULT_DEFRAG_CYCLE_MIN;
|
||||
server.active_defrag_cycle_max = CONFIG_DEFAULT_DEFRAG_CYCLE_MAX;
|
||||
server.client_max_querybuf_len = PROTO_MAX_QUERYBUF_LEN;
|
||||
server.saveparams = NULL;
|
||||
server.loading = 0;
|
||||
@@ -1365,6 +1401,7 @@ void initServerConfig(void) {
|
||||
server.rdb_checksum = CONFIG_DEFAULT_RDB_CHECKSUM;
|
||||
server.stop_writes_on_bgsave_err = CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR;
|
||||
server.activerehashing = CONFIG_DEFAULT_ACTIVE_REHASHING;
|
||||
server.active_defrag_running = 0;
|
||||
server.notify_keyspace_events = 0;
|
||||
server.maxclients = CONFIG_DEFAULT_MAX_CLIENTS;
|
||||
server.bpop_blocked_clients = 0;
|
||||
@@ -1397,8 +1434,11 @@ void initServerConfig(void) {
|
||||
server.lazyfree_lazy_eviction = CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION;
|
||||
server.lazyfree_lazy_expire = CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE;
|
||||
server.lazyfree_lazy_server_del = CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL;
|
||||
server.always_show_logo = CONFIG_DEFAULT_ALWAYS_SHOW_LOGO;
|
||||
server.lua_time_limit = LUA_SCRIPT_TIME_LIMIT;
|
||||
|
||||
server.lruclock = getLRUClock();
|
||||
unsigned int lruclock = getLRUClock();
|
||||
atomicSet(server.lruclock,lruclock);
|
||||
resetServerSaveParams();
|
||||
|
||||
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
|
||||
@@ -1462,6 +1502,8 @@ void initServerConfig(void) {
|
||||
server.rpopCommand = lookupCommandByCString("rpop");
|
||||
server.sremCommand = lookupCommandByCString("srem");
|
||||
server.execCommand = lookupCommandByCString("exec");
|
||||
server.expireCommand = lookupCommandByCString("expire");
|
||||
server.pexpireCommand = lookupCommandByCString("pexpire");
|
||||
|
||||
/* Slow log */
|
||||
server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
|
||||
@@ -1513,7 +1555,11 @@ int restartServer(int flags, mstime_t delay) {
|
||||
|
||||
/* Close all file descriptors, with the exception of stdin, stdout, strerr
|
||||
* which are useful if we restart a Redis server which is not daemonized. */
|
||||
for (j = 3; j < (int)server.maxclients + 1024; j++) close(j);
|
||||
for (j = 3; j < (int)server.maxclients + 1024; j++) {
|
||||
/* Test the descriptor validity before closing it, otherwise
|
||||
* Valgrind issues a warning on close(). */
|
||||
if (fcntl(j,F_GETFD) != -1) close(j);
|
||||
}
|
||||
|
||||
/* Execute the server with the original command line. */
|
||||
if (delay) usleep(delay*1000);
|
||||
@@ -1572,9 +1618,12 @@ void adjustOpenFilesLimit(void) {
|
||||
if (bestlimit < oldlimit) bestlimit = oldlimit;
|
||||
|
||||
if (bestlimit < maxfiles) {
|
||||
int old_maxclients = server.maxclients;
|
||||
unsigned int old_maxclients = server.maxclients;
|
||||
server.maxclients = bestlimit-CONFIG_MIN_RESERVED_FDS;
|
||||
if (server.maxclients < 1) {
|
||||
/* maxclients is unsigned so may overflow: in order
|
||||
* to check if maxclients is now logically less than 1
|
||||
* we test indirectly via bestlimit. */
|
||||
if (bestlimit <= CONFIG_MIN_RESERVED_FDS) {
|
||||
serverLog(LL_WARNING,"Your current 'ulimit -n' "
|
||||
"of %llu is not enough for the server to start. "
|
||||
"Please increase your open file limit to at least "
|
||||
@@ -1711,6 +1760,10 @@ void resetServerStats(void) {
|
||||
server.stat_evictedkeys = 0;
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_active_defrag_hits = 0;
|
||||
server.stat_active_defrag_misses = 0;
|
||||
server.stat_active_defrag_key_hits = 0;
|
||||
server.stat_active_defrag_key_misses = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.stat_fork_rate = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
@@ -1759,6 +1812,12 @@ void initServer(void) {
|
||||
createSharedObjects();
|
||||
adjustOpenFilesLimit();
|
||||
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
|
||||
if (server.el == NULL) {
|
||||
serverLog(LL_WARNING,
|
||||
"Failed creating the event loop. Error message: '%s'",
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
|
||||
|
||||
/* Open the TCP listening socket for the user commands. */
|
||||
@@ -1848,6 +1907,16 @@ void initServer(void) {
|
||||
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
|
||||
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
|
||||
|
||||
|
||||
/* Register a readable event for the pipe used to awake the event loop
|
||||
* when a blocked client in a module needs attention. */
|
||||
if (aeCreateFileEvent(server.el, server.module_blocked_pipe[0], AE_READABLE,
|
||||
moduleBlockedClientPipeReadable,NULL) == AE_ERR) {
|
||||
serverPanic(
|
||||
"Error registering the readable event for the module "
|
||||
"blocked clients subsystem.");
|
||||
}
|
||||
|
||||
/* Open the AOF file if needed. */
|
||||
if (server.aof_state == AOF_ON) {
|
||||
server.aof_fd = open(server.aof_filename,
|
||||
@@ -2153,7 +2222,7 @@ void call(client *c, int flags) {
|
||||
char *latency_event = (c->cmd->flags & CMD_FAST) ?
|
||||
"fast-command" : "command";
|
||||
latencyAddSampleIfNeeded(latency_event,duration/1000);
|
||||
slowlogPushEntryIfNeeded(c->argv,c->argc,duration);
|
||||
slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
|
||||
}
|
||||
if (flags & CMD_CALL_STATS) {
|
||||
c->lastcmd->microseconds += duration;
|
||||
@@ -2757,6 +2826,8 @@ sds genRedisInfoString(char *section) {
|
||||
call_uname = 0;
|
||||
}
|
||||
|
||||
unsigned int lruclock;
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
info = sdscatprintf(info,
|
||||
"# Server\r\n"
|
||||
"redis_version:%s\r\n"
|
||||
@@ -2767,6 +2838,7 @@ sds genRedisInfoString(char *section) {
|
||||
"os:%s %s %s\r\n"
|
||||
"arch_bits:%d\r\n"
|
||||
"multiplexing_api:%s\r\n"
|
||||
"atomicvar_api:%s\r\n"
|
||||
"gcc_version:%d.%d.%d\r\n"
|
||||
"process_id:%ld\r\n"
|
||||
"run_id:%s\r\n"
|
||||
@@ -2785,6 +2857,7 @@ sds genRedisInfoString(char *section) {
|
||||
name.sysname, name.release, name.machine,
|
||||
server.arch_bits,
|
||||
aeGetApiName(),
|
||||
REDIS_ATOMIC_API,
|
||||
#ifdef __GNUC__
|
||||
__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__,
|
||||
#else
|
||||
@@ -2796,7 +2869,7 @@ sds genRedisInfoString(char *section) {
|
||||
(intmax_t)uptime,
|
||||
(intmax_t)(uptime/(3600*24)),
|
||||
server.hz,
|
||||
(unsigned long) server.lruclock,
|
||||
(unsigned long) lruclock,
|
||||
server.executable ? server.executable : "",
|
||||
server.configfile ? server.configfile : "");
|
||||
}
|
||||
@@ -2866,6 +2939,7 @@ sds genRedisInfoString(char *section) {
|
||||
"maxmemory_policy:%s\r\n"
|
||||
"mem_fragmentation_ratio:%.2f\r\n"
|
||||
"mem_allocator:%s\r\n"
|
||||
"active_defrag_running:%d\r\n"
|
||||
"lazyfree_pending_objects:%zu\r\n",
|
||||
zmalloc_used,
|
||||
hmem,
|
||||
@@ -2887,6 +2961,7 @@ sds genRedisInfoString(char *section) {
|
||||
evict_policy,
|
||||
mh->fragmentation,
|
||||
ZMALLOC_LIB,
|
||||
server.active_defrag_running,
|
||||
lazyfreeGetPendingObjectsCount()
|
||||
);
|
||||
freeMemoryOverheadData(mh);
|
||||
@@ -3005,7 +3080,12 @@ sds genRedisInfoString(char *section) {
|
||||
"pubsub_channels:%ld\r\n"
|
||||
"pubsub_patterns:%lu\r\n"
|
||||
"latest_fork_usec:%lld\r\n"
|
||||
"migrate_cached_sockets:%ld\r\n",
|
||||
"migrate_cached_sockets:%ld\r\n"
|
||||
"slave_expires_tracked_keys:%zu\r\n"
|
||||
"active_defrag_hits:%lld\r\n"
|
||||
"active_defrag_misses:%lld\r\n"
|
||||
"active_defrag_key_hits:%lld\r\n"
|
||||
"active_defrag_key_misses:%lld\r\n",
|
||||
server.stat_numconnections,
|
||||
server.stat_numcommands,
|
||||
getInstantaneousMetric(STATS_METRIC_COMMAND),
|
||||
@@ -3024,7 +3104,12 @@ sds genRedisInfoString(char *section) {
|
||||
dictSize(server.pubsub_channels),
|
||||
listLength(server.pubsub_patterns),
|
||||
server.stat_fork_time,
|
||||
dictSize(server.migrate_cached_sockets));
|
||||
dictSize(server.migrate_cached_sockets),
|
||||
getSlaveKeyWithExpireCount(),
|
||||
server.stat_active_defrag_hits,
|
||||
server.stat_active_defrag_misses,
|
||||
server.stat_active_defrag_key_hits,
|
||||
server.stat_active_defrag_key_misses);
|
||||
}
|
||||
|
||||
/* Replication */
|
||||
@@ -3327,15 +3412,18 @@ void redisAsciiArt(void) {
|
||||
else if (server.sentinel_mode) mode = "sentinel";
|
||||
else mode = "standalone";
|
||||
|
||||
if (server.syslog_enabled) {
|
||||
/* Show the ASCII logo if: log file is stdout AND stdout is a
|
||||
* tty AND syslog logging is disabled. Also show logo if the user
|
||||
* forced us to do so via redis.conf. */
|
||||
int show_logo = ((!server.syslog_enabled &&
|
||||
server.logfile[0] == '\0' &&
|
||||
isatty(fileno(stdout))) ||
|
||||
server.always_show_logo);
|
||||
|
||||
if (!show_logo) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Redis %s (%s/%d) %s bit, %s mode, port %d, pid %ld ready to start.",
|
||||
REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
strtol(redisGitDirty(),NULL,10) > 0,
|
||||
(sizeof(long) == 8) ? "64" : "32",
|
||||
mode, server.port,
|
||||
(long) getpid()
|
||||
"Running mode=%s, port=%d.",
|
||||
mode, server.port
|
||||
);
|
||||
} else {
|
||||
snprintf(buf,1024*16,ascii_logo,
|
||||
@@ -3598,11 +3686,12 @@ int main(int argc, char **argv) {
|
||||
spt_init(argc, argv);
|
||||
#endif
|
||||
setlocale(LC_COLLATE,"");
|
||||
zmalloc_enable_thread_safeness();
|
||||
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
|
||||
srand(time(NULL)^getpid());
|
||||
gettimeofday(&tv,NULL);
|
||||
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
|
||||
char hashseed[16];
|
||||
getRandomHexChars(hashseed,sizeof(hashseed));
|
||||
dictSetHashFunctionSeed((uint8_t*)hashseed);
|
||||
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
||||
initServerConfig();
|
||||
moduleInitModulesSystem();
|
||||
@@ -3622,11 +3711,13 @@ int main(int argc, char **argv) {
|
||||
initSentinel();
|
||||
}
|
||||
|
||||
/* Check if we need to start in redis-check-rdb mode. We just execute
|
||||
/* Check if we need to start in redis-check-rdb/aof mode. We just execute
|
||||
* the program main. However the program is part of the Redis executable
|
||||
* so that we can easily execute an RDB check on loading errors. */
|
||||
if (strstr(argv[0],"redis-check-rdb") != NULL)
|
||||
redis_check_rdb_main(argc,argv);
|
||||
redis_check_rdb_main(argc,argv,NULL);
|
||||
else if (strstr(argv[0],"redis-check-aof") != NULL)
|
||||
redis_check_aof_main(argc,argv);
|
||||
|
||||
if (argc >= 2) {
|
||||
j = 1; /* First option to parse in argv[] */
|
||||
@@ -3692,8 +3783,21 @@ int main(int argc, char **argv) {
|
||||
resetServerSaveParams();
|
||||
loadServerConfig(configfile,options);
|
||||
sdsfree(options);
|
||||
} else {
|
||||
}
|
||||
|
||||
serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
|
||||
serverLog(LL_WARNING,
|
||||
"Redis version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
|
||||
REDIS_VERSION,
|
||||
(sizeof(long) == 8) ? 64 : 32,
|
||||
redisGitSHA1(),
|
||||
strtol(redisGitDirty(),NULL,10) > 0,
|
||||
(int)getpid());
|
||||
|
||||
if (argc == 1) {
|
||||
serverLog(LL_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
|
||||
} else {
|
||||
serverLog(LL_WARNING, "Configuration loaded");
|
||||
}
|
||||
|
||||
server.supervised = redisIsSupervised(server.supervised_mode);
|
||||
@@ -3708,7 +3812,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
if (!server.sentinel_mode) {
|
||||
/* Things not needed when running in Sentinel mode. */
|
||||
serverLog(LL_WARNING,"Server started, Redis version " REDIS_VERSION);
|
||||
serverLog(LL_WARNING,"Server initialized");
|
||||
#ifdef __linux__
|
||||
linuxMemoryWarnings();
|
||||
#endif
|
||||
@@ -3723,7 +3827,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
if (server.ipfd_count > 0)
|
||||
serverLog(LL_NOTICE,"The server is now ready to accept connections on port %d", server.port);
|
||||
serverLog(LL_NOTICE,"Ready to accept connections");
|
||||
if (server.sofd > 0)
|
||||
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
||||
} else {
|
||||
@@ -3736,6 +3840,7 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
aeSetBeforeSleepProc(server.el,beforeSleep);
|
||||
aeSetAfterSleepProc(server.el,afterSleep);
|
||||
aeMain(server.el);
|
||||
aeDeleteEventLoop(server.el);
|
||||
return 0;
|
||||
|
||||
+93
-19
@@ -63,7 +63,9 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#include "util.h" /* Misc functions useful in many places */
|
||||
#include "latency.h" /* Latency monitor API */
|
||||
#include "sparkline.h" /* ASCII graphs API */
|
||||
#include "quicklist.h"
|
||||
#include "quicklist.h" /* Lists are encoded as linked lists of
|
||||
N-elements flat arrays */
|
||||
#include "rax.h" /* Radix tree */
|
||||
|
||||
/* Following includes allow test functions to be called from Redis main() */
|
||||
#include "zipmap.h"
|
||||
@@ -151,6 +153,13 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#define CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION 0
|
||||
#define CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE 0
|
||||
#define CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL 0
|
||||
#define CONFIG_DEFAULT_ALWAYS_SHOW_LOGO 0
|
||||
#define CONFIG_DEFAULT_ACTIVE_DEFRAG 0
|
||||
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER 10 /* don't defrag when fragmentation is below 10% */
|
||||
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER 100 /* maximum defrag force at 100% fragmentation */
|
||||
#define CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES (100<<20) /* don't defrag if frag overhead is below 100mb */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 25 /* 25% CPU min (at lower threshold) */
|
||||
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@@ -428,7 +437,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
/* We can print the stacktrace, so our assert is defined this way: */
|
||||
#define serverAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_serverAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1)))
|
||||
#define serverAssert(_e) ((_e)?(void)0 : (_serverAssert(#_e,__FILE__,__LINE__),_exit(1)))
|
||||
#define serverPanic(_e) _serverPanic(#_e,__FILE__,__LINE__),_exit(1)
|
||||
#define serverPanic(...) _serverPanic(__FILE__,__LINE__,__VA_ARGS__),_exit(1)
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Data types
|
||||
@@ -476,7 +485,7 @@ typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
|
||||
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
|
||||
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
|
||||
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
|
||||
typedef size_t (*moduleTypeMemUsageFunc)(void *value);
|
||||
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
|
||||
typedef void (*moduleTypeFreeFunc)(void *value);
|
||||
|
||||
/* The module type, which is referenced in each value of a given type, defines
|
||||
@@ -521,17 +530,38 @@ typedef struct RedisModuleIO {
|
||||
rio *rio; /* Rio stream. */
|
||||
moduleType *type; /* Module type doing the operation. */
|
||||
int error; /* True if error condition happened. */
|
||||
int ver; /* Module serialization version: 1 (old),
|
||||
* 2 (current version with opcodes annotation). */
|
||||
struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
|
||||
} RedisModuleIO;
|
||||
|
||||
/* Macro to initialize an IO context. Note that the 'ver' field is populated
|
||||
* inside rdb.c according to the version of the value to load. */
|
||||
#define moduleInitIOContext(iovar,mtype,rioptr) do { \
|
||||
iovar.rio = rioptr; \
|
||||
iovar.type = mtype; \
|
||||
iovar.bytes = 0; \
|
||||
iovar.error = 0; \
|
||||
iovar.ver = 0; \
|
||||
iovar.ctx = NULL; \
|
||||
} while(0);
|
||||
|
||||
/* This is a structure used to export DEBUG DIGEST capabilities to Redis
|
||||
* modules. We want to capture both the ordered and unordered elements of
|
||||
* a data structure, so that a digest can be created in a way that correctly
|
||||
* reflects the values. See the DEBUG DIGEST command implementation for more
|
||||
* background. */
|
||||
typedef struct RedisModuleDigest {
|
||||
unsigned char o[20]; /* Ordered elements. */
|
||||
unsigned char x[20]; /* Xored elements. */
|
||||
} RedisModuleDigest;
|
||||
|
||||
/* Just start with a digest composed of all zero bytes. */
|
||||
#define moduleInitDigestContext(mdvar) do { \
|
||||
memset(mdvar.o,0,sizeof(mdvar.o)); \
|
||||
memset(mdvar.x,0,sizeof(mdvar.x)); \
|
||||
} while(0);
|
||||
|
||||
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
||||
* internally represented in multiple ways. The 'encoding' field of the object
|
||||
* is set to one of this fields for this object. */
|
||||
@@ -554,19 +584,13 @@ typedef struct RedisModuleIO {
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned encoding:4;
|
||||
unsigned lru:LRU_BITS; /* LRU time (relative to server.lruclock) or
|
||||
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
|
||||
* LFU data (least significant 8 bits frequency
|
||||
* and most significant 16 bits decreas time). */
|
||||
int refcount;
|
||||
void *ptr;
|
||||
} robj;
|
||||
|
||||
/* Macro used to obtain the current LRU clock.
|
||||
* If the current resolution is lower than the frequency we refresh the
|
||||
* LRU clock (as it should be in production servers) we return the
|
||||
* precomputed value, otherwise we need to resort to a system call. */
|
||||
#define LRU_CLOCK() ((1000/server.hz <= LRU_CLOCK_RESOLUTION) ? server.lruclock : getLRUClock())
|
||||
|
||||
/* Macro used to initialize a Redis object allocated on the stack.
|
||||
* Note that this macro is taken near the structure definition to make sure
|
||||
* we'll update it when the structure is changed, to avoid bugs like
|
||||
@@ -654,6 +678,9 @@ typedef struct client {
|
||||
redisDb *db; /* Pointer to currently SELECTed DB. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
sds querybuf; /* Buffer we use to accumulate client queries. */
|
||||
sds pending_querybuf; /* If this is a master, this buffer represents the
|
||||
yet not applied replication stream that we
|
||||
are receiving from the master. */
|
||||
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
|
||||
int argc; /* Num of arguments of current command. */
|
||||
robj **argv; /* Arguments of current command. */
|
||||
@@ -676,7 +703,8 @@ typedef struct client {
|
||||
off_t repldboff; /* Replication DB file offset. */
|
||||
off_t repldbsize; /* Replication DB file size. */
|
||||
sds replpreamble; /* Replication DB preamble. */
|
||||
long long reploff; /* Replication offset if this is our master. */
|
||||
long long read_reploff; /* Read replication offset if this is a master. */
|
||||
long long reploff; /* Applied replication offset if this is a master. */
|
||||
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
|
||||
long long repl_ack_time;/* Replication ack time, if this is a slave. */
|
||||
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
|
||||
@@ -853,9 +881,10 @@ struct redisServer {
|
||||
dict *commands; /* Command table */
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
aeEventLoop *el;
|
||||
unsigned lruclock:LRU_BITS; /* Clock for LRU eviction */
|
||||
unsigned int lruclock; /* Clock for LRU eviction */
|
||||
int shutdown_asap; /* SHUTDOWN needed ASAP */
|
||||
int activerehashing; /* Incremental rehash in serverCron() */
|
||||
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
|
||||
char *requirepass; /* Pass for AUTH command, or NULL */
|
||||
char *pidfile; /* PID file path */
|
||||
int arch_bits; /* 32 or 64 depending on sizeof(long) */
|
||||
@@ -863,9 +892,13 @@ struct redisServer {
|
||||
char runid[CONFIG_RUN_ID_SIZE+1]; /* ID always different at every exec. */
|
||||
int sentinel_mode; /* True if this instance is a Sentinel. */
|
||||
size_t initial_memory_usage; /* Bytes used after initialization. */
|
||||
int always_show_logo; /* Show logo even for non-stdout logging. */
|
||||
/* Modules */
|
||||
dict *moduleapi; /* Exported APIs dictionary for modules. */
|
||||
list *loadmodule_queue; /* List of modules to load at startup. */
|
||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||
client blocked on a module command needs
|
||||
to be processed. */
|
||||
/* Networking */
|
||||
int port; /* TCP listening port */
|
||||
int tcp_backlog; /* TCP listen() backlog */
|
||||
@@ -897,7 +930,8 @@ struct redisServer {
|
||||
off_t loading_process_events_interval_bytes;
|
||||
/* Fast pointers to often looked up command */
|
||||
struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
|
||||
*rpopCommand, *sremCommand, *execCommand;
|
||||
*rpopCommand, *sremCommand, *execCommand, *expireCommand,
|
||||
*pexpireCommand;
|
||||
/* Fields used only for stats */
|
||||
time_t stat_starttime; /* Server start time */
|
||||
long long stat_numcommands; /* Number of processed commands */
|
||||
@@ -906,6 +940,10 @@ struct redisServer {
|
||||
long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */
|
||||
long long stat_keyspace_hits; /* Number of successful lookups of keys */
|
||||
long long stat_keyspace_misses; /* Number of failed lookups of keys */
|
||||
long long stat_active_defrag_hits; /* number of allocations moved */
|
||||
long long stat_active_defrag_misses; /* number of allocations scanned but not moved */
|
||||
long long stat_active_defrag_key_hits; /* number of keys with moved allocations */
|
||||
long long stat_active_defrag_key_misses;/* number of keys scanned and not moved */
|
||||
size_t stat_peak_memory; /* Max used memory record */
|
||||
long long stat_fork_time; /* Time needed to perform latest fork() */
|
||||
double stat_fork_rate; /* Fork rate in GB/sec. */
|
||||
@@ -935,6 +973,12 @@ struct redisServer {
|
||||
int maxidletime; /* Client timeout in seconds */
|
||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||
int active_defrag_enabled;
|
||||
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_upper; /* maximum percentage of fragmentation at which we use maximum effort */
|
||||
int active_defrag_cycle_min; /* minimal effort for defrag in CPU percentage */
|
||||
int active_defrag_cycle_max; /* maximal effort for defrag in CPU percentage */
|
||||
size_t client_max_querybuf_len; /* Limit for client query buffer length */
|
||||
int dbnum; /* Total number of configured DBs */
|
||||
int supervised; /* 1 if supervised, 0 otherwise. */
|
||||
@@ -1149,6 +1193,12 @@ struct redisServer {
|
||||
int watchdog_period; /* Software watchdog period in ms. 0 = off */
|
||||
/* System hardware info */
|
||||
size_t system_memory_size; /* Total memory in system as reported by OS */
|
||||
|
||||
/* Mutexes used to protect atomic variables when atomic builtins are
|
||||
* not available. */
|
||||
pthread_mutex_t lruclock_mutex;
|
||||
pthread_mutex_t next_client_id_mutex;
|
||||
pthread_mutex_t unixtime_mutex;
|
||||
};
|
||||
|
||||
typedef struct pubsubPattern {
|
||||
@@ -1265,6 +1315,10 @@ void moduleFreeContext(struct RedisModuleCtx *ctx);
|
||||
void unblockClientFromModule(client *c);
|
||||
void moduleHandleBlockedClients(void);
|
||||
void moduleBlockedClientTimedOut(client *c);
|
||||
void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
size_t moduleCount(void);
|
||||
void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@@ -1450,6 +1504,7 @@ void changeReplicationId(void);
|
||||
void clearReplicationId2(void);
|
||||
void chopReplicationBacklog(void);
|
||||
void replicationCacheMasterUsingMyself(void);
|
||||
void feedReplicationBacklog(void *ptr, size_t len);
|
||||
|
||||
/* Generic persistence functions */
|
||||
void startLoading(FILE *fp);
|
||||
@@ -1574,7 +1629,9 @@ void adjustOpenFilesLimit(void);
|
||||
void closeListeningSockets(int unlink_unix_socket);
|
||||
void updateCachedTime(void);
|
||||
void resetServerStats(void);
|
||||
void activeDefragCycle(void);
|
||||
unsigned int getLRUClock(void);
|
||||
unsigned int LRU_CLOCK(void);
|
||||
const char *evictPolicyToString(void);
|
||||
struct redisMemOverhead *getMemoryOverheadData(void);
|
||||
void freeMemoryOverheadData(struct redisMemOverhead *mh);
|
||||
@@ -1648,13 +1705,15 @@ int removeExpire(redisDb *db, robj *key);
|
||||
void propagateExpire(redisDb *db, robj *key, int lazy);
|
||||
int expireIfNeeded(redisDb *db, robj *key);
|
||||
long long getExpire(redisDb *db, robj *key);
|
||||
void setExpire(redisDb *db, robj *key, long long when);
|
||||
void setExpire(client *c, redisDb *db, robj *key, long long when);
|
||||
robj *lookupKey(redisDb *db, robj *key, int flags);
|
||||
robj *lookupKeyRead(redisDb *db, robj *key);
|
||||
robj *lookupKeyWrite(redisDb *db, robj *key);
|
||||
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
||||
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
||||
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj *objectCommandLookup(client *c, robj *key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
#define LOOKUP_NONE 0
|
||||
#define LOOKUP_NOTOUCH (1<<0)
|
||||
void dbAdd(redisDb *db, robj *key, robj *val);
|
||||
@@ -1694,6 +1753,7 @@ int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *num
|
||||
int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *sortGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
@@ -1711,9 +1771,10 @@ void sentinelTimer(void);
|
||||
char *sentinelHandleConfiguration(char **argv, int argc);
|
||||
void sentinelIsRunning(void);
|
||||
|
||||
/* redis-check-rdb */
|
||||
int redis_check_rdb(char *rdbfilename);
|
||||
int redis_check_rdb_main(int argc, char **argv);
|
||||
/* redis-check-rdb & aof */
|
||||
int redis_check_rdb(char *rdbfilename, FILE *fp);
|
||||
int redis_check_rdb_main(int argc, char **argv, FILE *fp);
|
||||
int redis_check_aof_main(int argc, char **argv);
|
||||
|
||||
/* Scripting */
|
||||
void scriptingInit(int setup);
|
||||
@@ -1731,6 +1792,10 @@ void disconnectAllBlockedClients(void);
|
||||
|
||||
/* expire.c -- Handling of expired keys */
|
||||
void activeExpireCycle(int type);
|
||||
void expireSlaveKeys(void);
|
||||
void rememberSlaveKeyWithExpire(redisDb *db, robj *key);
|
||||
void flushSlaveKeysWithExpireList(void);
|
||||
size_t getSlaveKeyWithExpireCount(void);
|
||||
|
||||
/* evict.c -- maxmemory handling and LRU eviction. */
|
||||
void evictionPoolAlloc(void);
|
||||
@@ -1738,6 +1803,11 @@ void evictionPoolAlloc(void);
|
||||
unsigned long LFUGetTimeInMinutes(void);
|
||||
uint8_t LFULogIncr(uint8_t value);
|
||||
|
||||
/* Keys hashing / comparison functions for dict.c hash tables. */
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
void dictSdsDestructor(void *privdata, void *val);
|
||||
|
||||
/* Git SHA1 */
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
@@ -1904,8 +1974,10 @@ void replconfCommand(client *c);
|
||||
void waitCommand(client *c);
|
||||
void geoencodeCommand(client *c);
|
||||
void geodecodeCommand(client *c);
|
||||
void georadiusByMemberCommand(client *c);
|
||||
void georadiusbymemberCommand(client *c);
|
||||
void georadiusbymemberroCommand(client *c);
|
||||
void georadiusCommand(client *c);
|
||||
void georadiusroCommand(client *c);
|
||||
void geoaddCommand(client *c);
|
||||
void geohashCommand(client *c);
|
||||
void geoposCommand(client *c);
|
||||
@@ -1929,7 +2001,7 @@ void *realloc(void *ptr, size_t size) __attribute__ ((deprecated));
|
||||
/* Debugging stuff */
|
||||
void _serverAssertWithInfo(const client *c, const robj *o, const char *estr, const char *file, int line);
|
||||
void _serverAssert(const char *estr, const char *file, int line);
|
||||
void _serverPanic(const char *msg, const char *file, int line);
|
||||
void _serverPanic(const char *file, int line, const char *msg, ...);
|
||||
void bugReportStart(void);
|
||||
void serverLogObjectDebugInfo(const robj *o);
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
||||
@@ -1939,6 +2011,8 @@ void disableWatchdog(void);
|
||||
void watchdogScheduleSignal(int period);
|
||||
void serverLogHexDump(int level, char *descr, void *value, size_t len);
|
||||
int memtest_preserving_test(unsigned long *m, size_t bytes, int passes);
|
||||
void mixDigest(unsigned char *digest, void *ptr, size_t len);
|
||||
void xorDigest(unsigned char *digest, void *ptr, size_t len);
|
||||
|
||||
#define redisDebug(fmt, ...) \
|
||||
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
|
||||
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
SipHash reference C implementation
|
||||
|
||||
Copyright (c) 2012-2016 Jean-Philippe Aumasson
|
||||
<jeanphilippe.aumasson@gmail.com>
|
||||
Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
|
||||
Copyright (c) 2017 Salvatore Sanfilippo <antirez@gmail.com>
|
||||
|
||||
To the extent possible under law, the author(s) have dedicated all copyright
|
||||
and related and neighboring rights to this software to the public domain
|
||||
worldwide. This software is distributed without any warranty.
|
||||
|
||||
You should have received a copy of the CC0 Public Domain Dedication along
|
||||
with this software. If not, see
|
||||
<http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
This version was modified by Salvatore Sanfilippo <antirez@gmail.com>
|
||||
in the following ways:
|
||||
|
||||
1. We use SipHash 1-2. This is not believed to be as strong as the
|
||||
suggested 2-4 variant, but AFAIK there are not trivial attacks
|
||||
against this reduced-rounds version, and it runs at the same speed
|
||||
as Murmurhash2 that we used previously, why the 2-4 variant slowed
|
||||
down Redis by a 4% figure more or less.
|
||||
2. Hard-code rounds in the hope the compiler can optimize it more
|
||||
in this raw from. Anyway we always want the standard 2-4 variant.
|
||||
3. Modify the prototype and implementation so that the function directly
|
||||
returns an uint64_t value, the hash itself, instead of receiving an
|
||||
output buffer. This also means that the output size is set to 8 bytes
|
||||
and the 16 bytes output code handling was removed.
|
||||
4. Provide a case insensitive variant to be used when hashing strings that
|
||||
must be considered identical by the hash table regardless of the case.
|
||||
If we don't have directly a case insensitive hash function, we need to
|
||||
perform a text transformation in some temporary buffer, which is costly.
|
||||
5. Remove debugging code.
|
||||
6. Modified the original test.c file to be a stand-alone function testing
|
||||
the function in the new form (returing an uint64_t) using just the
|
||||
relevant test vector.
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* Fast tolower() alike function that does not care about locale
|
||||
* but just returns a-z insetad of A-Z. */
|
||||
int siptlw(int c) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
return c+('a'-'A');
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
/* Test of the CPU is Little Endian and supports not aligned accesses.
|
||||
* Two interesting conditions to speedup the function that happen to be
|
||||
* in most of x86 servers. */
|
||||
#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__)
|
||||
#define UNALIGNED_LE_CPU
|
||||
#endif
|
||||
|
||||
#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
|
||||
|
||||
#define U32TO8_LE(p, v) \
|
||||
(p)[0] = (uint8_t)((v)); \
|
||||
(p)[1] = (uint8_t)((v) >> 8); \
|
||||
(p)[2] = (uint8_t)((v) >> 16); \
|
||||
(p)[3] = (uint8_t)((v) >> 24);
|
||||
|
||||
#define U64TO8_LE(p, v) \
|
||||
U32TO8_LE((p), (uint32_t)((v))); \
|
||||
U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
|
||||
|
||||
#ifdef UNALIGNED_LE_CPU
|
||||
#define U8TO64_LE(p) (*((uint64_t*)(p)))
|
||||
#else
|
||||
#define U8TO64_LE(p) \
|
||||
(((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \
|
||||
((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
|
||||
((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \
|
||||
((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
|
||||
#endif
|
||||
|
||||
#define U8TO64_LE_NOCASE(p) \
|
||||
(((uint64_t)(siptlw((p)[0]))) | \
|
||||
((uint64_t)(siptlw((p)[1])) << 8) | \
|
||||
((uint64_t)(siptlw((p)[2])) << 16) | \
|
||||
((uint64_t)(siptlw((p)[3])) << 24) | \
|
||||
((uint64_t)(siptlw((p)[4])) << 32) | \
|
||||
((uint64_t)(siptlw((p)[5])) << 40) | \
|
||||
((uint64_t)(siptlw((p)[6])) << 48) | \
|
||||
((uint64_t)(siptlw((p)[7])) << 56))
|
||||
|
||||
#define SIPROUND \
|
||||
do { \
|
||||
v0 += v1; \
|
||||
v1 = ROTL(v1, 13); \
|
||||
v1 ^= v0; \
|
||||
v0 = ROTL(v0, 32); \
|
||||
v2 += v3; \
|
||||
v3 = ROTL(v3, 16); \
|
||||
v3 ^= v2; \
|
||||
v0 += v3; \
|
||||
v3 = ROTL(v3, 21); \
|
||||
v3 ^= v0; \
|
||||
v2 += v1; \
|
||||
v1 = ROTL(v1, 17); \
|
||||
v1 ^= v2; \
|
||||
v2 = ROTL(v2, 32); \
|
||||
} while (0)
|
||||
|
||||
uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k) {
|
||||
#ifndef UNALIGNED_LE_CPU
|
||||
uint64_t hash;
|
||||
uint8_t *out = (uint8_t*) &hash;
|
||||
#endif
|
||||
uint64_t v0 = 0x736f6d6570736575ULL;
|
||||
uint64_t v1 = 0x646f72616e646f6dULL;
|
||||
uint64_t v2 = 0x6c7967656e657261ULL;
|
||||
uint64_t v3 = 0x7465646279746573ULL;
|
||||
uint64_t k0 = U8TO64_LE(k);
|
||||
uint64_t k1 = U8TO64_LE(k + 8);
|
||||
uint64_t m;
|
||||
const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
|
||||
const int left = inlen & 7;
|
||||
uint64_t b = ((uint64_t)inlen) << 56;
|
||||
v3 ^= k1;
|
||||
v2 ^= k0;
|
||||
v1 ^= k1;
|
||||
v0 ^= k0;
|
||||
|
||||
for (; in != end; in += 8) {
|
||||
m = U8TO64_LE(in);
|
||||
v3 ^= m;
|
||||
|
||||
SIPROUND;
|
||||
|
||||
v0 ^= m;
|
||||
}
|
||||
|
||||
switch (left) {
|
||||
case 7: b |= ((uint64_t)in[6]) << 48;
|
||||
case 6: b |= ((uint64_t)in[5]) << 40;
|
||||
case 5: b |= ((uint64_t)in[4]) << 32;
|
||||
case 4: b |= ((uint64_t)in[3]) << 24;
|
||||
case 3: b |= ((uint64_t)in[2]) << 16;
|
||||
case 2: b |= ((uint64_t)in[1]) << 8;
|
||||
case 1: b |= ((uint64_t)in[0]); break;
|
||||
case 0: break;
|
||||
}
|
||||
|
||||
v3 ^= b;
|
||||
|
||||
SIPROUND;
|
||||
|
||||
v0 ^= b;
|
||||
v2 ^= 0xff;
|
||||
|
||||
SIPROUND;
|
||||
SIPROUND;
|
||||
|
||||
b = v0 ^ v1 ^ v2 ^ v3;
|
||||
#ifndef UNALIGNED_LE_CPU
|
||||
U64TO8_LE(out, b);
|
||||
return hash;
|
||||
#else
|
||||
return b;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k)
|
||||
{
|
||||
#ifndef UNALIGNED_LE_CPU
|
||||
uint64_t hash;
|
||||
uint8_t *out = (uint8_t*) &hash;
|
||||
#endif
|
||||
uint64_t v0 = 0x736f6d6570736575ULL;
|
||||
uint64_t v1 = 0x646f72616e646f6dULL;
|
||||
uint64_t v2 = 0x6c7967656e657261ULL;
|
||||
uint64_t v3 = 0x7465646279746573ULL;
|
||||
uint64_t k0 = U8TO64_LE(k);
|
||||
uint64_t k1 = U8TO64_LE(k + 8);
|
||||
uint64_t m;
|
||||
const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
|
||||
const int left = inlen & 7;
|
||||
uint64_t b = ((uint64_t)inlen) << 56;
|
||||
v3 ^= k1;
|
||||
v2 ^= k0;
|
||||
v1 ^= k1;
|
||||
v0 ^= k0;
|
||||
|
||||
for (; in != end; in += 8) {
|
||||
m = U8TO64_LE_NOCASE(in);
|
||||
v3 ^= m;
|
||||
|
||||
SIPROUND;
|
||||
|
||||
v0 ^= m;
|
||||
}
|
||||
|
||||
switch (left) {
|
||||
case 7: b |= ((uint64_t)siptlw(in[6])) << 48;
|
||||
case 6: b |= ((uint64_t)siptlw(in[5])) << 40;
|
||||
case 5: b |= ((uint64_t)siptlw(in[4])) << 32;
|
||||
case 4: b |= ((uint64_t)siptlw(in[3])) << 24;
|
||||
case 3: b |= ((uint64_t)siptlw(in[2])) << 16;
|
||||
case 2: b |= ((uint64_t)siptlw(in[1])) << 8;
|
||||
case 1: b |= ((uint64_t)siptlw(in[0])); break;
|
||||
case 0: break;
|
||||
}
|
||||
|
||||
v3 ^= b;
|
||||
|
||||
SIPROUND;
|
||||
|
||||
v0 ^= b;
|
||||
v2 ^= 0xff;
|
||||
|
||||
SIPROUND;
|
||||
SIPROUND;
|
||||
|
||||
b = v0 ^ v1 ^ v2 ^ v3;
|
||||
#ifndef UNALIGNED_LE_CPU
|
||||
U64TO8_LE(out, b);
|
||||
return hash;
|
||||
#else
|
||||
return b;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------- TEST ------------------------------------ */
|
||||
|
||||
#ifdef SIPHASH_TEST
|
||||
|
||||
const uint8_t vectors_sip64[64][8] = {
|
||||
{ 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72, },
|
||||
{ 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74, },
|
||||
{ 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d, },
|
||||
{ 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85, },
|
||||
{ 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf, },
|
||||
{ 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18, },
|
||||
{ 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb, },
|
||||
{ 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab, },
|
||||
{ 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93, },
|
||||
{ 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e, },
|
||||
{ 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a, },
|
||||
{ 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4, },
|
||||
{ 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75, },
|
||||
{ 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14, },
|
||||
{ 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7, },
|
||||
{ 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1, },
|
||||
{ 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f, },
|
||||
{ 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69, },
|
||||
{ 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b, },
|
||||
{ 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb, },
|
||||
{ 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe, },
|
||||
{ 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0, },
|
||||
{ 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93, },
|
||||
{ 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8, },
|
||||
{ 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8, },
|
||||
{ 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc, },
|
||||
{ 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17, },
|
||||
{ 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f, },
|
||||
{ 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde, },
|
||||
{ 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6, },
|
||||
{ 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad, },
|
||||
{ 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32, },
|
||||
{ 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71, },
|
||||
{ 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7, },
|
||||
{ 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12, },
|
||||
{ 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15, },
|
||||
{ 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31, },
|
||||
{ 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02, },
|
||||
{ 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca, },
|
||||
{ 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a, },
|
||||
{ 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e, },
|
||||
{ 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad, },
|
||||
{ 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18, },
|
||||
{ 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4, },
|
||||
{ 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9, },
|
||||
{ 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9, },
|
||||
{ 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb, },
|
||||
{ 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0, },
|
||||
{ 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6, },
|
||||
{ 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7, },
|
||||
{ 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee, },
|
||||
{ 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1, },
|
||||
{ 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a, },
|
||||
{ 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81, },
|
||||
{ 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f, },
|
||||
{ 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24, },
|
||||
{ 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7, },
|
||||
{ 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea, },
|
||||
{ 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60, },
|
||||
{ 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66, },
|
||||
{ 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c, },
|
||||
{ 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f, },
|
||||
{ 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5, },
|
||||
{ 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95, },
|
||||
};
|
||||
|
||||
|
||||
/* Test siphash using a test vector. Returns 0 if the function passed
|
||||
* all the tests, otherwise 1 is returned.
|
||||
*
|
||||
* IMPORTANT: The test vector is for SipHash 2-4. Before running
|
||||
* the test revert back the siphash() function to 2-4 rounds since
|
||||
* now it uses 1-2 rounds. */
|
||||
int siphash_test(void) {
|
||||
uint8_t in[64], k[16];
|
||||
int i;
|
||||
int fails = 0;
|
||||
|
||||
for (i = 0; i < 16; ++i)
|
||||
k[i] = i;
|
||||
|
||||
for (i = 0; i < 64; ++i) {
|
||||
in[i] = i;
|
||||
uint64_t hash = siphash(in, i, k);
|
||||
const uint8_t *v = NULL;
|
||||
v = (uint8_t *)vectors_sip64;
|
||||
if (memcmp(&hash, v + (i * 8), 8)) {
|
||||
/* printf("fail for %d bytes\n", i); */
|
||||
fails++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Run a few basic tests with the case insensitive version. */
|
||||
uint64_t h1, h2;
|
||||
h1 = siphash((uint8_t*)"hello world",11,(uint8_t*)"1234567812345678");
|
||||
h2 = siphash_nocase((uint8_t*)"hello world",11,(uint8_t*)"1234567812345678");
|
||||
if (h1 != h2) fails++;
|
||||
|
||||
h1 = siphash((uint8_t*)"hello world",11,(uint8_t*)"1234567812345678");
|
||||
h2 = siphash_nocase((uint8_t*)"HELLO world",11,(uint8_t*)"1234567812345678");
|
||||
if (h1 != h2) fails++;
|
||||
|
||||
h1 = siphash((uint8_t*)"HELLO world",11,(uint8_t*)"1234567812345678");
|
||||
h2 = siphash_nocase((uint8_t*)"HELLO world",11,(uint8_t*)"1234567812345678");
|
||||
if (h1 == h2) fails++;
|
||||
|
||||
if (!fails) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (siphash_test() == 0) {
|
||||
printf("SipHash test: OK\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("SipHash test: FAILED\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+11
-4
@@ -45,7 +45,7 @@
|
||||
/* Create a new slowlog entry.
|
||||
* Incrementing the ref count of all the objects retained is up to
|
||||
* this function. */
|
||||
slowlogEntry *slowlogCreateEntry(robj **argv, int argc, long long duration) {
|
||||
slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long duration) {
|
||||
slowlogEntry *se = zmalloc(sizeof(*se));
|
||||
int j, slargc = argc;
|
||||
|
||||
@@ -81,6 +81,8 @@ slowlogEntry *slowlogCreateEntry(robj **argv, int argc, long long duration) {
|
||||
se->time = time(NULL);
|
||||
se->duration = duration;
|
||||
se->id = server.slowlog_entry_id++;
|
||||
se->peerid = sdsnew(getClientPeerId(c));
|
||||
se->cname = c->name ? sdsnew(c->name->ptr) : sdsempty();
|
||||
return se;
|
||||
}
|
||||
|
||||
@@ -95,6 +97,8 @@ void slowlogFreeEntry(void *septr) {
|
||||
for (j = 0; j < se->argc; j++)
|
||||
decrRefCount(se->argv[j]);
|
||||
zfree(se->argv);
|
||||
sdsfree(se->peerid);
|
||||
sdsfree(se->cname);
|
||||
zfree(se);
|
||||
}
|
||||
|
||||
@@ -109,10 +113,11 @@ void slowlogInit(void) {
|
||||
/* Push a new entry into the slow log.
|
||||
* This function will make sure to trim the slow log accordingly to the
|
||||
* configured max length. */
|
||||
void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration) {
|
||||
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {
|
||||
if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
|
||||
if (duration >= server.slowlog_log_slower_than)
|
||||
listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration));
|
||||
listAddNodeHead(server.slowlog,
|
||||
slowlogCreateEntry(c,argv,argc,duration));
|
||||
|
||||
/* Remove old entries if needed. */
|
||||
while (listLength(server.slowlog) > server.slowlog_max_len)
|
||||
@@ -152,13 +157,15 @@ void slowlogCommand(client *c) {
|
||||
int j;
|
||||
|
||||
se = ln->value;
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyMultiBulkLen(c,6);
|
||||
addReplyLongLong(c,se->id);
|
||||
addReplyLongLong(c,se->time);
|
||||
addReplyLongLong(c,se->duration);
|
||||
addReplyMultiBulkLen(c,se->argc);
|
||||
for (j = 0; j < se->argc; j++)
|
||||
addReplyBulk(c,se->argv[j]);
|
||||
addReplyBulkCBuffer(c,se->peerid,sdslen(se->peerid));
|
||||
addReplyBulkCBuffer(c,se->cname,sdslen(se->cname));
|
||||
sent++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,totentries,sent);
|
||||
|
||||
+4
-2
@@ -35,13 +35,15 @@ typedef struct slowlogEntry {
|
||||
robj **argv;
|
||||
int argc;
|
||||
long long id; /* Unique entry identifier. */
|
||||
long long duration; /* Time spent by the query, in nanoseconds. */
|
||||
long long duration; /* Time spent by the query, in microseconds. */
|
||||
time_t time; /* Unix time at which the query was executed. */
|
||||
sds cname; /* Client name. */
|
||||
sds peerid; /* Client network address. */
|
||||
} slowlogEntry;
|
||||
|
||||
/* Exported API */
|
||||
void slowlogInit(void);
|
||||
void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration);
|
||||
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration);
|
||||
|
||||
/* Exported commands */
|
||||
void slowlogCommand(client *c);
|
||||
|
||||
+14
-18
@@ -511,19 +511,6 @@ void hashTypeConvert(robj *o, int enc) {
|
||||
* Hash type commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
void hsetCommand(client *c) {
|
||||
int update;
|
||||
robj *o;
|
||||
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
hashTypeTryConversion(o,c->argv,2,3);
|
||||
update = hashTypeSet(o,c->argv[2]->ptr,c->argv[3]->ptr,HASH_SET_COPY);
|
||||
addReply(c, update ? shared.czero : shared.cone);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
void hsetnxCommand(client *c) {
|
||||
robj *o;
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
@@ -540,8 +527,8 @@ void hsetnxCommand(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
void hmsetCommand(client *c) {
|
||||
int i;
|
||||
void hsetCommand(client *c) {
|
||||
int i, created = 0;
|
||||
robj *o;
|
||||
|
||||
if ((c->argc % 2) == 1) {
|
||||
@@ -551,10 +538,19 @@ void hmsetCommand(client *c) {
|
||||
|
||||
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
|
||||
hashTypeTryConversion(o,c->argv,2,c->argc-1);
|
||||
for (i = 2; i < c->argc; i += 2) {
|
||||
hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
|
||||
|
||||
for (i = 2; i < c->argc; i += 2)
|
||||
created += !hashTypeSet(o,c->argv[i]->ptr,c->argv[i+1]->ptr,HASH_SET_COPY);
|
||||
|
||||
/* HMSET (deprecated) and HSET return value is different. */
|
||||
char *cmdname = c->argv[0]->ptr;
|
||||
if (cmdname[1] == 's' || cmdname[1] == 'S') {
|
||||
/* HSET */
|
||||
addReplyLongLong(c, created);
|
||||
} else {
|
||||
/* HMSET */
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
addReply(c, shared.ok);
|
||||
signalModifiedKey(c->db,c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
}
|
||||
setKey(c->db,key,val);
|
||||
server.dirty++;
|
||||
if (expire) setExpire(c->db,key,mstime()+milliseconds);
|
||||
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
||||
if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
|
||||
"expire",key,c->db->id);
|
||||
|
||||
+2
-2
@@ -1521,7 +1521,7 @@ void zaddGenericCommand(client *c, int flags) {
|
||||
/* After the options, we expect to have an even number of args, since
|
||||
* we expect any number of score-element pairs. */
|
||||
elements = c->argc-scoreidx;
|
||||
if (elements % 2) {
|
||||
if (elements % 2 || !elements) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
@@ -2110,7 +2110,7 @@ inline static void zunionInterAggregate(double *target, double val, int aggregat
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int dictSdsHash(const void *key);
|
||||
uint64_t dictSdsHash(const void *key);
|
||||
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
|
||||
dictType setAccumulatorDictType = {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "999.999.999"
|
||||
#define REDIS_VERSION "4.0.0"
|
||||
|
||||
+299
-129
@@ -8,72 +8,150 @@
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* ZIPLIST OVERALL LAYOUT:
|
||||
* ZIPLIST OVERALL LAYOUT
|
||||
* ======================
|
||||
*
|
||||
* The general layout of the ziplist is as follows:
|
||||
* <zlbytes><zltail><zllen><entry><entry><zlend>
|
||||
*
|
||||
* <zlbytes> is an unsigned integer to hold the number of bytes that the
|
||||
* ziplist occupies. This value needs to be stored to be able to resize the
|
||||
* entire structure without the need to traverse it first.
|
||||
* <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
|
||||
*
|
||||
* <zltail> is the offset to the last entry in the list. This allows a pop
|
||||
* operation on the far side of the list without the need for full traversal.
|
||||
* NOTE: all fields are stored in little endian, if not specified otherwise.
|
||||
*
|
||||
* <zllen> is the number of entries.When this value is larger than 2**16-2,
|
||||
* we need to traverse the entire list to know how many items it holds.
|
||||
* <uint32_t zlbytes> is an unsigned integer to hold the number of bytes that
|
||||
* the ziplist occupies, including the four bytes of the zlbytes field itself.
|
||||
* This value needs to be stored to be able to resize the entire structure
|
||||
* without the need to traverse it first.
|
||||
*
|
||||
* <zlend> is a single byte special value, equal to 255, which indicates the
|
||||
* end of the list.
|
||||
* <uint32_t zltail> is the offset to the last entry in the list. This allows
|
||||
* a pop operation on the far side of the list without the need for full
|
||||
* traversal.
|
||||
*
|
||||
* ZIPLIST ENTRIES:
|
||||
* Every entry in the ziplist is prefixed by a header that contains two pieces
|
||||
* <uint16_t zllen> is the number of entries. When there are more than
|
||||
* 2^16-2 entires, this value is set to 2^16-1 and we need to traverse the
|
||||
* entire list to know how many items it holds.
|
||||
*
|
||||
* <uint8_t zlend> is a special entry representing the end of the ziplist.
|
||||
* Is encoded as a single byte equal to 255. No other normal entry starts
|
||||
* with a byte set to the value of 255.
|
||||
*
|
||||
* ZIPLIST ENTRIES
|
||||
* ===============
|
||||
*
|
||||
* Every entry in the ziplist is prefixed by metadata that contains two pieces
|
||||
* of information. First, the length of the previous entry is stored to be
|
||||
* able to traverse the list from back to front. Second, the encoding with an
|
||||
* optional string length of the entry itself is stored.
|
||||
* able to traverse the list from back to front. Second, the entry encoding is
|
||||
* provided. It represents the entry type, integer or string, and in the case
|
||||
* of strings it also represents the length of the string payload.
|
||||
* So a complete entry is stored like this:
|
||||
*
|
||||
* The length of the previous entry is encoded in the following way:
|
||||
* If this length is smaller than 254 bytes, it will only consume a single
|
||||
* byte that takes the length as value. When the length is greater than or
|
||||
* equal to 254, it will consume 5 bytes. The first byte is set to 254 to
|
||||
* indicate a larger value is following. The remaining 4 bytes take the
|
||||
* length of the previous entry as value.
|
||||
* <prevlen> <encoding> <entry-data>
|
||||
*
|
||||
* The other header field of the entry itself depends on the contents of the
|
||||
* entry. When the entry is a string, the first 2 bits of this header will hold
|
||||
* the type of encoding used to store the length of the string, followed by the
|
||||
* actual length of the string. When the entry is an integer the first 2 bits
|
||||
* are both set to 1. The following 2 bits are used to specify what kind of
|
||||
* integer will be stored after this header. An overview of the different
|
||||
* types and encodings is as follows:
|
||||
* Sometimes the encoding represents the entry itself, like for small integers
|
||||
* as we'll see later. In such a case the <entry-data> part is missing, and we
|
||||
* could have just:
|
||||
*
|
||||
* <prevlen> <encoding>
|
||||
*
|
||||
* The length of the previous entry, <prevlen>, is encoded in the following way:
|
||||
* If this length is smaller than 255 bytes, it will only consume a single
|
||||
* byte representing the length as an unsinged 8 bit integer. When the length
|
||||
* is greater than or equal to 255, it will consume 5 bytes. The first byte is
|
||||
* set to 255 (FF) to indicate a larger value is following. The remaining 4
|
||||
* bytes take the length of the previous entry as value.
|
||||
*
|
||||
* So practically an entry is encoded in the following way:
|
||||
*
|
||||
* <prevlen from 0 to 254> <encoding> <entry>
|
||||
*
|
||||
* Or alternatively if the previous entry length is greater than 254 bytes
|
||||
* the following encoding is used:
|
||||
*
|
||||
* 0xFF <4 bytes unsigned little endian prevlen> <encoding> <entry>
|
||||
*
|
||||
* The encoding field of the entry depends on the content of the
|
||||
* entry. When the entry is a string, the first 2 bits of the encoding first
|
||||
* byte will hold the type of encoding used to store the length of the string,
|
||||
* followed by the actual length of the string. When the entry is an integer
|
||||
* the first 2 bits are both set to 1. The following 2 bits are used to specify
|
||||
* what kind of integer will be stored after this header. An overview of the
|
||||
* different types and encodings is as follows. The first byte is always enough
|
||||
* to determine the kind of entry.
|
||||
*
|
||||
* |00pppppp| - 1 byte
|
||||
* String value with length less than or equal to 63 bytes (6 bits).
|
||||
* "pppppp" represents the unsigned 6 bit length.
|
||||
* |01pppppp|qqqqqqqq| - 2 bytes
|
||||
* String value with length less than or equal to 16383 bytes (14 bits).
|
||||
* |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
|
||||
* IMPORTANT: The 14 bit number is stored in big endian.
|
||||
* |10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| - 5 bytes
|
||||
* String value with length greater than or equal to 16384 bytes.
|
||||
* |11000000| - 1 byte
|
||||
* Only the 4 bytes following the first byte represents the length
|
||||
* up to 32^2-1. The 6 lower bits of the first byte are not used and
|
||||
* are set to zero.
|
||||
* IMPORTANT: The 32 bit number is stored in big endian.
|
||||
* |11000000| - 3 bytes
|
||||
* Integer encoded as int16_t (2 bytes).
|
||||
* |11010000| - 1 byte
|
||||
* |11010000| - 5 bytes
|
||||
* Integer encoded as int32_t (4 bytes).
|
||||
* |11100000| - 1 byte
|
||||
* |11100000| - 9 bytes
|
||||
* Integer encoded as int64_t (8 bytes).
|
||||
* |11110000| - 1 byte
|
||||
* |11110000| - 4 bytes
|
||||
* Integer encoded as 24 bit signed (3 bytes).
|
||||
* |11111110| - 1 byte
|
||||
* |11111110| - 2 bytes
|
||||
* Integer encoded as 8 bit signed (1 byte).
|
||||
* |1111xxxx| - (with xxxx between 0000 and 1101) immediate 4 bit integer.
|
||||
* Unsigned integer from 0 to 12. The encoded value is actually from
|
||||
* 1 to 13 because 0000 and 1111 can not be used, so 1 should be
|
||||
* subtracted from the encoded 4 bit value to obtain the right value.
|
||||
* |11111111| - End of ziplist.
|
||||
* |11111111| - End of ziplist special entry.
|
||||
*
|
||||
* All the integers are represented in little endian byte order.
|
||||
* Like for the ziplist header, all the integers are represented in little
|
||||
* endian byte order, even when this code is compiled in big endian systems.
|
||||
*
|
||||
* EXAMPLES OF ACTUAL ZIPLISTS
|
||||
* ===========================
|
||||
*
|
||||
* The following is a ziplist containing the two elements representing
|
||||
* the strings "2" and "5". It is composed of 15 bytes, that we visually
|
||||
* split into sections:
|
||||
*
|
||||
* [0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
|
||||
* | | | | | |
|
||||
* zlbytes zltail entries "2" "5" end
|
||||
*
|
||||
* The first 4 bytes represent the number 15, that is the number of bytes
|
||||
* the whole ziplist is composed of. The second 4 bytes are the offset
|
||||
* at which the last ziplist entry is found, that is 12, in fact the
|
||||
* last entry, that is "5", is at offset 12 inside the ziplist.
|
||||
* The next 16 bit integer represents the number of elements inside the
|
||||
* ziplist, its value is 2 since there are just two elements inside.
|
||||
* Finally "00 f3" is the first entry representing the number 2. It is
|
||||
* composed of the previous entry length, which is zero because this is
|
||||
* our first entry, and the byte F3 which corresponds to the encoding
|
||||
* |1111xxxx| with xxxx between 0001 and 1101. We need to remove the "F"
|
||||
* higher order bits 1111, and subtract 1 from the "3", so the entry value
|
||||
* is "2". The next entry has a prevlen of 02, since the first entry is
|
||||
* composed of exactly two bytes. The entry itself, F6, is encoded exactly
|
||||
* like the first entry, and 6-1 = 5, so the value of the entry is 5.
|
||||
* Finally the special entry FF signals the end of the ziplist.
|
||||
*
|
||||
* Adding another element to the above string with the value "Hello World"
|
||||
* allows us to show how the ziplist encodes small strings. We'll just show
|
||||
* the hex dump of the entry itself. Imagine the bytes as following the
|
||||
* entry that stores "5" in the ziplist above:
|
||||
*
|
||||
* [02] [0b] [48 65 6c 6c 6f 20 57 6f 72 6c 64]
|
||||
*
|
||||
* The first byte, 02, is the length of the previous entry. The next
|
||||
* byte represents the encoding in the pattern |00pppppp| that means
|
||||
* that the entry is a string of length <pppppp>, so 0B means that
|
||||
* an 11 bytes string follows. From the third byte (48) to the last (64)
|
||||
* there are just the ASCII characters for "Hello World".
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2009-2017, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -112,8 +190,13 @@
|
||||
#include "endianconv.h"
|
||||
#include "redisassert.h"
|
||||
|
||||
#define ZIP_END 255
|
||||
#define ZIP_BIGLEN 254
|
||||
#define ZIP_END 255 /* Special "end of ziplist" entry. */
|
||||
#define ZIP_BIG_PREVLEN 254 /* Max number of bytes of the previous entry, for
|
||||
the "prevlen" field prefixing each entry, to be
|
||||
represented with just a single byte. Otherwise
|
||||
it is represented as FF AA BB CC DD, where
|
||||
AA BB CC DD are a 4 bytes unsigned integer
|
||||
representing the previous entry len. */
|
||||
|
||||
/* Different encoding/length possibilities */
|
||||
#define ZIP_STR_MASK 0xc0
|
||||
@@ -126,41 +209,83 @@
|
||||
#define ZIP_INT_64B (0xc0 | 2<<4)
|
||||
#define ZIP_INT_24B (0xc0 | 3<<4)
|
||||
#define ZIP_INT_8B 0xfe
|
||||
/* 4 bit integer immediate encoding */
|
||||
#define ZIP_INT_IMM_MASK 0x0f
|
||||
|
||||
/* 4 bit integer immediate encoding |1111xxxx| with xxxx between
|
||||
* 0001 and 1101. */
|
||||
#define ZIP_INT_IMM_MASK 0x0f /* Mask to extract the 4 bits value. To add
|
||||
one is needed to reconstruct the value. */
|
||||
#define ZIP_INT_IMM_MIN 0xf1 /* 11110001 */
|
||||
#define ZIP_INT_IMM_MAX 0xfd /* 11111101 */
|
||||
#define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)
|
||||
|
||||
#define INT24_MAX 0x7fffff
|
||||
#define INT24_MIN (-INT24_MAX - 1)
|
||||
|
||||
/* Macro to determine type */
|
||||
/* Macro to determine if the entry is a string. String entries never start
|
||||
* with "11" as most significant bits of the first byte. */
|
||||
#define ZIP_IS_STR(enc) (((enc) & ZIP_STR_MASK) < ZIP_STR_MASK)
|
||||
|
||||
/* Utility macros */
|
||||
/* Utility macros.*/
|
||||
|
||||
/* Return total bytes a ziplist is composed of. */
|
||||
#define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl)))
|
||||
|
||||
/* Return the offset of the last item inside the ziplist. */
|
||||
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
|
||||
|
||||
/* Return the length of a ziplist, or UINT16_MAX if the length cannot be
|
||||
* determined without scanning the whole ziplist. */
|
||||
#define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
|
||||
|
||||
/* The size of a ziplist header: two 32 bit integers for the total
|
||||
* bytes count and last item offset. One 16 bit integer for the number
|
||||
* of items field. */
|
||||
#define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t))
|
||||
|
||||
/* Size of the "end of ziplist" entry. Just one byte. */
|
||||
#define ZIPLIST_END_SIZE (sizeof(uint8_t))
|
||||
|
||||
/* Return the pointer to the first entry of a ziplist. */
|
||||
#define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE)
|
||||
|
||||
/* Return the pointer to the last entry of a ziplist, using the
|
||||
* last entry offset inside the ziplist header. */
|
||||
#define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
|
||||
|
||||
/* Return the pointer to the last byte of a ziplist, which is, the
|
||||
* end of ziplist FF entry. */
|
||||
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
|
||||
|
||||
/* We know a positive increment can only be 1 because entries can only be
|
||||
* pushed one at a time. */
|
||||
/* Increment the number of items field in the ziplist header. Note that this
|
||||
* macro should never overflow the unsigned 16 bit integer, since entires are
|
||||
* always pushed one at a time. When UINT16_MAX is reached we want the count
|
||||
* to stay there to signal that a full scan is needed to get the number of
|
||||
* items inside the ziplist. */
|
||||
#define ZIPLIST_INCR_LENGTH(zl,incr) { \
|
||||
if (ZIPLIST_LENGTH(zl) < UINT16_MAX) \
|
||||
ZIPLIST_LENGTH(zl) = intrev16ifbe(intrev16ifbe(ZIPLIST_LENGTH(zl))+incr); \
|
||||
}
|
||||
|
||||
/* We use this function to receive information about a ziplist entry.
|
||||
* Note that this is not how the data is actually encoded, is just what we
|
||||
* get filled by a function in order to operate more easily. */
|
||||
typedef struct zlentry {
|
||||
unsigned int prevrawlensize, prevrawlen;
|
||||
unsigned int lensize, len;
|
||||
unsigned int headersize;
|
||||
unsigned char encoding;
|
||||
unsigned char *p;
|
||||
unsigned int prevrawlensize; /* Bytes used to encode the previos entry len*/
|
||||
unsigned int prevrawlen; /* Previous entry len. */
|
||||
unsigned int lensize; /* Bytes used to encode this entry type/len.
|
||||
For example strings have a 1, 2 or 5 bytes
|
||||
header. Integers always use a single byte.*/
|
||||
unsigned int len; /* Bytes used to represent the actual entry.
|
||||
For strings this is just the string length
|
||||
while for integers it is 1, 2, 3, 4, 8 or
|
||||
0 (for 4 bit immediate) depending on the
|
||||
number range. */
|
||||
unsigned int headersize; /* prevrawlensize + lensize. */
|
||||
unsigned char encoding; /* Set to ZIP_STR_* or ZIP_INT_* depending on
|
||||
the entry encoding. However for 4 bits
|
||||
immediate integers this can assume a range
|
||||
of values and must be range-checked. */
|
||||
unsigned char *p; /* Pointer to the very start of the entry, that
|
||||
is, this points to prev-entry-len field. */
|
||||
} zlentry;
|
||||
|
||||
#define ZIPLIST_ENTRY_ZERO(zle) { \
|
||||
@@ -171,31 +296,40 @@ typedef struct zlentry {
|
||||
}
|
||||
|
||||
/* Extract the encoding from the byte pointed by 'ptr' and set it into
|
||||
* 'encoding'. */
|
||||
* 'encoding' field of the zlentry structure. */
|
||||
#define ZIP_ENTRY_ENCODING(ptr, encoding) do { \
|
||||
(encoding) = (ptr[0]); \
|
||||
if ((encoding) < ZIP_STR_MASK) (encoding) &= ZIP_STR_MASK; \
|
||||
} while(0)
|
||||
|
||||
void ziplistRepr(unsigned char *zl);
|
||||
|
||||
/* Return bytes needed to store integer encoded by 'encoding' */
|
||||
static unsigned int zipIntSize(unsigned char encoding) {
|
||||
/* Return bytes needed to store integer encoded by 'encoding'. */
|
||||
unsigned int zipIntSize(unsigned char encoding) {
|
||||
switch(encoding) {
|
||||
case ZIP_INT_8B: return 1;
|
||||
case ZIP_INT_16B: return 2;
|
||||
case ZIP_INT_24B: return 3;
|
||||
case ZIP_INT_32B: return 4;
|
||||
case ZIP_INT_64B: return 8;
|
||||
default: return 0; /* 4 bit immediate */
|
||||
}
|
||||
assert(NULL);
|
||||
if (encoding >= ZIP_INT_IMM_MIN && encoding <= ZIP_INT_IMM_MAX)
|
||||
return 0; /* 4 bit immediate */
|
||||
panic("Invalid integer encoding 0x%02X", encoding);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Encode the length 'rawlen' writing it in 'p'. If p is NULL it just returns
|
||||
* the amount of bytes required to encode such a length. */
|
||||
static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, unsigned int rawlen) {
|
||||
/* Write the encoidng header of the entry in 'p'. If p is NULL it just returns
|
||||
* the amount of bytes required to encode such a length. Arguments:
|
||||
*
|
||||
* 'encoding' is the encoding we are using for the entry. It could be
|
||||
* ZIP_INT_* or ZIP_STR_* or between ZIP_INT_IMM_MIN and ZIP_INT_IMM_MAX
|
||||
* for single-byte small immediate integers.
|
||||
*
|
||||
* 'rawlen' is only used for ZIP_STR_* encodings and is the length of the
|
||||
* srting that this entry represents.
|
||||
*
|
||||
* The function returns the number of bytes used by the encoding/length
|
||||
* header stored in 'p'. */
|
||||
unsigned int zipStoreEntryEncoding(unsigned char *p, unsigned char encoding, unsigned int rawlen) {
|
||||
unsigned char len = 1, buf[5];
|
||||
|
||||
if (ZIP_IS_STR(encoding)) {
|
||||
@@ -224,15 +358,16 @@ static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, un
|
||||
buf[0] = encoding;
|
||||
}
|
||||
|
||||
/* Store this length at p */
|
||||
/* Store this length at p. */
|
||||
memcpy(p,buf,len);
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Decode the length encoded in 'ptr'. The 'encoding' variable will hold the
|
||||
* entries encoding, the 'lensize' variable will hold the number of bytes
|
||||
* required to encode the entries length, and the 'len' variable will hold the
|
||||
* entries length. */
|
||||
/* Decode the entry encoding type and data length (string length for strings,
|
||||
* number of bytes used for the integer for integer entries) encoded in 'ptr'.
|
||||
* The 'encoding' variable will hold the entry encoding, the 'lensize'
|
||||
* variable will hold the number of bytes required to encode the entry
|
||||
* length, and the 'len' variable will hold the entry length. */
|
||||
#define ZIP_DECODE_LENGTH(ptr, encoding, lensize, len) do { \
|
||||
ZIP_ENTRY_ENCODING((ptr), (encoding)); \
|
||||
if ((encoding) < ZIP_STR_MASK) { \
|
||||
@@ -242,14 +377,14 @@ static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, un
|
||||
} else if ((encoding) == ZIP_STR_14B) { \
|
||||
(lensize) = 2; \
|
||||
(len) = (((ptr)[0] & 0x3f) << 8) | (ptr)[1]; \
|
||||
} else if (encoding == ZIP_STR_32B) { \
|
||||
} else if ((encoding) == ZIP_STR_32B) { \
|
||||
(lensize) = 5; \
|
||||
(len) = ((ptr)[1] << 24) | \
|
||||
((ptr)[2] << 16) | \
|
||||
((ptr)[3] << 8) | \
|
||||
((ptr)[4]); \
|
||||
} else { \
|
||||
assert(NULL); \
|
||||
panic("Invalid string encoding 0x%02X", (encoding)); \
|
||||
} \
|
||||
} else { \
|
||||
(lensize) = 1; \
|
||||
@@ -257,45 +392,49 @@ static unsigned int zipEncodeLength(unsigned char *p, unsigned char encoding, un
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
/* Encode the length of the previous entry and write it to "p". This only
|
||||
* uses the larger encoding (required in __ziplistCascadeUpdate). */
|
||||
int zipStorePrevEntryLengthLarge(unsigned char *p, unsigned int len) {
|
||||
if (p != NULL) {
|
||||
p[0] = ZIP_BIG_PREVLEN;
|
||||
memcpy(p+1,&len,sizeof(len));
|
||||
memrev32ifbe(p+1);
|
||||
}
|
||||
return 1+sizeof(len);
|
||||
}
|
||||
|
||||
/* Encode the length of the previous entry and write it to "p". Return the
|
||||
* number of bytes needed to encode this length if "p" is NULL. */
|
||||
static unsigned int zipPrevEncodeLength(unsigned char *p, unsigned int len) {
|
||||
unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
|
||||
if (p == NULL) {
|
||||
return (len < ZIP_BIGLEN) ? 1 : sizeof(len)+1;
|
||||
return (len < ZIP_BIG_PREVLEN) ? 1 : sizeof(len)+1;
|
||||
} else {
|
||||
if (len < ZIP_BIGLEN) {
|
||||
if (len < ZIP_BIG_PREVLEN) {
|
||||
p[0] = len;
|
||||
return 1;
|
||||
} else {
|
||||
p[0] = ZIP_BIGLEN;
|
||||
memcpy(p+1,&len,sizeof(len));
|
||||
memrev32ifbe(p+1);
|
||||
return 1+sizeof(len);
|
||||
return zipStorePrevEntryLengthLarge(p,len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Encode the length of the previous entry and write it to "p". This only
|
||||
* uses the larger encoding (required in __ziplistCascadeUpdate). */
|
||||
static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len) {
|
||||
if (p == NULL) return;
|
||||
p[0] = ZIP_BIGLEN;
|
||||
memcpy(p+1,&len,sizeof(len));
|
||||
memrev32ifbe(p+1);
|
||||
}
|
||||
|
||||
/* Decode the number of bytes required to store the length of the previous
|
||||
* element, from the perspective of the entry pointed to by 'ptr'. */
|
||||
/* Return the number of bytes used to encode the length of the previous
|
||||
* entry. The length is returned by setting the var 'prevlensize'. */
|
||||
#define ZIP_DECODE_PREVLENSIZE(ptr, prevlensize) do { \
|
||||
if ((ptr)[0] < ZIP_BIGLEN) { \
|
||||
if ((ptr)[0] < ZIP_BIG_PREVLEN) { \
|
||||
(prevlensize) = 1; \
|
||||
} else { \
|
||||
(prevlensize) = 5; \
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
/* Decode the length of the previous element, from the perspective of the entry
|
||||
* pointed to by 'ptr'. */
|
||||
/* Return the length of the previous element, and the number of bytes that
|
||||
* are used in order to encode the previous element length.
|
||||
* 'ptr' must point to the prevlen prefix of an entry (that encodes the
|
||||
* length of the previos entry in order to navigate the elements backward).
|
||||
* The length of the previous entry is stored in 'prevlen', the number of
|
||||
* bytes needed to encode the previous entry length are stored in
|
||||
* 'prevlensize'. */
|
||||
#define ZIP_DECODE_PREVLEN(ptr, prevlensize, prevlen) do { \
|
||||
ZIP_DECODE_PREVLENSIZE(ptr, prevlensize); \
|
||||
if ((prevlensize) == 1) { \
|
||||
@@ -307,16 +446,29 @@ static void zipPrevEncodeLengthForceLarge(unsigned char *p, unsigned int len) {
|
||||
} \
|
||||
} while(0);
|
||||
|
||||
/* Return the difference in number of bytes needed to store the length of the
|
||||
* previous element 'len', in the entry pointed to by 'p'. */
|
||||
static int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
|
||||
/* Given a pointer 'p' to the prevlen info that prefixes an entry, this
|
||||
* function returns the difference in number of bytes needed to encode
|
||||
* the prevlen if the previous entry changes of size.
|
||||
*
|
||||
* So if A is the number of bytes used right now to encode the 'prevlen'
|
||||
* field.
|
||||
*
|
||||
* And B is the number of bytes that are needed in order to encode the
|
||||
* 'prevlen' if the previous element will be updated to one of size 'len'.
|
||||
*
|
||||
* Then the function returns B - A
|
||||
*
|
||||
* So the function returns a positive number if more space is needed,
|
||||
* a negative number if less space is needed, or zero if the same space
|
||||
* is needed. */
|
||||
int zipPrevLenByteDiff(unsigned char *p, unsigned int len) {
|
||||
unsigned int prevlensize;
|
||||
ZIP_DECODE_PREVLENSIZE(p, prevlensize);
|
||||
return zipPrevEncodeLength(NULL, len) - prevlensize;
|
||||
return zipStorePrevEntryLength(NULL, len) - prevlensize;
|
||||
}
|
||||
|
||||
/* Return the total number of bytes used by the entry pointed to by 'p'. */
|
||||
static unsigned int zipRawEntryLength(unsigned char *p) {
|
||||
unsigned int zipRawEntryLength(unsigned char *p) {
|
||||
unsigned int prevlensize, encoding, lensize, len;
|
||||
ZIP_DECODE_PREVLENSIZE(p, prevlensize);
|
||||
ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);
|
||||
@@ -325,7 +477,7 @@ static unsigned int zipRawEntryLength(unsigned char *p) {
|
||||
|
||||
/* Check if string pointed to by 'entry' can be encoded as an integer.
|
||||
* Stores the integer value in 'v' and its encoding in 'encoding'. */
|
||||
static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
|
||||
int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {
|
||||
long long value;
|
||||
|
||||
if (entrylen >= 32 || entrylen == 0) return 0;
|
||||
@@ -352,7 +504,7 @@ static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long
|
||||
}
|
||||
|
||||
/* Store integer 'value' at 'p', encoded as 'encoding' */
|
||||
static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding) {
|
||||
void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encoding) {
|
||||
int16_t i16;
|
||||
int32_t i32;
|
||||
int64_t i64;
|
||||
@@ -382,7 +534,7 @@ static void zipSaveInteger(unsigned char *p, int64_t value, unsigned char encodi
|
||||
}
|
||||
|
||||
/* Read integer encoded as 'encoding' from 'p' */
|
||||
static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
|
||||
int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
|
||||
int16_t i16;
|
||||
int32_t i32;
|
||||
int64_t i64, ret = 0;
|
||||
@@ -414,7 +566,7 @@ static int64_t zipLoadInteger(unsigned char *p, unsigned char encoding) {
|
||||
}
|
||||
|
||||
/* Return a struct with all information about an entry. */
|
||||
static void zipEntry(unsigned char *p, zlentry *e) {
|
||||
void zipEntry(unsigned char *p, zlentry *e) {
|
||||
|
||||
ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
|
||||
ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
|
||||
@@ -434,7 +586,7 @@ unsigned char *ziplistNew(void) {
|
||||
}
|
||||
|
||||
/* Resize the ziplist. */
|
||||
static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
|
||||
unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
|
||||
zl = zrealloc(zl,len);
|
||||
ZIPLIST_BYTES(zl) = intrev32ifbe(len);
|
||||
zl[len-1] = ZIP_END;
|
||||
@@ -449,8 +601,8 @@ static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
|
||||
* causes a realloc and memmove). However, encoding the prevlen may require
|
||||
* that this entry is grown as well. This effect may cascade throughout
|
||||
* the ziplist when there are consecutive entries with a size close to
|
||||
* ZIP_BIGLEN, so we need to check that the prevlen can be encoded in every
|
||||
* consecutive entry.
|
||||
* ZIP_BIG_PREVLEN, so we need to check that the prevlen can be encoded in
|
||||
* every consecutive entry.
|
||||
*
|
||||
* Note that this effect can also happen in reverse, where the bytes required
|
||||
* to encode the prevlen field can shrink. This effect is deliberately ignored,
|
||||
@@ -461,7 +613,7 @@ static unsigned char *ziplistResize(unsigned char *zl, unsigned int len) {
|
||||
*
|
||||
* The pointer "p" points to the first entry that does NOT need to be
|
||||
* updated, i.e. consecutive fields MAY need an update. */
|
||||
static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
|
||||
unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
|
||||
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize;
|
||||
size_t offset, noffset, extra;
|
||||
unsigned char *np;
|
||||
@@ -470,7 +622,7 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
|
||||
while (p[0] != ZIP_END) {
|
||||
zipEntry(p, &cur);
|
||||
rawlen = cur.headersize + cur.len;
|
||||
rawlensize = zipPrevEncodeLength(NULL,rawlen);
|
||||
rawlensize = zipStorePrevEntryLength(NULL,rawlen);
|
||||
|
||||
/* Abort if there is no next entry. */
|
||||
if (p[rawlen] == ZIP_END) break;
|
||||
@@ -501,7 +653,7 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
|
||||
memmove(np+rawlensize,
|
||||
np+next.prevrawlensize,
|
||||
curlen-noffset-next.prevrawlensize-1);
|
||||
zipPrevEncodeLength(np,rawlen);
|
||||
zipStorePrevEntryLength(np,rawlen);
|
||||
|
||||
/* Advance the cursor */
|
||||
p += rawlen;
|
||||
@@ -510,9 +662,9 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
|
||||
if (next.prevrawlensize > rawlensize) {
|
||||
/* This would result in shrinking, which we want to avoid.
|
||||
* So, set "rawlen" in the available bytes. */
|
||||
zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);
|
||||
zipStorePrevEntryLengthLarge(p+rawlen,rawlen);
|
||||
} else {
|
||||
zipPrevEncodeLength(p+rawlen,rawlen);
|
||||
zipStorePrevEntryLength(p+rawlen,rawlen);
|
||||
}
|
||||
|
||||
/* Stop here, as the raw length of "next" has not changed. */
|
||||
@@ -523,7 +675,7 @@ static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p
|
||||
}
|
||||
|
||||
/* Delete "num" entries, starting at "p". Returns pointer to the ziplist. */
|
||||
static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
|
||||
unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
|
||||
unsigned int i, totlen, deleted = 0;
|
||||
size_t offset;
|
||||
int nextdiff = 0;
|
||||
@@ -535,7 +687,7 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
|
||||
deleted++;
|
||||
}
|
||||
|
||||
totlen = p-first.p;
|
||||
totlen = p-first.p; /* Bytes taken by the element(s) to delete. */
|
||||
if (totlen > 0) {
|
||||
if (p[0] != ZIP_END) {
|
||||
/* Storing `prevrawlen` in this entry may increase or decrease the
|
||||
@@ -543,8 +695,13 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
|
||||
* There always is room to store this, because it was previously
|
||||
* stored by an entry that is now being deleted. */
|
||||
nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
|
||||
|
||||
/* Note that there is always space when p jumps backward: if
|
||||
* the new previous entry is large, one of the deleted elements
|
||||
* had a 5 bytes prevlen header, so there is for sure at least
|
||||
* 5 bytes free and we need just 4. */
|
||||
p -= nextdiff;
|
||||
zipPrevEncodeLength(p,first.prevrawlen);
|
||||
zipStorePrevEntryLength(p,first.prevrawlen);
|
||||
|
||||
/* Update offset for tail */
|
||||
ZIPLIST_TAIL_OFFSET(zl) =
|
||||
@@ -583,7 +740,7 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
|
||||
}
|
||||
|
||||
/* Insert item at "p". */
|
||||
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
|
||||
unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
|
||||
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
|
||||
unsigned int prevlensize, prevlen = 0;
|
||||
size_t offset;
|
||||
@@ -609,19 +766,24 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
|
||||
/* 'encoding' is set to the appropriate integer encoding */
|
||||
reqlen = zipIntSize(encoding);
|
||||
} else {
|
||||
/* 'encoding' is untouched, however zipEncodeLength will use the
|
||||
/* 'encoding' is untouched, however zipStoreEntryEncoding will use the
|
||||
* string length to figure out how to encode it. */
|
||||
reqlen = slen;
|
||||
}
|
||||
/* We need space for both the length of the previous entry and
|
||||
* the length of the payload. */
|
||||
reqlen += zipPrevEncodeLength(NULL,prevlen);
|
||||
reqlen += zipEncodeLength(NULL,encoding,slen);
|
||||
reqlen += zipStorePrevEntryLength(NULL,prevlen);
|
||||
reqlen += zipStoreEntryEncoding(NULL,encoding,slen);
|
||||
|
||||
/* When the insert position is not equal to the tail, we need to
|
||||
* make sure that the next entry can hold this entry's length in
|
||||
* its prevlen field. */
|
||||
int forcelarge = 0;
|
||||
nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;
|
||||
if (nextdiff == -4 && reqlen < 4) {
|
||||
nextdiff = 0;
|
||||
forcelarge = 1;
|
||||
}
|
||||
|
||||
/* Store offset because a realloc may change the address of zl. */
|
||||
offset = p-zl;
|
||||
@@ -634,7 +796,10 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
|
||||
memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);
|
||||
|
||||
/* Encode this entry's raw length in the next entry. */
|
||||
zipPrevEncodeLength(p+reqlen,reqlen);
|
||||
if (forcelarge)
|
||||
zipStorePrevEntryLengthLarge(p+reqlen,reqlen);
|
||||
else
|
||||
zipStorePrevEntryLength(p+reqlen,reqlen);
|
||||
|
||||
/* Update offset for tail */
|
||||
ZIPLIST_TAIL_OFFSET(zl) =
|
||||
@@ -662,8 +827,8 @@ static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsig
|
||||
}
|
||||
|
||||
/* Write the entry */
|
||||
p += zipPrevEncodeLength(p,prevlen);
|
||||
p += zipEncodeLength(p,encoding,slen);
|
||||
p += zipStorePrevEntryLength(p,prevlen);
|
||||
p += zipStoreEntryEncoding(p,encoding,slen);
|
||||
if (ZIP_IS_STR(encoding)) {
|
||||
memcpy(p,s,slen);
|
||||
} else {
|
||||
@@ -1029,7 +1194,7 @@ void ziplistRepr(unsigned char *zl) {
|
||||
|
||||
printf(
|
||||
"{total bytes %d} "
|
||||
"{length %u}\n"
|
||||
"{num entries %u}\n"
|
||||
"{tail offset %u}\n",
|
||||
intrev32ifbe(ZIPLIST_BYTES(zl)),
|
||||
intrev16ifbe(ZIPLIST_LENGTH(zl)),
|
||||
@@ -1038,16 +1203,15 @@ void ziplistRepr(unsigned char *zl) {
|
||||
while(*p != ZIP_END) {
|
||||
zipEntry(p, &entry);
|
||||
printf(
|
||||
"{"
|
||||
"addr 0x%08lx, "
|
||||
"index %2d, "
|
||||
"offset %5ld, "
|
||||
"rl: %5u, "
|
||||
"hs %2u, "
|
||||
"pl: %5u, "
|
||||
"pls: %2u, "
|
||||
"payload %5u"
|
||||
"} ",
|
||||
"{\n"
|
||||
"\taddr 0x%08lx,\n"
|
||||
"\tindex %2d,\n"
|
||||
"\toffset %5ld,\n"
|
||||
"\thdr+entry len: %5u,\n"
|
||||
"\thdr len%2u,\n"
|
||||
"\tprevrawlen: %5u,\n"
|
||||
"\tprevrawlensize: %2u,\n"
|
||||
"\tpayload %5u\n",
|
||||
(long unsigned)p,
|
||||
index,
|
||||
(unsigned long) (p-zl),
|
||||
@@ -1056,8 +1220,14 @@ void ziplistRepr(unsigned char *zl) {
|
||||
entry.prevrawlen,
|
||||
entry.prevrawlensize,
|
||||
entry.len);
|
||||
printf("\tbytes: ");
|
||||
for (unsigned int i = 0; i < entry.headersize+entry.len; i++) {
|
||||
printf("%02x|",p[i]);
|
||||
}
|
||||
printf("\n");
|
||||
p += entry.headersize;
|
||||
if (ZIP_IS_STR(entry.encoding)) {
|
||||
printf("\t[str]");
|
||||
if (entry.len > 40) {
|
||||
if (fwrite(p,40,1,stdout) == 0) perror("fwrite");
|
||||
printf("...");
|
||||
@@ -1066,9 +1236,9 @@ void ziplistRepr(unsigned char *zl) {
|
||||
fwrite(p,entry.len,1,stdout) == 0) perror("fwrite");
|
||||
}
|
||||
} else {
|
||||
printf("%lld", (long long) zipLoadInteger(p,entry.encoding));
|
||||
printf("\t[int]%lld", (long long) zipLoadInteger(p,entry.encoding));
|
||||
}
|
||||
printf("\n");
|
||||
printf("\n}\n");
|
||||
p += entry.len;
|
||||
index++;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user