Merge remote-tracking branch 'origin/unstable' into 7.2
This commit is contained in:
@@ -18,6 +18,12 @@ jobs:
|
||||
./runtest --verbose --tags -slow --dump-logs
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --verbose --dump-logs
|
||||
- name: validate commands.def up to date
|
||||
run: |
|
||||
touch src/commands/ping.json
|
||||
make commands.def
|
||||
dirty=$(git diff)
|
||||
if [[ ! -z $dirty ]]; then echo $dirty; exit 1; fi
|
||||
|
||||
test-sanitizer-address:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -891,7 +891,7 @@ jobs:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: test
|
||||
uses: vmactions/freebsd-vm@v0.3.0
|
||||
uses: vmactions/freebsd-vm@v0.3.1
|
||||
with:
|
||||
usesh: true
|
||||
sync: rsync
|
||||
@@ -923,7 +923,7 @@ jobs:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: test
|
||||
uses: vmactions/freebsd-vm@v0.3.0
|
||||
uses: vmactions/freebsd-vm@v0.3.1
|
||||
with:
|
||||
usesh: true
|
||||
sync: rsync
|
||||
@@ -954,7 +954,7 @@ jobs:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: test
|
||||
uses: vmactions/freebsd-vm@v0.3.0
|
||||
uses: vmactions/freebsd-vm@v0.3.1
|
||||
with:
|
||||
usesh: true
|
||||
sync: rsync
|
||||
|
||||
Vendored
+7
-1
@@ -3,6 +3,7 @@
|
||||
uname_S:= $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
|
||||
LUA_DEBUG?=no
|
||||
LUA_COVERAGE?=no
|
||||
|
||||
CCCOLOR="\033[34m"
|
||||
LINKCOLOR="\033[34;1m"
|
||||
@@ -85,6 +86,11 @@ ifeq ($(LUA_DEBUG),yes)
|
||||
else
|
||||
LUA_CFLAGS+= -O2
|
||||
endif
|
||||
ifeq ($(LUA_COVERAGE),yes)
|
||||
LUA_CFLAGS += -fprofile-arcs -ftest-coverage
|
||||
LUA_LDFLAGS += -fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
|
||||
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
|
||||
# challenging to cross-compile lua (and redis). These defines make it easier
|
||||
# to fit redis into cross-compilation environments, which typically set AR.
|
||||
@@ -106,7 +112,7 @@ endif
|
||||
|
||||
jemalloc: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd jemalloc && ./configure --disable-cxx --with-version=5.3.0-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" $(JEMALLOC_CONFIGURE_OPTS)
|
||||
cd jemalloc && ./configure --disable-cxx --with-version=5.3.0-0-g0 --with-lg-quantum=3 --disable-cache-oblivious --with-jemalloc-prefix=je_ CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" $(JEMALLOC_CONFIGURE_OPTS)
|
||||
cd jemalloc && $(MAKE) lib/libjemalloc.a
|
||||
|
||||
.PHONY: jemalloc
|
||||
|
||||
Vendored
+1
-6
@@ -61,12 +61,7 @@ cd deps/jemalloc
|
||||
Hiredis
|
||||
---
|
||||
|
||||
Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care:
|
||||
|
||||
1. Check with diff if hiredis API changed and what impact it could have in Redis.
|
||||
2. Make sure that the SDS library inside Hiredis and inside Redis are compatible.
|
||||
3. After the upgrade, run the Redis Sentinel test.
|
||||
4. Check manually that redis-cli and redis-benchmark behave as expected, since we have no tests for CLI utilities currently.
|
||||
Hiredis is used by Sentinel, `redis-cli` and `redis-benchmark`. Like Redis, uses the SDS string library, but not necessarily the same version. In order to avoid conflicts, this version has all SDS identifiers prefixed by `hi`.
|
||||
|
||||
Linenoise
|
||||
---
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
name-template: '$NEXT_MAJOR_VERSION'
|
||||
tag-template: 'v$NEXT_MAJOR_VERSION'
|
||||
autolabeler:
|
||||
- label: 'maintenance'
|
||||
files:
|
||||
- '*.md'
|
||||
- '.github/*'
|
||||
- label: 'bug'
|
||||
branch:
|
||||
- '/bug-.+'
|
||||
- label: 'maintenance'
|
||||
branch:
|
||||
- '/maintenance-.+'
|
||||
- label: 'feature'
|
||||
branch:
|
||||
- '/feature-.+'
|
||||
categories:
|
||||
- title: 'Breaking Changes'
|
||||
labels:
|
||||
- 'breakingchange'
|
||||
|
||||
- title: '🧪 Experimental Features'
|
||||
labels:
|
||||
- 'experimental'
|
||||
- title: '🚀 New Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- 'fix'
|
||||
- 'bugfix'
|
||||
- 'bug'
|
||||
- 'BUG'
|
||||
- title: '🧰 Maintenance'
|
||||
label: 'maintenance'
|
||||
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
|
||||
exclude-labels:
|
||||
- 'skip-changelog'
|
||||
template: |
|
||||
## Changes
|
||||
|
||||
$CHANGES
|
||||
|
||||
## Contributors
|
||||
We'd like to thank all the contributors who worked on this release!
|
||||
|
||||
$CONTRIBUTORS
|
||||
|
||||
+23
-51
@@ -6,24 +6,21 @@ jobs:
|
||||
name: Ubuntu
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:chris-lea/redis-server
|
||||
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
|
||||
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y redis-server valgrind libevent-dev
|
||||
|
||||
|
||||
- name: Build using cmake
|
||||
env:
|
||||
env:
|
||||
EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON
|
||||
CFLAGS: -Werror
|
||||
CXXFLAGS: -Werror
|
||||
run: mkdir build-ubuntu && cd build-ubuntu && cmake ..
|
||||
run: mkdir build && cd build && cmake .. && make
|
||||
|
||||
- name: Build using makefile
|
||||
run: USE_SSL=1 TEST_ASYNC=1 make
|
||||
@@ -45,11 +42,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: centos:7
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -58,11 +51,11 @@ jobs:
|
||||
yum -y install gcc gcc-c++ make openssl openssl-devel cmake3 valgrind libevent-devel
|
||||
|
||||
- name: Build using cmake
|
||||
env:
|
||||
env:
|
||||
EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON
|
||||
CFLAGS: -Werror
|
||||
CXXFLAGS: -Werror
|
||||
run: mkdir build-centos7 && cd build-centos7 && cmake3 ..
|
||||
run: mkdir build && cd build && cmake3 .. && make
|
||||
|
||||
- name: Build using Makefile
|
||||
run: USE_SSL=1 TEST_ASYNC=1 make
|
||||
@@ -85,25 +78,22 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: rockylinux:8
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
dnf -y upgrade --refresh
|
||||
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
|
||||
dnf -y module install redis:remi-6.0
|
||||
dnf -y group install "Development Tools"
|
||||
dnf -y install openssl-devel cmake valgrind libevent-devel
|
||||
|
||||
- name: Build using cmake
|
||||
env:
|
||||
env:
|
||||
EXTRA_CMAKE_OPTS: -DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON -DENABLE_SSL_TESTS:BOOL=ON -DENABLE_ASYNC_TESTS:BOOL=ON
|
||||
CFLAGS: -Werror
|
||||
CXXFLAGS: -Werror
|
||||
run: mkdir build-centos8 && cd build-centos8 && cmake ..
|
||||
run: mkdir build && cd build && cmake .. && make
|
||||
|
||||
- name: Build using Makefile
|
||||
run: USE_SSL=1 TEST_ASYNC=1 make
|
||||
@@ -122,17 +112,13 @@ jobs:
|
||||
run: $GITHUB_WORKSPACE/test.sh
|
||||
|
||||
freebsd:
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-12
|
||||
name: FreeBSD
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build in FreeBSD
|
||||
uses: vmactions/freebsd-vm@v0.1.5
|
||||
uses: vmactions/freebsd-vm@v0
|
||||
with:
|
||||
prepare: pkg install -y gmake cmake
|
||||
run: |
|
||||
@@ -143,15 +129,12 @@ jobs:
|
||||
name: macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew install openssl redis
|
||||
brew install openssl redis@6.2
|
||||
brew link redis@6.2 --force
|
||||
|
||||
- name: Build hiredis
|
||||
run: USE_SSL=1 make
|
||||
@@ -165,11 +148,7 @@ jobs:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -186,20 +165,13 @@ jobs:
|
||||
run: |
|
||||
./build/hiredis-test.exe
|
||||
|
||||
- name: Setup cygwin
|
||||
uses: egor-tensin/setup-cygwin@v3
|
||||
- name: Install Cygwin Action
|
||||
uses: cygwin/cygwin-install-action@v2
|
||||
with:
|
||||
platform: x64
|
||||
packages: make git gcc-core
|
||||
|
||||
- name: Build in cygwin
|
||||
env:
|
||||
HIREDIS_PATH: ${{ github.workspace }}
|
||||
run: |
|
||||
build_hiredis() {
|
||||
cd $(cygpath -u $HIREDIS_PATH)
|
||||
git clean -xfd
|
||||
make
|
||||
}
|
||||
build_hiredis
|
||||
shell: C:\tools\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}'
|
||||
make clean && make
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
# branches to consider in the event; optional, defaults to all
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Drafts your next Release notes as Pull Requests are merged into "master"
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
|
||||
config-name: release-drafter-config.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
name: C/C++ CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
full-build:
|
||||
name: Build all, plus default examples, run tests against redis
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# the docker image used by the test.sh
|
||||
REDIS_DOCKER: redis:alpine
|
||||
|
||||
steps:
|
||||
- name: Install prerequisites
|
||||
run: sudo apt-get update && sudo apt-get install -y libev-dev libevent-dev libglib2.0-dev libssl-dev valgrind
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run make
|
||||
run: make all examples
|
||||
- name: Run unittests
|
||||
run: make check
|
||||
- name: Run tests with valgrind
|
||||
env:
|
||||
TEST_PREFIX: valgrind --error-exitcode=100
|
||||
SKIPS_ARG: --skip-throughput
|
||||
run: make check
|
||||
|
||||
build-32-bit:
|
||||
name: Build and test minimal 32 bit linux
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install prerequisites
|
||||
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run make
|
||||
run: make all
|
||||
env:
|
||||
PLATFORM_FLAGS: -m32
|
||||
- name: Run unittests
|
||||
env:
|
||||
REDIS_DOCKER: redis:alpine
|
||||
run: make check
|
||||
|
||||
build-arm:
|
||||
name: Cross-compile and test arm linux with Qemu
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: arm
|
||||
toolset: arm-linux-gnueabi
|
||||
emulator: qemu-arm
|
||||
- name: aarch64
|
||||
toolset: aarch64-linux-gnu
|
||||
emulator: qemu-aarch64
|
||||
|
||||
steps:
|
||||
- name: Install qemu
|
||||
if: matrix.emulator
|
||||
run: sudo apt-get install -y qemu-user
|
||||
- name: Install platform toolset
|
||||
if: matrix.toolset
|
||||
run: sudo apt-get install -y gcc-${{matrix.toolset}}
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run make
|
||||
run: make all
|
||||
env:
|
||||
CC: ${{matrix.toolset}}-gcc
|
||||
AR: ${{matrix.toolset}}-ar
|
||||
- name: Run unittests
|
||||
env:
|
||||
REDIS_DOCKER: redis:alpine
|
||||
TEST_PREFIX: ${{matrix.emulator}} -L /usr/${{matrix.toolset}}/
|
||||
run: make check
|
||||
|
||||
build-windows:
|
||||
name: Build and test on windows 64 bit Intel
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: microsoft/setup-msbuild@v1.0.2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run CMake (shared lib)
|
||||
run: cmake -Wno-dev CMakeLists.txt
|
||||
- name: Build hiredis (shared lib)
|
||||
run: MSBuild hiredis.vcxproj /p:Configuration=Debug
|
||||
- name: Run CMake (static lib)
|
||||
run: cmake -Wno-dev CMakeLists.txt -DBUILD_SHARED_LIBS=OFF
|
||||
- name: Build hiredis (static lib)
|
||||
run: MSBuild hiredis.vcxproj /p:Configuration=Debug
|
||||
- name: Build hiredis-test
|
||||
run: MSBuild hiredis-test.vcxproj /p:Configuration=Debug
|
||||
# use memurai, redis compatible server, since it is easy to install. Can't
|
||||
# install official redis containers on the windows runner
|
||||
- name: Install Memurai redis server
|
||||
run: choco install -y memurai-developer.install
|
||||
- name: Run tests
|
||||
run: Debug\hiredis-test.exe
|
||||
Vendored
+165
@@ -1,3 +1,168 @@
|
||||
## [1.1.0](https://github.com/redis/hiredis/tree/v1.1.0) - (2022-11-15)
|
||||
|
||||
Announcing Hiredis v1.1.0 GA with better SSL convenience, new async adapters and a great many bug fixes.
|
||||
|
||||
**NOTE**: Hiredis can now return `nan` in addition to `-inf` and `inf` when returning a `REDIS_REPLY_DOUBLE`.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
- Add support for nan in RESP3 double [@filipecosta90](https://github.com/filipecosta90)
|
||||
([\#1133](https://github.com/redis/hiredis/pull/1133))
|
||||
|
||||
## 🧰 Maintenance
|
||||
|
||||
- Add an example that calls redisCommandArgv [@michael-grunder](https://github.com/michael-grunder)
|
||||
([\#1140](https://github.com/redis/hiredis/pull/1140))
|
||||
- fix flag reference [@pata00](https://github.com/pata00) ([\#1136](https://github.com/redis/hiredis/pull/1136))
|
||||
- Make freeing a NULL redisAsyncContext a no op. [@michael-grunder](https://github.com/michael-grunder)
|
||||
([\#1135](https://github.com/redis/hiredis/pull/1135))
|
||||
- CI updates ([@bjosv](https://github.com/redis/bjosv) ([\#1139](https://github.com/redis/hiredis/pull/1139))
|
||||
|
||||
|
||||
## Contributors
|
||||
We'd like to thank all the contributors who worked on this release!
|
||||
|
||||
<a href="https://github.com/bjsov"><img src="https://github.com/bjosv.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/filipecosta90"><img src="https://github.com/filipecosta90.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/michael-grunder"><img src="https://github.com/michael-grunder.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/pata00"><img src="https://github.com/pata00.png" width="32" height="32"></a>
|
||||
|
||||
## [1.1.0-rc1](https://github.com/redis/hiredis/tree/v1.1.0-rc1) - (2022-11-06)
|
||||
|
||||
Announcing Hiredis v1.1.0-rc1, with better SSL convenience, new async adapters, and a great many bug fixes.
|
||||
|
||||
## 🚀 New Features
|
||||
|
||||
- Add possibility to prefer IPv6, IPv4 or unspecified [@zuiderkwast](https://github.com/zuiderkwast)
|
||||
([\#1096](https://github.com/redis/hiredis/pull/1096))
|
||||
- Add adapters/libhv [@ithewei](https://github.com/ithewei) ([\#904](https://github.com/redis/hiredis/pull/904))
|
||||
- Add timeout support to libhv adapter. [@michael-grunder](https://github.com/michael-grunder) ([\#1109](https://github.com/redis/hiredis/pull/1109))
|
||||
- set default SSL verification path [@adobeturchenko](https://github.com/adobeturchenko) ([\#928](https://github.com/redis/hiredis/pull/928))
|
||||
- Introduce .close method for redisContextFuncs [@pizhenwei](https://github.com/pizhenwei) ([\#1094](https://github.com/redis/hiredis/pull/1094))
|
||||
- Make it possible to set SSL verify mode [@stanhu](https://github.com/stanhu) ([\#1085](https://github.com/redis/hiredis/pull/1085))
|
||||
- Polling adapter and example [@kristjanvalur](https://github.com/kristjanvalur) ([\#932](https://github.com/redis/hiredis/pull/932))
|
||||
- Unsubscribe handling in async [@bjosv](https://github.com/bjosv) ([\#1047](https://github.com/redis/hiredis/pull/1047))
|
||||
- Add timeout support for libuv adapter [@MichaelSuen-thePointer](https://github.com/@MichaelSuenthePointer) ([\#1016](https://github.com/redis/hiredis/pull/1016))
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
- Update for MinGW cross compile [@bit0fun](https://github.com/bit0fun) ([\#1127](https://github.com/redis/hiredis/pull/1127))
|
||||
- fixed CPP build error with adapters/libhv.h [@mtdxc](https://github.com/mtdxc) ([\#1125](https://github.com/redis/hiredis/pull/1125))
|
||||
- Fix protocol error
|
||||
[@michael-grunder](https://github.com/michael-grunder),
|
||||
[@mtuleika-appcast](https://github.com/mtuleika-appcast) ([\#1106](https://github.com/redis/hiredis/pull/1106))
|
||||
- Use a windows specific keepalive function. [@michael-grunder](https://github.com/michael-grunder) ([\#1104](https://github.com/redis/hiredis/pull/1104))
|
||||
- Fix CMake config path on Linux. [@xkszltl](https://github.com/xkszltl) ([\#989](https://github.com/redis/hiredis/pull/989))
|
||||
- Fix potential fault at createDoubleObject [@afcidk](https://github.com/afcidk) ([\#964](https://github.com/redis/hiredis/pull/964))
|
||||
- Fix some undefined behavior [@jengab](https://github.com/jengab) ([\#1091](https://github.com/redis/hiredis/pull/1091))
|
||||
- Copy OOM errors to redisAsyncContext when finding subscribe callback [@bjosv](https://github.com/bjosv) ([\#1090](https://github.com/redis/hiredis/pull/1090))
|
||||
- Maintain backward compatibility with our onConnect callback. [@michael-grunder](https://github.com/michael-grunder) ([\#1087](https://github.com/redis/hiredis/pull/1087))
|
||||
- Fix PUSH handler tests for Redis >= 7.0.5 [@michael-grunder](https://github.com/michael-grunder) ([\#1121](https://github.com/redis/hiredis/pull/1121))
|
||||
- fix heap-buffer-overflow [@zhangtaoXT5](https://github.com/zhangtaoXT5) ([\#957](https://github.com/redis/hiredis/pull/957))
|
||||
- Fix heap-buffer-overflow issue in redisvFormatCommad [@bjosv](https://github.com/bjosv) ([\#1097](https://github.com/redis/hiredis/pull/1097))
|
||||
- Polling adapter requires sockcompat.h [@michael-grunder](https://github.com/michael-grunder) ([\#1095](https://github.com/redis/hiredis/pull/1095))
|
||||
- Illumos test fixes, error message difference for bad hostname test. [@devnexen](https://github.com/devnexen) ([\#901](https://github.com/redis/hiredis/pull/901))
|
||||
- Remove semicolon after do-while in \_EL\_CLEANUP [@sundb](https://github.com/sundb) ([\#905](https://github.com/redis/hiredis/pull/905))
|
||||
- Stability: Support calling redisAsyncCommand and redisAsyncDisconnect from the onConnected callback [@kristjanvalur](https://github.com/kristjanvalur)
|
||||
([\#931](https://github.com/redis/hiredis/pull/931))
|
||||
- Fix async connect on Windows [@kristjanvalur](https://github.com/kristjanvalur) ([\#1073](https://github.com/redis/hiredis/pull/1073))
|
||||
- Fix tests so they work for Redis 7.0 [@michael-grunder](https://github.com/michael-grunder) ([\#1072](https://github.com/redis/hiredis/pull/1072))
|
||||
- Fix warnings on Win64 [@orgads](https://github.com/orgads) ([\#1058](https://github.com/redis/hiredis/pull/1058))
|
||||
- Handle push notifications before or after reply. [@yossigo](https://github.com/yossigo) ([\#1062](https://github.com/redis/hiredis/pull/1062))
|
||||
- Update hiredis sds with improvements found in redis [@bjosv](https://github.com/bjosv) ([\#1045](https://github.com/redis/hiredis/pull/1045))
|
||||
- Avoid incorrect call to the previous reply's callback [@bjosv](https://github.com/bjosv) ([\#1040](https://github.com/redis/hiredis/pull/1040))
|
||||
- fix building on AIX and SunOS [\#1031](https://github.com/redis/hiredis/pull/1031) ([@scddev](https://github.com/scddev))
|
||||
- Allow sending commands after sending an unsubscribe [@bjosv](https://github.com/bjosv) ([\#1036](https://github.com/redis/hiredis/pull/1036))
|
||||
- Correction for command timeout during pubsub [@bjosv](https://github.com/bjosv) ([\#1038](https://github.com/redis/hiredis/pull/1038))
|
||||
- Fix adapters/libevent.h compilation for 64-bit Windows [@pbtummillo](https://github.com/pbtummillo) ([\#937](https://github.com/redis/hiredis/pull/937))
|
||||
- Fix integer overflow when format command larger than 4GB [@sundb](https://github.com/sundb) ([\#1030](https://github.com/redis/hiredis/pull/1030))
|
||||
- Handle array response during subscribe in RESP3 [@bjosv](https://github.com/bjosv) ([\#1014](https://github.com/redis/hiredis/pull/1014))
|
||||
- Support PING while subscribing (RESP2) [@bjosv](https://github.com/bjosv) ([\#1027](https://github.com/redis/hiredis/pull/1027))
|
||||
|
||||
## 🧰 Maintenance
|
||||
|
||||
- CI fixes in preparation of release [@michael-grunder](https://github.com/michael-grunder) ([\#1130](https://github.com/redis/hiredis/pull/1130))
|
||||
- Add do while(0) (protection for macros [@afcidk](https://github.com/afcidk) [\#959](https://github.com/redis/hiredis/pull/959))
|
||||
- Fixup of PR734: Coverage of hiredis.c [@bjosv](https://github.com/bjosv) ([\#1124](https://github.com/redis/hiredis/pull/1124))
|
||||
- CMake corrections for building on Windows [@bjosv](https://github.com/bjosv) ([\#1122](https://github.com/redis/hiredis/pull/1122))
|
||||
- Install on windows fixes [@bjosv](https://github.com/bjosv) ([\#1117](https://github.com/redis/hiredis/pull/1117))
|
||||
- Add libhv example to our standard Makefile [@michael-grunder](https://github.com/michael-grunder) ([\#1108](https://github.com/redis/hiredis/pull/1108))
|
||||
- Additional include directory given by pkg-config [@bjosv](https://github.com/bjosv) ([\#1118](https://github.com/redis/hiredis/pull/1118))
|
||||
- Use __attribute__ when building with Clang on Windows [@bjosv](https://github.com/bjosv) ([\#1115](https://github.com/redis/hiredis/pull/1115))
|
||||
- Minor refactor [@michael-grunder](https://github.com/michael-grunder) ([\#1110](https://github.com/redis/hiredis/pull/1110))
|
||||
- Fix pkgconfig result for hiredis_ssl [@bjosv](https://github.com/bjosv) ([\#1107](https://github.com/redis/hiredis/pull/1107))
|
||||
- Update documentation to explain redisConnectWithOptions. [@michael-grunder](https://github.com/michael-grunder) ([\#1099](https://github.com/redis/hiredis/pull/1099))
|
||||
- uvadapter: reduce number of uv_poll_start calls [@noxiouz](https://github.com/noxiouz) ([\#1098](https://github.com/redis/hiredis/pull/1098))
|
||||
- Regression test for off-by-one parsing error [@bugwz](https://github.com/bugwz) ([\#1092](https://github.com/redis/hiredis/pull/1092))
|
||||
- CMake: remove dict.c form hiredis_sources [@Lipraxde](https://github.com/Lipraxde) ([\#1055](https://github.com/redis/hiredis/pull/1055))
|
||||
- Do store command timeout in the context for redisSetTimeout [@catterer](https://github.com/catterer) ([\#593](https://github.com/redis/hiredis/pull/593), [\#1093](https://github.com/redis/hiredis/pull/1093))
|
||||
- Add GitHub Actions CI workflow for hiredis: Arm, Arm64, 386, windows. [@kristjanvalur](https://github.com/kristjanvalur) ([\#943](https://github.com/redis/hiredis/pull/943))
|
||||
- CI: bump macOS runner version [@SukkaW](https://github.com/SukkaW) ([\#1079](https://github.com/redis/hiredis/pull/1079))
|
||||
- Support for generating release notes [@chayim](https://github.com/chayim) ([\#1083](https://github.com/redis/hiredis/pull/1083))
|
||||
- Improve example for SSL initialization in README.md [@stanhu](https://github.com/stanhu) ([\#1084](https://github.com/redis/hiredis/pull/1084))
|
||||
- Fix README typos [@bjosv](https://github.com/bjosv) ([\#1080](https://github.com/redis/hiredis/pull/1080))
|
||||
- fix cmake version [@smmir-cent](https://github.com/@smmircent) ([\#1050](https://github.com/redis/hiredis/pull/1050))
|
||||
- Use the same name for static and shared libraries [@orgads](https://github.com/orgads) ([\#1057](https://github.com/redis/hiredis/pull/1057))
|
||||
- Embed debug information in windows static .lib file [@kristjanvalur](https://github.com/kristjanvalur) ([\#1054](https://github.com/redis/hiredis/pull/1054))
|
||||
- Improved async documentation [@kristjanvalur](https://github.com/kristjanvalur) ([\#1074](https://github.com/redis/hiredis/pull/1074))
|
||||
- Use official repository for redis package. [@yossigo](https://github.com/yossigo) ([\#1061](https://github.com/redis/hiredis/pull/1061))
|
||||
- Whitelist hiredis repo path in cygwin [@michael-grunder](https://github.com/michael-grunder) ([\#1063](https://github.com/redis/hiredis/pull/1063))
|
||||
- CentOS 8 is EOL, switch to RockyLinux [@michael-grunder](https://github.com/michael-grunder) ([\#1046](https://github.com/redis/hiredis/pull/1046))
|
||||
- CMakeLists.txt: allow building without a C++ compiler [@ffontaine](https://github.com/ffontaine) ([\#872](https://github.com/redis/hiredis/pull/872))
|
||||
- Makefile: move SSL options into a block and refine rules [@pizhenwei](https://github.com/pizhenwei) ([\#997](https://github.com/redis/hiredis/pull/997))
|
||||
- Update CMakeLists.txt for more portability [@EricDeng1001](https://github.com/EricDeng1001) ([\#1005](https://github.com/redis/hiredis/pull/1005))
|
||||
- FreeBSD build fixes + CI [@michael-grunder](https://github.com/michael-grunder) ([\#1026](https://github.com/redis/hiredis/pull/1026))
|
||||
- Add asynchronous test for pubsub using RESP3 [@bjosv](https://github.com/bjosv) ([\#1012](https://github.com/redis/hiredis/pull/1012))
|
||||
- Trigger CI failure when Valgrind issues are found [@bjosv](https://github.com/bjosv) ([\#1011](https://github.com/redis/hiredis/pull/1011))
|
||||
- Move to using make directly in Cygwin [@michael-grunder](https://github.com/michael-grunder) ([\#1020](https://github.com/redis/hiredis/pull/1020))
|
||||
- Add asynchronous API tests [@bjosv](https://github.com/bjosv) ([\#1010](https://github.com/redis/hiredis/pull/1010))
|
||||
- Correcting the build target `coverage` for enabled SSL [@bjosv](https://github.com/bjosv) ([\#1009](https://github.com/redis/hiredis/pull/1009))
|
||||
- GH Actions: Run SSL tests during CI [@bjosv](https://github.com/bjosv) ([\#1008](https://github.com/redis/hiredis/pull/1008))
|
||||
- GH: Actions - Add valgrind and CMake [@michael-grunder](https://github.com/michael-grunder) ([\#1004](https://github.com/redis/hiredis/pull/1004))
|
||||
- Add Centos8 tests in GH Actions [@michael-grunder](https://github.com/michael-grunder) ([\#1001](https://github.com/redis/hiredis/pull/1001))
|
||||
- We should run actions on PRs [@michael-grunder](https://github.com/michael-grunder) (([\#1000](https://github.com/redis/hiredis/pull/1000))
|
||||
- Add Cygwin test in GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#999](https://github.com/redis/hiredis/pull/999))
|
||||
- Add Windows tests in GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#996](https://github.com/redis/hiredis/pull/996))
|
||||
- Switch to GitHub actions [@michael-grunder](https://github.com/michael-grunder) ([\#995](https://github.com/redis/hiredis/pull/995))
|
||||
- Minor refactor of CVE-2021-32765 fix. [@michael-grunder](https://github.com/michael-grunder) ([\#993](https://github.com/redis/hiredis/pull/993))
|
||||
- Remove extra comma from CMake var. [@xkszltl](https://github.com/xkszltl) ([\#988](https://github.com/redis/hiredis/pull/988))
|
||||
- Add REDIS\_OPT\_PREFER\_UNSPEC [@michael-grunder](https://github.com/michael-grunder) ([\#1101](https://github.com/redis/hiredis/pull/1101))
|
||||
|
||||
## Contributors
|
||||
We'd like to thank all the contributors who worked on this release!
|
||||
|
||||
<a href="https://github.com/EricDeng1001"><img src="https://github.com/EricDeng1001.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/Lipraxde"><img src="https://github.com/Lipraxde.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/MichaelSuen-thePointer"><img src="https://github.com/MichaelSuen-thePointer.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/SukkaW"><img src="https://github.com/SukkaW.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/adobeturchenko"><img src="https://github.com/adobeturchenko.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/afcidk"><img src="https://github.com/afcidk.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/bit0fun"><img src="https://github.com/bit0fun.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/bjosv"><img src="https://github.com/bjosv.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/bugwz"><img src="https://github.com/bugwz.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/catterer"><img src="https://github.com/catterer.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/chayim"><img src="https://github.com/chayim.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/devnexen"><img src="https://github.com/devnexen.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/ffontaine"><img src="https://github.com/ffontaine.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/ithewei"><img src="https://github.com/ithewei.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/jengab"><img src="https://github.com/jengab.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/kristjanvalur"><img src="https://github.com/kristjanvalur.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/michael-grunder"><img src="https://github.com/michael-grunder.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/noxiouz"><img src="https://github.com/noxiouz.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/mtdxc"><img src="https://github.com/mtdxc.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/orgads"><img src="https://github.com/orgads.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/pbtummillo"><img src="https://github.com/pbtummillo.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/pizhenwei"><img src="https://github.com/pizhenwei.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/scddev"><img src="https://github.com/scddev.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/smmir-cent"><img src="https://github.com/smmir-cent.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/stanhu"><img src="https://github.com/stanhu.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/sundb"><img src="https://github.com/sundb.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/vturchenko"><img src="https://github.com/vturchenko.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/xkszltl"><img src="https://github.com/xkszltl.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/yossigo"><img src="https://github.com/yossigo.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/zhangtaoXT5"><img src="https://github.com/zhangtaoXT5.png" width="32" height="32"></a>
|
||||
<a href="https://github.com/zuiderkwast"><img src="https://github.com/zuiderkwast.png" width="32" height="32"></a>
|
||||
|
||||
## [1.0.2](https://github.com/redis/hiredis/tree/v1.0.2) - (2021-10-07)
|
||||
|
||||
Announcing Hiredis v1.0.2, which fixes CVE-2021-32765 but returns the SONAME to the correct value of `1.0.0`.
|
||||
|
||||
Vendored
+40
-48
@@ -1,8 +1,10 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.0.0)
|
||||
|
||||
OPTION(BUILD_SHARED_LIBS "Build shared libraries" ON)
|
||||
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
|
||||
OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF)
|
||||
OPTION(ENABLE_SSL_TESTS "Should we test SSL connections" OFF)
|
||||
OPTION(ENABLE_EXAMPLES "Enable building hiredis examples" OFF)
|
||||
OPTION(ENABLE_ASYNC_TESTS "Should we run all asynchronous API tests" OFF)
|
||||
|
||||
MACRO(getVersionBit name)
|
||||
@@ -24,15 +26,11 @@ INCLUDE(GNUInstallDirs)
|
||||
|
||||
# Hiredis requires C99
|
||||
SET(CMAKE_C_STANDARD 99)
|
||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
SET(CMAKE_DEBUG_POSTFIX d)
|
||||
|
||||
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
|
||||
|
||||
SET(hiredis_sources
|
||||
alloc.c
|
||||
async.c
|
||||
dict.c
|
||||
hiredis.c
|
||||
net.c
|
||||
read.c
|
||||
@@ -42,34 +40,30 @@ SET(hiredis_sources
|
||||
SET(hiredis_sources ${hiredis_sources})
|
||||
|
||||
IF(WIN32)
|
||||
ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN)
|
||||
ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN)
|
||||
ENDIF()
|
||||
|
||||
ADD_LIBRARY(hiredis SHARED ${hiredis_sources})
|
||||
ADD_LIBRARY(hiredis_static STATIC ${hiredis_sources})
|
||||
ADD_LIBRARY(hiredis ${hiredis_sources})
|
||||
ADD_LIBRARY(hiredis::hiredis ALIAS hiredis)
|
||||
ADD_LIBRARY(hiredis::hiredis_static ALIAS hiredis_static)
|
||||
set(hiredis_export_name hiredis CACHE STRING "Name of the exported target")
|
||||
set_target_properties(hiredis PROPERTIES EXPORT_NAME ${hiredis_export_name})
|
||||
|
||||
SET_TARGET_PROPERTIES(hiredis
|
||||
PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE
|
||||
VERSION "${HIREDIS_SONAME}")
|
||||
SET_TARGET_PROPERTIES(hiredis_static
|
||||
PROPERTIES COMPILE_PDB_NAME hiredis_static)
|
||||
SET_TARGET_PROPERTIES(hiredis_static
|
||||
PROPERTIES COMPILE_PDB_NAME_DEBUG hiredis_static${CMAKE_DEBUG_POSTFIX})
|
||||
IF(WIN32 OR MINGW)
|
||||
IF(MSVC)
|
||||
SET_TARGET_PROPERTIES(hiredis
|
||||
PROPERTIES COMPILE_FLAGS /Z7)
|
||||
ENDIF()
|
||||
IF(WIN32)
|
||||
TARGET_LINK_LIBRARIES(hiredis PUBLIC ws2_32 crypt32)
|
||||
TARGET_LINK_LIBRARIES(hiredis_static PUBLIC ws2_32 crypt32)
|
||||
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
TARGET_LINK_LIBRARIES(hiredis PUBLIC m)
|
||||
TARGET_LINK_LIBRARIES(hiredis_static PUBLIC m)
|
||||
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "SunOS")
|
||||
TARGET_LINK_LIBRARIES(hiredis PUBLIC socket)
|
||||
TARGET_LINK_LIBRARIES(hiredis_static PUBLIC socket)
|
||||
ENDIF()
|
||||
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis_static PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
|
||||
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
|
||||
|
||||
@@ -99,26 +93,23 @@ set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)
|
||||
|
||||
include(CPack)
|
||||
|
||||
INSTALL(TARGETS hiredis hiredis_static
|
||||
INSTALL(TARGETS hiredis
|
||||
EXPORT hiredis-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if (MSVC)
|
||||
if (MSVC AND BUILD_SHARED_LIBS)
|
||||
INSTALL(FILES $<TARGET_PDB_FILE:hiredis>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
INSTALL(FILES $<TARGET_FILE_DIR:hiredis_static>/$<TARGET_FILE_BASE_NAME:hiredis_static>.pdb
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
endif()
|
||||
|
||||
# For NuGet packages
|
||||
INSTALL(FILES hiredis.targets
|
||||
DESTINATION build/native)
|
||||
|
||||
INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h
|
||||
INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h sockcompat.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
|
||||
|
||||
INSTALL(DIRECTORY adapters
|
||||
@@ -131,9 +122,15 @@ export(EXPORT hiredis-targets
|
||||
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake"
|
||||
NAMESPACE hiredis::)
|
||||
|
||||
SET(CMAKE_CONF_INSTALL_DIR share/hiredis)
|
||||
if(WIN32)
|
||||
SET(CMAKE_CONF_INSTALL_DIR share/hiredis)
|
||||
else()
|
||||
SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis)
|
||||
endif()
|
||||
SET(INCLUDE_INSTALL_DIR include)
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake"
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
configure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
|
||||
PATH_VARS INCLUDE_INSTALL_DIR)
|
||||
@@ -144,6 +141,7 @@ INSTALL(EXPORT hiredis-targets
|
||||
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
|
||||
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/hiredis-config-version.cmake
|
||||
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
|
||||
|
||||
|
||||
@@ -156,12 +154,10 @@ IF(ENABLE_SSL)
|
||||
FIND_PACKAGE(OpenSSL REQUIRED)
|
||||
SET(hiredis_ssl_sources
|
||||
ssl.c)
|
||||
ADD_LIBRARY(hiredis_ssl SHARED
|
||||
${hiredis_ssl_sources})
|
||||
ADD_LIBRARY(hiredis_ssl_static STATIC
|
||||
${hiredis_ssl_sources})
|
||||
ADD_LIBRARY(hiredis_ssl ${hiredis_ssl_sources})
|
||||
ADD_LIBRARY(hiredis::hiredis_ssl ALIAS hiredis_ssl)
|
||||
|
||||
IF (APPLE)
|
||||
IF (APPLE AND BUILD_SHARED_LIBS)
|
||||
SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup")
|
||||
ENDIF()
|
||||
|
||||
@@ -169,34 +165,26 @@ IF(ENABLE_SSL)
|
||||
PROPERTIES
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS TRUE
|
||||
VERSION "${HIREDIS_SONAME}")
|
||||
SET_TARGET_PROPERTIES(hiredis_ssl_static
|
||||
PROPERTIES COMPILE_PDB_NAME hiredis_ssl_static)
|
||||
SET_TARGET_PROPERTIES(hiredis_ssl_static
|
||||
PROPERTIES COMPILE_PDB_NAME_DEBUG hiredis_ssl_static${CMAKE_DEBUG_POSTFIX})
|
||||
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
|
||||
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl_static PRIVATE "${OPENSSL_INCLUDE_DIR}")
|
||||
|
||||
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
|
||||
IF (WIN32 OR MINGW)
|
||||
IF(MSVC)
|
||||
SET_TARGET_PROPERTIES(hiredis_ssl
|
||||
PROPERTIES COMPILE_FLAGS /Z7)
|
||||
ENDIF()
|
||||
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE OpenSSL::SSL)
|
||||
IF(WIN32)
|
||||
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis)
|
||||
TARGET_LINK_LIBRARIES(hiredis_ssl_static PUBLIC hiredis_static)
|
||||
ENDIF()
|
||||
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
|
||||
|
||||
INSTALL(TARGETS hiredis_ssl hiredis_ssl_static
|
||||
INSTALL(TARGETS hiredis_ssl
|
||||
EXPORT hiredis_ssl-targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if (MSVC)
|
||||
if (MSVC AND BUILD_SHARED_LIBS)
|
||||
INSTALL(FILES $<TARGET_PDB_FILE:hiredis_ssl>
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
INSTALL(FILES $<TARGET_FILE_DIR:hiredis_ssl_static>/$<TARGET_FILE_BASE_NAME:hiredis_ssl_static>.pdb
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
CONFIGURATIONS Debug RelWithDebInfo)
|
||||
endif()
|
||||
|
||||
INSTALL(FILES hiredis_ssl.h
|
||||
@@ -209,7 +197,11 @@ IF(ENABLE_SSL)
|
||||
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake"
|
||||
NAMESPACE hiredis::)
|
||||
|
||||
SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)
|
||||
if(WIN32)
|
||||
SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)
|
||||
else()
|
||||
SET(CMAKE_CONF_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/hiredis_ssl)
|
||||
endif()
|
||||
configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
|
||||
PATH_VARS INCLUDE_INSTALL_DIR)
|
||||
@@ -241,5 +233,5 @@ ENDIF()
|
||||
|
||||
# Add examples
|
||||
IF(ENABLE_EXAMPLES)
|
||||
ADD_SUBDIRECTORY(examples)
|
||||
ADD_SUBDIRECTORY(examples)
|
||||
ENDIF(ENABLE_EXAMPLES)
|
||||
|
||||
Vendored
+35
-19
@@ -4,7 +4,7 @@
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o
|
||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push
|
||||
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push hiredis-example-poll
|
||||
TESTS=hiredis-test
|
||||
LIBNAME=libhiredis
|
||||
PKGCONFNAME=hiredis.pc
|
||||
@@ -41,7 +41,7 @@ CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || ec
|
||||
OPTIMIZATION?=-O3
|
||||
WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
|
||||
DEBUG_FLAGS?= -g -ggdb
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
|
||||
REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(PLATFORM_FLAGS)
|
||||
REAL_LDFLAGS=$(LDFLAGS)
|
||||
|
||||
DYLIBSUFFIX=so
|
||||
@@ -50,7 +50,7 @@ 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)
|
||||
DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
|
||||
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
|
||||
STLIB_MAKE_CMD=$(AR) rcs
|
||||
|
||||
@@ -63,7 +63,7 @@ SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
|
||||
SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
|
||||
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
|
||||
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
|
||||
SSL_DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME)
|
||||
SSL_DYLIB_MAKE_CMD=$(CC) $(PLATFORM_FLAGS) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME)
|
||||
|
||||
USE_SSL?=0
|
||||
ifeq ($(USE_SSL),1)
|
||||
@@ -92,18 +92,25 @@ ifeq ($(TEST_ASYNC),1)
|
||||
endif
|
||||
|
||||
ifeq ($(USE_SSL),1)
|
||||
ifeq ($(uname_S),Linux)
|
||||
ifdef OPENSSL_PREFIX
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
|
||||
else
|
||||
SSL_LDFLAGS=-lssl -lcrypto
|
||||
ifndef OPENSSL_PREFIX
|
||||
ifeq ($(uname_S),Darwin)
|
||||
SEARCH_PATH1=/opt/homebrew/opt/openssl
|
||||
SEARCH_PATH2=/usr/local/opt/openssl
|
||||
|
||||
ifneq ($(wildcard $(SEARCH_PATH1)),)
|
||||
OPENSSL_PREFIX=$(SEARCH_PATH1)
|
||||
else ifneq ($(wildcard $(SEARCH_PATH2)),)
|
||||
OPENSSL_PREFIX=$(SEARCH_PATH2)
|
||||
endif
|
||||
endif
|
||||
else
|
||||
OPENSSL_PREFIX?=/usr/local/opt/openssl
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
|
||||
endif
|
||||
|
||||
ifdef OPENSSL_PREFIX
|
||||
CFLAGS+=-I$(OPENSSL_PREFIX)/include
|
||||
SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib
|
||||
endif
|
||||
|
||||
SSL_LDFLAGS+=-lssl -lcrypto
|
||||
endif
|
||||
|
||||
ifeq ($(uname_S),FreeBSD)
|
||||
@@ -180,6 +187,9 @@ hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.
|
||||
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-libhv: examples/example-libhv.c adapters/libhv.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lhv $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
@@ -192,6 +202,9 @@ hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
|
||||
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
|
||||
|
||||
hiredis-example-poll: examples/example-poll.c adapters/poll.h $(STLIBNAME)
|
||||
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
|
||||
|
||||
ifndef AE_DIR
|
||||
hiredis-example-ae:
|
||||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||
@@ -269,20 +282,22 @@ $(PKGCONFNAME): hiredis.h
|
||||
@echo prefix=$(PREFIX) > $@
|
||||
@echo exec_prefix=\$${prefix} >> $@
|
||||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/include >> $@
|
||||
@echo pkgincludedir=$(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 >> $@
|
||||
@echo Cflags: -I\$${pkgincludedir} -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
|
||||
|
||||
$(SSL_PKGCONFNAME): hiredis_ssl.h
|
||||
@echo "Generating $@ for pkgconfig..."
|
||||
@echo prefix=$(PREFIX) > $@
|
||||
@echo exec_prefix=\$${prefix} >> $@
|
||||
@echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||
@echo includedir=$(PREFIX)/include >> $@
|
||||
@echo pkgincludedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
|
||||
@echo >> $@
|
||||
@echo Name: hiredis_ssl >> $@
|
||||
@echo Description: SSL Support for hiredis. >> $@
|
||||
@@ -293,7 +308,7 @@ $(SSL_PKGCONFNAME): hiredis_ssl.h
|
||||
|
||||
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) $(SSL_INSTALL)
|
||||
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) hiredis.h async.h read.h sds.h alloc.h sockcompat.h $(INSTALL_INCLUDE_PATH)
|
||||
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
|
||||
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
|
||||
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
|
||||
@@ -330,7 +345,8 @@ coverage: gcov
|
||||
make check
|
||||
mkdir -p tmp/lcov
|
||||
lcov -d . -c --exclude '/usr*' -o tmp/lcov/hiredis.info
|
||||
genhtml --legend -o tmp/lcov/report tmp/lcov/hiredis.info
|
||||
lcov -q -l tmp/lcov/hiredis.info
|
||||
genhtml --legend -q -o tmp/lcov/report tmp/lcov/hiredis.info
|
||||
|
||||
noopt:
|
||||
$(MAKE) OPTIMIZATION=""
|
||||
|
||||
Vendored
+168
-18
@@ -23,6 +23,13 @@ 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 to `1.1.0`
|
||||
|
||||
Almost all users will simply need to recompile their applications against the newer version of hiredis.
|
||||
|
||||
**NOTE**: Hiredis can now return `nan` in addition to `-inf` and `inf` in a `REDIS_REPLY_DOUBLE`.
|
||||
Applications that deal with `RESP3` doubles should make sure to account for this.
|
||||
|
||||
## Upgrading to `1.0.2`
|
||||
|
||||
<span style="color:red">NOTE: v1.0.1 erroneously bumped SONAME, which is why it is skipped here.</span>
|
||||
@@ -82,6 +89,7 @@ an error state. The field `errstr` will contain a string with a description of
|
||||
the error. More information on errors can be found in the **Errors** section.
|
||||
After trying to connect to Redis using `redisConnect` you should
|
||||
check the `err` field to see if establishing the connection was successful:
|
||||
|
||||
```c
|
||||
redisContext *c = redisConnect("127.0.0.1", 6379);
|
||||
if (c == NULL || c->err) {
|
||||
@@ -94,8 +102,74 @@ if (c == NULL || c->err) {
|
||||
}
|
||||
```
|
||||
|
||||
One can also use `redisConnectWithOptions` which takes a `redisOptions` argument
|
||||
that can be configured with endpoint information as well as many different flags
|
||||
to change how the `redisContext` will be configured.
|
||||
|
||||
```c
|
||||
redisOptions opt = {0};
|
||||
|
||||
/* One can set the endpoint with one of our helper macros */
|
||||
if (tcp) {
|
||||
REDIS_OPTIONS_SET_TCP(&opt, "localhost", 6379);
|
||||
} else {
|
||||
REDIS_OPTIONS_SET_UNIX(&opt, "/tmp/redis.sock");
|
||||
}
|
||||
|
||||
/* And privdata can be specified with another helper */
|
||||
REDIS_OPTIONS_SET_PRIVDATA(&opt, myPrivData, myPrivDataDtor);
|
||||
|
||||
/* Finally various options may be set via the `options` member, as described below */
|
||||
opt->options |= REDIS_OPT_PREFER_IPV4;
|
||||
```
|
||||
|
||||
If a connection is lost, `int redisReconnect(redisContext *c)` can be used to restore the connection using the same endpoint and options as the given context.
|
||||
|
||||
### Configurable redisOptions flags
|
||||
|
||||
There are several flags you may set in the `redisOptions` struct to change default behavior. You can specify the flags via the `redisOptions->options` member.
|
||||
|
||||
| Flag | Description |
|
||||
| --- | --- |
|
||||
| REDIS\_OPT\_NONBLOCK | Tells hiredis to make a non-blocking connection. |
|
||||
| REDIS\_OPT\_REUSEADDR | Tells hiredis to set the [SO_REUSEADDR](https://man7.org/linux/man-pages/man7/socket.7.html) socket option |
|
||||
| REDIS\_OPT\_PREFER\_IPV4<br>REDIS\_OPT\_PREFER_IPV6<br>REDIS\_OPT\_PREFER\_IP\_UNSPEC | Informs hiredis to either prefer IPv4 or IPv6 when invoking [getaddrinfo](https://man7.org/linux/man-pages/man3/gai_strerror.3.html). `REDIS_OPT_PREFER_IP_UNSPEC` will cause hiredis to specify `AF_UNSPEC` in the getaddrinfo call, which means both IPv4 and IPv6 addresses will be searched simultaneously.<br>Hiredis prefers IPv4 by default. |
|
||||
| REDIS\_OPT\_NO\_PUSH\_AUTOFREE | Tells hiredis to not install the default RESP3 PUSH handler (which just intercepts and frees the replies). This is useful in situations where you want to process these messages in-band. |
|
||||
| REDIS\_OPT\_NOAUTOFREEREPLIES | **ASYNC**: tells hiredis not to automatically invoke `freeReplyObject` after executing the reply callback. |
|
||||
| REDIS\_OPT\_NOAUTOFREE | **ASYNC**: Tells hiredis not to automatically free the `redisAsyncContext` on connection/communication failure, but only if the user makes an explicit call to `redisAsyncDisconnect` or `redisAsyncFree` |
|
||||
|
||||
*Note: A `redisContext` is not thread-safe.*
|
||||
|
||||
### Other configuration using socket options
|
||||
|
||||
The following socket options are applied directly to the underlying socket.
|
||||
The values are not stored in the `redisContext`, so they are not automatically applied when reconnecting using `redisReconnect()`.
|
||||
These functions return `REDIS_OK` on success.
|
||||
On failure, `REDIS_ERR` is returned and the underlying connection is closed.
|
||||
|
||||
To configure these for an asyncronous context (see *Asynchronous API* below), use `ac->c` to get the redisContext out of an asyncRedisContext.
|
||||
|
||||
```C
|
||||
int redisEnableKeepAlive(redisContext *c);
|
||||
int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
|
||||
```
|
||||
|
||||
Enables TCP keepalive by setting the following socket options (with some variations depending on OS):
|
||||
|
||||
* `SO_KEEPALIVE`;
|
||||
* `TCP_KEEPALIVE` or `TCP_KEEPIDLE`, value configurable using the `interval` parameter, default 15 seconds;
|
||||
* `TCP_KEEPINTVL` set to 1/3 of `interval`;
|
||||
* `TCP_KEEPCNT` set to 3.
|
||||
|
||||
```C
|
||||
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
|
||||
```
|
||||
|
||||
Set the `TCP_USER_TIMEOUT` Linux-specific socket option which is as described in the `tcp` man page:
|
||||
|
||||
> When the value is greater than 0, it specifies the maximum amount of time in milliseconds that trans mitted data may remain unacknowledged before TCP will forcibly close the corresponding connection and return ETIMEDOUT to the application.
|
||||
> If the option value is specified as 0, TCP will use the system default.
|
||||
|
||||
### Sending commands
|
||||
|
||||
There are several ways to issue commands to Redis. The first that will be introduced is
|
||||
@@ -250,7 +324,7 @@ following two execution paths:
|
||||
* Read from the socket until a single reply could be parsed
|
||||
|
||||
The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
|
||||
is expected on the socket. To pipeline commands, the only things that needs to be done is
|
||||
is expected on the socket. To pipeline commands, the only thing that needs to be done is
|
||||
filling up the output buffer. For this cause, two commands can be used that are identical
|
||||
to the `redisCommand` family, apart from not returning a reply:
|
||||
```c
|
||||
@@ -320,23 +394,48 @@ Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The
|
||||
should be checked after creation to see if there were errors creating the connection.
|
||||
Because the connection that will be created is non-blocking, the kernel is not able to
|
||||
instantly return if the specified host and port is able to accept a connection.
|
||||
In case of error, it is the caller's responsibility to free the context using `redisAsyncFree()`
|
||||
|
||||
*Note: A `redisAsyncContext` is not thread-safe.*
|
||||
|
||||
An application function creating a connection might look like this:
|
||||
|
||||
```c
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
void appConnect(myAppData *appData)
|
||||
{
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
redisAsyncFree(c);
|
||||
c = NULL;
|
||||
} else {
|
||||
appData->context = c;
|
||||
appData->connecting = 1;
|
||||
c->data = appData; /* store application pointer for the callbacks */
|
||||
redisAsyncSetConnectCallback(c, appOnConnect);
|
||||
redisAsyncSetDisconnectCallback(c, appOnDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
The asynchronous context _should_ hold a *connect* callback function that is called when the connection
|
||||
attempt completes, either successfully or with an error.
|
||||
It _can_ also hold a *disconnect* callback function that is called when the
|
||||
connection is disconnected (either because of an error or per user request). Both callbacks should
|
||||
have the following prototype:
|
||||
```c
|
||||
void(const redisAsyncContext *c, int status);
|
||||
```
|
||||
|
||||
On a *connect*, the `status` argument is set to `REDIS_OK` if the connection attempt succeeded. In this
|
||||
case, the context is ready to accept commands. If it is called with `REDIS_ERR` then the
|
||||
connection attempt failed. The `err` field in the context can be accessed to find out the cause of the error.
|
||||
After a failed connection attempt, the context object is automatically freed by the library after calling
|
||||
the connect callback. This may be a good point to create a new context and retry the connection.
|
||||
|
||||
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.
|
||||
@@ -344,12 +443,46 @@ field in the context can be accessed to find out the cause of the error.
|
||||
The context object is always freed after the disconnect callback fired. When a reconnect is needed,
|
||||
the disconnect callback is a good point to do so.
|
||||
|
||||
Setting the disconnect callback can only be done once per context. For subsequent calls it will
|
||||
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
|
||||
Setting the connect or disconnect callbacks can only be done once per context. For subsequent calls the
|
||||
api will return `REDIS_ERR`. The function to set the callbacks have the following prototype:
|
||||
```c
|
||||
/* Alternatively you can use redisAsyncSetConnectCallbackNC which will be passed a non-const
|
||||
redisAsyncContext* on invocation (e.g. allowing writes to the privdata member). */
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
```
|
||||
`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
|
||||
`ac->data` may be used to pass user data to both callbacks. A typical implementation
|
||||
might look something like this:
|
||||
```c
|
||||
void appOnConnect(redisAsyncContext *c, int status)
|
||||
{
|
||||
myAppData *appData = (myAppData*)c->data; /* get my application specific context*/
|
||||
appData->connecting = 0;
|
||||
if (status == REDIS_OK) {
|
||||
appData->connected = 1;
|
||||
} else {
|
||||
appData->connected = 0;
|
||||
appData->err = c->err;
|
||||
appData->context = NULL; /* avoid stale pointer when callback returns */
|
||||
}
|
||||
appAttemptReconnect();
|
||||
}
|
||||
|
||||
void appOnDisconnect(redisAsyncContext *c, int status)
|
||||
{
|
||||
myAppData *appData = (myAppData*)c->data; /* get my application specific context*/
|
||||
appData->connected = 0;
|
||||
appData->err = c->err;
|
||||
appData->context = NULL; /* avoid stale pointer when callback returns */
|
||||
if (status == REDIS_OK) {
|
||||
appNotifyDisconnectCompleted(mydata);
|
||||
} else {
|
||||
appNotifyUnexpectedDisconnect(mydata);
|
||||
appAttemptReconnect();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sending commands and their callbacks
|
||||
|
||||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
||||
@@ -382,6 +515,14 @@ valid for the duration of the callback.
|
||||
|
||||
All pending callbacks are called with a `NULL` reply when the context encountered an error.
|
||||
|
||||
For every command issued, with the exception of **SUBSCRIBE** and **PSUBSCRIBE**, the callback is
|
||||
called exactly once. Even if the context object id disconnected or deleted, every pending callback
|
||||
will be called with a `NULL` reply.
|
||||
|
||||
For **SUBSCRIBE** and **PSUBSCRIBE**, the callbacks may be called repeatedly until an `unsubscribe`
|
||||
message arrives. This will be the last invocation of the callback. In case of error, the callbacks
|
||||
may receive a final `NULL` reply instead.
|
||||
|
||||
### Disconnecting
|
||||
|
||||
An asynchronous connection can be terminated using:
|
||||
@@ -394,6 +535,15 @@ have been written to the socket, their respective replies have been read and the
|
||||
callbacks have been executed. After this, the disconnection callback is executed with the
|
||||
`REDIS_OK` status and the context object is freed.
|
||||
|
||||
The connection can be forcefully disconnected using
|
||||
```c
|
||||
void redisAsyncFree(redisAsyncContext *ac);
|
||||
```
|
||||
In this case, nothing more is written to the socket, all pending callbacks are called with a `NULL`
|
||||
reply and the disconnection callback is called with `REDIS_OK`, after which the context object
|
||||
is freed.
|
||||
|
||||
|
||||
### Hooking it up to event library *X*
|
||||
|
||||
There are a few hooks that need to be set on the context object after it is created.
|
||||
@@ -497,8 +647,8 @@ unaffected so no additional dependencies are introduced.
|
||||
First, you'll need to make sure you include the SSL header file:
|
||||
|
||||
```c
|
||||
#include "hiredis.h"
|
||||
#include "hiredis_ssl.h"
|
||||
#include <hiredis/hiredis.h>
|
||||
#include <hiredis/hiredis_ssl.h>
|
||||
```
|
||||
|
||||
You will also need to link against `libhiredis_ssl`, **in addition** to
|
||||
@@ -529,7 +679,7 @@ redisSSLContext *ssl_context;
|
||||
/* An error variable to indicate what went wrong, if the context fails to
|
||||
* initialize.
|
||||
*/
|
||||
redisSSLContextError ssl_error;
|
||||
redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
|
||||
|
||||
/* Initialize global OpenSSL state.
|
||||
*
|
||||
@@ -547,11 +697,11 @@ ssl_context = redisCreateSSLContext(
|
||||
"redis.mydomain.com", /* Server name to request (SNI), optional */
|
||||
&ssl_error);
|
||||
|
||||
if(ssl_context == NULL || ssl_error != 0) {
|
||||
if(ssl_context == NULL || ssl_error != REDIS_SSL_CTX_NONE) {
|
||||
/* Handle error and abort... */
|
||||
/* e.g.
|
||||
printf("SSL error: %s\n",
|
||||
(ssl_error != 0) ?
|
||||
/* e.g.
|
||||
printf("SSL error: %s\n",
|
||||
(ssl_error != REDIS_SSL_CTX_NONE) ?
|
||||
redisSSLContextGetError(ssl_error) : "Unknown error");
|
||||
// Abort
|
||||
*/
|
||||
@@ -633,7 +783,7 @@ If you have a unique use-case where you don't want hiredis to automatically inte
|
||||
redisSetPushCallback(context, NULL);
|
||||
```
|
||||
|
||||
_Note: With no handler configured, calls to `redisCommand` may generate more than one reply, so this strategy is only applicable when there's some kind of blocking`redisGetReply()` loop (e.g. `MONITOR` or `SUBSCRIBE` workloads)._
|
||||
_Note: With no handler configured, calls to `redisCommand` may generate more than one reply, so this strategy is only applicable when there's some kind of blocking `redisGetReply()` loop (e.g. `MONITOR` or `SUBSCRIBE` workloads)._
|
||||
|
||||
## Allocator injection
|
||||
|
||||
|
||||
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
#ifndef __HIREDIS_LIBHV_H__
|
||||
#define __HIREDIS_LIBHV_H__
|
||||
|
||||
#include <hv/hloop.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisLibhvEvents {
|
||||
hio_t *io;
|
||||
htimer_t *timer;
|
||||
} redisLibhvEvents;
|
||||
|
||||
static void redisLibhvHandleEvents(hio_t* io) {
|
||||
redisAsyncContext* context = (redisAsyncContext*)hevent_userdata(io);
|
||||
int events = hio_events(io);
|
||||
int revents = hio_revents(io);
|
||||
if (context && (events & HV_READ) && (revents & HV_READ)) {
|
||||
redisAsyncHandleRead(context);
|
||||
}
|
||||
if (context && (events & HV_WRITE) && (revents & HV_WRITE)) {
|
||||
redisAsyncHandleWrite(context);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibhvAddRead(void *privdata) {
|
||||
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||
hio_add(events->io, redisLibhvHandleEvents, HV_READ);
|
||||
}
|
||||
|
||||
static void redisLibhvDelRead(void *privdata) {
|
||||
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||
hio_del(events->io, HV_READ);
|
||||
}
|
||||
|
||||
static void redisLibhvAddWrite(void *privdata) {
|
||||
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||
hio_add(events->io, redisLibhvHandleEvents, HV_WRITE);
|
||||
}
|
||||
|
||||
static void redisLibhvDelWrite(void *privdata) {
|
||||
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||
hio_del(events->io, HV_WRITE);
|
||||
}
|
||||
|
||||
static void redisLibhvCleanup(void *privdata) {
|
||||
redisLibhvEvents* events = (redisLibhvEvents*)privdata;
|
||||
|
||||
if (events->timer)
|
||||
htimer_del(events->timer);
|
||||
|
||||
hio_close(events->io);
|
||||
hevent_set_userdata(events->io, NULL);
|
||||
|
||||
hi_free(events);
|
||||
}
|
||||
|
||||
static void redisLibhvTimeout(htimer_t* timer) {
|
||||
hio_t* io = (hio_t*)hevent_userdata(timer);
|
||||
redisAsyncHandleTimeout((redisAsyncContext*)hevent_userdata(io));
|
||||
}
|
||||
|
||||
static void redisLibhvSetTimeout(void *privdata, struct timeval tv) {
|
||||
redisLibhvEvents* events;
|
||||
uint32_t millis;
|
||||
hloop_t* loop;
|
||||
|
||||
events = (redisLibhvEvents*)privdata;
|
||||
millis = tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||
|
||||
if (millis == 0) {
|
||||
/* Libhv disallows zero'd timers so treat this as a delete or NO OP */
|
||||
if (events->timer) {
|
||||
htimer_del(events->timer);
|
||||
events->timer = NULL;
|
||||
}
|
||||
} else if (events->timer == NULL) {
|
||||
/* Add new timer */
|
||||
loop = hevent_loop(events->io);
|
||||
events->timer = htimer_add(loop, redisLibhvTimeout, millis, 1);
|
||||
hevent_set_userdata(events->timer, events->io);
|
||||
} else {
|
||||
/* Update existing timer */
|
||||
htimer_reset(events->timer, millis);
|
||||
}
|
||||
}
|
||||
|
||||
static int redisLibhvAttach(redisAsyncContext* ac, hloop_t* loop) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisLibhvEvents *events;
|
||||
hio_t* io = NULL;
|
||||
|
||||
if (ac->ev.data != NULL) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Create container struct to keep track of our io and any timer */
|
||||
events = (redisLibhvEvents*)hi_malloc(sizeof(*events));
|
||||
if (events == NULL) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
io = hio_get(loop, c->fd);
|
||||
if (io == NULL) {
|
||||
hi_free(events);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
hevent_set_userdata(io, ac);
|
||||
|
||||
events->io = io;
|
||||
events->timer = NULL;
|
||||
|
||||
ac->ev.addRead = redisLibhvAddRead;
|
||||
ac->ev.delRead = redisLibhvDelRead;
|
||||
ac->ev.addWrite = redisLibhvAddWrite;
|
||||
ac->ev.delWrite = redisLibhvDelWrite;
|
||||
ac->ev.cleanup = redisLibhvCleanup;
|
||||
ac->ev.scheduleTimer = redisLibhvSetTimeout;
|
||||
ac->ev.data = events;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif
|
||||
Vendored
+177
@@ -0,0 +1,177 @@
|
||||
#ifndef HIREDIS_LIBSDEVENT_H
|
||||
#define HIREDIS_LIBSDEVENT_H
|
||||
#include <systemd/sd-event.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
#define REDIS_LIBSDEVENT_DELETED 0x01
|
||||
#define REDIS_LIBSDEVENT_ENTERED 0x02
|
||||
|
||||
typedef struct redisLibsdeventEvents {
|
||||
redisAsyncContext *context;
|
||||
struct sd_event *event;
|
||||
struct sd_event_source *fdSource;
|
||||
struct sd_event_source *timerSource;
|
||||
int fd;
|
||||
short flags;
|
||||
short state;
|
||||
} redisLibsdeventEvents;
|
||||
|
||||
static void redisLibsdeventDestroy(redisLibsdeventEvents *e) {
|
||||
if (e->fdSource) {
|
||||
e->fdSource = sd_event_source_disable_unref(e->fdSource);
|
||||
}
|
||||
if (e->timerSource) {
|
||||
e->timerSource = sd_event_source_disable_unref(e->timerSource);
|
||||
}
|
||||
sd_event_unref(e->event);
|
||||
hi_free(e);
|
||||
}
|
||||
|
||||
static int redisLibsdeventTimeoutHandler(sd_event_source *s, uint64_t usec, void *userdata) {
|
||||
((void)s);
|
||||
((void)usec);
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||
redisAsyncHandleTimeout(e->context);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int redisLibsdeventHandler(sd_event_source *s, int fd, uint32_t event, void *userdata) {
|
||||
((void)s);
|
||||
((void)fd);
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||
e->state |= REDIS_LIBSDEVENT_ENTERED;
|
||||
|
||||
#define CHECK_DELETED() if (e->state & REDIS_LIBSDEVENT_DELETED) {\
|
||||
redisLibsdeventDestroy(e);\
|
||||
return 0; \
|
||||
}
|
||||
|
||||
if ((event & EPOLLIN) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleRead(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
if ((event & EPOLLOUT) && e->context && (e->state & REDIS_LIBSDEVENT_DELETED) == 0) {
|
||||
redisAsyncHandleWrite(e->context);
|
||||
CHECK_DELETED();
|
||||
}
|
||||
|
||||
e->state &= ~REDIS_LIBSDEVENT_ENTERED;
|
||||
#undef CHECK_DELETED
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void redisLibsdeventAddRead(void *userdata) {
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||
|
||||
if (e->flags & EPOLLIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
e->flags |= EPOLLIN;
|
||||
|
||||
if (e->flags & EPOLLOUT) {
|
||||
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||
} else {
|
||||
sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibsdeventDelRead(void *userdata) {
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||
|
||||
e->flags &= ~EPOLLIN;
|
||||
|
||||
if (e->flags) {
|
||||
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||
} else {
|
||||
e->fdSource = sd_event_source_disable_unref(e->fdSource);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibsdeventAddWrite(void *userdata) {
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||
|
||||
if (e->flags & EPOLLOUT) {
|
||||
return;
|
||||
}
|
||||
|
||||
e->flags |= EPOLLOUT;
|
||||
|
||||
if (e->flags & EPOLLIN) {
|
||||
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||
} else {
|
||||
sd_event_add_io(e->event, &e->fdSource, e->fd, e->flags, redisLibsdeventHandler, e);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibsdeventDelWrite(void *userdata) {
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||
|
||||
e->flags &= ~EPOLLOUT;
|
||||
|
||||
if (e->flags) {
|
||||
sd_event_source_set_io_events(e->fdSource, e->flags);
|
||||
} else {
|
||||
e->fdSource = sd_event_source_disable_unref(e->fdSource);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibsdeventCleanup(void *userdata) {
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents*)userdata;
|
||||
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e->state & REDIS_LIBSDEVENT_ENTERED) {
|
||||
e->state |= REDIS_LIBSDEVENT_DELETED;
|
||||
} else {
|
||||
redisLibsdeventDestroy(e);
|
||||
}
|
||||
}
|
||||
|
||||
static void redisLibsdeventSetTimeout(void *userdata, struct timeval tv) {
|
||||
redisLibsdeventEvents *e = (redisLibsdeventEvents *)userdata;
|
||||
|
||||
uint64_t usec = tv.tv_sec * 1000000 + tv.tv_usec;
|
||||
if (!e->timerSource) {
|
||||
sd_event_add_time_relative(e->event, &e->timerSource, CLOCK_MONOTONIC, usec, 1, redisLibsdeventTimeoutHandler, e);
|
||||
} else {
|
||||
sd_event_source_set_time_relative(e->timerSource, usec);
|
||||
}
|
||||
}
|
||||
|
||||
static int redisLibsdeventAttach(redisAsyncContext *ac, struct sd_event *event) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisLibsdeventEvents *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 = (redisLibsdeventEvents*)hi_calloc(1, sizeof(*e));
|
||||
if (e == NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Initialize and increase event refcount */
|
||||
e->context = ac;
|
||||
e->event = event;
|
||||
e->fd = c->fd;
|
||||
sd_event_ref(event);
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->ev.addRead = redisLibsdeventAddRead;
|
||||
ac->ev.delRead = redisLibsdeventDelRead;
|
||||
ac->ev.addWrite = redisLibsdeventAddWrite;
|
||||
ac->ev.delWrite = redisLibsdeventDelWrite;
|
||||
ac->ev.cleanup = redisLibsdeventCleanup;
|
||||
ac->ev.scheduleTimer = redisLibsdeventSetTimeout;
|
||||
ac->ev.data = e;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif
|
||||
Vendored
+8
@@ -30,6 +30,10 @@ static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
|
||||
static void redisLibuvAddRead(void *privdata) {
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||
|
||||
if (p->events & UV_READABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
p->events |= UV_READABLE;
|
||||
|
||||
uv_poll_start(&p->handle, p->events, redisLibuvPoll);
|
||||
@@ -52,6 +56,10 @@ static void redisLibuvDelRead(void *privdata) {
|
||||
static void redisLibuvAddWrite(void *privdata) {
|
||||
redisLibuvEvents* p = (redisLibuvEvents*)privdata;
|
||||
|
||||
if (p->events & UV_WRITABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
p->events |= UV_WRITABLE;
|
||||
|
||||
uv_poll_start(&p->handle, p->events, redisLibuvPoll);
|
||||
|
||||
Vendored
+197
@@ -0,0 +1,197 @@
|
||||
|
||||
#ifndef HIREDIS_POLL_H
|
||||
#define HIREDIS_POLL_H
|
||||
|
||||
#include "../async.h"
|
||||
#include "../sockcompat.h"
|
||||
#include <string.h> // for memset
|
||||
#include <errno.h>
|
||||
|
||||
/* Values to return from redisPollTick */
|
||||
#define REDIS_POLL_HANDLED_READ 1
|
||||
#define REDIS_POLL_HANDLED_WRITE 2
|
||||
#define REDIS_POLL_HANDLED_TIMEOUT 4
|
||||
|
||||
/* An adapter to allow manual polling of the async context by checking the state
|
||||
* of the underlying file descriptor. Useful in cases where there is no formal
|
||||
* IO event loop but regular ticking can be used, such as in game engines. */
|
||||
|
||||
typedef struct redisPollEvents {
|
||||
redisAsyncContext *context;
|
||||
redisFD fd;
|
||||
char reading, writing;
|
||||
char in_tick;
|
||||
char deleted;
|
||||
double deadline;
|
||||
} redisPollEvents;
|
||||
|
||||
static double redisPollTimevalToDouble(struct timeval *tv) {
|
||||
if (tv == NULL)
|
||||
return 0.0;
|
||||
return tv->tv_sec + tv->tv_usec / 1000000.00;
|
||||
}
|
||||
|
||||
static double redisPollGetNow(void) {
|
||||
#ifndef _MSC_VER
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
return redisPollTimevalToDouble(&tv);
|
||||
#else
|
||||
FILETIME ft;
|
||||
ULARGE_INTEGER li;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
li.HighPart = ft.dwHighDateTime;
|
||||
li.LowPart = ft.dwLowDateTime;
|
||||
return (double)li.QuadPart * 1e-7;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Poll for io, handling any pending callbacks. The timeout argument can be
|
||||
* positive to wait for a maximum given time for IO, zero to poll, or negative
|
||||
* to wait forever */
|
||||
static int redisPollTick(redisAsyncContext *ac, double timeout) {
|
||||
int reading, writing;
|
||||
struct pollfd pfd;
|
||||
int handled;
|
||||
int ns;
|
||||
int itimeout;
|
||||
|
||||
redisPollEvents *e = (redisPollEvents*)ac->ev.data;
|
||||
if (!e)
|
||||
return 0;
|
||||
|
||||
/* local flags, won't get changed during callbacks */
|
||||
reading = e->reading;
|
||||
writing = e->writing;
|
||||
if (!reading && !writing)
|
||||
return 0;
|
||||
|
||||
pfd.fd = e->fd;
|
||||
pfd.events = 0;
|
||||
if (reading)
|
||||
pfd.events = POLLIN;
|
||||
if (writing)
|
||||
pfd.events |= POLLOUT;
|
||||
|
||||
if (timeout >= 0.0) {
|
||||
itimeout = (int)(timeout * 1000.0);
|
||||
} else {
|
||||
itimeout = -1;
|
||||
}
|
||||
|
||||
ns = poll(&pfd, 1, itimeout);
|
||||
if (ns < 0) {
|
||||
/* ignore the EINTR error */
|
||||
if (errno != EINTR)
|
||||
return ns;
|
||||
ns = 0;
|
||||
}
|
||||
|
||||
handled = 0;
|
||||
e->in_tick = 1;
|
||||
if (ns) {
|
||||
if (reading && (pfd.revents & POLLIN)) {
|
||||
redisAsyncHandleRead(ac);
|
||||
handled |= REDIS_POLL_HANDLED_READ;
|
||||
}
|
||||
/* on Windows, connection failure is indicated with the Exception fdset.
|
||||
* handle it the same as writable. */
|
||||
if (writing && (pfd.revents & (POLLOUT | POLLERR))) {
|
||||
/* context Read callback may have caused context to be deleted, e.g.
|
||||
by doing an redisAsyncDisconnect() */
|
||||
if (!e->deleted) {
|
||||
redisAsyncHandleWrite(ac);
|
||||
handled |= REDIS_POLL_HANDLED_WRITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* perform timeouts */
|
||||
if (!e->deleted && e->deadline != 0.0) {
|
||||
double now = redisPollGetNow();
|
||||
if (now >= e->deadline) {
|
||||
/* deadline has passed. disable timeout and perform callback */
|
||||
e->deadline = 0.0;
|
||||
redisAsyncHandleTimeout(ac);
|
||||
handled |= REDIS_POLL_HANDLED_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
/* do a delayed cleanup if required */
|
||||
if (e->deleted)
|
||||
hi_free(e);
|
||||
else
|
||||
e->in_tick = 0;
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
static void redisPollAddRead(void *data) {
|
||||
redisPollEvents *e = (redisPollEvents*)data;
|
||||
e->reading = 1;
|
||||
}
|
||||
|
||||
static void redisPollDelRead(void *data) {
|
||||
redisPollEvents *e = (redisPollEvents*)data;
|
||||
e->reading = 0;
|
||||
}
|
||||
|
||||
static void redisPollAddWrite(void *data) {
|
||||
redisPollEvents *e = (redisPollEvents*)data;
|
||||
e->writing = 1;
|
||||
}
|
||||
|
||||
static void redisPollDelWrite(void *data) {
|
||||
redisPollEvents *e = (redisPollEvents*)data;
|
||||
e->writing = 0;
|
||||
}
|
||||
|
||||
static void redisPollCleanup(void *data) {
|
||||
redisPollEvents *e = (redisPollEvents*)data;
|
||||
|
||||
/* if we are currently processing a tick, postpone deletion */
|
||||
if (e->in_tick)
|
||||
e->deleted = 1;
|
||||
else
|
||||
hi_free(e);
|
||||
}
|
||||
|
||||
static void redisPollScheduleTimer(void *data, struct timeval tv)
|
||||
{
|
||||
redisPollEvents *e = (redisPollEvents*)data;
|
||||
double now = redisPollGetNow();
|
||||
e->deadline = now + redisPollTimevalToDouble(&tv);
|
||||
}
|
||||
|
||||
static int redisPollAttach(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisPollEvents *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 = (redisPollEvents*)hi_malloc(sizeof(*e));
|
||||
if (e == NULL)
|
||||
return REDIS_ERR;
|
||||
memset(e, 0, sizeof(*e));
|
||||
|
||||
e->context = ac;
|
||||
e->fd = c->fd;
|
||||
e->reading = e->writing = 0;
|
||||
e->in_tick = e->deleted = 0;
|
||||
e->deadline = 0.0;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->ev.addRead = redisPollAddRead;
|
||||
ac->ev.delRead = redisPollDelRead;
|
||||
ac->ev.addWrite = redisPollAddWrite;
|
||||
ac->ev.delWrite = redisPollDelWrite;
|
||||
ac->ev.scheduleTimer = redisPollScheduleTimer;
|
||||
ac->ev.cleanup = redisPollCleanup;
|
||||
ac->ev.data = e;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
#endif /* HIREDIS_POLL_H */
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
#ifndef __HIREDIS_REDISMODULEAPI_H__
|
||||
#define __HIREDIS_REDISMODULEAPI_H__
|
||||
|
||||
#include "redismodule.h"
|
||||
|
||||
#include "../async.h"
|
||||
#include "../hiredis.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct redisModuleEvents {
|
||||
redisAsyncContext *context;
|
||||
RedisModuleCtx *module_ctx;
|
||||
int fd;
|
||||
int reading, writing;
|
||||
int timer_active;
|
||||
RedisModuleTimerID timer_id;
|
||||
} redisModuleEvents;
|
||||
|
||||
static inline void redisModuleReadEvent(int fd, void *privdata, int mask) {
|
||||
(void) fd;
|
||||
(void) mask;
|
||||
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
static inline void redisModuleWriteEvent(int fd, void *privdata, int mask) {
|
||||
(void) fd;
|
||||
(void) mask;
|
||||
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
static inline void redisModuleAddRead(void *privdata) {
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
if (!e->reading) {
|
||||
e->reading = 1;
|
||||
RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_READABLE, redisModuleReadEvent, e);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void redisModuleDelRead(void *privdata) {
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
if (e->reading) {
|
||||
e->reading = 0;
|
||||
RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_READABLE);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void redisModuleAddWrite(void *privdata) {
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
if (!e->writing) {
|
||||
e->writing = 1;
|
||||
RedisModule_EventLoopAdd(e->fd, REDISMODULE_EVENTLOOP_WRITABLE, redisModuleWriteEvent, e);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void redisModuleDelWrite(void *privdata) {
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
if (e->writing) {
|
||||
e->writing = 0;
|
||||
RedisModule_EventLoopDel(e->fd, REDISMODULE_EVENTLOOP_WRITABLE);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void redisModuleStopTimer(void *privdata) {
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
if (e->timer_active) {
|
||||
RedisModule_StopTimer(e->module_ctx, e->timer_id, NULL);
|
||||
}
|
||||
e->timer_active = 0;
|
||||
}
|
||||
|
||||
static inline void redisModuleCleanup(void *privdata) {
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
redisModuleDelRead(privdata);
|
||||
redisModuleDelWrite(privdata);
|
||||
redisModuleStopTimer(privdata);
|
||||
hi_free(e);
|
||||
}
|
||||
|
||||
static inline void redisModuleTimeout(RedisModuleCtx *ctx, void *privdata) {
|
||||
(void) ctx;
|
||||
|
||||
redisModuleEvents *e = (redisModuleEvents*)privdata;
|
||||
e->timer_active = 0;
|
||||
redisAsyncHandleTimeout(e->context);
|
||||
}
|
||||
|
||||
static inline void redisModuleSetTimeout(void *privdata, struct timeval tv) {
|
||||
redisModuleEvents* e = (redisModuleEvents*)privdata;
|
||||
|
||||
redisModuleStopTimer(privdata);
|
||||
|
||||
mstime_t millis = tv.tv_sec * 1000 + tv.tv_usec / 1000.0;
|
||||
e->timer_id = RedisModule_CreateTimer(e->module_ctx, millis, redisModuleTimeout, e);
|
||||
e->timer_active = 1;
|
||||
}
|
||||
|
||||
/* Check if Redis version is compatible with the adapter. */
|
||||
static inline int redisModuleCompatibilityCheck(void) {
|
||||
if (!RedisModule_EventLoopAdd ||
|
||||
!RedisModule_EventLoopDel ||
|
||||
!RedisModule_CreateTimer ||
|
||||
!RedisModule_StopTimer) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static inline int redisModuleAttach(redisAsyncContext *ac, RedisModuleCtx *module_ctx) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisModuleEvents *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 = (redisModuleEvents*)hi_malloc(sizeof(*e));
|
||||
if (e == NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
e->context = ac;
|
||||
e->module_ctx = module_ctx;
|
||||
e->fd = c->fd;
|
||||
e->reading = e->writing = 0;
|
||||
e->timer_active = 0;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->ev.addRead = redisModuleAddRead;
|
||||
ac->ev.delRead = redisModuleDelRead;
|
||||
ac->ev.addWrite = redisModuleAddWrite;
|
||||
ac->ev.delWrite = redisModuleDelWrite;
|
||||
ac->ev.cleanup = redisModuleCleanup;
|
||||
ac->ev.scheduleTimer = redisModuleSetTimeout;
|
||||
ac->ev.data = e;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
Vendored
+176
-51
@@ -140,6 +140,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
ac->ev.scheduleTimer = NULL;
|
||||
|
||||
ac->onConnect = NULL;
|
||||
ac->onConnectNC = NULL;
|
||||
ac->onDisconnect = NULL;
|
||||
|
||||
ac->replies.head = NULL;
|
||||
@@ -148,6 +149,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
ac->sub.replies.tail = NULL;
|
||||
ac->sub.channels = channels;
|
||||
ac->sub.patterns = patterns;
|
||||
ac->sub.pending_unsubs = 0;
|
||||
|
||||
return ac;
|
||||
oom:
|
||||
@@ -225,17 +227,34 @@ redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
||||
return redisAsyncConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
||||
if (ac->onConnect == NULL) {
|
||||
ac->onConnect = fn;
|
||||
static int
|
||||
redisAsyncSetConnectCallbackImpl(redisAsyncContext *ac, redisConnectCallback *fn,
|
||||
redisConnectCallbackNC *fn_nc)
|
||||
{
|
||||
/* If either are already set, this is an error */
|
||||
if (ac->onConnect || ac->onConnectNC)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* The common way to detect an established connection is to wait for
|
||||
* the first write event to be fired. This assumes the related event
|
||||
* library functions are already set. */
|
||||
_EL_ADD_WRITE(ac);
|
||||
return REDIS_OK;
|
||||
if (fn) {
|
||||
ac->onConnect = fn;
|
||||
} else if (fn_nc) {
|
||||
ac->onConnectNC = fn_nc;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
|
||||
/* The common way to detect an established connection is to wait for
|
||||
* the first write event to be fired. This assumes the related event
|
||||
* library functions are already set. */
|
||||
_EL_ADD_WRITE(ac);
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
||||
return redisAsyncSetConnectCallbackImpl(ac, fn, NULL);
|
||||
}
|
||||
|
||||
int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn) {
|
||||
return redisAsyncSetConnectCallbackImpl(ac, NULL, fn);
|
||||
}
|
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
|
||||
@@ -302,6 +321,43 @@ static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {
|
||||
}
|
||||
}
|
||||
|
||||
static void __redisRunConnectCallback(redisAsyncContext *ac, int status)
|
||||
{
|
||||
if (ac->onConnect == NULL && ac->onConnectNC == NULL)
|
||||
return;
|
||||
|
||||
if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
|
||||
ac->c.flags |= REDIS_IN_CALLBACK;
|
||||
if (ac->onConnect) {
|
||||
ac->onConnect(ac, status);
|
||||
} else {
|
||||
ac->onConnectNC(ac, status);
|
||||
}
|
||||
ac->c.flags &= ~REDIS_IN_CALLBACK;
|
||||
} else {
|
||||
/* already in callback */
|
||||
if (ac->onConnect) {
|
||||
ac->onConnect(ac, status);
|
||||
} else {
|
||||
ac->onConnectNC(ac, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __redisRunDisconnectCallback(redisAsyncContext *ac, int status)
|
||||
{
|
||||
if (ac->onDisconnect) {
|
||||
if (!(ac->c.flags & REDIS_IN_CALLBACK)) {
|
||||
ac->c.flags |= REDIS_IN_CALLBACK;
|
||||
ac->onDisconnect(ac, status);
|
||||
ac->c.flags &= ~REDIS_IN_CALLBACK;
|
||||
} else {
|
||||
/* already in callback */
|
||||
ac->onDisconnect(ac, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function to free the context. */
|
||||
static void __redisAsyncFree(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
@@ -337,12 +393,11 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
|
||||
|
||||
/* Execute disconnect callback. When redisAsyncFree() initiated destroying
|
||||
* this context, the status will always be REDIS_OK. */
|
||||
if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
|
||||
if (c->flags & REDIS_FREEING) {
|
||||
ac->onDisconnect(ac,REDIS_OK);
|
||||
} else {
|
||||
ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
|
||||
}
|
||||
if (c->flags & REDIS_CONNECTED) {
|
||||
int status = ac->err == 0 ? REDIS_OK : REDIS_ERR;
|
||||
if (c->flags & REDIS_FREEING)
|
||||
status = REDIS_OK;
|
||||
__redisRunDisconnectCallback(ac, status);
|
||||
}
|
||||
|
||||
if (ac->dataCleanup) {
|
||||
@@ -358,7 +413,11 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
|
||||
* free'ing. To do so, a flag is set on the context which is picked up by
|
||||
* redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
|
||||
void redisAsyncFree(redisAsyncContext *ac) {
|
||||
if (ac == NULL)
|
||||
return;
|
||||
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
c->flags |= REDIS_FREEING;
|
||||
if (!(c->flags & REDIS_IN_CALLBACK))
|
||||
__redisAsyncFree(ac);
|
||||
@@ -411,11 +470,11 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
|
||||
redisContext *c = &(ac->c);
|
||||
dict *callbacks;
|
||||
redisCallback *cb;
|
||||
redisCallback *cb = NULL;
|
||||
dictEntry *de;
|
||||
int pvariant;
|
||||
char *stype;
|
||||
hisds sname;
|
||||
hisds sname = NULL;
|
||||
|
||||
/* Match reply with the expected format of a pushed message.
|
||||
* The type and number of elements (3 to 4) are specified at:
|
||||
@@ -432,42 +491,43 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
callbacks = ac->sub.channels;
|
||||
|
||||
/* Locate the right callback */
|
||||
assert(reply->element[1]->type == REDIS_REPLY_STRING);
|
||||
sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
||||
if (sname == NULL)
|
||||
goto oom;
|
||||
if (reply->element[1]->type == REDIS_REPLY_STRING) {
|
||||
sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len);
|
||||
if (sname == NULL) goto oom;
|
||||
|
||||
de = dictFind(callbacks,sname);
|
||||
if (de != NULL) {
|
||||
cb = dictGetEntryVal(de);
|
||||
|
||||
/* If this is an subscribe reply decrease pending counter. */
|
||||
if (strcasecmp(stype+pvariant,"subscribe") == 0) {
|
||||
cb->pending_subs -= 1;
|
||||
if ((de = dictFind(callbacks,sname)) != NULL) {
|
||||
cb = dictGetEntryVal(de);
|
||||
memcpy(dstcb,cb,sizeof(*dstcb));
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(dstcb,cb,sizeof(*dstcb));
|
||||
/* If this is an subscribe reply decrease pending counter. */
|
||||
if (strcasecmp(stype+pvariant,"subscribe") == 0) {
|
||||
assert(cb != NULL);
|
||||
cb->pending_subs -= 1;
|
||||
|
||||
/* If this is an unsubscribe message, remove it. */
|
||||
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
|
||||
if (cb->pending_subs == 0)
|
||||
dictDelete(callbacks,sname);
|
||||
} else if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
|
||||
if (cb == NULL)
|
||||
ac->sub.pending_unsubs -= 1;
|
||||
else if (cb->pending_subs == 0)
|
||||
dictDelete(callbacks,sname);
|
||||
|
||||
/* If this was the last unsubscribe message, revert to
|
||||
* non-subscribe mode. */
|
||||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
||||
/* If this was the last unsubscribe message, revert to
|
||||
* non-subscribe mode. */
|
||||
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
|
||||
|
||||
/* Unset subscribed flag only when no pipelined pending subscribe. */
|
||||
if (reply->element[2]->integer == 0
|
||||
&& dictSize(ac->sub.channels) == 0
|
||||
&& dictSize(ac->sub.patterns) == 0) {
|
||||
c->flags &= ~REDIS_SUBSCRIBED;
|
||||
/* Unset subscribed flag only when no pipelined pending subscribe
|
||||
* or pending unsubscribe replies. */
|
||||
if (reply->element[2]->integer == 0
|
||||
&& dictSize(ac->sub.channels) == 0
|
||||
&& dictSize(ac->sub.patterns) == 0
|
||||
&& ac->sub.pending_unsubs == 0) {
|
||||
c->flags &= ~REDIS_SUBSCRIBED;
|
||||
|
||||
/* Move ongoing regular command callbacks. */
|
||||
redisCallback cb;
|
||||
while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) {
|
||||
__redisPushCallback(&ac->replies,&cb);
|
||||
}
|
||||
/* Move ongoing regular command callbacks. */
|
||||
redisCallback cb;
|
||||
while (__redisShiftCallback(&ac->sub.replies,&cb) == REDIS_OK) {
|
||||
__redisPushCallback(&ac->replies,&cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,6 +539,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
|
||||
return REDIS_OK;
|
||||
oom:
|
||||
__redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
|
||||
__redisAsyncCopyError(ac);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
@@ -540,7 +601,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
|
||||
/* Even if the context is subscribed, pending regular
|
||||
* callbacks will get a reply before pub/sub messages arrive. */
|
||||
redisCallback cb = {NULL, NULL, 0, NULL};
|
||||
redisCallback cb = {NULL, NULL, 0, 0, NULL};
|
||||
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
|
||||
/*
|
||||
* A spontaneous reply in a not-subscribed context can be the error
|
||||
@@ -601,7 +662,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
}
|
||||
|
||||
static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
|
||||
__redisRunConnectCallback(ac, REDIS_ERR);
|
||||
__redisAsyncDisconnect(ac);
|
||||
}
|
||||
|
||||
@@ -626,8 +687,19 @@ static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
|
||||
/* flag us as fully connect, but allow the callback
|
||||
* to disconnect. For that reason, permit the function
|
||||
* to delete the context here after callback return.
|
||||
*/
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
__redisRunConnectCallback(ac, REDIS_OK);
|
||||
if ((ac->c.flags & REDIS_DISCONNECTING)) {
|
||||
redisAsyncDisconnect(ac);
|
||||
return REDIS_ERR;
|
||||
} else if ((ac->c.flags & REDIS_FREEING)) {
|
||||
redisAsyncFree(ac);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
return REDIS_OK;
|
||||
@@ -651,6 +723,8 @@ void redisAsyncRead(redisAsyncContext *ac) {
|
||||
*/
|
||||
void redisAsyncHandleRead(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
/* must not be called from a callback */
|
||||
assert(!(c->flags & REDIS_IN_CALLBACK));
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
/* Abort connect was not successful. */
|
||||
@@ -684,6 +758,8 @@ void redisAsyncWrite(redisAsyncContext *ac) {
|
||||
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
/* must not be called from a callback */
|
||||
assert(!(c->flags & REDIS_IN_CALLBACK));
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
/* Abort connect was not successful. */
|
||||
@@ -700,6 +776,8 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
/* must not be called from a callback */
|
||||
assert(!(c->flags & REDIS_IN_CALLBACK));
|
||||
|
||||
if ((c->flags & REDIS_CONNECTED)) {
|
||||
if (ac->replies.head == NULL && ac->sub.replies.head == NULL) {
|
||||
@@ -719,8 +797,8 @@ void redisAsyncHandleTimeout(redisAsyncContext *ac) {
|
||||
__redisAsyncCopyError(ac);
|
||||
}
|
||||
|
||||
if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
|
||||
ac->onConnect(ac, REDIS_ERR);
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
__redisRunConnectCallback(ac, REDIS_ERR);
|
||||
}
|
||||
|
||||
while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
|
||||
@@ -757,6 +835,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
struct dict *cbdict;
|
||||
dictIterator it;
|
||||
dictEntry *de;
|
||||
redisCallback *existcb;
|
||||
int pvariant, hasnext;
|
||||
@@ -773,6 +852,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
cb.fn = fn;
|
||||
cb.privdata = privdata;
|
||||
cb.pending_subs = 1;
|
||||
cb.unsubscribe_sent = 0;
|
||||
|
||||
/* Find out which command will be appended. */
|
||||
p = nextArgument(cmd,&cstr,&clen);
|
||||
@@ -812,6 +892,51 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
|
||||
* subscribed to one or more channels or patterns. */
|
||||
if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
|
||||
|
||||
if (pvariant)
|
||||
cbdict = ac->sub.patterns;
|
||||
else
|
||||
cbdict = ac->sub.channels;
|
||||
|
||||
if (hasnext) {
|
||||
/* Send an unsubscribe with specific channels/patterns.
|
||||
* Bookkeeping the number of expected replies */
|
||||
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
|
||||
sname = hi_sdsnewlen(astr,alen);
|
||||
if (sname == NULL)
|
||||
goto oom;
|
||||
|
||||
de = dictFind(cbdict,sname);
|
||||
if (de != NULL) {
|
||||
existcb = dictGetEntryVal(de);
|
||||
if (existcb->unsubscribe_sent == 0)
|
||||
existcb->unsubscribe_sent = 1;
|
||||
else
|
||||
/* Already sent, reply to be ignored */
|
||||
ac->sub.pending_unsubs += 1;
|
||||
} else {
|
||||
/* Not subscribed to, reply to be ignored */
|
||||
ac->sub.pending_unsubs += 1;
|
||||
}
|
||||
hi_sdsfree(sname);
|
||||
}
|
||||
} else {
|
||||
/* Send an unsubscribe without specific channels/patterns.
|
||||
* Bookkeeping the number of expected replies */
|
||||
int no_subs = 1;
|
||||
dictInitIterator(&it,cbdict);
|
||||
while ((de = dictNext(&it)) != NULL) {
|
||||
existcb = dictGetEntryVal(de);
|
||||
if (existcb->unsubscribe_sent == 0) {
|
||||
existcb->unsubscribe_sent = 1;
|
||||
no_subs = 0;
|
||||
}
|
||||
}
|
||||
/* Unsubscribing to all channels/patterns, where none is
|
||||
* subscribed to, results in a single reply to be ignored. */
|
||||
if (no_subs == 1)
|
||||
ac->sub.pending_unsubs += 1;
|
||||
}
|
||||
|
||||
/* (P)UNSUBSCRIBE does not have its own response: every channel or
|
||||
* pattern that is unsubscribed will receive a message. This means we
|
||||
* should not append a callback function for this command. */
|
||||
|
||||
Vendored
+5
@@ -46,6 +46,7 @@ typedef struct redisCallback {
|
||||
struct redisCallback *next; /* simple singly linked list */
|
||||
redisCallbackFn *fn;
|
||||
int pending_subs;
|
||||
int unsubscribe_sent;
|
||||
void *privdata;
|
||||
} redisCallback;
|
||||
|
||||
@@ -57,6 +58,7 @@ typedef struct redisCallbackList {
|
||||
/* Connection callback prototypes */
|
||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void (redisConnectCallbackNC)(struct redisAsyncContext *, int status);
|
||||
typedef void(redisTimerCallback)(void *timer, void *privdata);
|
||||
|
||||
/* Context for an async connection to Redis */
|
||||
@@ -92,6 +94,7 @@ typedef struct redisAsyncContext {
|
||||
|
||||
/* Called when the first write event was received. */
|
||||
redisConnectCallback *onConnect;
|
||||
redisConnectCallbackNC *onConnectNC;
|
||||
|
||||
/* Regular command callbacks */
|
||||
redisCallbackList replies;
|
||||
@@ -105,6 +108,7 @@ typedef struct redisAsyncContext {
|
||||
redisCallbackList replies;
|
||||
struct dict *channels;
|
||||
struct dict *patterns;
|
||||
int pending_unsubs;
|
||||
} sub;
|
||||
|
||||
/* Any configured RESP3 PUSH handler */
|
||||
@@ -119,6 +123,7 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
|
||||
const char *source_addr);
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path);
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||
int redisAsyncSetConnectCallbackNC(redisAsyncContext *ac, redisConnectCallbackNC *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
|
||||
redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);
|
||||
|
||||
Vendored
+12
@@ -25,12 +25,24 @@ if (LIBEVENT)
|
||||
TARGET_LINK_LIBRARIES(example-libevent hiredis event)
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LIBHV hv/hv.h)
|
||||
IF (LIBHV)
|
||||
ADD_EXECUTABLE(example-libhv example-libhv.c)
|
||||
TARGET_LINK_LIBRARIES(example-libhv hiredis hv)
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LIBUV uv.h)
|
||||
IF (LIBUV)
|
||||
ADD_EXECUTABLE(example-libuv example-libuv.c)
|
||||
TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
|
||||
ENDIF()
|
||||
|
||||
FIND_PATH(LIBSDEVENT systemd/sd-event.h)
|
||||
IF (LIBSDEVENT)
|
||||
ADD_EXECUTABLE(example-libsdevent example-libsdevent.c)
|
||||
TARGET_LINK_LIBRARIES(example-libsdevent hiredis systemd)
|
||||
ENDIF()
|
||||
|
||||
IF (APPLE)
|
||||
FIND_LIBRARY(CF CoreFoundation)
|
||||
ADD_EXECUTABLE(example-macosx example-macosx.c)
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ int main (int argc, char **argv) {
|
||||
const char *caCert = argc > 5 ? argv[6] : NULL;
|
||||
|
||||
redisSSLContext *ssl;
|
||||
redisSSLContextError ssl_error;
|
||||
redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
|
||||
|
||||
redisInitOpenSSL();
|
||||
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <async.h>
|
||||
#include <adapters/libhv.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 debugCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
(void)privdata;
|
||||
redisReply *reply = r;
|
||||
|
||||
if (reply == NULL) {
|
||||
printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error");
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
#ifndef _WIN32
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
hloop_t* loop = hloop_new(HLOOP_FLAG_QUIT_WHEN_NO_ACTIVE_EVENTS);
|
||||
redisLibhvAttach(c, loop);
|
||||
redisAsyncSetTimeout(c, (struct timeval){.tv_sec = 0, .tv_usec = 500000});
|
||||
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");
|
||||
redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %d", 1);
|
||||
hloop_run(loop);
|
||||
hloop_free(&loop);
|
||||
return 0;
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <async.h>
|
||||
#include <adapters/libsdevent.h>
|
||||
|
||||
void debugCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
(void)privdata;
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) {
|
||||
/* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */
|
||||
printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error");
|
||||
return;
|
||||
}
|
||||
/* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) {
|
||||
printf("`GET key` error: %s\n", c->errstr ? c->errstr : "unknown error");
|
||||
return;
|
||||
}
|
||||
printf("`GET key` result: argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* start another request that demonstrate timeout */
|
||||
redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("connect error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("disconnect because of error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
printf("Disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
struct sd_event *event;
|
||||
sd_event_default(&event);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
redisAsyncFree(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisLibsdeventAttach(c,event);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});
|
||||
|
||||
/*
|
||||
In this demo, we first `set key`, then `get key` to demonstrate the basic usage of libsdevent adapter.
|
||||
Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.
|
||||
Because we have set a 1 second timeout to the connection, the command will always fail with a
|
||||
timeout error, which is shown in the `debugCallback`.
|
||||
*/
|
||||
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
|
||||
/* sd-event does not quit when there are no handlers registered. Manually exit after 1.5 seconds */
|
||||
sd_event_source *s;
|
||||
sd_event_add_time_relative(event, &s, CLOCK_MONOTONIC, 1500000, 1, NULL, NULL);
|
||||
|
||||
sd_event_loop(event);
|
||||
sd_event_source_disable_unref(s);
|
||||
sd_event_unref(event);
|
||||
return 0;
|
||||
}
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <async.h>
|
||||
#include <adapters/poll.h>
|
||||
|
||||
/* Put in the global scope, so that loop can be explicitly stopped */
|
||||
static int exit_loop = 0;
|
||||
|
||||
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);
|
||||
exit_loop = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
exit_loop = 1;
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisPollAttach(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");
|
||||
while (!exit_loop)
|
||||
{
|
||||
redisPollTick(c, 0.1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <hiredis.h>
|
||||
#include <async.h>
|
||||
#include <adapters/redismoduleapi.h>
|
||||
|
||||
void debugCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
(void)privdata; //unused
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) {
|
||||
/* The DEBUG SLEEP command will almost always fail, because we have set a 1 second timeout */
|
||||
printf("`DEBUG SLEEP` error: %s\n", c->errstr ? c->errstr : "unknown error");
|
||||
return;
|
||||
}
|
||||
/* Disconnect after receiving the reply of DEBUG SLEEP (which will not)*/
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) {
|
||||
if (c->errstr) {
|
||||
printf("errstr: %s\n", c->errstr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* start another request that demonstrate timeout */
|
||||
redisAsyncCommand(c, debugCallback, NULL, "DEBUG SLEEP %f", 1.5);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/*
|
||||
* This example requires Redis 7.0 or above.
|
||||
*
|
||||
* 1- Compile this file as a shared library. Directory of "redismodule.h" must
|
||||
* be in the include path.
|
||||
* gcc -fPIC -shared -I../../redis/src/ -I.. example-redismoduleapi.c -o example-redismoduleapi.so
|
||||
*
|
||||
* 2- Load module:
|
||||
* redis-server --loadmodule ./example-redismoduleapi.so value
|
||||
*/
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
|
||||
int ret = RedisModule_Init(ctx, "example-redismoduleapi", 1, REDISMODULE_APIVER_1);
|
||||
if (ret != REDISMODULE_OK) {
|
||||
printf("error module init \n");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
if (redisModuleCompatibilityCheck() != REDIS_OK) {
|
||||
printf("Redis 7.0 or above is required! \n");
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t len;
|
||||
const char *val = RedisModule_StringPtrLen(argv[argc-1], &len);
|
||||
|
||||
RedisModuleCtx *module_ctx = RedisModule_GetDetachedThreadSafeContext(ctx);
|
||||
redisModuleAttach(c, module_ctx);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncSetTimeout(c, (struct timeval){ .tv_sec = 1, .tv_usec = 0});
|
||||
|
||||
/*
|
||||
In this demo, we first `set key`, then `get key` to demonstrate the basic usage of the adapter.
|
||||
Then in `getCallback`, we start a `debug sleep` command to create 1.5 second long request.
|
||||
Because we have set a 1 second timeout to the connection, the command will always fail with a
|
||||
timeout error, which is shown in the `debugCallback`.
|
||||
*/
|
||||
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", val, len);
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
return 0;
|
||||
}
|
||||
Vendored
+3
-4
@@ -12,7 +12,7 @@
|
||||
int main(int argc, char **argv) {
|
||||
unsigned int j;
|
||||
redisSSLContext *ssl;
|
||||
redisSSLContextError ssl_error;
|
||||
redisSSLContextError ssl_error = REDIS_SSL_CTX_NONE;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
if (argc < 4) {
|
||||
@@ -27,9 +27,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
redisInitOpenSSL();
|
||||
ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
|
||||
if (!ssl) {
|
||||
printf("SSL Context error: %s\n",
|
||||
redisSSLContextGetError(ssl_error));
|
||||
if (!ssl || ssl_error != REDIS_SSL_CTX_NONE) {
|
||||
printf("SSL Context error: %s\n", redisSSLContextGetError(ssl_error));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+51
@@ -7,6 +7,54 @@
|
||||
#include <winsock2.h> /* For struct timeval */
|
||||
#endif
|
||||
|
||||
static void example_argv_command(redisContext *c, size_t n) {
|
||||
char **argv, tmp[42];
|
||||
size_t *argvlen;
|
||||
redisReply *reply;
|
||||
|
||||
/* We're allocating two additional elements for command and key */
|
||||
argv = malloc(sizeof(*argv) * (2 + n));
|
||||
argvlen = malloc(sizeof(*argvlen) * (2 + n));
|
||||
|
||||
/* First the command */
|
||||
argv[0] = (char*)"RPUSH";
|
||||
argvlen[0] = sizeof("RPUSH") - 1;
|
||||
|
||||
/* Now our key */
|
||||
argv[1] = (char*)"argvlist";
|
||||
argvlen[1] = sizeof("argvlist") - 1;
|
||||
|
||||
/* Now add the entries we wish to add to the list */
|
||||
for (size_t i = 2; i < (n + 2); i++) {
|
||||
argvlen[i] = snprintf(tmp, sizeof(tmp), "argv-element-%zu", i - 2);
|
||||
argv[i] = strdup(tmp);
|
||||
}
|
||||
|
||||
/* Execute the command using redisCommandArgv. We're sending the arguments with
|
||||
* two explicit arrays. One for each argument's string, and the other for its
|
||||
* length. */
|
||||
reply = redisCommandArgv(c, n + 2, (const char **)argv, (const size_t*)argvlen);
|
||||
|
||||
if (reply == NULL || c->err) {
|
||||
fprintf(stderr, "Error: Couldn't execute redisCommandArgv\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (reply->type == REDIS_REPLY_INTEGER) {
|
||||
printf("%s reply: %lld\n", argv[0], reply->integer);
|
||||
}
|
||||
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Clean up */
|
||||
for (size_t i = 2; i < (n + 2); i++) {
|
||||
free(argv[i]);
|
||||
}
|
||||
|
||||
free(argv);
|
||||
free(argvlen);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned int j, isunix = 0;
|
||||
redisContext *c;
|
||||
@@ -87,6 +135,9 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* See function for an example of redisCommandArgv */
|
||||
example_argv_command(c, 10);
|
||||
|
||||
/* Disconnects and frees the context */
|
||||
redisFree(c);
|
||||
|
||||
|
||||
+2
-3
@@ -48,10 +48,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
memcpy(new_str, data, size);
|
||||
new_str[size] = '\0';
|
||||
|
||||
redisFormatCommand(&cmd, new_str);
|
||||
|
||||
if (cmd != NULL)
|
||||
if (redisFormatCommand(&cmd, new_str) != -1)
|
||||
hi_free(cmd);
|
||||
|
||||
free(new_str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -2,11 +2,11 @@
|
||||
|
||||
set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
|
||||
|
||||
IF (NOT TARGET hiredis::hiredis)
|
||||
IF (NOT TARGET hiredis::@hiredis_export_name@)
|
||||
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)
|
||||
ENDIF()
|
||||
|
||||
SET(hiredis_LIBRARIES hiredis::hiredis)
|
||||
SET(hiredis_LIBRARIES hiredis::@hiredis_export_name@)
|
||||
SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})
|
||||
|
||||
check_required_components(hiredis)
|
||||
|
||||
Vendored
+40
-9
@@ -48,6 +48,7 @@ extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeva
|
||||
extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
|
||||
|
||||
static redisContextFuncs redisContextDefaultFuncs = {
|
||||
.close = redisNetClose,
|
||||
.free_privctx = NULL,
|
||||
.async_read = redisAsyncRead,
|
||||
.async_write = redisAsyncWrite,
|
||||
@@ -221,6 +222,9 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
|
||||
static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
|
||||
redisReply *r, *parent;
|
||||
|
||||
if (len == SIZE_MAX) // Prevents hi_malloc(0) if len equals to SIZE_MAX
|
||||
return NULL;
|
||||
|
||||
r = createReplyObject(REDIS_REPLY_DOUBLE);
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
@@ -399,6 +403,11 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
/* Copy va_list before consuming with va_arg */
|
||||
va_copy(_cpy,ap);
|
||||
|
||||
/* Make sure we have more characters otherwise strchr() accepts
|
||||
* '\0' as an integer specifier. This is checked after above
|
||||
* va_copy() to avoid UB in fmt_invalid's call to va_end(). */
|
||||
if (*_p == '\0') goto fmt_invalid;
|
||||
|
||||
/* Integer conversion (without modifiers) */
|
||||
if (strchr(intfmts,*_p) != NULL) {
|
||||
va_arg(ap,int);
|
||||
@@ -477,6 +486,8 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
|
||||
|
||||
touched = 1;
|
||||
c++;
|
||||
if (*c == '\0')
|
||||
break;
|
||||
}
|
||||
c++;
|
||||
}
|
||||
@@ -719,7 +730,10 @@ static redisContext *redisContextInit(void) {
|
||||
void redisFree(redisContext *c) {
|
||||
if (c == NULL)
|
||||
return;
|
||||
redisNetClose(c);
|
||||
|
||||
if (c->funcs && c->funcs->close) {
|
||||
c->funcs->close(c);
|
||||
}
|
||||
|
||||
hi_sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
@@ -733,7 +747,7 @@ void redisFree(redisContext *c) {
|
||||
if (c->privdata && c->free_privdata)
|
||||
c->free_privdata(c->privdata);
|
||||
|
||||
if (c->funcs->free_privctx)
|
||||
if (c->funcs && c->funcs->free_privctx)
|
||||
c->funcs->free_privctx(c->privctx);
|
||||
|
||||
memset(c, 0xff, sizeof(*c));
|
||||
@@ -756,7 +770,9 @@ int redisReconnect(redisContext *c) {
|
||||
c->privctx = NULL;
|
||||
}
|
||||
|
||||
redisNetClose(c);
|
||||
if (c->funcs && c->funcs->close) {
|
||||
c->funcs->close(c);
|
||||
}
|
||||
|
||||
hi_sdsfree(c->obuf);
|
||||
redisReaderFree(c->reader);
|
||||
@@ -806,6 +822,12 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
|
||||
if (options->options & REDIS_OPT_NOAUTOFREEREPLIES) {
|
||||
c->flags |= REDIS_NO_AUTO_FREE_REPLIES;
|
||||
}
|
||||
if (options->options & REDIS_OPT_PREFER_IPV4) {
|
||||
c->flags |= REDIS_PREFER_IPV4;
|
||||
}
|
||||
if (options->options & REDIS_OPT_PREFER_IPV6) {
|
||||
c->flags |= REDIS_PREFER_IPV6;
|
||||
}
|
||||
|
||||
/* Set any user supplied RESP3 PUSH handler or use freeReplyObject
|
||||
* as a default unless specifically flagged that we don't want one. */
|
||||
@@ -838,7 +860,9 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (options->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
|
||||
if (c->err == 0 && c->fd != REDIS_INVALID_FD &&
|
||||
options->command_timeout != NULL && (c->flags & REDIS_BLOCK))
|
||||
{
|
||||
redisContextSetTimeout(c, *options->command_timeout);
|
||||
}
|
||||
|
||||
@@ -920,11 +944,18 @@ int redisSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int redisEnableKeepAliveWithInterval(redisContext *c, int interval) {
|
||||
return redisKeepAlive(c, interval);
|
||||
}
|
||||
|
||||
/* Enable connection KeepAlive. */
|
||||
int redisEnableKeepAlive(redisContext *c) {
|
||||
if (redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
return REDIS_OK;
|
||||
return redisKeepAlive(c, REDIS_KEEPALIVE_INTERVAL);
|
||||
}
|
||||
|
||||
/* Set the socket option TCP_USER_TIMEOUT. */
|
||||
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout) {
|
||||
return redisContextSetTcpUserTimeout(c, timeout);
|
||||
}
|
||||
|
||||
/* Set a user provided RESP3 PUSH handler and return any old one set. */
|
||||
@@ -964,8 +995,8 @@ int redisBufferRead(redisContext *c) {
|
||||
* 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 occurred trying to write and sets
|
||||
* c->errstr to hold the appropriate error string.
|
||||
* Returns REDIS_ERR if an unrecoverable error occurred in the underlying
|
||||
* c->funcs->write function.
|
||||
*/
|
||||
int redisBufferWrite(redisContext *c, int *done) {
|
||||
|
||||
|
||||
Vendored
+41
-27
@@ -46,9 +46,9 @@ typedef long long ssize_t;
|
||||
#include "alloc.h" /* for allocation wrappers */
|
||||
|
||||
#define HIREDIS_MAJOR 1
|
||||
#define HIREDIS_MINOR 0
|
||||
#define HIREDIS_PATCH 3
|
||||
#define HIREDIS_SONAME 1.0.3-dev
|
||||
#define HIREDIS_MINOR 1
|
||||
#define HIREDIS_PATCH 1
|
||||
#define HIREDIS_SONAME 1.1.1-dev
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
@@ -92,6 +92,11 @@ typedef long long ssize_t;
|
||||
/* Flag that indicates the user does not want replies to be automatically freed */
|
||||
#define REDIS_NO_AUTO_FREE_REPLIES 0x400
|
||||
|
||||
/* Flags to prefer IPv6 or IPv4 when doing DNS lookup. (If both are set,
|
||||
* AF_UNSPEC is used.) */
|
||||
#define REDIS_PREFER_IPV4 0x800
|
||||
#define REDIS_PREFER_IPV6 0x1000
|
||||
|
||||
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
|
||||
|
||||
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
|
||||
@@ -149,20 +154,17 @@ struct redisSsl;
|
||||
|
||||
#define REDIS_OPT_NONBLOCK 0x01
|
||||
#define REDIS_OPT_REUSEADDR 0x02
|
||||
|
||||
/**
|
||||
* Don't automatically free the async object on a connection failure,
|
||||
* or other implicit conditions. Only free on an explicit call to disconnect() or free()
|
||||
*/
|
||||
#define REDIS_OPT_NOAUTOFREE 0x04
|
||||
|
||||
/* Don't automatically intercept and free RESP3 PUSH replies. */
|
||||
#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08
|
||||
|
||||
/**
|
||||
* Don't automatically free replies
|
||||
*/
|
||||
#define REDIS_OPT_NOAUTOFREEREPLIES 0x10
|
||||
#define REDIS_OPT_NOAUTOFREE 0x04 /* Don't automatically free the async
|
||||
* object on a connection failure, or
|
||||
* other implicit conditions. Only free
|
||||
* on an explicit call to disconnect()
|
||||
* or free() */
|
||||
#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08 /* Don't automatically intercept and
|
||||
* free RESP3 PUSH replies. */
|
||||
#define REDIS_OPT_NOAUTOFREEREPLIES 0x10 /* Don't automatically free replies. */
|
||||
#define REDIS_OPT_PREFER_IPV4 0x20 /* Prefer IPv4 in DNS lookups. */
|
||||
#define REDIS_OPT_PREFER_IPV6 0x40 /* Prefer IPv6 in DNS lookups. */
|
||||
#define REDIS_OPT_PREFER_IP_UNSPEC (REDIS_OPT_PREFER_IPV4 | REDIS_OPT_PREFER_IPV6)
|
||||
|
||||
/* In Unix systems a file descriptor is a regular signed int, with -1
|
||||
* representing an invalid descriptor. In Windows it is a SOCKET
|
||||
@@ -220,27 +222,37 @@ typedef struct {
|
||||
/**
|
||||
* Helper macros to initialize options to their specified fields.
|
||||
*/
|
||||
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \
|
||||
(opts)->type = REDIS_CONN_TCP; \
|
||||
(opts)->endpoint.tcp.ip = ip_; \
|
||||
(opts)->endpoint.tcp.port = port_;
|
||||
#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) do { \
|
||||
(opts)->type = REDIS_CONN_TCP; \
|
||||
(opts)->endpoint.tcp.ip = ip_; \
|
||||
(opts)->endpoint.tcp.port = port_; \
|
||||
} while(0)
|
||||
|
||||
#define REDIS_OPTIONS_SET_UNIX(opts, path) \
|
||||
(opts)->type = REDIS_CONN_UNIX; \
|
||||
(opts)->endpoint.unix_socket = path;
|
||||
#define REDIS_OPTIONS_SET_UNIX(opts, path) do { \
|
||||
(opts)->type = REDIS_CONN_UNIX; \
|
||||
(opts)->endpoint.unix_socket = path; \
|
||||
} while(0)
|
||||
|
||||
#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \
|
||||
(opts)->privdata = data; \
|
||||
(opts)->free_privdata = dtor; \
|
||||
#define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) do { \
|
||||
(opts)->privdata = data; \
|
||||
(opts)->free_privdata = dtor; \
|
||||
} while(0)
|
||||
|
||||
typedef struct redisContextFuncs {
|
||||
void (*close)(struct redisContext *);
|
||||
void (*free_privctx)(void *);
|
||||
void (*async_read)(struct redisAsyncContext *);
|
||||
void (*async_write)(struct redisAsyncContext *);
|
||||
|
||||
/* Read/Write data to the underlying communication stream, returning the
|
||||
* number of bytes read/written. In the event of an unrecoverable error
|
||||
* these functions shall return a value < 0. In the event of a
|
||||
* recoverable error, they should return 0. */
|
||||
ssize_t (*read)(struct redisContext *, char *, size_t);
|
||||
ssize_t (*write)(struct redisContext *);
|
||||
} redisContextFuncs;
|
||||
|
||||
|
||||
/* Context for a connection to Redis */
|
||||
typedef struct redisContext {
|
||||
const redisContextFuncs *funcs; /* Function table */
|
||||
@@ -310,6 +322,8 @@ int redisReconnect(redisContext *c);
|
||||
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
|
||||
int redisSetTimeout(redisContext *c, const struct timeval tv);
|
||||
int redisEnableKeepAlive(redisContext *c);
|
||||
int redisEnableKeepAliveWithInterval(redisContext *c, int interval);
|
||||
int redisSetTcpUserTimeout(redisContext *c, unsigned int timeout);
|
||||
void redisFree(redisContext *c);
|
||||
redisFD redisFreeKeepFd(redisContext *c);
|
||||
int redisBufferRead(redisContext *c);
|
||||
|
||||
Vendored
+1
-1
@@ -9,4 +9,4 @@ Name: hiredis
|
||||
Description: Minimalistic C client library for Redis.
|
||||
Version: @PROJECT_VERSION@
|
||||
Libs: -L${libdir} -lhiredis
|
||||
Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
|
||||
Cflags: -I${pkgincludedir} -I${includedir} -D_FILE_OFFSET_BITS=64
|
||||
|
||||
+3
@@ -2,6 +2,9 @@
|
||||
|
||||
set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(OpenSSL)
|
||||
|
||||
IF (NOT TARGET hiredis::hiredis_ssl)
|
||||
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake)
|
||||
ENDIF()
|
||||
|
||||
Vendored
+34
@@ -56,11 +56,33 @@ typedef enum {
|
||||
REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */
|
||||
REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
|
||||
REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
|
||||
REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED, /* Failed to set client default certificate directory */
|
||||
REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED, /* Failed to load private key */
|
||||
REDIS_SSL_CTX_OS_CERTSTORE_OPEN_FAILED, /* Failed to open system certificate store */
|
||||
REDIS_SSL_CTX_OS_CERT_ADD_FAILED /* Failed to add CA certificates obtained from system to the SSL context */
|
||||
} redisSSLContextError;
|
||||
|
||||
/* Constants that mirror OpenSSL's verify modes. By default,
|
||||
* REDIS_SSL_VERIFY_PEER is used with redisCreateSSLContext().
|
||||
* Some Redis clients disable peer verification if there are no
|
||||
* certificates specified.
|
||||
*/
|
||||
#define REDIS_SSL_VERIFY_NONE 0x00
|
||||
#define REDIS_SSL_VERIFY_PEER 0x01
|
||||
#define REDIS_SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
|
||||
#define REDIS_SSL_VERIFY_CLIENT_ONCE 0x04
|
||||
#define REDIS_SSL_VERIFY_POST_HANDSHAKE 0x08
|
||||
|
||||
/* Options to create an OpenSSL context. */
|
||||
typedef struct {
|
||||
const char *cacert_filename;
|
||||
const char *capath;
|
||||
const char *cert_filename;
|
||||
const char *private_key_filename;
|
||||
const char *server_name;
|
||||
int verify_mode;
|
||||
} redisSSLOptions;
|
||||
|
||||
/**
|
||||
* Return the error message corresponding with the specified error code.
|
||||
*/
|
||||
@@ -101,6 +123,18 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
|
||||
const char *cert_filename, const char *private_key_filename,
|
||||
const char *server_name, redisSSLContextError *error);
|
||||
|
||||
/**
|
||||
* Helper function to initialize an OpenSSL context that can be used
|
||||
* to initiate SSL connections. This is a more extensible version of redisCreateSSLContext().
|
||||
*
|
||||
* options contains a structure of SSL options to use.
|
||||
*
|
||||
* If error is non-null, it will be populated in case the context creation fails
|
||||
* (returning a NULL).
|
||||
*/
|
||||
redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options,
|
||||
redisSSLContextError *error);
|
||||
|
||||
/**
|
||||
* Free a previously created OpenSSL context.
|
||||
*/
|
||||
|
||||
Vendored
+2
-1
@@ -1,6 +1,7 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
install_libdir=@CMAKE_INSTALL_LIBDIR@
|
||||
exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
libdir=${exec_prefix}/${install_libdir}
|
||||
includedir=${prefix}/include
|
||||
pkgincludedir=${includedir}/hiredis
|
||||
|
||||
|
||||
Vendored
+77
-18
@@ -50,6 +50,8 @@
|
||||
/* Defined in hiredis.c */
|
||||
void __redisSetError(redisContext *c, int type, const char *str);
|
||||
|
||||
int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
|
||||
|
||||
void redisNetClose(redisContext *c) {
|
||||
if (c && c->fd != REDIS_INVALID_FD) {
|
||||
close(c->fd);
|
||||
@@ -68,7 +70,7 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
__redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
|
||||
return -1;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
__redisSetError(c, REDIS_ERR_IO, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
} else if (nread == 0) {
|
||||
@@ -80,15 +82,19 @@ ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
|
||||
}
|
||||
|
||||
ssize_t redisNetWrite(redisContext *c) {
|
||||
ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0);
|
||||
ssize_t nwritten;
|
||||
|
||||
nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0);
|
||||
if (nwritten < 0) {
|
||||
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
|
||||
/* Try again later */
|
||||
/* Try again */
|
||||
return 0;
|
||||
} else {
|
||||
__redisSetError(c, REDIS_ERR_IO, NULL);
|
||||
__redisSetError(c, REDIS_ERR_IO, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return nwritten;
|
||||
}
|
||||
|
||||
@@ -166,6 +172,7 @@ int redisKeepAlive(redisContext *c, int interval) {
|
||||
int val = 1;
|
||||
redisFD fd = c->fd;
|
||||
|
||||
#ifndef _WIN32
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
|
||||
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
|
||||
return REDIS_ERR;
|
||||
@@ -199,7 +206,15 @@ int redisKeepAlive(redisContext *c, int interval) {
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
int res;
|
||||
|
||||
res = win32_redisKeepAlive(fd, interval * 1000);
|
||||
if (res != 0) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, strerror(res));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
#endif
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -213,6 +228,22 @@ int redisSetTcpNoDelay(redisContext *c) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout) {
|
||||
int res;
|
||||
#ifdef TCP_USER_TIMEOUT
|
||||
res = setsockopt(c->fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
|
||||
#else
|
||||
res = -1;
|
||||
(void)timeout;
|
||||
#endif
|
||||
if (res == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_USER_TIMEOUT)");
|
||||
redisNetClose(c);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
|
||||
|
||||
static int redisContextTimeoutMsec(redisContext *c, long *result)
|
||||
@@ -223,6 +254,7 @@ static int redisContextTimeoutMsec(redisContext *c, long *result)
|
||||
/* Only use timeout when not NULL. */
|
||||
if (timeout != NULL) {
|
||||
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
|
||||
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
|
||||
*result = msec;
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -277,12 +309,28 @@ int redisCheckConnectDone(redisContext *c, int *completed) {
|
||||
*completed = 1;
|
||||
return REDIS_OK;
|
||||
}
|
||||
switch (errno) {
|
||||
int error = errno;
|
||||
if (error == EINPROGRESS) {
|
||||
/* must check error to see if connect failed. Get the socket error */
|
||||
int fail, so_error;
|
||||
socklen_t optlen = sizeof(so_error);
|
||||
fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen);
|
||||
if (fail == 0) {
|
||||
if (so_error == 0) {
|
||||
/* Socket is connected! */
|
||||
*completed = 1;
|
||||
return REDIS_OK;
|
||||
}
|
||||
/* connection error; */
|
||||
errno = so_error;
|
||||
error = so_error;
|
||||
}
|
||||
}
|
||||
switch (error) {
|
||||
case EISCONN:
|
||||
*completed = 1;
|
||||
return REDIS_OK;
|
||||
case EALREADY:
|
||||
case EINPROGRESS:
|
||||
case EWOULDBLOCK:
|
||||
*completed = 0;
|
||||
return REDIS_OK;
|
||||
@@ -317,6 +365,10 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
|
||||
const void *to_ptr = &tv;
|
||||
size_t to_sz = sizeof(tv);
|
||||
|
||||
if (redisContextUpdateCommandTimeout(c, &tv) != REDIS_OK) {
|
||||
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
|
||||
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
|
||||
return REDIS_ERR;
|
||||
@@ -400,7 +452,6 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
}
|
||||
|
||||
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
|
||||
__redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -417,17 +468,25 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
/* Try with IPv6 if no IPv4 address was found. We do it in this order since
|
||||
* in a Redis client you can't afford to test if you have IPv6 connectivity
|
||||
* as this would add latency to every connect. Otherwise a more sensible
|
||||
* route could be: Use IPv6 if both addresses are available and there is IPv6
|
||||
* connectivity. */
|
||||
if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
|
||||
hints.ai_family = AF_INET6;
|
||||
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
/* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and
|
||||
* IPv6. By default, for historical reasons, we try IPv4 first and then we
|
||||
* try IPv6 only if no IPv4 address was found. */
|
||||
if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4)
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
else if (c->flags & REDIS_PREFER_IPV6)
|
||||
hints.ai_family = AF_INET6;
|
||||
else
|
||||
hints.ai_family = AF_INET;
|
||||
|
||||
rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
|
||||
if (rv != 0 && hints.ai_family != AF_UNSPEC) {
|
||||
/* Try again with the other IP version. */
|
||||
hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET;
|
||||
rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo);
|
||||
}
|
||||
if (rv != 0) {
|
||||
__redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
for (p = servinfo; p != NULL; p = p->ai_next) {
|
||||
addrretry:
|
||||
|
||||
Vendored
+1
@@ -52,5 +52,6 @@ int redisKeepAlive(redisContext *c, int interval);
|
||||
int redisCheckConnectDone(redisContext *c, int *completed);
|
||||
|
||||
int redisSetTcpNoDelay(redisContext *c);
|
||||
int redisContextSetTcpUserTimeout(redisContext *c, unsigned int timeout);
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+8
-5
@@ -303,11 +303,14 @@ static int processLineItem(redisReader *r) {
|
||||
d = INFINITY; /* Positive infinite. */
|
||||
} else if (len == 4 && strcasecmp(buf,"-inf") == 0) {
|
||||
d = -INFINITY; /* Negative infinite. */
|
||||
} else if ((len == 3 && strcasecmp(buf,"nan") == 0) ||
|
||||
(len == 4 && strcasecmp(buf, "-nan") == 0)) {
|
||||
d = NAN; /* nan. */
|
||||
} else {
|
||||
d = strtod((char*)buf,&eptr);
|
||||
/* RESP3 only allows "inf", "-inf", and finite values, while
|
||||
* strtod() allows other variations on infinity, NaN,
|
||||
* etc. We explicity handle our two allowed infinite cases
|
||||
* strtod() allows other variations on infinity,
|
||||
* etc. We explicity handle our two allowed infinite cases and NaN
|
||||
* above, so strtod() should only result in finite values. */
|
||||
if (buf[0] == '\0' || eptr != &buf[len] || !isfinite(d)) {
|
||||
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||
@@ -374,7 +377,7 @@ static int processLineItem(redisReader *r) {
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,p,len);
|
||||
else
|
||||
obj = (void*)(size_t)(cur->type);
|
||||
obj = (void*)(uintptr_t)(cur->type);
|
||||
}
|
||||
|
||||
if (obj == NULL) {
|
||||
@@ -439,7 +442,7 @@ static int processBulkItem(redisReader *r) {
|
||||
if (r->fn && r->fn->createString)
|
||||
obj = r->fn->createString(cur,s+2,len);
|
||||
else
|
||||
obj = (void*)(long)cur->type;
|
||||
obj = (void*)(uintptr_t)cur->type;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
@@ -536,7 +539,7 @@ static int processAggregateItem(redisReader *r) {
|
||||
if (r->fn && r->fn->createArray)
|
||||
obj = r->fn->createArray(cur,elements);
|
||||
else
|
||||
obj = (void*)(long)cur->type;
|
||||
obj = (void*)(uintptr_t)cur->type;
|
||||
|
||||
if (obj == NULL) {
|
||||
__redisReaderSetErrorOOM(r);
|
||||
|
||||
Vendored
+11
-8
@@ -90,6 +90,7 @@ hisds hi_sdsnewlen(const void *init, size_t initlen) {
|
||||
int hdrlen = hi_sdsHdrSize(type);
|
||||
unsigned char *fp; /* flags pointer. */
|
||||
|
||||
if (hdrlen+initlen+1 <= initlen) return NULL; /* Catch size_t overflow */
|
||||
sh = hi_s_malloc(hdrlen+initlen+1);
|
||||
if (sh == NULL) return NULL;
|
||||
if (!init)
|
||||
@@ -174,7 +175,7 @@ void hi_sdsfree(hisds s) {
|
||||
* the output will be "6" as the string was modified but the logical length
|
||||
* remains 6 bytes. */
|
||||
void hi_sdsupdatelen(hisds s) {
|
||||
int reallen = strlen(s);
|
||||
size_t reallen = strlen(s);
|
||||
hi_sdssetlen(s, reallen);
|
||||
}
|
||||
|
||||
@@ -196,7 +197,7 @@ void hi_sdsclear(hisds s) {
|
||||
hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) {
|
||||
void *sh, *newsh;
|
||||
size_t avail = hi_sdsavail(s);
|
||||
size_t len, newlen;
|
||||
size_t len, newlen, reqlen;
|
||||
char type, oldtype = s[-1] & HI_SDS_TYPE_MASK;
|
||||
int hdrlen;
|
||||
|
||||
@@ -205,7 +206,8 @@ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) {
|
||||
|
||||
len = hi_sdslen(s);
|
||||
sh = (char*)s-hi_sdsHdrSize(oldtype);
|
||||
newlen = (len+addlen);
|
||||
reqlen = newlen = (len+addlen);
|
||||
if (newlen <= len) return NULL; /* Catch size_t overflow */
|
||||
if (newlen < HI_SDS_MAX_PREALLOC)
|
||||
newlen *= 2;
|
||||
else
|
||||
@@ -219,6 +221,7 @@ hisds hi_sdsMakeRoomFor(hisds s, size_t addlen) {
|
||||
if (type == HI_SDS_TYPE_5) type = HI_SDS_TYPE_8;
|
||||
|
||||
hdrlen = hi_sdsHdrSize(type);
|
||||
if (hdrlen+newlen+1 <= reqlen) return NULL; /* Catch size_t overflow */
|
||||
if (oldtype==type) {
|
||||
newsh = hi_s_realloc(sh, hdrlen+newlen+1);
|
||||
if (newsh == NULL) return NULL;
|
||||
@@ -580,7 +583,7 @@ hisds hi_sdscatprintf(hisds s, const char *fmt, ...) {
|
||||
*/
|
||||
hisds hi_sdscatfmt(hisds s, char const *fmt, ...) {
|
||||
const char *f = fmt;
|
||||
int i;
|
||||
long i;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
@@ -753,16 +756,16 @@ int hi_sdsrange(hisds s, ssize_t start, ssize_t end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Apply tolower() to every character of the hisds string 's'. */
|
||||
/* Apply tolower() to every character of the sds string 's'. */
|
||||
void hi_sdstolower(hisds s) {
|
||||
int len = hi_sdslen(s), j;
|
||||
size_t len = hi_sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
|
||||
}
|
||||
|
||||
/* Apply toupper() to every character of the hisds string 's'. */
|
||||
/* Apply toupper() to every character of the sds string 's'. */
|
||||
void hi_sdstoupper(hisds s) {
|
||||
int len = hi_sdslen(s), j;
|
||||
size_t len = hi_sdslen(s), j;
|
||||
|
||||
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
|
||||
}
|
||||
|
||||
Vendored
+3
-1
@@ -35,9 +35,11 @@
|
||||
|
||||
#define HI_SDS_MAX_PREALLOC (1024*1024)
|
||||
#ifdef _MSC_VER
|
||||
#define __attribute__(x)
|
||||
typedef long long ssize_t;
|
||||
#define SSIZE_MAX (LLONG_MAX >> 1)
|
||||
#ifndef __clang__
|
||||
#define __attribute__(x)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
Vendored
+34
-2
@@ -180,10 +180,17 @@ int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
|
||||
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
|
||||
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
|
||||
* logic consistent. */
|
||||
if (errno == EWOULDBLOCK) {
|
||||
* logic consistent.
|
||||
* Additionally, WSAALREADY is can be reported as WSAEINVAL to and this is
|
||||
* translated to EIO. Convert appropriately
|
||||
*/
|
||||
int err = errno;
|
||||
if (err == EWOULDBLOCK) {
|
||||
errno = EINPROGRESS;
|
||||
}
|
||||
else if (err == EIO) {
|
||||
errno = EALREADY;
|
||||
}
|
||||
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
@@ -205,6 +212,14 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle
|
||||
} else {
|
||||
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
|
||||
}
|
||||
if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) {
|
||||
/* translate SO_ERROR codes, if non-zero */
|
||||
int err = *(int*)optval;
|
||||
if (err != 0) {
|
||||
err = _wsaErrorToErrno(err);
|
||||
*(int*)optval = err;
|
||||
}
|
||||
}
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
@@ -245,4 +260,21 @@ int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
|
||||
_updateErrno(ret != SOCKET_ERROR);
|
||||
return ret != SOCKET_ERROR ? ret : -1;
|
||||
}
|
||||
|
||||
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms) {
|
||||
struct tcp_keepalive cfg;
|
||||
DWORD bytes_in;
|
||||
int res;
|
||||
|
||||
cfg.onoff = 1;
|
||||
cfg.keepaliveinterval = interval_ms;
|
||||
cfg.keepalivetime = interval_ms;
|
||||
|
||||
res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg,
|
||||
sizeof(struct tcp_keepalive), NULL, 0,
|
||||
&bytes_in, NULL, NULL);
|
||||
|
||||
return res == 0 ? 0 : _wsaErrorToErrno(res);
|
||||
}
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
Vendored
+3
@@ -50,6 +50,7 @@
|
||||
#include <ws2tcpip.h>
|
||||
#include <stddef.h>
|
||||
#include <errno.h>
|
||||
#include <mstcpip.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
typedef long long ssize_t;
|
||||
@@ -71,6 +72,8 @@ ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
|
||||
typedef ULONG nfds_t;
|
||||
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
|
||||
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms);
|
||||
|
||||
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
|
||||
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
|
||||
#undef gai_strerror
|
||||
|
||||
Vendored
+35
-1
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "net.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
@@ -39,6 +40,14 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#ifdef OPENSSL_IS_BORINGSSL
|
||||
#undef X509_NAME
|
||||
#undef X509_EXTENSIONS
|
||||
#undef PKCS7_ISSUER_AND_SERIAL
|
||||
#undef PKCS7_SIGNER_INFO
|
||||
#undef OCSP_REQUEST
|
||||
#undef OCSP_RESPONSE
|
||||
#endif
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
@@ -219,6 +228,25 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
|
||||
const char *cert_filename, const char *private_key_filename,
|
||||
const char *server_name, redisSSLContextError *error)
|
||||
{
|
||||
redisSSLOptions options = {
|
||||
.cacert_filename = cacert_filename,
|
||||
.capath = capath,
|
||||
.cert_filename = cert_filename,
|
||||
.private_key_filename = private_key_filename,
|
||||
.server_name = server_name,
|
||||
.verify_mode = REDIS_SSL_VERIFY_PEER,
|
||||
};
|
||||
|
||||
return redisCreateSSLContextWithOptions(&options, error);
|
||||
}
|
||||
|
||||
redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error) {
|
||||
const char *cacert_filename = options->cacert_filename;
|
||||
const char *capath = options->capath;
|
||||
const char *cert_filename = options->cert_filename;
|
||||
const char *private_key_filename = options->private_key_filename;
|
||||
const char *server_name = options->server_name;
|
||||
|
||||
#ifdef _WIN32
|
||||
HCERTSTORE win_store = NULL;
|
||||
PCCERT_CONTEXT win_ctx = NULL;
|
||||
@@ -235,7 +263,7 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
|
||||
}
|
||||
|
||||
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
|
||||
SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||
SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);
|
||||
|
||||
if ((cert_filename != NULL && private_key_filename == NULL) ||
|
||||
(private_key_filename != NULL && cert_filename == NULL)) {
|
||||
@@ -273,6 +301,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
|
||||
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) {
|
||||
if (error) *error = REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (cert_filename) {
|
||||
@@ -560,6 +593,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
|
||||
}
|
||||
|
||||
redisContextFuncs redisContextSSLFuncs = {
|
||||
.close = redisNetClose,
|
||||
.free_privctx = redisSSLFree,
|
||||
.async_read = redisSSLAsyncRead,
|
||||
.async_write = redisSSLAsyncWrite,
|
||||
|
||||
Vendored
+400
-18
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "adapters/poll.h"
|
||||
#ifdef HIREDIS_TEST_SSL
|
||||
#include "hiredis_ssl.h"
|
||||
#endif
|
||||
@@ -34,11 +35,11 @@ enum connection_type {
|
||||
|
||||
struct config {
|
||||
enum connection_type type;
|
||||
struct timeval connect_timeout;
|
||||
|
||||
struct {
|
||||
const char *host;
|
||||
int port;
|
||||
struct timeval timeout;
|
||||
} tcp;
|
||||
|
||||
struct {
|
||||
@@ -75,6 +76,15 @@ static int tests = 0, fails = 0, skips = 0;
|
||||
#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
|
||||
#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
|
||||
|
||||
static void millisleep(int ms)
|
||||
{
|
||||
#if _MSC_VER
|
||||
Sleep(ms);
|
||||
#else
|
||||
usleep(ms*1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
static long long usec(void) {
|
||||
#ifndef _MSC_VER
|
||||
struct timeval tv;
|
||||
@@ -329,10 +339,14 @@ static void test_format_commands(void) {
|
||||
FLOAT_WIDTH_TEST(float);
|
||||
FLOAT_WIDTH_TEST(double);
|
||||
|
||||
test("Format command with invalid printf format: ");
|
||||
test("Format command with unhandled printf format (specifier 'p' not supported): ");
|
||||
len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
|
||||
test_cond(len == -1);
|
||||
|
||||
test("Format command with invalid printf format (specifier missing): ");
|
||||
len = redisFormatCommand(&cmd,"%-");
|
||||
test_cond(len == -1);
|
||||
|
||||
const char *argv[3];
|
||||
argv[0] = "SET";
|
||||
argv[1] = "foo\0xxx";
|
||||
@@ -391,6 +405,16 @@ static void test_append_formatted_commands(struct config config) {
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
static void test_tcp_options(struct config cfg) {
|
||||
redisContext *c;
|
||||
|
||||
c = do_connect(cfg);
|
||||
test("We can enable TCP_KEEPALIVE: ");
|
||||
test_cond(redisEnableKeepAlive(c) == REDIS_OK);
|
||||
|
||||
disconnect(c, 0);
|
||||
}
|
||||
|
||||
static void test_reply_reader(void) {
|
||||
redisReader *reader;
|
||||
void *reply, *root;
|
||||
@@ -568,6 +592,19 @@ static void test_reply_reader(void) {
|
||||
test_cond(ret == REDIS_ERR && reply == NULL);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Don't reset state after protocol error(not segfault): ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader,(char*)"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$", 25);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
assert(ret == REDIS_OK);
|
||||
redisReaderFeed(reader,(char*)"3\r\nval\r\n", 8);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
|
||||
((redisReply*)reply)->elements == 3);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
/* Regression test for issue #45 on GitHub. */
|
||||
test("Don't do empty allocation for empty multi bulk: ");
|
||||
reader = redisReaderCreate();
|
||||
@@ -637,12 +674,23 @@ static void test_reply_reader(void) {
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Set error when RESP3 double is NaN: ");
|
||||
test("Correctly parses RESP3 double NaN: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ",nan\r\n",6);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(reader->errstr,"Bad double value") == 0);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
|
||||
isnan(((redisReply*)reply)->dval));
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Correctly parses RESP3 double -Nan: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, ",-nan\r\n", 7);
|
||||
ret = redisReaderGetReply(reader, &reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
|
||||
isnan(((redisReply*)reply)->dval));
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
@@ -745,6 +793,20 @@ static void test_reply_reader(void) {
|
||||
!strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385"));
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
|
||||
test("Can parse RESP3 doubles in an array: ");
|
||||
reader = redisReaderCreate();
|
||||
redisReaderFeed(reader, "*1\r\n,3.14159265358979323846\r\n",31);
|
||||
ret = redisReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK &&
|
||||
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
|
||||
((redisReply*)reply)->elements == 1 &&
|
||||
((redisReply*)reply)->element[0]->type == REDIS_REPLY_DOUBLE &&
|
||||
fabs(((redisReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 &&
|
||||
((redisReply*)reply)->element[0]->len == 22 &&
|
||||
strcmp(((redisReply*)reply)->element[0]->str, "3.14159265358979323846") == 0);
|
||||
freeReplyObject(reply);
|
||||
redisReaderFree(reader);
|
||||
}
|
||||
|
||||
static void test_free_null(void) {
|
||||
@@ -819,9 +881,9 @@ static void test_allocator_injection(void) {
|
||||
|
||||
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
|
||||
static void test_blocking_connection_errors(void) {
|
||||
redisContext *c;
|
||||
struct addrinfo hints = {.ai_family = AF_INET};
|
||||
struct addrinfo *ai_tmp = NULL;
|
||||
redisContext *c;
|
||||
|
||||
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
|
||||
if (rv != 0) {
|
||||
@@ -835,6 +897,7 @@ static void test_blocking_connection_errors(void) {
|
||||
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
|
||||
strcmp(c->errstr, "Name does not resolve") == 0 ||
|
||||
strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
|
||||
strcmp(c->errstr, "node name or service name 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 ||
|
||||
@@ -847,12 +910,26 @@ static void test_blocking_connection_errors(void) {
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
redisOptions opt = {0};
|
||||
struct timeval tv;
|
||||
|
||||
test("Returns error when the port is not open: ");
|
||||
c = redisConnect((char*)"localhost", 1);
|
||||
test_cond(c->err == REDIS_ERR_IO &&
|
||||
strcmp(c->errstr,"Connection refused") == 0);
|
||||
redisFree(c);
|
||||
|
||||
|
||||
/* Verify we don't regress from the fix in PR #1180 */
|
||||
test("We don't clobber connection exception with setsockopt error: ");
|
||||
tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000};
|
||||
opt.command_timeout = opt.connect_timeout = &tv;
|
||||
REDIS_OPTIONS_SET_TCP(&opt, "localhost", 10337);
|
||||
c = redisConnectWithOptions(&opt);
|
||||
test_cond(c->err == REDIS_ERR_IO &&
|
||||
strcmp(c->errstr, "Connection refused") == 0);
|
||||
redisFree(c);
|
||||
|
||||
test("Returns error when the unix_sock socket path doesn't accept connections: ");
|
||||
c = redisConnectUnix((char*)"/tmp/idontexist.sock");
|
||||
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
|
||||
@@ -914,11 +991,19 @@ static void test_resp3_push_handler(redisContext *c) {
|
||||
old = redisSetPushCallback(c, push_handler);
|
||||
test("We can set a custom RESP3 PUSH handler: ");
|
||||
reply = redisCommand(c, "SET key:0 val:0");
|
||||
/* We need another command because depending on the version of Redis, the
|
||||
* notification may be delivered after the command's reply. */
|
||||
assert(reply != NULL);
|
||||
freeReplyObject(reply);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1);
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("We properly handle a NIL invalidation payload: ");
|
||||
reply = redisCommand(c, "FLUSHDB");
|
||||
assert(reply != NULL);
|
||||
freeReplyObject(reply);
|
||||
reply = redisCommand(c, "PING");
|
||||
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1);
|
||||
freeReplyObject(reply);
|
||||
|
||||
@@ -929,6 +1014,12 @@ static void test_resp3_push_handler(redisContext *c) {
|
||||
assert((reply = redisCommand(c, "GET key:0")) != NULL);
|
||||
freeReplyObject(reply);
|
||||
assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
|
||||
/* Depending on Redis version, we may receive either push notification or
|
||||
* status reply. Both cases are valid. */
|
||||
if (reply->type == REDIS_REPLY_STATUS) {
|
||||
freeReplyObject(reply);
|
||||
reply = redisCommand(c, "PING");
|
||||
}
|
||||
test_cond(reply->type == REDIS_REPLY_PUSH);
|
||||
freeReplyObject(reply);
|
||||
|
||||
@@ -1089,6 +1180,13 @@ static void test_blocking_connection(struct config config) {
|
||||
strcasecmp(reply->element[1]->str,"pong") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Send command by passing argc/argv: ");
|
||||
const char *argv[3] = {"SET", "foo", "bar"};
|
||||
size_t argvlen[3] = {3, 3, 3};
|
||||
reply = redisCommandArgv(c,3,argv,argvlen);
|
||||
test_cond(reply->type == REDIS_REPLY_STATUS);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Make sure passing NULL to redisGetReply is safe */
|
||||
test("Can pass NULL to redisGetReply: ");
|
||||
assert(redisAppendCommand(c, "PING") == REDIS_OK);
|
||||
@@ -1143,7 +1241,13 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
test("Does not return a reply when the command times out: ");
|
||||
if (detect_debug_sleep(c)) {
|
||||
redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
|
||||
|
||||
// flush connection buffer without waiting for the reply
|
||||
s = c->funcs->write(c);
|
||||
assert(s == (ssize_t)hi_sdslen(c->obuf));
|
||||
hi_sdsfree(c->obuf);
|
||||
c->obuf = hi_sdsempty();
|
||||
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 10000;
|
||||
redisSetTimeout(c, tv);
|
||||
@@ -1156,6 +1260,9 @@ static void test_blocking_connection_timeouts(struct config config) {
|
||||
strcmp(c->errstr, "recv timeout") == 0);
|
||||
#endif
|
||||
freeReplyObject(reply);
|
||||
|
||||
// wait for the DEBUG SLEEP to complete so that Redis server is unblocked for the following tests
|
||||
millisleep(3000);
|
||||
} else {
|
||||
test_skipped();
|
||||
}
|
||||
@@ -1226,22 +1333,34 @@ static void test_blocking_io_errors(struct config config) {
|
||||
static void test_invalid_timeout_errors(struct config config) {
|
||||
redisContext *c;
|
||||
|
||||
test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: ");
|
||||
test("Set error when an invalid timeout usec value is used during connect: ");
|
||||
|
||||
config.tcp.timeout.tv_sec = 0;
|
||||
config.tcp.timeout.tv_usec = 10000001;
|
||||
config.connect_timeout.tv_sec = 0;
|
||||
config.connect_timeout.tv_usec = 10000001;
|
||||
|
||||
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
|
||||
if (config.type == CONN_TCP || config.type == CONN_SSL) {
|
||||
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
|
||||
} else if(config.type == CONN_UNIX) {
|
||||
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
|
||||
} else {
|
||||
assert(NULL);
|
||||
}
|
||||
|
||||
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: ");
|
||||
test("Set error when an invalid timeout sec value is used during connect: ");
|
||||
|
||||
config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
|
||||
config.tcp.timeout.tv_usec = 0;
|
||||
config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
|
||||
config.connect_timeout.tv_usec = 0;
|
||||
|
||||
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout);
|
||||
if (config.type == CONN_TCP || config.type == CONN_SSL) {
|
||||
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
|
||||
} else if(config.type == CONN_UNIX) {
|
||||
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
|
||||
} else {
|
||||
assert(NULL);
|
||||
}
|
||||
|
||||
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
|
||||
redisFree(c);
|
||||
@@ -1729,10 +1848,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
|
||||
strcmp(reply->element[2]->str,"Hello!") == 0);
|
||||
state->checkpoint++;
|
||||
|
||||
/* Unsubscribe to channels, including a channel X which we don't subscribe to */
|
||||
/* Unsubscribe to channels, including channel X & Z which we don't subscribe to */
|
||||
redisAsyncCommand(ac,unexpected_cb,
|
||||
(void*)"unsubscribe should not call unexpected_cb()",
|
||||
"unsubscribe B X A");
|
||||
"unsubscribe B X A A Z");
|
||||
/* Unsubscribe to patterns, none which we subscribe to */
|
||||
redisAsyncCommand(ac,unexpected_cb,
|
||||
(void*)"punsubscribe should not call unexpected_cb()",
|
||||
"punsubscribe");
|
||||
/* Send a regular command after unsubscribing, then disconnect */
|
||||
state->disconnect = 1;
|
||||
redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo");
|
||||
@@ -1749,6 +1872,7 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
|
||||
void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
TestState *state = privdata;
|
||||
(void)ac;
|
||||
|
||||
assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&
|
||||
reply->elements == 3);
|
||||
@@ -1767,8 +1891,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
|
||||
|
||||
/* Test handling of multiple channels
|
||||
* - subscribe to channel A and B
|
||||
* - a published message on A triggers an unsubscribe of channel B, X and A
|
||||
* where channel X is not subscribed to.
|
||||
* - a published message on A triggers an unsubscribe of channel B, X, A and Z
|
||||
* where channel X and Z are not subscribed to.
|
||||
* - the published message also triggers an unsubscribe to patterns. Since no
|
||||
* pattern is subscribed to the responded pattern element type is NIL.
|
||||
* - a command sent after unsubscribe triggers a disconnect */
|
||||
static void test_pubsub_multiple_channels(struct config config) {
|
||||
test("Subscribe to multiple channels: ");
|
||||
@@ -1881,6 +2007,250 @@ static void test_monitor(struct config config) {
|
||||
}
|
||||
#endif /* HIREDIS_TEST_ASYNC */
|
||||
|
||||
/* tests for async api using polling adapter, requires no extra libraries*/
|
||||
|
||||
/* enum for the test cases, the callbacks have different logic based on them */
|
||||
typedef enum astest_no
|
||||
{
|
||||
ASTEST_CONNECT=0,
|
||||
ASTEST_CONN_TIMEOUT,
|
||||
ASTEST_PINGPONG,
|
||||
ASTEST_PINGPONG_TIMEOUT,
|
||||
ASTEST_ISSUE_931,
|
||||
ASTEST_ISSUE_931_PING
|
||||
}astest_no;
|
||||
|
||||
/* a static context for the async tests */
|
||||
struct _astest {
|
||||
redisAsyncContext *ac;
|
||||
astest_no testno;
|
||||
int counter;
|
||||
int connects;
|
||||
int connect_status;
|
||||
int disconnects;
|
||||
int pongs;
|
||||
int disconnect_status;
|
||||
int connected;
|
||||
int err;
|
||||
char errstr[256];
|
||||
};
|
||||
static struct _astest astest;
|
||||
|
||||
/* async callbacks */
|
||||
static void asCleanup(void* data)
|
||||
{
|
||||
struct _astest *t = (struct _astest *)data;
|
||||
t->ac = NULL;
|
||||
}
|
||||
|
||||
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata);
|
||||
|
||||
static void connectCallback(redisAsyncContext *c, int status) {
|
||||
struct _astest *t = (struct _astest *)c->data;
|
||||
assert(t == &astest);
|
||||
assert(t->connects == 0);
|
||||
t->err = c->err;
|
||||
strcpy(t->errstr, c->errstr);
|
||||
t->connects++;
|
||||
t->connect_status = status;
|
||||
t->connected = status == REDIS_OK ? 1 : -1;
|
||||
|
||||
if (t->testno == ASTEST_ISSUE_931) {
|
||||
/* disconnect again */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
else if (t->testno == ASTEST_ISSUE_931_PING)
|
||||
{
|
||||
redisAsyncCommand(c, commandCallback, NULL, "PING");
|
||||
}
|
||||
}
|
||||
static void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
assert(c->data == (void*)&astest);
|
||||
assert(astest.disconnects == 0);
|
||||
astest.err = c->err;
|
||||
strcpy(astest.errstr, c->errstr);
|
||||
astest.disconnects++;
|
||||
astest.disconnect_status = status;
|
||||
astest.connected = 0;
|
||||
}
|
||||
|
||||
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata)
|
||||
{
|
||||
redisReply *reply = (redisReply*)_reply;
|
||||
struct _astest *t = (struct _astest *)ac->data;
|
||||
assert(t == &astest);
|
||||
(void)_privdata;
|
||||
t->err = ac->err;
|
||||
strcpy(t->errstr, ac->errstr);
|
||||
t->counter++;
|
||||
if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING)
|
||||
{
|
||||
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
t->pongs++;
|
||||
redisAsyncFree(ac);
|
||||
}
|
||||
if (t->testno == ASTEST_PINGPONG_TIMEOUT)
|
||||
{
|
||||
/* two ping pongs */
|
||||
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
|
||||
t->pongs++;
|
||||
if (t->counter == 1) {
|
||||
int status = redisAsyncCommand(ac, commandCallback, NULL, "PING");
|
||||
assert(status == REDIS_OK);
|
||||
} else {
|
||||
redisAsyncFree(ac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static redisAsyncContext *do_aconnect(struct config config, astest_no testno)
|
||||
{
|
||||
redisOptions options = {0};
|
||||
memset(&astest, 0, sizeof(astest));
|
||||
|
||||
astest.testno = testno;
|
||||
astest.connect_status = astest.disconnect_status = -2;
|
||||
|
||||
if (config.type == CONN_TCP) {
|
||||
options.type = REDIS_CONN_TCP;
|
||||
options.connect_timeout = &config.connect_timeout;
|
||||
REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
|
||||
} else if (config.type == CONN_SSL) {
|
||||
options.type = REDIS_CONN_TCP;
|
||||
options.connect_timeout = &config.connect_timeout;
|
||||
REDIS_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port);
|
||||
} else if (config.type == CONN_UNIX) {
|
||||
options.type = REDIS_CONN_UNIX;
|
||||
options.endpoint.unix_socket = config.unix_sock.path;
|
||||
} else if (config.type == CONN_FD) {
|
||||
options.type = REDIS_CONN_USERFD;
|
||||
/* Create a dummy connection just to get an fd to inherit */
|
||||
redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
|
||||
if (dummy_ctx) {
|
||||
redisFD fd = disconnect(dummy_ctx, 1);
|
||||
printf("Connecting to inherited fd %d\n", (int)fd);
|
||||
options.endpoint.fd = fd;
|
||||
}
|
||||
}
|
||||
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
|
||||
assert(c);
|
||||
astest.ac = c;
|
||||
c->data = &astest;
|
||||
c->dataCleanup = asCleanup;
|
||||
redisPollAttach(c);
|
||||
redisAsyncSetConnectCallbackNC(c, connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c, disconnectCallback);
|
||||
return c;
|
||||
}
|
||||
|
||||
static void as_printerr(void) {
|
||||
printf("Async err %d : %s\n", astest.err, astest.errstr);
|
||||
}
|
||||
|
||||
#define ASASSERT(e) do { \
|
||||
if (!(e)) \
|
||||
as_printerr(); \
|
||||
assert(e); \
|
||||
} while (0);
|
||||
|
||||
static void test_async_polling(struct config config) {
|
||||
int status;
|
||||
redisAsyncContext *c;
|
||||
struct config defaultconfig = config;
|
||||
|
||||
test("Async connect: ");
|
||||
c = do_aconnect(config, ASTEST_CONNECT);
|
||||
assert(c);
|
||||
while(astest.connected == 0)
|
||||
redisPollTick(c, 0.1);
|
||||
assert(astest.connects == 1);
|
||||
ASASSERT(astest.connect_status == REDIS_OK);
|
||||
assert(astest.disconnects == 0);
|
||||
test_cond(astest.connected == 1);
|
||||
|
||||
test("Async free after connect: ");
|
||||
assert(astest.ac != NULL);
|
||||
redisAsyncFree(c);
|
||||
assert(astest.disconnects == 1);
|
||||
assert(astest.ac == NULL);
|
||||
test_cond(astest.disconnect_status == REDIS_OK);
|
||||
|
||||
if (config.type == CONN_TCP || config.type == CONN_SSL) {
|
||||
/* timeout can only be simulated with network */
|
||||
test("Async connect timeout: ");
|
||||
config.tcp.host = "192.168.254.254"; /* blackhole ip */
|
||||
config.connect_timeout.tv_usec = 100000;
|
||||
c = do_aconnect(config, ASTEST_CONN_TIMEOUT);
|
||||
assert(c);
|
||||
assert(c->err == 0);
|
||||
while(astest.connected == 0)
|
||||
redisPollTick(c, 0.1);
|
||||
assert(astest.connected == -1);
|
||||
/*
|
||||
* freeing should not be done, clearing should have happened.
|
||||
*redisAsyncFree(c);
|
||||
*/
|
||||
assert(astest.ac == NULL);
|
||||
test_cond(astest.connect_status == REDIS_ERR);
|
||||
config = defaultconfig;
|
||||
}
|
||||
|
||||
/* Test a ping/pong after connection */
|
||||
test("Async PING/PONG: ");
|
||||
c = do_aconnect(config, ASTEST_PINGPONG);
|
||||
while(astest.connected == 0)
|
||||
redisPollTick(c, 0.1);
|
||||
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
|
||||
assert(status == REDIS_OK);
|
||||
while(astest.ac)
|
||||
redisPollTick(c, 0.1);
|
||||
test_cond(astest.pongs == 1);
|
||||
|
||||
/* Test a ping/pong after connection that didn't time out.
|
||||
* see https://github.com/redis/hiredis/issues/945
|
||||
*/
|
||||
if (config.type == CONN_TCP || config.type == CONN_SSL) {
|
||||
test("Async PING/PONG after connect timeout: ");
|
||||
config.connect_timeout.tv_usec = 10000; /* 10ms */
|
||||
c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT);
|
||||
while(astest.connected == 0)
|
||||
redisPollTick(c, 0.1);
|
||||
/* sleep 0.1 s, allowing old timeout to arrive */
|
||||
millisleep(10);
|
||||
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
|
||||
assert(status == REDIS_OK);
|
||||
while(astest.ac)
|
||||
redisPollTick(c, 0.1);
|
||||
test_cond(astest.pongs == 2);
|
||||
config = defaultconfig;
|
||||
}
|
||||
|
||||
/* Test disconnect from an on_connect callback
|
||||
* see https://github.com/redis/hiredis/issues/931
|
||||
*/
|
||||
test("Disconnect from onConnected callback (Issue #931): ");
|
||||
c = do_aconnect(config, ASTEST_ISSUE_931);
|
||||
while(astest.disconnects == 0)
|
||||
redisPollTick(c, 0.1);
|
||||
assert(astest.connected == 0);
|
||||
assert(astest.connects == 1);
|
||||
test_cond(astest.disconnects == 1);
|
||||
|
||||
/* Test ping/pong from an on_connect callback
|
||||
* see https://github.com/redis/hiredis/issues/931
|
||||
*/
|
||||
test("Ping/Pong from onConnected callback (Issue #931): ");
|
||||
c = do_aconnect(config, ASTEST_ISSUE_931_PING);
|
||||
/* connect callback issues ping, reponse callback destroys context */
|
||||
while(astest.ac)
|
||||
redisPollTick(c, 0.1);
|
||||
assert(astest.connected == 0);
|
||||
assert(astest.connects == 1);
|
||||
assert(astest.disconnects == 1);
|
||||
test_cond(astest.pongs == 1);
|
||||
}
|
||||
/* End of Async polling_adapter driven tests */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct config cfg = {
|
||||
.tcp = {
|
||||
@@ -1963,6 +2333,7 @@ int main(int argc, char **argv) {
|
||||
test_blocking_io_errors(cfg);
|
||||
test_invalid_timeout_errors(cfg);
|
||||
test_append_formatted_commands(cfg);
|
||||
test_tcp_options(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
|
||||
printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
|
||||
@@ -1972,6 +2343,7 @@ int main(int argc, char **argv) {
|
||||
test_blocking_connection(cfg);
|
||||
test_blocking_connection_timeouts(cfg);
|
||||
test_blocking_io_errors(cfg);
|
||||
test_invalid_timeout_errors(cfg);
|
||||
if (throughput) test_throughput(cfg);
|
||||
} else {
|
||||
test_skipped();
|
||||
@@ -2000,6 +2372,7 @@ int main(int argc, char **argv) {
|
||||
#endif
|
||||
|
||||
#ifdef HIREDIS_TEST_ASYNC
|
||||
cfg.type = CONN_TCP;
|
||||
printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
|
||||
cfg.type = CONN_TCP;
|
||||
|
||||
@@ -2017,6 +2390,15 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
#endif /* HIREDIS_TEST_ASYNC */
|
||||
|
||||
cfg.type = CONN_TCP;
|
||||
printf("\nTesting asynchronous API using polling_adapter TCP (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
|
||||
test_async_polling(cfg);
|
||||
if (test_unix_socket) {
|
||||
cfg.type = CONN_UNIX;
|
||||
printf("\nTesting asynchronous API using polling_adapter UNIX (%s):\n", cfg.unix_sock.path);
|
||||
test_async_polling(cfg);
|
||||
}
|
||||
|
||||
if (test_inherit_fd) {
|
||||
printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
|
||||
if (test_unix_socket) {
|
||||
|
||||
Vendored
+43
-9
@@ -4,9 +4,17 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server}
|
||||
REDIS_PORT=${REDIS_PORT:-56379}
|
||||
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
|
||||
TEST_SSL=${TEST_SSL:-0}
|
||||
SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0}
|
||||
SKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0}
|
||||
ENABLE_DEBUG_CMD=
|
||||
SSL_TEST_ARGS=
|
||||
SKIPS_ARG=
|
||||
SKIPS_ARG=${SKIPS_ARG:-}
|
||||
REDIS_DOCKER=${REDIS_DOCKER:-}
|
||||
|
||||
# We need to enable the DEBUG command for redis-server >= 7.0.0
|
||||
REDIS_MAJOR_VERSION="$(redis-server --version|awk -F'[^0-9]+' '{ print $2 }')"
|
||||
if [ "$REDIS_MAJOR_VERSION" -gt "6" ]; then
|
||||
ENABLE_DEBUG_CMD="enable-debug-command local"
|
||||
fi
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
PID_FILE=${tmpdir}/hiredis-test-redis.pid
|
||||
@@ -43,20 +51,34 @@ if [ "$TEST_SSL" = "1" ]; then
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
kill $(cat ${PID_FILE})
|
||||
if [ -n "${REDIS_DOCKER}" ] ; then
|
||||
docker kill redis-test-server
|
||||
else
|
||||
set +e
|
||||
kill $(cat ${PID_FILE})
|
||||
fi
|
||||
rm -rf ${tmpdir}
|
||||
}
|
||||
trap cleanup INT TERM EXIT
|
||||
|
||||
# base config
|
||||
cat > ${tmpdir}/redis.conf <<EOF
|
||||
daemonize yes
|
||||
pidfile ${PID_FILE}
|
||||
port ${REDIS_PORT}
|
||||
bind 127.0.0.1
|
||||
unixsocket ${SOCK_FILE}
|
||||
unixsocketperm 777
|
||||
EOF
|
||||
|
||||
# if not running in docker add these:
|
||||
if [ ! -n "${REDIS_DOCKER}" ]; then
|
||||
cat >> ${tmpdir}/redis.conf <<EOF
|
||||
daemonize yes
|
||||
${ENABLE_DEBUG_CMD}
|
||||
bind 127.0.0.1
|
||||
EOF
|
||||
fi
|
||||
|
||||
# if doing ssl, add these
|
||||
if [ "$TEST_SSL" = "1" ]; then
|
||||
cat >> ${tmpdir}/redis.conf <<EOF
|
||||
tls-port ${REDIS_SSL_PORT}
|
||||
@@ -66,13 +88,25 @@ tls-key-file ${SSL_KEY}
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo ${tmpdir}
|
||||
cat ${tmpdir}/redis.conf
|
||||
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||
|
||||
if [ -n "${REDIS_DOCKER}" ] ; then
|
||||
chmod a+wx ${tmpdir}
|
||||
chmod a+r ${tmpdir}/*
|
||||
docker run -d --rm --name redis-test-server \
|
||||
-p ${REDIS_PORT}:${REDIS_PORT} \
|
||||
-p ${REDIS_SSL_PORT}:${REDIS_SSL_PORT} \
|
||||
-v ${tmpdir}:${tmpdir} \
|
||||
${REDIS_DOCKER} \
|
||||
redis-server ${tmpdir}/redis.conf
|
||||
else
|
||||
${REDIS_SERVER} ${tmpdir}/redis.conf
|
||||
fi
|
||||
# Wait until we detect the unix socket
|
||||
echo waiting for server
|
||||
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
|
||||
|
||||
# Treat skips as failures if directed
|
||||
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails"
|
||||
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="${SKIPS_ARG} --skips-as-fails"
|
||||
|
||||
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}
|
||||
|
||||
Vendored
+6
-3
@@ -39,6 +39,7 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
@@ -141,13 +142,13 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
json_token_type_t type;
|
||||
int index;
|
||||
size_t index;
|
||||
union {
|
||||
const char *string;
|
||||
double number;
|
||||
int boolean;
|
||||
} value;
|
||||
int string_len;
|
||||
size_t string_len;
|
||||
} json_token_t;
|
||||
|
||||
static const char *char2escape[256] = {
|
||||
@@ -473,6 +474,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
|
||||
* This buffer is reused constantly for small strings
|
||||
* If there are any excess pages, they won't be hit anyway.
|
||||
* This gains ~5% speedup. */
|
||||
if (len > SIZE_MAX / 6 - 3)
|
||||
abort(); /* Overflow check */
|
||||
strbuf_ensure_empty_length(json, len * 6 + 2);
|
||||
|
||||
strbuf_append_char_unsafe(json, '\"');
|
||||
@@ -706,7 +709,7 @@ static int json_encode(lua_State *l)
|
||||
strbuf_t local_encode_buf;
|
||||
strbuf_t *encode_buf;
|
||||
char *json;
|
||||
int len;
|
||||
size_t len;
|
||||
|
||||
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
|
||||
|
||||
|
||||
Vendored
+17
-14
@@ -117,7 +117,9 @@ mp_buf *mp_buf_new(lua_State *L) {
|
||||
|
||||
void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
if (buf->free < len) {
|
||||
size_t newsize = (buf->len+len)*2;
|
||||
size_t newsize = buf->len+len;
|
||||
if (newsize < buf->len || newsize >= SIZE_MAX/2) abort();
|
||||
newsize *= 2;
|
||||
|
||||
buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize);
|
||||
buf->free = newsize - buf->len;
|
||||
@@ -173,7 +175,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
|
||||
|
||||
void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
|
||||
unsigned char hdr[5];
|
||||
int hdrlen;
|
||||
size_t hdrlen;
|
||||
|
||||
if (len < 32) {
|
||||
hdr[0] = 0xa0 | (len&0xff); /* fix raw */
|
||||
@@ -220,7 +222,7 @@ void mp_encode_double(lua_State *L, mp_buf *buf, double d) {
|
||||
|
||||
void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
unsigned char b[9];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n >= 0) {
|
||||
if (n <= 127) {
|
||||
@@ -290,9 +292,9 @@ void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_array(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
size_t enclen;
|
||||
|
||||
if (n <= 15) {
|
||||
b[0] = 0x90 | (n & 0xf); /* fix array */
|
||||
@@ -313,7 +315,7 @@ void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
mp_buf_append(L,buf,b,enclen);
|
||||
}
|
||||
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) {
|
||||
void mp_encode_map(lua_State *L, mp_buf *buf, uint64_t n) {
|
||||
unsigned char b[5];
|
||||
int enclen;
|
||||
|
||||
@@ -791,7 +793,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
|
||||
}
|
||||
}
|
||||
|
||||
int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
int mp_unpack_full(lua_State *L, lua_Integer limit, lua_Integer offset) {
|
||||
size_t len;
|
||||
const char *s;
|
||||
mp_cur c;
|
||||
@@ -803,10 +805,10 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */
|
||||
return luaL_error(L,
|
||||
"Invalid request to unpack with offset of %d and limit of %d.",
|
||||
offset, len);
|
||||
(int) offset, (int) len);
|
||||
else if (offset > len)
|
||||
return luaL_error(L,
|
||||
"Start offset %d greater than input length %d.", offset, len);
|
||||
"Start offset %d greater than input length %d.", (int) offset, (int) len);
|
||||
|
||||
if (decode_all) limit = INT_MAX;
|
||||
|
||||
@@ -828,12 +830,13 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
|
||||
/* c->left is the remaining size of the input buffer.
|
||||
* subtract the entire buffer size from the unprocessed size
|
||||
* to get our next start offset */
|
||||
int offset = len - c.left;
|
||||
size_t new_offset = len - c.left;
|
||||
if (new_offset > LONG_MAX) abort();
|
||||
|
||||
luaL_checkstack(L, 1, "in function mp_unpack_full");
|
||||
|
||||
/* Return offset -1 when we have have processed the entire buffer. */
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : offset);
|
||||
lua_pushinteger(L, c.left == 0 ? -1 : (lua_Integer) new_offset);
|
||||
/* Results are returned with the arg elements still
|
||||
* in place. Lua takes care of only returning
|
||||
* elements above the args for us.
|
||||
@@ -852,15 +855,15 @@ int mp_unpack(lua_State *L) {
|
||||
}
|
||||
|
||||
int mp_unpack_one(lua_State *L) {
|
||||
int offset = luaL_optinteger(L, 2, 0);
|
||||
lua_Integer offset = luaL_optinteger(L, 2, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
return mp_unpack_full(L, 1, offset);
|
||||
}
|
||||
|
||||
int mp_unpack_limit(lua_State *L) {
|
||||
int limit = luaL_checkinteger(L, 2);
|
||||
int offset = luaL_optinteger(L, 3, 0);
|
||||
lua_Integer limit = luaL_checkinteger(L, 2);
|
||||
lua_Integer offset = luaL_optinteger(L, 3, 0);
|
||||
/* Variable pop because offset may not exist */
|
||||
lua_pop(L, lua_gettop(L)-1);
|
||||
|
||||
|
||||
Vendored
+28
-81
@@ -26,6 +26,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
@@ -38,22 +39,22 @@ static void die(const char *fmt, ...)
|
||||
va_end(arg);
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
exit(-1);
|
||||
abort();
|
||||
}
|
||||
|
||||
void strbuf_init(strbuf_t *s, int len)
|
||||
void strbuf_init(strbuf_t *s, size_t len)
|
||||
{
|
||||
int size;
|
||||
size_t size;
|
||||
|
||||
if (len <= 0)
|
||||
if (!len)
|
||||
size = STRBUF_DEFAULT_SIZE;
|
||||
else
|
||||
size = len + 1; /* \0 terminator */
|
||||
|
||||
size = len + 1;
|
||||
if (size < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
s->buf = NULL;
|
||||
s->size = size;
|
||||
s->length = 0;
|
||||
s->increment = STRBUF_DEFAULT_INCREMENT;
|
||||
s->dynamic = 0;
|
||||
s->reallocs = 0;
|
||||
s->debug = 0;
|
||||
@@ -65,7 +66,7 @@ void strbuf_init(strbuf_t *s, int len)
|
||||
strbuf_ensure_null(s);
|
||||
}
|
||||
|
||||
strbuf_t *strbuf_new(int len)
|
||||
strbuf_t *strbuf_new(size_t len)
|
||||
{
|
||||
strbuf_t *s;
|
||||
|
||||
@@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len)
|
||||
return s;
|
||||
}
|
||||
|
||||
void strbuf_set_increment(strbuf_t *s, int increment)
|
||||
{
|
||||
/* Increment > 0: Linear buffer growth rate
|
||||
* Increment < -1: Exponential buffer growth rate */
|
||||
if (increment == 0 || increment == -1)
|
||||
die("BUG: Invalid string increment");
|
||||
|
||||
s->increment = increment;
|
||||
}
|
||||
|
||||
static inline void debug_stats(strbuf_t *s)
|
||||
{
|
||||
if (s->debug) {
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n",
|
||||
(long)s, s->reallocs, s->length, s->size);
|
||||
}
|
||||
}
|
||||
@@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s)
|
||||
free(s);
|
||||
}
|
||||
|
||||
char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
char *strbuf_free_to_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
@@ -131,57 +122,62 @@ char *strbuf_free_to_string(strbuf_t *s, int *len)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int calculate_new_size(strbuf_t *s, int len)
|
||||
static size_t calculate_new_size(strbuf_t *s, size_t len)
|
||||
{
|
||||
int reqsize, newsize;
|
||||
size_t reqsize, newsize;
|
||||
|
||||
if (len <= 0)
|
||||
die("BUG: Invalid strbuf length requested");
|
||||
|
||||
/* Ensure there is room for optional NULL termination */
|
||||
reqsize = len + 1;
|
||||
if (reqsize < len)
|
||||
die("Overflow, len: %zu", len);
|
||||
|
||||
/* If the user has requested to shrink the buffer, do it exactly */
|
||||
if (s->size > reqsize)
|
||||
return reqsize;
|
||||
|
||||
newsize = s->size;
|
||||
if (s->increment < 0) {
|
||||
if (reqsize >= SIZE_MAX / 2) {
|
||||
newsize = reqsize;
|
||||
} else {
|
||||
/* Exponential sizing */
|
||||
while (newsize < reqsize)
|
||||
newsize *= -s->increment;
|
||||
} else {
|
||||
/* Linear sizing */
|
||||
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
|
||||
newsize *= 2;
|
||||
}
|
||||
|
||||
if (newsize < reqsize)
|
||||
die("BUG: strbuf length would overflow, len: %zu", len);
|
||||
|
||||
return newsize;
|
||||
}
|
||||
|
||||
|
||||
/* Ensure strbuf can handle a string length bytes long (ignoring NULL
|
||||
* optional termination). */
|
||||
void strbuf_resize(strbuf_t *s, int len)
|
||||
void strbuf_resize(strbuf_t *s, size_t len)
|
||||
{
|
||||
int newsize;
|
||||
size_t newsize;
|
||||
|
||||
newsize = calculate_new_size(s, len);
|
||||
|
||||
if (s->debug > 1) {
|
||||
fprintf(stderr, "strbuf(%lx) resize: %d => %d\n",
|
||||
fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n",
|
||||
(long)s, s->size, newsize);
|
||||
}
|
||||
|
||||
s->size = newsize;
|
||||
s->buf = realloc(s->buf, s->size);
|
||||
if (!s->buf)
|
||||
die("Out of memory");
|
||||
die("Out of memory, len: %zu", len);
|
||||
s->reallocs++;
|
||||
}
|
||||
|
||||
void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
{
|
||||
int space, i;
|
||||
int i;
|
||||
size_t space;
|
||||
|
||||
space = strbuf_empty_length(s);
|
||||
|
||||
@@ -197,55 +193,6 @@ void strbuf_append_string(strbuf_t *s, const char *str)
|
||||
}
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt() should only be used when an upper bound
|
||||
* is known for the output string. */
|
||||
void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len;
|
||||
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
|
||||
va_start(arg, fmt);
|
||||
fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len < 0)
|
||||
die("BUG: Unable to convert number"); /* This should never happen.. */
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* strbuf_append_fmt_retry() can be used when the there is no known
|
||||
* upper bound for the output string. */
|
||||
void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...)
|
||||
{
|
||||
va_list arg;
|
||||
int fmt_len, try;
|
||||
int empty_len;
|
||||
|
||||
/* If the first attempt to append fails, resize the buffer appropriately
|
||||
* and try again */
|
||||
for (try = 0; ; try++) {
|
||||
va_start(arg, fmt);
|
||||
/* Append the new formatted string */
|
||||
/* fmt_len is the length of the string required, excluding the
|
||||
* trailing NULL */
|
||||
empty_len = strbuf_empty_length(s);
|
||||
/* Add 1 since there is also space to store the terminating NULL. */
|
||||
fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg);
|
||||
va_end(arg);
|
||||
|
||||
if (fmt_len <= empty_len)
|
||||
break; /* SUCCESS */
|
||||
if (try > 0)
|
||||
die("BUG: length of formatted string changed");
|
||||
|
||||
strbuf_resize(s, s->length + fmt_len);
|
||||
}
|
||||
|
||||
s->length += fmt_len;
|
||||
}
|
||||
|
||||
/* vi:ai et sw=4 ts=4:
|
||||
*/
|
||||
|
||||
Vendored
+19
-27
@@ -27,15 +27,13 @@
|
||||
|
||||
/* Size: Total bytes allocated to *buf
|
||||
* Length: String length, excluding optional NULL terminator.
|
||||
* Increment: Allocation increments when resizing the string buffer.
|
||||
* Dynamic: True if created via strbuf_new()
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
int size;
|
||||
int length;
|
||||
int increment;
|
||||
size_t size;
|
||||
size_t length;
|
||||
int dynamic;
|
||||
int reallocs;
|
||||
int debug;
|
||||
@@ -44,32 +42,26 @@ typedef struct {
|
||||
#ifndef STRBUF_DEFAULT_SIZE
|
||||
#define STRBUF_DEFAULT_SIZE 1023
|
||||
#endif
|
||||
#ifndef STRBUF_DEFAULT_INCREMENT
|
||||
#define STRBUF_DEFAULT_INCREMENT -2
|
||||
#endif
|
||||
|
||||
/* Initialise */
|
||||
extern strbuf_t *strbuf_new(int len);
|
||||
extern void strbuf_init(strbuf_t *s, int len);
|
||||
extern void strbuf_set_increment(strbuf_t *s, int increment);
|
||||
extern strbuf_t *strbuf_new(size_t len);
|
||||
extern void strbuf_init(strbuf_t *s, size_t len);
|
||||
|
||||
/* Release */
|
||||
extern void strbuf_free(strbuf_t *s);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, int *len);
|
||||
extern char *strbuf_free_to_string(strbuf_t *s, size_t *len);
|
||||
|
||||
/* Management */
|
||||
extern void strbuf_resize(strbuf_t *s, int len);
|
||||
static int strbuf_empty_length(strbuf_t *s);
|
||||
static int strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, int *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
|
||||
extern void strbuf_resize(strbuf_t *s, size_t len);
|
||||
static size_t strbuf_empty_length(strbuf_t *s);
|
||||
static size_t strbuf_length(strbuf_t *s);
|
||||
static char *strbuf_string(strbuf_t *s, size_t *len);
|
||||
static void strbuf_ensure_empty_length(strbuf_t *s, size_t len);
|
||||
static char *strbuf_empty_ptr(strbuf_t *s);
|
||||
static void strbuf_extend_length(strbuf_t *s, int len);
|
||||
static void strbuf_extend_length(strbuf_t *s, size_t len);
|
||||
|
||||
/* Update */
|
||||
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
|
||||
extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
|
||||
static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len);
|
||||
extern void strbuf_append_string(strbuf_t *s, const char *str);
|
||||
static void strbuf_append_char(strbuf_t *s, const char c);
|
||||
static void strbuf_ensure_null(strbuf_t *s);
|
||||
@@ -87,12 +79,12 @@ static inline int strbuf_allocated(strbuf_t *s)
|
||||
|
||||
/* Return bytes remaining in the string buffer
|
||||
* Ensure there is space for a NULL terminator. */
|
||||
static inline int strbuf_empty_length(strbuf_t *s)
|
||||
static inline size_t strbuf_empty_length(strbuf_t *s)
|
||||
{
|
||||
return s->size - s->length - 1;
|
||||
}
|
||||
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
|
||||
static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
if (len > strbuf_empty_length(s))
|
||||
strbuf_resize(s, s->length + len);
|
||||
@@ -103,12 +95,12 @@ static inline char *strbuf_empty_ptr(strbuf_t *s)
|
||||
return s->buf + s->length;
|
||||
}
|
||||
|
||||
static inline void strbuf_extend_length(strbuf_t *s, int len)
|
||||
static inline void strbuf_extend_length(strbuf_t *s, size_t len)
|
||||
{
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline int strbuf_length(strbuf_t *s)
|
||||
static inline size_t strbuf_length(strbuf_t *s)
|
||||
{
|
||||
return s->length;
|
||||
}
|
||||
@@ -124,14 +116,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
|
||||
s->buf[s->length++] = c;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
strbuf_ensure_empty_length(s, len);
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
}
|
||||
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len)
|
||||
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len)
|
||||
{
|
||||
memcpy(s->buf + s->length, c, len);
|
||||
s->length += len;
|
||||
@@ -142,7 +134,7 @@ static inline void strbuf_ensure_null(strbuf_t *s)
|
||||
s->buf[s->length] = 0;
|
||||
}
|
||||
|
||||
static inline char *strbuf_string(strbuf_t *s, int *len)
|
||||
static inline char *strbuf_string(strbuf_t *s, size_t *len)
|
||||
{
|
||||
if (len)
|
||||
*len = s->length;
|
||||
|
||||
@@ -346,6 +346,7 @@ pidfile /var/run/redis_6379.pid
|
||||
# verbose (many rarely useful info, but not a mess like the debug level)
|
||||
# notice (moderately verbose, what you want in production probably)
|
||||
# warning (only very important / critical messages are logged)
|
||||
# nothing (nothing is logged)
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
@@ -1742,6 +1743,11 @@ aof-timestamp-enabled no
|
||||
#
|
||||
# cluster-announce-hostname ""
|
||||
|
||||
# Clusters can configure an optional nodename to be used in addition to the node ID for
|
||||
# debugging and admin information. This name is broadcasted between nodes, so will be used
|
||||
# in addition to the node ID when reporting cross node events such as node failures.
|
||||
# cluster-announce-human-nodename ""
|
||||
|
||||
# Clusters can advertise how clients should connect to them using either their IP address,
|
||||
# a user defined hostname, or by declaring they have no endpoint. Which endpoint is
|
||||
# shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type
|
||||
|
||||
@@ -25,6 +25,7 @@ pidfile /var/run/redis-sentinel.pid
|
||||
# verbose (many rarely useful info, but not a mess like the debug level)
|
||||
# notice (moderately verbose, what you want in production probably)
|
||||
# warning (only very important / critical messages are logged)
|
||||
# nothing (nothing is logged)
|
||||
loglevel notice
|
||||
|
||||
# Specify the log file name. Also the empty string can be used to force
|
||||
|
||||
+3
-3
@@ -23,7 +23,7 @@ ifeq ($(OPTIMIZATION),-O3)
|
||||
else
|
||||
REDIS_CFLAGS+=-flto=auto
|
||||
endif
|
||||
REDIS_LDFLAGS+=-flto
|
||||
REDIS_LDFLAGS+=-O3 -flto
|
||||
endif
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv
|
||||
NODEPS:=clean distclean
|
||||
@@ -47,10 +47,10 @@ OPT=$(OPTIMIZATION)
|
||||
# NUMBER_SIGN_CHAR is a workaround to support both GNU Make 4.3 and older versions.
|
||||
NUMBER_SIGN_CHAR := \#
|
||||
C11_ATOMIC := $(shell sh -c 'echo "$(NUMBER_SIGN_CHAR)include <stdatomic.h>" > foo.c; \
|
||||
$(CC) -std=c11 -c foo.c -o foo.o > /dev/null 2>&1; \
|
||||
$(CC) -std=gnu11 -c foo.c -o foo.o > /dev/null 2>&1; \
|
||||
if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c')
|
||||
ifeq ($(C11_ATOMIC),yes)
|
||||
STD+=-std=c11
|
||||
STD+=-std=gnu11
|
||||
else
|
||||
STD+=-std=c99
|
||||
endif
|
||||
|
||||
@@ -1923,26 +1923,28 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
|
||||
|
||||
if (c->user == original && getClientType(c) == CLIENT_TYPE_PUBSUB) {
|
||||
/* Check for pattern violations. */
|
||||
listRewind(c->pubsub_patterns,&lpi);
|
||||
while (!kill && ((lpn = listNext(&lpi)) != NULL)) {
|
||||
|
||||
o = lpn->value;
|
||||
dictIterator *di = dictGetIterator(c->pubsub_patterns);
|
||||
dictEntry *de;
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 1);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Check for channel violations. */
|
||||
if (!kill) {
|
||||
/* Check for global channels violation. */
|
||||
dictIterator *di = dictGetIterator(c->pubsub_channels);
|
||||
|
||||
dictEntry *de;
|
||||
di = dictGetIterator(c->pubsub_channels);
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
o = dictGetKey(de);
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
if (!kill) {
|
||||
/* Check for shard channels violation. */
|
||||
di = dictGetIterator(c->pubsubshard_channels);
|
||||
while (!kill && ((de = dictNext(di)) != NULL)) {
|
||||
@@ -1950,7 +1952,6 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
|
||||
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
|
||||
kill = (res == ACL_DENIED_CHANNEL);
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
@@ -2112,10 +2113,6 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Try to apply the user rules in a fake user to see if they
|
||||
* are actually valid. */
|
||||
user *fakeuser = ACLCreateUnlinkedUser();
|
||||
|
||||
/* Merged selectors before trying to process */
|
||||
int merged_argc;
|
||||
sds *acl_args = ACLMergeSelectorArguments(argv + 2, argc - 2, &merged_argc, argc_err);
|
||||
@@ -2124,6 +2121,10 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Try to apply the user rules in a fake user to see if they
|
||||
* are actually valid. */
|
||||
user *fakeuser = ACLCreateUnlinkedUser();
|
||||
|
||||
for (int j = 0; j < merged_argc; j++) {
|
||||
if (ACLSetUser(fakeuser,acl_args[j],sdslen(acl_args[j])) == C_ERR) {
|
||||
if (errno != ENOENT) {
|
||||
|
||||
@@ -142,7 +142,7 @@ void bioInit(void) {
|
||||
for (j = 0; j < BIO_WORKER_NUM; j++) {
|
||||
void *arg = (void*)(unsigned long) j;
|
||||
if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) {
|
||||
serverLog(LL_WARNING,"Fatal: Can't initialize Background Jobs.");
|
||||
serverLog(LL_WARNING, "Fatal: Can't initialize Background Jobs. Error message: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
bio_threads[j] = thread;
|
||||
|
||||
+34
-1
@@ -325,6 +325,9 @@ void handleClientsBlockedOnKeys(void) {
|
||||
* (i.e. not from call(), module context, etc.) */
|
||||
serverAssert(server.also_propagate.numops == 0);
|
||||
|
||||
/* If a command being unblocked causes another command to get unblocked,
|
||||
* like a BLMOVE would do, then the new unblocked command will get processed
|
||||
* right away rather than wait for later. */
|
||||
while(listLength(server.ready_keys) != 0) {
|
||||
list *l;
|
||||
|
||||
@@ -564,7 +567,10 @@ static void handleClientsBlockedOnKey(readyList *rl) {
|
||||
listIter li;
|
||||
listRewind(clients,&li);
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
/* Avoid processing more than the initial count so that we're not stuck
|
||||
* in an endless loop in case the reprocessing of the command blocks again. */
|
||||
long count = listLength(clients);
|
||||
while ((ln = listNext(&li)) && count--) {
|
||||
client *receiver = listNodeValue(ln);
|
||||
robj *o = lookupKeyReadWithFlags(rl->db, rl->key, LOOKUP_NOEFFECTS);
|
||||
/* 1. In case new key was added/touched we need to verify it satisfy the
|
||||
@@ -728,3 +734,30 @@ void totalNumberOfBlockingKeys(unsigned long *blocking_keys, unsigned long *blok
|
||||
if (bloking_keys_on_nokey)
|
||||
*bloking_keys_on_nokey = bkeys_on_nokey;
|
||||
}
|
||||
|
||||
void blockedBeforeSleep(void) {
|
||||
/* Handle precise timeouts of blocked clients. */
|
||||
handleBlockedClientsTimeout();
|
||||
|
||||
/* Unblock all the clients blocked for synchronous replication
|
||||
* in WAIT or WAITAOF. */
|
||||
if (listLength(server.clients_waiting_acks))
|
||||
processClientsWaitingReplicas();
|
||||
|
||||
/* Try to process blocked clients every once in while.
|
||||
*
|
||||
* Example: A module calls RM_SignalKeyAsReady from within a timer callback
|
||||
* (So we don't visit processCommand() at all).
|
||||
*
|
||||
* This may unblock clients, so must be done before processUnblockedClients */
|
||||
handleClientsBlockedOnKeys();
|
||||
|
||||
/* Check if there are clients unblocked by modules that implement
|
||||
* blocking commands. */
|
||||
if (moduleCount())
|
||||
moduleHandleBlockedClients();
|
||||
|
||||
/* Try to process pending commands for clients that were just unblocked. */
|
||||
if (listLength(server.unblocked_clients))
|
||||
processUnblockedClients();
|
||||
}
|
||||
|
||||
+13
-3
@@ -352,9 +352,19 @@ void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo
|
||||
path = strchr(curr, '/');
|
||||
if (*curr != '/') {
|
||||
host = path ? path - 1 : end;
|
||||
if ((port = strchr(curr, ':'))) {
|
||||
connInfo->hostport = atoi(port + 1);
|
||||
host = port - 1;
|
||||
if (*curr == '[') {
|
||||
curr += 1;
|
||||
if ((port = strchr(curr, ']'))) {
|
||||
if (*(port+1) == ':') {
|
||||
connInfo->hostport = atoi(port + 2);
|
||||
}
|
||||
host = port - 1;
|
||||
}
|
||||
} else {
|
||||
if ((port = strchr(curr, ':'))) {
|
||||
connInfo->hostport = atoi(port + 1);
|
||||
host = port - 1;
|
||||
}
|
||||
}
|
||||
sdsfree(connInfo->hostip);
|
||||
connInfo->hostip = sdsnewlen(curr, host - curr + 1);
|
||||
|
||||
+359
-154
File diff suppressed because it is too large
Load Diff
+25
-8
@@ -141,9 +141,9 @@ typedef struct clusterNode {
|
||||
long long repl_offset; /* Last known repl offset for this node. */
|
||||
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
|
||||
sds hostname; /* The known hostname for this node */
|
||||
int port; /* Latest known clients port (TLS or plain). */
|
||||
int pport; /* Latest known clients plaintext port. Only used
|
||||
if the main clients port is for TLS. */
|
||||
sds human_nodename; /* The known human readable nodename for this node */
|
||||
int tcp_port; /* Latest known clients TCP port. */
|
||||
int tls_port; /* Latest known clients TLS port */
|
||||
int cport; /* Latest known cluster port of this node. */
|
||||
clusterLink *link; /* TCP/IP link established toward this node */
|
||||
clusterLink *inbound_link; /* TCP/IP link accepted from this node */
|
||||
@@ -213,6 +213,13 @@ typedef struct clusterState {
|
||||
long long stats_pfail_nodes; /* Number of nodes in PFAIL status,
|
||||
excluding nodes without address. */
|
||||
unsigned long long stat_cluster_links_buffer_limit_exceeded; /* Total number of cluster links freed due to exceeding buffer limit */
|
||||
|
||||
/* Bit map for slots that are no longer claimed by the owner in cluster PING
|
||||
* messages. During slot migration, the owner will stop claiming the slot after
|
||||
* the ownership transfer. Set the bit corresponding to the slot when a node
|
||||
* stops claiming the slot. This prevents spreading incorrect information (that
|
||||
* source still owns the slot) using UPDATE messages. */
|
||||
unsigned char owner_not_claiming_slot[CLUSTER_SLOTS / 8];
|
||||
} clusterState;
|
||||
|
||||
/* Redis cluster messages header */
|
||||
@@ -225,10 +232,10 @@ typedef struct {
|
||||
uint32_t ping_sent;
|
||||
uint32_t pong_received;
|
||||
char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
|
||||
uint16_t port; /* base port last time it was seen */
|
||||
uint16_t port; /* primary port last time it was seen */
|
||||
uint16_t cport; /* cluster port last time it was seen */
|
||||
uint16_t flags; /* node->flags copy */
|
||||
uint16_t pport; /* plaintext-port, when base port is TLS */
|
||||
uint16_t pport; /* secondary port last time it was seen */
|
||||
uint16_t notused1;
|
||||
} clusterMsgDataGossip;
|
||||
|
||||
@@ -260,6 +267,7 @@ typedef struct {
|
||||
* consistent manner. */
|
||||
typedef enum {
|
||||
CLUSTERMSG_EXT_TYPE_HOSTNAME,
|
||||
CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME,
|
||||
CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE,
|
||||
CLUSTERMSG_EXT_TYPE_SHARDID,
|
||||
} clusterMsgPingtypes;
|
||||
@@ -271,6 +279,10 @@ typedef struct {
|
||||
char hostname[1]; /* The announced hostname, ends with \0. */
|
||||
} clusterMsgPingExtHostname;
|
||||
|
||||
typedef struct {
|
||||
char human_nodename[1]; /* The announced nodename, ends with \0. */
|
||||
} clusterMsgPingExtHumanNodename;
|
||||
|
||||
typedef struct {
|
||||
char name[CLUSTER_NAMELEN]; /* Node name. */
|
||||
uint64_t ttl; /* Remaining time to blacklist the node, in seconds. */
|
||||
@@ -288,6 +300,7 @@ typedef struct {
|
||||
uint16_t unused; /* 16 bits of padding to make this structure 8 byte aligned. */
|
||||
union {
|
||||
clusterMsgPingExtHostname hostname;
|
||||
clusterMsgPingExtHumanNodename human_nodename;
|
||||
clusterMsgPingExtForgottenNode forgotten_node;
|
||||
clusterMsgPingExtShardId shard_id;
|
||||
} ext[]; /* Actual extension information, formatted so that the data is 8
|
||||
@@ -331,7 +344,7 @@ typedef struct {
|
||||
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
|
||||
uint32_t totlen; /* Total length of this message */
|
||||
uint16_t ver; /* Protocol version, currently set to 1. */
|
||||
uint16_t port; /* TCP base port number. */
|
||||
uint16_t port; /* Primary port number (TCP or TLS). */
|
||||
uint16_t type; /* Message type */
|
||||
uint16_t count; /* Only used for some kind of messages. */
|
||||
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
|
||||
@@ -346,7 +359,8 @@ typedef struct {
|
||||
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
|
||||
uint16_t extensions; /* Number of extensions sent along with this packet. */
|
||||
char notused1[30]; /* 30 bytes reserved for future usage. */
|
||||
uint16_t pport; /* Sender TCP plaintext port, if base port is TLS */
|
||||
uint16_t pport; /* Secondary port number: if primary port is TCP port, this is
|
||||
TLS port, and if primary port is TLS port, this is TCP port.*/
|
||||
uint16_t cport; /* Sender TCP cluster bus port */
|
||||
uint16_t flags; /* Sender node flags */
|
||||
unsigned char state; /* Cluster state from the POV of the sender */
|
||||
@@ -422,8 +436,11 @@ void slotToChannelAdd(sds channel);
|
||||
void slotToChannelDel(sds channel);
|
||||
void clusterUpdateMyselfHostname(void);
|
||||
void clusterUpdateMyselfAnnouncedPorts(void);
|
||||
sds clusterGenNodesDescription(int filter, int use_pport);
|
||||
sds clusterGenNodesDescription(client *c, int filter, int tls_primary);
|
||||
sds genClusterInfoString(void);
|
||||
void freeClusterLink(clusterLink *link);
|
||||
void clusterUpdateMyselfHumanNodename(void);
|
||||
int isValidAuxString(char *s, unsigned int length);
|
||||
int getNodeDefaultClientPort(clusterNode *n);
|
||||
|
||||
#endif /* __CLUSTER_H */
|
||||
|
||||
+12
-10
@@ -970,9 +970,9 @@ struct COMMAND_STRUCT CLUSTER_Subcommands[] = {
|
||||
{MAKE_CMD("saveconfig","Forces a node to save the cluster configuration to disk.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SAVECONFIG_History,0,CLUSTER_SAVECONFIG_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SAVECONFIG_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("set-config-epoch","Sets the configuration epoch for a new node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SET_CONFIG_EPOCH_History,0,CLUSTER_SET_CONFIG_EPOCH_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SET_CONFIG_EPOCH_Keyspecs,0,NULL,1),.args=CLUSTER_SET_CONFIG_EPOCH_Args},
|
||||
{MAKE_CMD("setslot","Binds a hash slot to a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SETSLOT_History,0,CLUSTER_SETSLOT_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SETSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_SETSLOT_Args},
|
||||
{MAKE_CMD("shards","Returns the mapping of cluster slots to shards.","O(N) where N is the total number of cluster nodes","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SHARDS_History,0,CLUSTER_SHARDS_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_SHARDS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("shards","Returns the mapping of cluster slots to shards.","O(N) where N is the total number of cluster nodes","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SHARDS_History,0,CLUSTER_SHARDS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,0,CLUSTER_SHARDS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("slaves","Lists the replica nodes of a master node.","O(1)","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER REPLICAS`","5.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLAVES_History,0,CLUSTER_SLAVES_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,0,CLUSTER_SLAVES_Keyspecs,0,NULL,1),.args=CLUSTER_SLAVES_Args},
|
||||
{MAKE_CMD("slots","Returns the mapping of cluster slots to nodes.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER SHARDS`","7.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOTS_History,2,CLUSTER_SLOTS_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_SLOTS_Keyspecs,0,NULL,0)},
|
||||
{MAKE_CMD("slots","Returns the mapping of cluster slots to nodes.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER SHARDS`","7.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOTS_History,2,CLUSTER_SLOTS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,0,CLUSTER_SLOTS_Keyspecs,0,NULL,0)},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -5359,7 +5359,9 @@ struct COMMAND_ARG SENTINEL_CKQUORUM_Args[] = {
|
||||
|
||||
#ifndef SKIP_CMD_HISTORY_TABLE
|
||||
/* SENTINEL CONFIG history */
|
||||
#define SENTINEL_CONFIG_History NULL
|
||||
commandHistory SENTINEL_CONFIG_History[] = {
|
||||
{"7.2.0","Added the ability to set and get multiple parameters in one call."},
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SKIP_CMD_TIPS_TABLE
|
||||
@@ -5380,8 +5382,8 @@ struct COMMAND_ARG SENTINEL_CONFIG_action_set_Subargs[] = {
|
||||
|
||||
/* SENTINEL CONFIG action argument table */
|
||||
struct COMMAND_ARG SENTINEL_CONFIG_action_Subargs[] = {
|
||||
{MAKE_ARG("set",ARG_TYPE_BLOCK,-1,"SET",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=SENTINEL_CONFIG_action_set_Subargs},
|
||||
{MAKE_ARG("parameter",ARG_TYPE_STRING,-1,"GET",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("set",ARG_TYPE_BLOCK,-1,"SET",NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=SENTINEL_CONFIG_action_set_Subargs},
|
||||
{MAKE_ARG("parameter",ARG_TYPE_STRING,-1,"GET",NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
|
||||
};
|
||||
|
||||
/* SENTINEL CONFIG argument table */
|
||||
@@ -5811,7 +5813,7 @@ struct COMMAND_ARG SENTINEL_SLAVES_Args[] = {
|
||||
/* SENTINEL command table */
|
||||
struct COMMAND_STRUCT SENTINEL_Subcommands[] = {
|
||||
{MAKE_CMD("ckquorum","Checks for a Redis Sentinel quorum.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CKQUORUM_History,0,SENTINEL_CKQUORUM_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CKQUORUM_Keyspecs,0,NULL,1),.args=SENTINEL_CKQUORUM_Args},
|
||||
{MAKE_CMD("config","Configures Redis Sentinel.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CONFIG_History,0,SENTINEL_CONFIG_Tips,0,sentinelCommand,-4,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CONFIG_Keyspecs,0,NULL,1),.args=SENTINEL_CONFIG_Args},
|
||||
{MAKE_CMD("config","Configures Redis Sentinel.","O(N) when N is the number of configuration parameters provided","6.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CONFIG_History,1,SENTINEL_CONFIG_Tips,0,sentinelCommand,-4,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CONFIG_Keyspecs,0,NULL,1),.args=SENTINEL_CONFIG_Args},
|
||||
{MAKE_CMD("debug","Lists or updates the current configurable parameters of Redis Sentinel.","O(N) where N is the number of configurable parameters","7.0.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_DEBUG_History,0,SENTINEL_DEBUG_Tips,0,sentinelCommand,-2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_DEBUG_Keyspecs,0,NULL,1),.args=SENTINEL_DEBUG_Args},
|
||||
{MAKE_CMD("failover","Forces a Redis Sentinel failover.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FAILOVER_History,0,SENTINEL_FAILOVER_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FAILOVER_Keyspecs,0,NULL,1),.args=SENTINEL_FAILOVER_Args},
|
||||
{MAKE_CMD("flushconfig","Rewrites the Redis Sentinel configuration file.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FLUSHCONFIG_History,0,SENTINEL_FLUSHCONFIG_Tips,0,sentinelCommand,2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FLUSHCONFIG_Keyspecs,0,NULL,0)},
|
||||
@@ -10690,15 +10692,15 @@ struct COMMAND_STRUCT redisCommandTable[] = {
|
||||
{MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args},
|
||||
{MAKE_CMD("rpushx","Appends an element to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSHX_History,1,RPUSHX_Tips,0,rpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSHX_Keyspecs,1,NULL,2),.args=RPUSHX_Args},
|
||||
/* pubsub */
|
||||
{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns the client is already subscribed to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args},
|
||||
{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args},
|
||||
{MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,0,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args},
|
||||
{MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,0,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands},
|
||||
{MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N+M) where N is the number of patterns the client is already subscribed and M is the number of total patterns subscribed in the system (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args},
|
||||
{MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args},
|
||||
{MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,0,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args},
|
||||
{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args},
|
||||
{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args},
|
||||
{MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of clients already subscribed to a shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args},
|
||||
{MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of clients already subscribed to a channel.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args},
|
||||
{MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of shard channels to unsubscribe.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args},
|
||||
{MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of channels to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args},
|
||||
/* scripting */
|
||||
{MAKE_CMD("eval","Executes a server-side Lua script.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVAL_History,0,EVAL_Tips,0,evalCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVAL_Keyspecs,1,evalGetKeys,4),.args=EVAL_Args},
|
||||
{MAKE_CMD("evalsha","Executes a server-side Lua script by SHA1 digest.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVALSHA_History,0,EVALSHA_Tips,0,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVALSHA_Keyspecs,1,evalGetKeys,4),.args=EVALSHA_Args},
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"function": "clusterCommand",
|
||||
"history": [],
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
],
|
||||
"command_tips": [
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
"LOADING",
|
||||
"STALE"
|
||||
],
|
||||
"command_tips": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"PSUBSCRIBE": {
|
||||
"summary": "Listens for messages published to channels that match one or more patterns.",
|
||||
"complexity": "O(N) where N is the number of patterns the client is already subscribed to.",
|
||||
"complexity": "O(N) where N is the number of patterns to subscribe to.",
|
||||
"group": "pubsub",
|
||||
"since": "2.0.0",
|
||||
"arity": -2,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"PUNSUBSCRIBE": {
|
||||
"summary": "Stops listening to messages published to channels that match one or more patterns.",
|
||||
"complexity": "O(N+M) where N is the number of patterns the client is already subscribed and M is the number of total patterns subscribed in the system (by any client).",
|
||||
"complexity": "O(N) where N is the number of patterns to unsubscribe.",
|
||||
"group": "pubsub",
|
||||
"since": "2.0.0",
|
||||
"arity": -1,
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
{
|
||||
"CONFIG": {
|
||||
"summary": "Configures Redis Sentinel.",
|
||||
"complexity": "O(1)",
|
||||
"complexity": "O(N) when N is the number of configuration parameters provided",
|
||||
"group": "sentinel",
|
||||
"since": "6.2.0",
|
||||
"arity": -4,
|
||||
"container": "SENTINEL",
|
||||
"function": "sentinelCommand",
|
||||
"history": [
|
||||
[
|
||||
"7.2.0",
|
||||
"Added the ability to set and get multiple parameters in one call."
|
||||
]
|
||||
],
|
||||
"command_flags": [
|
||||
"ADMIN",
|
||||
"SENTINEL",
|
||||
@@ -42,7 +48,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"announce-port": {
|
||||
"type": "integer"
|
||||
"type": "string"
|
||||
},
|
||||
"sentinel-user": {
|
||||
"type": "string"
|
||||
@@ -64,6 +70,9 @@
|
||||
{
|
||||
"const": "warning"
|
||||
},
|
||||
{
|
||||
"const": "nothing"
|
||||
},
|
||||
{
|
||||
"const": "unknown"
|
||||
}
|
||||
@@ -87,6 +96,7 @@
|
||||
"name":"set",
|
||||
"token":"SET",
|
||||
"type":"block",
|
||||
"multiple": true,
|
||||
"arguments":[
|
||||
{
|
||||
"name":"parameter",
|
||||
@@ -101,7 +111,8 @@
|
||||
{
|
||||
"token":"GET",
|
||||
"name":"parameter",
|
||||
"type":"string"
|
||||
"type":"string",
|
||||
"multiple": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"SUNSUBSCRIBE": {
|
||||
"summary": "Stops listening to messages posted to shard channels.",
|
||||
"complexity": "O(N) where N is the number of clients already subscribed to a shard channel.",
|
||||
"complexity": "O(N) where N is the number of shard channels to unsubscribe.",
|
||||
"group": "pubsub",
|
||||
"since": "7.0.0",
|
||||
"arity": -1,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"UNSUBSCRIBE": {
|
||||
"summary": "Stops listening to messages posted to channels.",
|
||||
"complexity": "O(N) where N is the number of clients already subscribed to a channel.",
|
||||
"complexity": "O(N) where N is the number of channels to unsubscribe.",
|
||||
"group": "pubsub",
|
||||
"since": "2.0.0",
|
||||
"arity": -1,
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "The rank of the member when 'WITHSCORES' is not used."
|
||||
"description": "The rank of the member when 'WITHSCORE' is not used."
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"description": "The rank and score of the member when 'WITHSCORES' is used.",
|
||||
"description": "The rank and score of the member when 'WITHSCORE' is used.",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
|
||||
@@ -47,11 +47,11 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "The rank of the member when 'WITHSCORES' is not used."
|
||||
"description": "The rank of the member when 'WITHSCORE' is not used."
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"description": "The rank and score of the member when 'WITHSCORES' is used.",
|
||||
"description": "The rank and score of the member when 'WITHSCORE' is used.",
|
||||
"minItems": 2,
|
||||
"maxItems": 2,
|
||||
"items": [
|
||||
|
||||
+27
-5
@@ -38,6 +38,7 @@
|
||||
#include <glob.h>
|
||||
#include <string.h>
|
||||
#include <locale.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Config file name-value maps.
|
||||
@@ -79,6 +80,7 @@ configEnum loglevel_enum[] = {
|
||||
{"verbose", LL_VERBOSE},
|
||||
{"notice", LL_NOTICE},
|
||||
{"warning", LL_WARNING},
|
||||
{"nothing", LL_NOTHING},
|
||||
{NULL,0}
|
||||
};
|
||||
|
||||
@@ -453,14 +455,13 @@ void loadServerConfigFromString(char *config) {
|
||||
const char *err = NULL;
|
||||
int linenum = 0, totlines, i;
|
||||
sds *lines;
|
||||
sds *argv = NULL;
|
||||
int argc;
|
||||
|
||||
reading_config_file = 1;
|
||||
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
|
||||
|
||||
for (i = 0; i < totlines; i++) {
|
||||
sds *argv;
|
||||
int argc;
|
||||
|
||||
linenum = i+1;
|
||||
lines[i] = sdstrim(lines[i]," \t\r\n");
|
||||
|
||||
@@ -477,6 +478,7 @@ void loadServerConfigFromString(char *config) {
|
||||
/* Skip this line if the resulting command vector is empty. */
|
||||
if (argc == 0) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
argv = NULL;
|
||||
continue;
|
||||
}
|
||||
sdstolower(argv[0]);
|
||||
@@ -499,6 +501,7 @@ void loadServerConfigFromString(char *config) {
|
||||
int new_argc;
|
||||
new_argv = sdssplitargs(argv[1], &new_argc);
|
||||
if (!config->interface.set(config, new_argv, new_argc, &err)) {
|
||||
if(new_argv) sdsfreesplitres(new_argv, new_argc);
|
||||
goto loaderr;
|
||||
}
|
||||
sdsfreesplitres(new_argv, new_argc);
|
||||
@@ -510,6 +513,7 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
|
||||
sdsfreesplitres(argv,argc);
|
||||
argv = NULL;
|
||||
continue;
|
||||
} else {
|
||||
int match = 0;
|
||||
@@ -524,6 +528,7 @@ void loadServerConfigFromString(char *config) {
|
||||
}
|
||||
if (match) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
argv = NULL;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -590,6 +595,7 @@ void loadServerConfigFromString(char *config) {
|
||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||
}
|
||||
sdsfreesplitres(argv,argc);
|
||||
argv = NULL;
|
||||
}
|
||||
|
||||
if (server.logfile[0] != '\0') {
|
||||
@@ -627,6 +633,7 @@ void loadServerConfigFromString(char *config) {
|
||||
return;
|
||||
|
||||
loaderr:
|
||||
if (argv) sdsfreesplitres(argv,argc);
|
||||
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n",
|
||||
REDIS_VERSION);
|
||||
if (i < totlines) {
|
||||
@@ -1684,7 +1691,7 @@ int rewriteConfigOverwriteFile(char *configfile, sds content) {
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef _GNU_SOURCE
|
||||
#if defined(_GNU_SOURCE) && !defined(__HAIKU__)
|
||||
fd = mkostemp(tmp_conffile, O_CLOEXEC);
|
||||
#else
|
||||
/* There's a theoretical chance here to leak the FD if a module thread forks & execv in the middle */
|
||||
@@ -2377,6 +2384,14 @@ static int isValidShutdownOnSigFlags(int val, const char **err) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int isValidAnnouncedNodename(char *val,const char **err) {
|
||||
if (!(isValidAuxString(val,sdslen(val)))) {
|
||||
*err = "Announced human node name contained invalid character";
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int isValidAnnouncedHostname(char *val, const char **err) {
|
||||
if (strlen(val) >= NET_HOST_STR_LEN) {
|
||||
*err = "Hostnames must be less than "
|
||||
@@ -2628,6 +2643,12 @@ int updateClusterHostname(const char **err) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int updateClusterHumanNodename(const char **err) {
|
||||
UNUSED(err);
|
||||
clusterUpdateMyselfHumanNodename();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int applyTlsCfg(const char **err) {
|
||||
UNUSED(err);
|
||||
|
||||
@@ -3072,7 +3093,7 @@ standardConfig static_configs[] = {
|
||||
createBoolConfig("cluster-allow-replica-migration", NULL, MODIFIABLE_CONFIG, server.cluster_allow_replica_migration, 1, NULL, NULL),
|
||||
createBoolConfig("replica-announced", NULL, MODIFIABLE_CONFIG, server.replica_announced, 1, NULL, NULL),
|
||||
createBoolConfig("latency-tracking", NULL, MODIFIABLE_CONFIG, server.latency_tracking_enabled, 1, NULL, NULL),
|
||||
createBoolConfig("aof-disable-auto-gc", NULL, MODIFIABLE_CONFIG, server.aof_disable_auto_gc, 0, NULL, updateAofAutoGCEnabled),
|
||||
createBoolConfig("aof-disable-auto-gc", NULL, MODIFIABLE_CONFIG | HIDDEN_CONFIG, server.aof_disable_auto_gc, 0, NULL, updateAofAutoGCEnabled),
|
||||
createBoolConfig("replica-ignore-disk-write-errors", NULL, MODIFIABLE_CONFIG, server.repl_ignore_disk_write_error, 0, NULL, NULL),
|
||||
|
||||
/* String Configs */
|
||||
@@ -3084,6 +3105,7 @@ standardConfig static_configs[] = {
|
||||
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, updateClusterIp),
|
||||
createStringConfig("cluster-config-file", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.cluster_configfile, "nodes.conf", NULL, NULL),
|
||||
createStringConfig("cluster-announce-hostname", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_hostname, NULL, isValidAnnouncedHostname, updateClusterHostname),
|
||||
createStringConfig("cluster-announce-human-nodename", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_human_nodename, NULL, isValidAnnouncedNodename, updateClusterHumanNodename),
|
||||
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL),
|
||||
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL),
|
||||
createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof", isValidAOFfilename, NULL),
|
||||
|
||||
+8
-2
@@ -114,14 +114,15 @@ typedef struct ConnectionType {
|
||||
struct connection {
|
||||
ConnectionType *type;
|
||||
ConnectionState state;
|
||||
int last_errno;
|
||||
int fd;
|
||||
short int flags;
|
||||
short int refs;
|
||||
int last_errno;
|
||||
unsigned short int iovcnt;
|
||||
void *private_data;
|
||||
ConnectionCallbackFunc conn_handler;
|
||||
ConnectionCallbackFunc write_handler;
|
||||
ConnectionCallbackFunc read_handler;
|
||||
int fd;
|
||||
};
|
||||
|
||||
#define CONFIG_BINDADDR_MAX 16
|
||||
@@ -445,4 +446,9 @@ int RedisRegisterConnectionTypeSocket(void);
|
||||
int RedisRegisterConnectionTypeUnix(void);
|
||||
int RedisRegisterConnectionTypeTLS(void);
|
||||
|
||||
/* Return 1 if connection is using TLS protocol, 0 if otherwise. */
|
||||
static inline int connIsTLS(connection *conn) {
|
||||
return conn && conn->type == connectionTypeTls();
|
||||
}
|
||||
|
||||
#endif /* __REDIS_CONNECTION_H */
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
|
||||
int expireIfNeeded(redisDb *db, robj *key, int flags);
|
||||
int keyIsExpired(redisDb *db, robj *key);
|
||||
static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de);
|
||||
|
||||
/* Update LFU when an object is accessed.
|
||||
* Firstly, decrement the counter if the decrement time is reached.
|
||||
@@ -187,17 +188,28 @@ robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
|
||||
/* Add the key to the DB. It's up to the caller to increment the reference
|
||||
* counter of the value if needed.
|
||||
*
|
||||
* The program is aborted if the key already exists. */
|
||||
void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
sds copy = sdsdup(key->ptr);
|
||||
dictEntry *de = dictAddRaw(db->dict, copy, NULL);
|
||||
* If the update_if_existing argument is false, the the program is aborted
|
||||
* if the key already exists, otherwise, it can fall back to dbOverwite. */
|
||||
static void dbAddInternal(redisDb *db, robj *key, robj *val, int update_if_existing) {
|
||||
dictEntry *existing;
|
||||
dictEntry *de = dictAddRaw(db->dict, key->ptr, &existing);
|
||||
if (update_if_existing && existing) {
|
||||
dbSetValue(db, key, val, 1, existing);
|
||||
return;
|
||||
}
|
||||
serverAssertWithInfo(NULL, key, de != NULL);
|
||||
dictSetKey(db->dict, de, sdsdup(key->ptr));
|
||||
initObjectLRUOrLFU(val);
|
||||
dictSetVal(db->dict, de, val);
|
||||
signalKeyAsReady(db, key, val->type);
|
||||
if (server.cluster_enabled) slotToKeyAddEntry(de, db);
|
||||
notifyKeyspaceEvent(NOTIFY_NEW,"new",key,db->id);
|
||||
}
|
||||
|
||||
void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
dbAddInternal(db, key, val, 0);
|
||||
}
|
||||
|
||||
/* This is a special version of dbAdd() that is used only when loading
|
||||
* keys from the RDB file: the key is passed as an SDS string that is
|
||||
* retained by the function (and not freed by the caller).
|
||||
@@ -212,6 +224,7 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
|
||||
dictEntry *de = dictAddRaw(db->dict, key, NULL);
|
||||
if (de == NULL) return 0;
|
||||
initObjectLRUOrLFU(val);
|
||||
dictSetVal(db->dict, de, val);
|
||||
if (server.cluster_enabled) slotToKeyAddEntry(de, db);
|
||||
return 1;
|
||||
@@ -226,15 +239,16 @@ int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
|
||||
* replacement (in which case we need to emit deletion signals), or just an
|
||||
* update of a value of an existing key (when false).
|
||||
*
|
||||
* The dictEntry input is optional, can be used if we already have one.
|
||||
*
|
||||
* The program is aborted if the key was not already present. */
|
||||
static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite) {
|
||||
dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite, dictEntry *de) {
|
||||
if (!de) de = dictFind(db->dict,key->ptr);
|
||||
serverAssertWithInfo(NULL,key,de != NULL);
|
||||
robj *old = dictGetVal(de);
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
val->lru = old->lru;
|
||||
}
|
||||
|
||||
val->lru = old->lru;
|
||||
|
||||
if (overwrite) {
|
||||
/* RM_StringDMA may call dbUnshareStringValue which may free val, so we
|
||||
* need to incr to retain old */
|
||||
@@ -262,7 +276,7 @@ static void dbSetValue(redisDb *db, robj *key, robj *val, int overwrite) {
|
||||
/* Replace an existing key with a new value, we just replace value and don't
|
||||
* emit any events */
|
||||
void dbReplaceValue(redisDb *db, robj *key, robj *val) {
|
||||
dbSetValue(db, key, val, 0);
|
||||
dbSetValue(db, key, val, 0, NULL);
|
||||
}
|
||||
|
||||
/* High level Set operation. This function can be used in order to set
|
||||
@@ -283,13 +297,17 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) {
|
||||
|
||||
if (flags & SETKEY_ALREADY_EXIST)
|
||||
keyfound = 1;
|
||||
else if (flags & SETKEY_ADD_OR_UPDATE)
|
||||
keyfound = -1;
|
||||
else if (!(flags & SETKEY_DOESNT_EXIST))
|
||||
keyfound = (lookupKeyWrite(db,key) != NULL);
|
||||
|
||||
if (!keyfound) {
|
||||
dbAdd(db,key,val);
|
||||
} else if (keyfound<0) {
|
||||
dbAddInternal(db,key,val,1);
|
||||
} else {
|
||||
dbSetValue(db,key,val,1);
|
||||
dbSetValue(db,key,val,1,NULL);
|
||||
}
|
||||
incrRefCount(val);
|
||||
if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key);
|
||||
@@ -792,29 +810,70 @@ void keysCommand(client *c) {
|
||||
setDeferredArrayLen(c,replylen,numkeys);
|
||||
}
|
||||
|
||||
/* Data used by the dict scan callback. */
|
||||
typedef struct {
|
||||
list *keys; /* elements that collect from dict */
|
||||
robj *o; /* o must be a hash/set/zset object, NULL means current db */
|
||||
long long type; /* the particular type when scan the db */
|
||||
sds pattern; /* pattern string, NULL means no pattern */
|
||||
long sampled; /* cumulative number of keys sampled */
|
||||
} scanData;
|
||||
|
||||
/* Helper function to compare key type in scan commands */
|
||||
int objectTypeCompare(robj *o, long long target) {
|
||||
if (o->type != OBJ_MODULE) {
|
||||
if (o->type != target)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
/* module type compare */
|
||||
long long mt = (long long)REDISMODULE_TYPE_SIGN(((moduleValue *)o->ptr)->type->id);
|
||||
if (target != -mt)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
/* This callback is used by scanGenericCommand in order to collect elements
|
||||
* returned by the dictionary iterator into a list. */
|
||||
void scanCallback(void *privdata, const dictEntry *de) {
|
||||
void **pd = (void**) privdata;
|
||||
list *keys = pd[0];
|
||||
robj *o = pd[1];
|
||||
robj *key, *val = NULL;
|
||||
scanData *data = (scanData *)privdata;
|
||||
list *keys = data->keys;
|
||||
robj *o = data->o;
|
||||
sds val = NULL;
|
||||
sds key = NULL;
|
||||
data->sampled++;
|
||||
|
||||
/* o and typename can not have values at the same time. */
|
||||
serverAssert(!((data->type != LLONG_MAX) && o));
|
||||
|
||||
/* Filter an element if it isn't the type we want. */
|
||||
/* TODO: uncomment in redis 8.0
|
||||
if (!o && data->type != LLONG_MAX) {
|
||||
robj *rval = dictGetVal(de);
|
||||
if (!objectTypeCompare(rval, data->type)) return;
|
||||
}*/
|
||||
|
||||
/* Filter element if it does not match the pattern. */
|
||||
sds keysds = dictGetKey(de);
|
||||
if (data->pattern) {
|
||||
if (!stringmatchlen(data->pattern, sdslen(data->pattern), keysds, sdslen(keysds), 0)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (o == NULL) {
|
||||
sds sdskey = dictGetKey(de);
|
||||
key = createStringObject(sdskey, sdslen(sdskey));
|
||||
key = keysds;
|
||||
} else if (o->type == OBJ_SET) {
|
||||
sds keysds = dictGetKey(de);
|
||||
key = createStringObject(keysds,sdslen(keysds));
|
||||
key = keysds;
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
sds sdskey = dictGetKey(de);
|
||||
sds sdsval = dictGetVal(de);
|
||||
key = createStringObject(sdskey,sdslen(sdskey));
|
||||
val = createStringObject(sdsval,sdslen(sdsval));
|
||||
key = keysds;
|
||||
val = dictGetVal(de);
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
sds sdskey = dictGetKey(de);
|
||||
key = createStringObject(sdskey,sdslen(sdskey));
|
||||
val = createStringObjectFromLongDouble(*(double*)dictGetVal(de),0);
|
||||
char buf[MAX_LONG_DOUBLE_CHARS];
|
||||
int len = ld2string(buf, sizeof(buf), *(double *)dictGetVal(de), LD_STR_AUTO);
|
||||
key = sdsdup(keysds);
|
||||
val = sdsnewlen(buf, len);
|
||||
} else {
|
||||
serverPanic("Type not handled in SCAN callback.");
|
||||
}
|
||||
@@ -842,6 +901,46 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
char *obj_type_name[OBJ_TYPE_MAX] = {
|
||||
"string",
|
||||
"list",
|
||||
"set",
|
||||
"zset",
|
||||
"hash",
|
||||
NULL, /* module type is special */
|
||||
"stream"
|
||||
};
|
||||
|
||||
/* Helper function to get type from a string in scan commands */
|
||||
long long getObjectTypeByName(char *name) {
|
||||
|
||||
for (long long i = 0; i < OBJ_TYPE_MAX; i++) {
|
||||
if (obj_type_name[i] && !strcasecmp(name, obj_type_name[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
moduleType *mt = moduleTypeLookupModuleByNameIgnoreCase(name);
|
||||
if (mt != NULL) return -(REDISMODULE_TYPE_SIGN(mt->id));
|
||||
|
||||
return LLONG_MAX;
|
||||
}
|
||||
|
||||
char *getObjectTypeName(robj *o) {
|
||||
if (o == NULL) {
|
||||
return "none";
|
||||
}
|
||||
|
||||
serverAssert(o->type >= 0 && o->type < OBJ_TYPE_MAX);
|
||||
|
||||
if (o->type == OBJ_MODULE) {
|
||||
moduleValue *mv = o->ptr;
|
||||
return mv->type->name;
|
||||
} else {
|
||||
return obj_type_name[o->type];
|
||||
}
|
||||
}
|
||||
|
||||
/* This command implements SCAN, HSCAN and SSCAN commands.
|
||||
* If object 'o' is passed, then it must be a Hash, Set or Zset object, otherwise
|
||||
* if 'o' is NULL the command will operate on the dictionary associated with
|
||||
@@ -855,11 +954,11 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
|
||||
* of every element on the Hash. */
|
||||
void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
int i, j;
|
||||
list *keys = listCreate();
|
||||
listNode *node, *nextnode;
|
||||
listNode *node;
|
||||
long count = 10;
|
||||
sds pat = NULL;
|
||||
sds typename = NULL;
|
||||
long long type = LLONG_MAX;
|
||||
int patlen = 0, use_pattern = 0;
|
||||
dict *ht;
|
||||
|
||||
@@ -878,12 +977,12 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
|
||||
!= C_OK)
|
||||
{
|
||||
goto cleanup;
|
||||
return;
|
||||
}
|
||||
|
||||
if (count < 1) {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
goto cleanup;
|
||||
return;
|
||||
}
|
||||
|
||||
i += 2;
|
||||
@@ -899,10 +998,16 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
} else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) {
|
||||
/* SCAN for a particular type only applies to the db dict */
|
||||
typename = c->argv[i+1]->ptr;
|
||||
type = getObjectTypeByName(typename);
|
||||
if (type == LLONG_MAX) {
|
||||
/* TODO: uncomment in redis 8.0
|
||||
addReplyErrorFormat(c, "unknown type name '%s'", typename);
|
||||
return; */
|
||||
}
|
||||
i+= 2;
|
||||
} else {
|
||||
addReplyErrorObject(c,shared.syntaxerr);
|
||||
goto cleanup;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -922,42 +1027,67 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
ht = o->ptr;
|
||||
} else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) {
|
||||
ht = o->ptr;
|
||||
count *= 2; /* We return key / value for this type. */
|
||||
} else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
zset *zs = o->ptr;
|
||||
ht = zs->dict;
|
||||
count *= 2; /* We return key / value for this type. */
|
||||
}
|
||||
|
||||
list *keys = listCreate();
|
||||
/* Set a free callback for the contents of the collected keys list.
|
||||
* For the main keyspace dict, and when we scan a key that's dict encoded
|
||||
* (we have 'ht'), we don't need to define free method because the strings
|
||||
* in the list are just a shallow copy from the pointer in the dictEntry.
|
||||
* When scanning a key with other encodings (e.g. listpack), we need to
|
||||
* free the temporary strings we add to that list.
|
||||
* The exception to the above is ZSET, where we do allocate temporary
|
||||
* strings even when scanning a dict. */
|
||||
if (o && (!ht || o->type == OBJ_ZSET)) {
|
||||
listSetFreeMethod(keys, (void (*)(void*))sdsfree);
|
||||
}
|
||||
|
||||
if (ht) {
|
||||
void *privdata[2];
|
||||
/* We set the max number of iterations to ten times the specified
|
||||
* COUNT, so if the hash table is in a pathological state (very
|
||||
* sparsely populated) we avoid to block too much time at the cost
|
||||
* of returning no or very few elements. */
|
||||
long maxiterations = count*10;
|
||||
|
||||
/* We pass two pointers to the callback: the list to which it will
|
||||
* add new elements, and the object containing the dictionary so that
|
||||
* it is possible to fetch more data in a type-dependent way. */
|
||||
privdata[0] = keys;
|
||||
privdata[1] = o;
|
||||
/* We pass scanData which have three pointers to the callback:
|
||||
* 1. data.keys: the list to which it will add new elements;
|
||||
* 2. data.o: the object containing the dictionary so that
|
||||
* it is possible to fetch more data in a type-dependent way;
|
||||
* 3. data.type: the specified type scan in the db, LLONG_MAX means
|
||||
* type matching is no needed;
|
||||
* 4. data.pattern: the pattern string
|
||||
* 5. data.sampled: the maxiteration limit is there in case we're
|
||||
* working on an empty dict, one with a lot of empty buckets, and
|
||||
* for the buckets are not empty, we need to limit the spampled number
|
||||
* to prevent a long hang time caused by filtering too many keys*/
|
||||
scanData data = {
|
||||
.keys = keys,
|
||||
.o = o,
|
||||
.type = type,
|
||||
.pattern = use_pattern ? pat : NULL,
|
||||
.sampled = 0,
|
||||
};
|
||||
do {
|
||||
cursor = dictScan(ht, cursor, scanCallback, privdata);
|
||||
} while (cursor &&
|
||||
maxiterations-- &&
|
||||
listLength(keys) < (unsigned long)count);
|
||||
cursor = dictScan(ht, cursor, scanCallback, &data);
|
||||
} while (cursor && maxiterations-- && data.sampled < count);
|
||||
} else if (o->type == OBJ_SET) {
|
||||
char *str;
|
||||
char buf[LONG_STR_SIZE];
|
||||
size_t len;
|
||||
int64_t llele;
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
while (setTypeNext(si, &str, &len, &llele) != -1) {
|
||||
if (str == NULL) {
|
||||
listAddNodeTail(keys, createStringObjectFromLongLong(llele));
|
||||
} else {
|
||||
listAddNodeTail(keys, createStringObject(str, len));
|
||||
len = ll2string(buf, sizeof(buf), llele);
|
||||
}
|
||||
char *key = str ? str : buf;
|
||||
if (use_pattern && !stringmatchlen(pat, sdslen(pat), key, len, 0)) {
|
||||
continue;
|
||||
}
|
||||
listAddNodeTail(keys, sdsnewlen(key, len));
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
cursor = 0;
|
||||
@@ -965,72 +1095,53 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
o->encoding == OBJ_ENCODING_LISTPACK)
|
||||
{
|
||||
unsigned char *p = lpFirst(o->ptr);
|
||||
unsigned char *vstr;
|
||||
int64_t vlen;
|
||||
unsigned char *str;
|
||||
int64_t len;
|
||||
unsigned char intbuf[LP_INTBUF_SIZE];
|
||||
|
||||
while(p) {
|
||||
vstr = lpGet(p,&vlen,intbuf);
|
||||
listAddNodeTail(keys, createStringObject((char*)vstr,vlen));
|
||||
p = lpNext(o->ptr,p);
|
||||
str = lpGet(p, &len, intbuf);
|
||||
/* point to the value */
|
||||
p = lpNext(o->ptr, p);
|
||||
if (use_pattern && !stringmatchlen(pat, sdslen(pat), (char *)str, len, 0)) {
|
||||
/* jump to the next key/val pair */
|
||||
p = lpNext(o->ptr, p);
|
||||
continue;
|
||||
}
|
||||
/* add key object */
|
||||
listAddNodeTail(keys, sdsnewlen(str, len));
|
||||
/* add value object */
|
||||
str = lpGet(p, &len, intbuf);
|
||||
listAddNodeTail(keys, sdsnewlen(str, len));
|
||||
p = lpNext(o->ptr, p);
|
||||
}
|
||||
cursor = 0;
|
||||
} else {
|
||||
serverPanic("Not handled encoding in SCAN.");
|
||||
}
|
||||
|
||||
/* Step 3: Filter elements. */
|
||||
node = listFirst(keys);
|
||||
while (node) {
|
||||
robj *kobj = listNodeValue(node);
|
||||
nextnode = listNextNode(node);
|
||||
int filter = 0;
|
||||
|
||||
/* Filter element if it does not match the pattern. */
|
||||
if (use_pattern) {
|
||||
if (sdsEncodedObject(kobj)) {
|
||||
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
|
||||
filter = 1;
|
||||
} else {
|
||||
char buf[LONG_STR_SIZE];
|
||||
int len;
|
||||
|
||||
serverAssert(kobj->encoding == OBJ_ENCODING_INT);
|
||||
len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
|
||||
if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
|
||||
/* Step 3: Filter the expired keys */
|
||||
if (o == NULL && listLength(keys)) {
|
||||
robj kobj;
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(keys, &li);
|
||||
while ((ln = listNext(&li))) {
|
||||
sds key = listNodeValue(ln);
|
||||
initStaticStringObject(kobj, key);
|
||||
/* Filter an element if it isn't the type we want. */
|
||||
/* TODO: remove this in redis 8.0 */
|
||||
if (typename) {
|
||||
robj* typecheck = lookupKeyReadWithFlags(c->db, &kobj, LOOKUP_NOTOUCH|LOOKUP_NONOTIFY);
|
||||
if (!typecheck || !objectTypeCompare(typecheck, type)) {
|
||||
listDelNode(keys, ln);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (expireIfNeeded(c->db, &kobj, 0)) {
|
||||
listDelNode(keys, ln);
|
||||
}
|
||||
}
|
||||
|
||||
/* Filter an element if it isn't the type we want. */
|
||||
if (!filter && o == NULL && typename){
|
||||
robj* typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);
|
||||
char* type = getObjectTypeName(typecheck);
|
||||
if (strcasecmp((char*) typename, type)) filter = 1;
|
||||
}
|
||||
|
||||
/* Filter element if it is an expired key. */
|
||||
if (!filter && o == NULL && expireIfNeeded(c->db, kobj, 0)) filter = 1;
|
||||
|
||||
/* Remove the element and its associated value if needed. */
|
||||
if (filter) {
|
||||
decrRefCount(kobj);
|
||||
listDelNode(keys, node);
|
||||
}
|
||||
|
||||
/* If this is a hash or a sorted set, we have a flat list of
|
||||
* key-value elements, so if this element was filtered, remove the
|
||||
* value, or skip it if it was not filtered: we only match keys. */
|
||||
if (o && (o->type == OBJ_ZSET || o->type == OBJ_HASH)) {
|
||||
node = nextnode;
|
||||
serverAssert(node); /* assertion for valgrind (avoid NPD) */
|
||||
nextnode = listNextNode(node);
|
||||
if (filter) {
|
||||
kobj = listNodeValue(node);
|
||||
decrRefCount(kobj);
|
||||
listDelNode(keys, node);
|
||||
}
|
||||
}
|
||||
node = nextnode;
|
||||
}
|
||||
|
||||
/* Step 4: Reply to the client. */
|
||||
@@ -1039,14 +1150,11 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
|
||||
|
||||
addReplyArrayLen(c, listLength(keys));
|
||||
while ((node = listFirst(keys)) != NULL) {
|
||||
robj *kobj = listNodeValue(node);
|
||||
addReplyBulk(c, kobj);
|
||||
decrRefCount(kobj);
|
||||
sds key = listNodeValue(node);
|
||||
addReplyBulkCBuffer(c, key, sdslen(key));
|
||||
listDelNode(keys, node);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
listSetFreeMethod(keys,decrRefCountVoid);
|
||||
listRelease(keys);
|
||||
}
|
||||
|
||||
@@ -1065,28 +1173,6 @@ void lastsaveCommand(client *c) {
|
||||
addReplyLongLong(c,server.lastsave);
|
||||
}
|
||||
|
||||
char* getObjectTypeName(robj *o) {
|
||||
char* type;
|
||||
if (o == NULL) {
|
||||
type = "none";
|
||||
} else {
|
||||
switch(o->type) {
|
||||
case OBJ_STRING: type = "string"; break;
|
||||
case OBJ_LIST: type = "list"; break;
|
||||
case OBJ_SET: type = "set"; break;
|
||||
case OBJ_ZSET: type = "zset"; break;
|
||||
case OBJ_HASH: type = "hash"; break;
|
||||
case OBJ_STREAM: type = "stream"; break;
|
||||
case OBJ_MODULE: {
|
||||
moduleValue *mv = o->ptr;
|
||||
type = mv->type->name;
|
||||
}; break;
|
||||
default: type = "unknown"; break;
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
void typeCommand(client *c) {
|
||||
robj *o;
|
||||
o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
|
||||
@@ -1557,9 +1643,6 @@ void swapdbCommand(client *c) {
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
int removeExpire(redisDb *db, robj *key) {
|
||||
/* An expire may only be removed if there is a corresponding entry in the
|
||||
* main dict. Otherwise, the key will never be freed. */
|
||||
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
|
||||
return dictDelete(db->expires,key->ptr) == DICT_OK;
|
||||
}
|
||||
|
||||
@@ -1590,9 +1673,6 @@ long long getExpire(redisDb *db, robj *key) {
|
||||
if (dictSize(db->expires) == 0 ||
|
||||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
|
||||
|
||||
/* The entry was found in the expire dict, this means it should also
|
||||
* be present in the main dict (safety check). */
|
||||
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
|
||||
return dictGetSignedIntegerVal(de);
|
||||
}
|
||||
|
||||
@@ -1724,8 +1804,16 @@ int expireIfNeeded(redisDb *db, robj *key, int flags) {
|
||||
* will have failed over and the new primary will send us the expire. */
|
||||
if (isPausedActionsWithUpdate(PAUSE_ACTION_EXPIRE)) return 1;
|
||||
|
||||
/* The key needs to be converted from static to heap before deleted */
|
||||
int static_key = key->refcount == OBJ_STATIC_REFCOUNT;
|
||||
if (static_key) {
|
||||
key = createStringObject(key->ptr, sdslen(key->ptr));
|
||||
}
|
||||
/* Delete the key */
|
||||
deleteExpiredKeyAndPropagate(db,key);
|
||||
if (static_key) {
|
||||
decrRefCount(key);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1783,8 +1871,9 @@ int64_t getAllKeySpecsFlags(struct redisCommand *cmd, int inv) {
|
||||
* found in other valid keyspecs.
|
||||
*/
|
||||
int getKeysUsingKeySpecs(struct redisCommand *cmd, robj **argv, int argc, int search_flags, getKeysResult *result) {
|
||||
int j, i, k = 0, last, first, step;
|
||||
int j, i, last, first, step;
|
||||
keyReference *keys;
|
||||
serverAssert(result->numkeys == 0); /* caller should initialize or reset it */
|
||||
|
||||
for (j = 0; j < cmd->key_specs_num; j++) {
|
||||
keySpec *spec = cmd->key_specs + j;
|
||||
@@ -1849,7 +1938,7 @@ int getKeysUsingKeySpecs(struct redisCommand *cmd, robj **argv, int argc, int se
|
||||
}
|
||||
|
||||
int count = ((last - first)+1);
|
||||
keys = getKeysPrepareResult(result, count);
|
||||
keys = getKeysPrepareResult(result, result->numkeys + count);
|
||||
|
||||
/* First or last is out of bounds, which indicates a syntax error */
|
||||
if (last >= argc || last < first || first >= argc) {
|
||||
@@ -1870,8 +1959,9 @@ int getKeysUsingKeySpecs(struct redisCommand *cmd, robj **argv, int argc, int se
|
||||
serverPanic("Redis built-in command declared keys positions not matching the arity requirements.");
|
||||
}
|
||||
}
|
||||
keys[k].pos = i;
|
||||
keys[k++].flags = spec->flags;
|
||||
keys[result->numkeys].pos = i;
|
||||
keys[result->numkeys].flags = spec->flags;
|
||||
result->numkeys++;
|
||||
}
|
||||
|
||||
/* Handle incomplete specs (only after we added the current spec
|
||||
@@ -1892,8 +1982,7 @@ invalid_spec:
|
||||
}
|
||||
}
|
||||
|
||||
result->numkeys = k;
|
||||
return k;
|
||||
return result->numkeys;
|
||||
}
|
||||
|
||||
/* Return all the arguments that are keys in the command passed via argc / argv.
|
||||
@@ -2110,7 +2199,7 @@ void getKeysFreeResult(getKeysResult *result) {
|
||||
* 'firstKeyOfs': firstkey index.
|
||||
* 'keyStep': the interval of each key, usually this value is 1.
|
||||
*
|
||||
* The commands using this functoin have a fully defined keyspec, so returning flags isn't needed. */
|
||||
* The commands using this function have a fully defined keyspec, so returning flags isn't needed. */
|
||||
int genericGetKeys(int storeKeyOfs, int keyCountOfs, int firstKeyOfs, int keyStep,
|
||||
robj **argv, int argc, getKeysResult *result) {
|
||||
int i, num;
|
||||
|
||||
+64
-9
@@ -413,9 +413,9 @@ void debugCommand(client *c) {
|
||||
" Create a memory leak of the input string.",
|
||||
"LOG <message>",
|
||||
" Write <message> to the server log.",
|
||||
"HTSTATS <dbid>",
|
||||
"HTSTATS <dbid> [full]",
|
||||
" Return hash table statistics of the specified Redis database.",
|
||||
"HTSTATS-KEY <key>",
|
||||
"HTSTATS-KEY <key> [full]",
|
||||
" Like HTSTATS but for the hash table stored at <key>'s value.",
|
||||
"LOADAOF",
|
||||
" Flush the AOF buffers on disk and reload the AOF in memory.",
|
||||
@@ -713,7 +713,10 @@ NULL
|
||||
if (getPositiveLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
dictExpand(c->db->dict,keys);
|
||||
if (dictTryExpand(c->db->dict, keys) != DICT_OK) {
|
||||
addReplyError(c, "OOM in dictTryExpand");
|
||||
return;
|
||||
}
|
||||
long valsize = 0;
|
||||
if ( c->argc == 5 && getPositiveLongFromObjectOrReply(c, c->argv[4], &valsize, NULL) != C_OK )
|
||||
return;
|
||||
@@ -883,10 +886,11 @@ NULL
|
||||
sizes = sdscatprintf(sizes,"sdshdr32:%d ",(int)sizeof(struct sdshdr32));
|
||||
sizes = sdscatprintf(sizes,"sdshdr64:%d ",(int)sizeof(struct sdshdr64));
|
||||
addReplyBulkSds(c,sizes);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats") && c->argc == 3) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats") && c->argc >= 3) {
|
||||
long dbid;
|
||||
sds stats = sdsempty();
|
||||
char buf[4096];
|
||||
int full = 0;
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &dbid, NULL) != C_OK) {
|
||||
sdsfree(stats);
|
||||
@@ -897,20 +901,26 @@ NULL
|
||||
addReplyError(c,"Out of range database");
|
||||
return;
|
||||
}
|
||||
if (c->argc >= 4 && !strcasecmp(c->argv[3]->ptr,"full"))
|
||||
full = 1;
|
||||
|
||||
stats = sdscatprintf(stats,"[Dictionary HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].dict);
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].dict,full);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
stats = sdscatprintf(stats,"[Expires HT]\n");
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires,full);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
addReplyVerbatim(c,stats,sdslen(stats),"txt");
|
||||
sdsfree(stats);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc >= 3) {
|
||||
robj *o;
|
||||
dict *ht = NULL;
|
||||
int full = 0;
|
||||
|
||||
if (c->argc >= 4 && !strcasecmp(c->argv[3]->ptr,"full"))
|
||||
full = 1;
|
||||
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
|
||||
== NULL) return;
|
||||
@@ -933,7 +943,7 @@ NULL
|
||||
"represented using an hash table");
|
||||
} else {
|
||||
char buf[4096];
|
||||
dictGetStats(buf,sizeof(buf),ht);
|
||||
dictGetStats(buf,sizeof(buf),ht,full);
|
||||
addReplyVerbatim(c,buf,strlen(buf),"txt");
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
||||
@@ -1211,6 +1221,8 @@ static void* getAndSetMcontextEip(ucontext_t *uc, void *eip) {
|
||||
GET_SET_RETURN(uc->uc_mcontext.gregs[16], eip);
|
||||
#elif defined(__ia64__) /* Linux IA64 */
|
||||
GET_SET_RETURN(uc->uc_mcontext.sc_ip, eip);
|
||||
#elif defined(__riscv) /* Linux RISC-V */
|
||||
GET_SET_RETURN(uc->uc_mcontext.__gregs[REG_PC], eip);
|
||||
#elif defined(__arm__) /* Linux ARM */
|
||||
GET_SET_RETURN(uc->uc_mcontext.arm_pc, eip);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
@@ -1445,6 +1457,49 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.gregs[18]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#elif defined(__riscv) /* Linux RISC-V */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"ra:%016lx gp:%016lx\ntp:%016lx t0:%016lx\n"
|
||||
"t1:%016lx t2:%016lx\ns0:%016lx s1:%016lx\n"
|
||||
"a0:%016lx a1:%016lx\na2:%016lx a3:%016lx\n"
|
||||
"a4:%016lx a5:%016lx\na6:%016lx a7:%016lx\n"
|
||||
"s2:%016lx s3:%016lx\ns4:%016lx s5:%016lx\n"
|
||||
"s6:%016lx s7:%016lx\ns8:%016lx s9:%016lx\n"
|
||||
"s10:%016lx s11:%016lx\nt3:%016lx t4:%016lx\n"
|
||||
"t5:%016lx t6:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.__gregs[1],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[3],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[4],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[5],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[6],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[7],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[8],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[9],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[10],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[11],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[12],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[13],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[14],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[15],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[16],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[17],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[18],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[19],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[20],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[21],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[22],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[23],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[24],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[25],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[26],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[27],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[28],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[29],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[30],
|
||||
(unsigned long) uc->uc_mcontext.__gregs[31]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.__gregs[REG_SP]);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
@@ -1803,7 +1858,7 @@ sds genClusterDebugString(sds infostring) {
|
||||
infostring = sdscatprintf(infostring, "\r\n# Cluster info\r\n");
|
||||
infostring = sdscatsds(infostring, genClusterInfoString());
|
||||
infostring = sdscatprintf(infostring, "\n------ CLUSTER NODES OUTPUT ------\n");
|
||||
infostring = sdscatsds(infostring, clusterGenNodesDescription(0, 0));
|
||||
infostring = sdscatsds(infostring, clusterGenNodesDescription(NULL, 0, 0));
|
||||
|
||||
return infostring;
|
||||
}
|
||||
|
||||
+48
-17
@@ -294,9 +294,12 @@ int dictTryExpand(dict *d, unsigned long size) {
|
||||
* work it does would be unbound and the function may block for a long time. */
|
||||
int dictRehash(dict *d, int n) {
|
||||
int empty_visits = n*10; /* Max number of empty buckets to visit. */
|
||||
unsigned long s0 = DICTHT_SIZE(d->ht_size_exp[0]);
|
||||
unsigned long s1 = DICTHT_SIZE(d->ht_size_exp[1]);
|
||||
if (dict_can_resize == DICT_RESIZE_FORBID || !dictIsRehashing(d)) return 0;
|
||||
if (dict_can_resize == DICT_RESIZE_AVOID &&
|
||||
(DICTHT_SIZE(d->ht_size_exp[1]) / DICTHT_SIZE(d->ht_size_exp[0]) < dict_force_resize_ratio))
|
||||
((s1 > s0 && s1 / s0 < dict_force_resize_ratio) ||
|
||||
(s1 < s0 && s0 / s1 < dict_force_resize_ratio)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -1095,19 +1098,30 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
|
||||
} else {
|
||||
emptylen = 0;
|
||||
while (he) {
|
||||
/* Collect all the elements of the buckets found non
|
||||
* empty while iterating. */
|
||||
*des = he;
|
||||
des++;
|
||||
/* Collect all the elements of the buckets found non empty while iterating.
|
||||
* To avoid the issue of being unable to sample the end of a long chain,
|
||||
* we utilize the Reservoir Sampling algorithm to optimize the sampling process.
|
||||
* This means that even when the maximum number of samples has been reached,
|
||||
* we continue sampling until we reach the end of the chain.
|
||||
* See https://en.wikipedia.org/wiki/Reservoir_sampling. */
|
||||
if (stored < count) {
|
||||
des[stored] = he;
|
||||
} else {
|
||||
unsigned long r = randomULong() % (stored + 1);
|
||||
if (r < count) des[r] = he;
|
||||
}
|
||||
|
||||
he = dictGetNext(he);
|
||||
stored++;
|
||||
if (stored == count) return stored;
|
||||
}
|
||||
if (stored >= count) goto end;
|
||||
}
|
||||
}
|
||||
i = (i+1) & maxsizemask;
|
||||
}
|
||||
return stored;
|
||||
|
||||
end:
|
||||
return stored > count ? count : stored;
|
||||
}
|
||||
|
||||
|
||||
@@ -1502,7 +1516,7 @@ dictEntry *dictFindEntryByPtrAndHash(dict *d, const void *oldptr, uint64_t hash)
|
||||
/* ------------------------------- Debugging ---------------------------------*/
|
||||
|
||||
#define DICT_STATS_VECTLEN 50
|
||||
size_t _dictGetStatsHt(char *buf, size_t bufsize, dict *d, int htidx) {
|
||||
size_t _dictGetStatsHt(char *buf, size_t bufsize, dict *d, int htidx, int full) {
|
||||
unsigned long i, slots = 0, chainlen, maxchainlen = 0;
|
||||
unsigned long totchainlen = 0;
|
||||
unsigned long clvector[DICT_STATS_VECTLEN];
|
||||
@@ -1510,7 +1524,23 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dict *d, int htidx) {
|
||||
|
||||
if (d->ht_used[htidx] == 0) {
|
||||
return snprintf(buf,bufsize,
|
||||
"No stats available for empty dictionaries\n");
|
||||
"Hash table %d stats (%s):\n"
|
||||
"No stats available for empty dictionaries\n",
|
||||
htidx, (htidx == 0) ? "main hash table" : "rehashing target");
|
||||
}
|
||||
|
||||
if (!full) {
|
||||
l += snprintf(buf+l,bufsize-l,
|
||||
"Hash table %d stats (%s):\n"
|
||||
" table size: %lu\n"
|
||||
" number of elements: %lu\n",
|
||||
htidx, (htidx == 0) ? "main hash table" : "rehashing target",
|
||||
DICTHT_SIZE(d->ht_size_exp[htidx]), d->ht_used[htidx]);
|
||||
|
||||
/* Make sure there is a NULL term at the end. */
|
||||
buf[bufsize-1] = '\0';
|
||||
/* Unlike snprintf(), return the number of characters actually written. */
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
/* Compute stats. */
|
||||
@@ -1557,24 +1587,25 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dict *d, int htidx) {
|
||||
i, clvector[i], ((float)clvector[i]/DICTHT_SIZE(d->ht_size_exp[htidx]))*100);
|
||||
}
|
||||
|
||||
/* Make sure there is a NULL term at the end. */
|
||||
buf[bufsize-1] = '\0';
|
||||
/* Unlike snprintf(), return the number of characters actually written. */
|
||||
if (bufsize) buf[bufsize-1] = '\0';
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d) {
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d, int full) {
|
||||
size_t l;
|
||||
char *orig_buf = buf;
|
||||
size_t orig_bufsize = bufsize;
|
||||
|
||||
l = _dictGetStatsHt(buf,bufsize,d,0);
|
||||
buf += l;
|
||||
bufsize -= l;
|
||||
if (dictIsRehashing(d) && bufsize > 0) {
|
||||
_dictGetStatsHt(buf,bufsize,d,1);
|
||||
l = _dictGetStatsHt(buf,bufsize,d,0,full);
|
||||
if (dictIsRehashing(d) && bufsize > l) {
|
||||
buf += l;
|
||||
bufsize -= l;
|
||||
_dictGetStatsHt(buf,bufsize,d,1,full);
|
||||
}
|
||||
/* Make sure there is a NULL term at the end. */
|
||||
if (orig_bufsize) orig_buf[orig_bufsize-1] = '\0';
|
||||
orig_buf[orig_bufsize-1] = '\0';
|
||||
}
|
||||
|
||||
/* ------------------------------- Benchmark ---------------------------------*/
|
||||
|
||||
+1
-1
@@ -210,7 +210,7 @@ void dictReleaseIterator(dictIterator *iter);
|
||||
dictEntry *dictGetRandomKey(dict *d);
|
||||
dictEntry *dictGetFairRandomKey(dict *d);
|
||||
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d);
|
||||
void dictGetStats(char *buf, size_t bufsize, dict *d, int full);
|
||||
uint64_t dictGenHashFunction(const void *key, size_t len);
|
||||
uint64_t dictGenCaseHashFunction(const unsigned char *buf, size_t len);
|
||||
void dictEmpty(dict *d, void(callback)(dict*));
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ unsigned int getLRUClock(void) {
|
||||
unsigned int LRU_CLOCK(void) {
|
||||
unsigned int lruclock;
|
||||
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
lruclock = server.lruclock;
|
||||
} else {
|
||||
lruclock = getLRUClock();
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ void geoaddCommand(client *c) {
|
||||
GeoHashBits hash;
|
||||
geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
|
||||
GeoHashFix52Bits bits = geohashAlign52Bits(hash);
|
||||
robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits));
|
||||
robj *score = createStringObjectFromLongLongWithSds(bits);
|
||||
robj *val = c->argv[longidx + i * 3 + 2];
|
||||
argv[longidx+i*2] = score;
|
||||
argv[longidx+1+i*2] = val;
|
||||
|
||||
+85
-42
@@ -356,6 +356,7 @@ typedef struct RedisModuleCommandFilterCtx {
|
||||
RedisModuleString **argv;
|
||||
int argv_len;
|
||||
int argc;
|
||||
client *c;
|
||||
} RedisModuleCommandFilterCtx;
|
||||
|
||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||
@@ -620,7 +621,7 @@ void *RM_PoolAlloc(RedisModuleCtx *ctx, size_t bytes) {
|
||||
* Helpers for modules API implementation
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
client *moduleAllocTempClient(user *user) {
|
||||
client *moduleAllocTempClient(void) {
|
||||
client *c = NULL;
|
||||
|
||||
if (moduleTempClientCount > 0) {
|
||||
@@ -630,10 +631,8 @@ client *moduleAllocTempClient(user *user) {
|
||||
} else {
|
||||
c = createClient(NULL);
|
||||
c->flags |= CLIENT_MODULE;
|
||||
c->user = NULL; /* Root user */
|
||||
}
|
||||
|
||||
c->user = user;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -858,7 +857,7 @@ void moduleCallCommandUnblockedHandler(client *c) {
|
||||
moduleCreateContext(&ctx, module, REDISMODULE_CTX_TEMP_CLIENT);
|
||||
selectDb(ctx.client, c->db->id);
|
||||
|
||||
CallReply *reply = moduleParseReply(c, &ctx);
|
||||
CallReply *reply = moduleParseReply(c, NULL);
|
||||
module->in_call++;
|
||||
promise->on_unblocked(&ctx, reply, promise->private_data);
|
||||
module->in_call--;
|
||||
@@ -879,7 +878,7 @@ void moduleCreateContext(RedisModuleCtx *out_ctx, RedisModule *module, int ctx_f
|
||||
out_ctx->module = module;
|
||||
out_ctx->flags = ctx_flags;
|
||||
if (ctx_flags & REDISMODULE_CTX_TEMP_CLIENT)
|
||||
out_ctx->client = moduleAllocTempClient(NULL);
|
||||
out_ctx->client = moduleAllocTempClient();
|
||||
else if (ctx_flags & REDISMODULE_CTX_NEW_CLIENT)
|
||||
out_ctx->client = createClient(NULL);
|
||||
|
||||
@@ -2999,15 +2998,15 @@ int RM_ReplyWithError(RedisModuleCtx *ctx, const char *err) {
|
||||
|
||||
/* Reply with the error create from a printf format and arguments.
|
||||
*
|
||||
* If the error code is already passed in the string 'fmt', the error
|
||||
* code provided is used, otherwise the string "-ERR " for the generic
|
||||
* error code is automatically added.
|
||||
* Note that 'fmt' must contain all the error, including
|
||||
* the initial error code. The function only provides the initial "-", so
|
||||
* the usage is, for example:
|
||||
*
|
||||
* The usage is, for example:
|
||||
* RedisModule_ReplyWithErrorFormat(ctx,"ERR Wrong Type: %s",type);
|
||||
*
|
||||
* RedisModule_ReplyWithErrorFormat(ctx, "An error: %s", "foo");
|
||||
* and not just:
|
||||
*
|
||||
* RedisModule_ReplyWithErrorFormat(ctx, "-WRONGTYPE Wrong Type: %s", "foo");
|
||||
* RedisModule_ReplyWithErrorFormat(ctx,"Wrong Type: %s",type);
|
||||
*
|
||||
* The function always returns REDISMODULE_OK.
|
||||
*/
|
||||
@@ -3015,11 +3014,17 @@ int RM_ReplyWithErrorFormat(RedisModuleCtx *ctx, const char *fmt, ...) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
|
||||
int len = strlen(fmt) + 2; /* 1 for the \0 and 1 for the hyphen */
|
||||
char *hyphenfmt = zmalloc(len);
|
||||
snprintf(hyphenfmt, len, "-%s", fmt);
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
addReplyErrorFormatInternal(c, 0, fmt, ap);
|
||||
addReplyErrorFormatInternal(c, 0, hyphenfmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
zfree(hyphenfmt);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -6038,7 +6043,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
argv[argc++] = createStringObject(buf,len);
|
||||
} else if (*p == 'l') {
|
||||
long long ll = va_arg(ap,long long);
|
||||
argv[argc++] = createObject(OBJ_STRING,sdsfromlonglong(ll));
|
||||
argv[argc++] = createStringObjectFromLongLongWithSds(ll);
|
||||
} else if (*p == 'v') {
|
||||
/* A vector of strings */
|
||||
robj **v = va_arg(ap, void*);
|
||||
@@ -6215,20 +6220,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
error_as_call_replies = flags & REDISMODULE_ARGV_CALL_REPLIES_AS_ERRORS;
|
||||
va_end(ap);
|
||||
|
||||
user *user = NULL;
|
||||
if (flags & REDISMODULE_ARGV_RUN_AS_USER) {
|
||||
user = ctx->user ? ctx->user->user : ctx->client->user;
|
||||
if (!user) {
|
||||
errno = ENOTSUP;
|
||||
if (error_as_call_replies) {
|
||||
sds msg = sdsnew("cannot run as user, no user directly attached to context or context's client");
|
||||
reply = callReplyCreateError(msg, ctx);
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
|
||||
c = moduleAllocTempClient(user);
|
||||
c = moduleAllocTempClient();
|
||||
|
||||
if (!(flags & REDISMODULE_ARGV_ALLOW_BLOCK)) {
|
||||
/* We do not want to allow block, the module do not expect it */
|
||||
@@ -6248,6 +6240,20 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
}
|
||||
if (ctx->module) ctx->module->in_call++;
|
||||
|
||||
user *user = NULL;
|
||||
if (flags & REDISMODULE_ARGV_RUN_AS_USER) {
|
||||
user = ctx->user ? ctx->user->user : ctx->client->user;
|
||||
if (!user) {
|
||||
errno = ENOTSUP;
|
||||
if (error_as_call_replies) {
|
||||
sds msg = sdsnew("cannot run as user, no user directly attached to context or context's client");
|
||||
reply = callReplyCreateError(msg, ctx);
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
c->user = user;
|
||||
}
|
||||
|
||||
/* We handle the above format error only when the client is setup so that
|
||||
* we can free it normally. */
|
||||
if (argv == NULL) {
|
||||
@@ -6567,7 +6573,7 @@ uint64_t moduleTypeEncodeId(const char *name, int encver) {
|
||||
/* Search, in the list of exported data types of all the modules registered,
|
||||
* a type with the same name as the one given. Returns the moduleType
|
||||
* structure pointer if such a module is found, or NULL otherwise. */
|
||||
moduleType *moduleTypeLookupModuleByName(const char *name) {
|
||||
moduleType *moduleTypeLookupModuleByNameInternal(const char *name, int ignore_case) {
|
||||
dictIterator *di = dictGetIterator(modules);
|
||||
dictEntry *de;
|
||||
|
||||
@@ -6579,7 +6585,9 @@ moduleType *moduleTypeLookupModuleByName(const char *name) {
|
||||
listRewind(module->types,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
moduleType *mt = ln->value;
|
||||
if (memcmp(name,mt->name,sizeof(mt->name)) == 0) {
|
||||
if ((!ignore_case && memcmp(name,mt->name,sizeof(mt->name)) == 0)
|
||||
|| (ignore_case && !strcasecmp(name, mt->name)))
|
||||
{
|
||||
dictReleaseIterator(di);
|
||||
return mt;
|
||||
}
|
||||
@@ -6588,6 +6596,15 @@ moduleType *moduleTypeLookupModuleByName(const char *name) {
|
||||
dictReleaseIterator(di);
|
||||
return NULL;
|
||||
}
|
||||
/* Search all registered modules by name, and name is case sensitive */
|
||||
moduleType *moduleTypeLookupModuleByName(const char *name) {
|
||||
return moduleTypeLookupModuleByNameInternal(name, 0);
|
||||
}
|
||||
|
||||
/* Search all registered modules by name, but case insensitive */
|
||||
moduleType *moduleTypeLookupModuleByNameIgnoreCase(const char *name) {
|
||||
return moduleTypeLookupModuleByNameInternal(name, 1);
|
||||
}
|
||||
|
||||
/* Lookup a module by ID, with caching. This function is used during RDB
|
||||
* loading. Modules exporting data types should never be able to unload, so
|
||||
@@ -7671,7 +7688,6 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
|
||||
* commands from Lua or MULTI. We actually create an already aborted
|
||||
* (client set to NULL) blocked client handle, and actually reply with
|
||||
* an error. */
|
||||
mstime_t timeout = timeout_ms ? (mstime()+timeout_ms) : 0;
|
||||
bc->client = (islua || ismulti) ? NULL : c;
|
||||
bc->module = ctx->module;
|
||||
bc->reply_callback = reply_callback;
|
||||
@@ -7680,8 +7696,8 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
|
||||
bc->disconnect_callback = NULL; /* Set by RM_SetDisconnectCallback() */
|
||||
bc->free_privdata = free_privdata;
|
||||
bc->privdata = privdata;
|
||||
bc->reply_client = moduleAllocTempClient(NULL);
|
||||
bc->thread_safe_ctx_client = moduleAllocTempClient(NULL);
|
||||
bc->reply_client = moduleAllocTempClient();
|
||||
bc->thread_safe_ctx_client = moduleAllocTempClient();
|
||||
if (bc->client)
|
||||
bc->reply_client->resp = bc->client->resp;
|
||||
bc->dbid = c->db->id;
|
||||
@@ -7689,7 +7705,17 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
|
||||
bc->unblocked = 0;
|
||||
bc->background_timer = 0;
|
||||
bc->background_duration = 0;
|
||||
c->bstate.timeout = timeout;
|
||||
|
||||
c->bstate.timeout = 0;
|
||||
if (timeout_ms) {
|
||||
mstime_t now = mstime();
|
||||
if (timeout_ms > LLONG_MAX - now) {
|
||||
c->bstate.module_blocked_handle = NULL;
|
||||
addReplyError(c, "timeout is out of range"); /* 'timeout_ms+now' would overflow */
|
||||
return bc;
|
||||
}
|
||||
c->bstate.timeout = timeout_ms + now;
|
||||
}
|
||||
|
||||
if (islua || ismulti) {
|
||||
c->bstate.module_blocked_handle = NULL;
|
||||
@@ -7699,13 +7725,12 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
|
||||
} else if (ctx->flags & REDISMODULE_CTX_BLOCKED_REPLY) {
|
||||
c->bstate.module_blocked_handle = NULL;
|
||||
addReplyError(c, "Blocking module command called from a Reply callback context");
|
||||
}
|
||||
else if (!auth_reply_callback && clientHasModuleAuthInProgress(c)) {
|
||||
} else if (!auth_reply_callback && clientHasModuleAuthInProgress(c)) {
|
||||
c->bstate.module_blocked_handle = NULL;
|
||||
addReplyError(c, "Clients undergoing module based authentication can only be blocked on auth");
|
||||
} else {
|
||||
if (keys) {
|
||||
blockForKeys(c,BLOCKED_MODULE,keys,numkeys,timeout,flags&REDISMODULE_BLOCK_UNBLOCK_DELETED);
|
||||
blockForKeys(c,BLOCKED_MODULE,keys,numkeys,c->bstate.timeout,flags&REDISMODULE_BLOCK_UNBLOCK_DELETED);
|
||||
} else {
|
||||
blockClient(c,BLOCKED_MODULE);
|
||||
}
|
||||
@@ -8242,6 +8267,11 @@ void moduleHandleBlockedClients(void) {
|
||||
* properly unblocked by the module. */
|
||||
bc->disconnect_callback = NULL;
|
||||
unblockClient(c, 1);
|
||||
|
||||
/* Update the wait offset, we don't know if this blocked client propagated anything,
|
||||
* currently we rather not add any API for that, so we just assume it did. */
|
||||
c->woff = server.master_repl_offset;
|
||||
|
||||
/* 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. */
|
||||
@@ -8610,8 +8640,14 @@ void firePostExecutionUnitJobs(void) {
|
||||
* infinite loops by halting the execution could result in violation of the feature correctness
|
||||
* and so Redis will make no attempt to protect the module from infinite loops.
|
||||
*
|
||||
* 'free_pd' can be NULL and in such case will not be used. */
|
||||
* 'free_pd' can be NULL and in such case will not be used.
|
||||
*
|
||||
* Return REDISMODULE_OK on success and REDISMODULE_ERR if was called while loading data from disk (AOF or RDB) or
|
||||
* if the instance is a readonly replica. */
|
||||
int RM_AddPostNotificationJob(RedisModuleCtx *ctx, RedisModulePostNotificationJobFunc callback, void *privdata, void (*free_privdata)(void*)) {
|
||||
if (server.loading|| (server.masterhost && server.repl_slave_ro)) {
|
||||
return REDISMODULE_ERR;
|
||||
}
|
||||
RedisModulePostExecUnitJob *job = zmalloc(sizeof(*job));
|
||||
job->module = ctx->module;
|
||||
job->callback = callback;
|
||||
@@ -8919,7 +8955,7 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
||||
else
|
||||
memset(master_id,0,REDISMODULE_NODE_ID_LEN);
|
||||
}
|
||||
if (port) *port = node->port;
|
||||
if (port) *port = getNodeDefaultClientPort(node);
|
||||
|
||||
/* As usually we have to remap flags for modules, in order to ensure
|
||||
* we can provide binary compatibility. */
|
||||
@@ -10635,7 +10671,8 @@ void moduleCallCommandFilters(client *c) {
|
||||
RedisModuleCommandFilterCtx filter = {
|
||||
.argv = c->argv,
|
||||
.argv_len = c->argv_len,
|
||||
.argc = c->argc
|
||||
.argc = c->argc,
|
||||
.c = c
|
||||
};
|
||||
|
||||
while((ln = listNext(&li))) {
|
||||
@@ -10728,6 +10765,11 @@ int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Get Client ID for client that issued the command we are filtering */
|
||||
unsigned long long RM_CommandFilterGetClientId(RedisModuleCommandFilterCtx *fctx) {
|
||||
return fctx->c->id;
|
||||
}
|
||||
|
||||
/* For a given pointer allocated via RedisModule_Alloc() or
|
||||
* RedisModule_Realloc(), return the amount of memory allocated for it.
|
||||
* Note that this may be different (larger) than the memory we allocated
|
||||
@@ -11032,12 +11074,12 @@ int RM_ScanKey(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleSc
|
||||
vstr = lpGetValue(p,&vlen,&vll);
|
||||
robj *field = (vstr != NULL) ?
|
||||
createStringObject((char*)vstr,vlen) :
|
||||
createObject(OBJ_STRING,sdsfromlonglong(vll));
|
||||
createStringObjectFromLongLongWithSds(vll);
|
||||
p = lpNext(o->ptr,p);
|
||||
vstr = lpGetValue(p,&vlen,&vll);
|
||||
robj *value = (vstr != NULL) ?
|
||||
createStringObject((char*)vstr,vlen) :
|
||||
createObject(OBJ_STRING,sdsfromlonglong(vll));
|
||||
createStringObjectFromLongLongWithSds(vll);
|
||||
fn(key, field, value, privdata);
|
||||
p = lpNext(o->ptr,p);
|
||||
decrRefCount(field);
|
||||
@@ -13712,6 +13754,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(CommandFilterArgInsert);
|
||||
REGISTER_API(CommandFilterArgReplace);
|
||||
REGISTER_API(CommandFilterArgDelete);
|
||||
REGISTER_API(CommandFilterGetClientId);
|
||||
REGISTER_API(Fork);
|
||||
REGISTER_API(SendChildHeartbeat);
|
||||
REGISTER_API(ExitFromChild);
|
||||
|
||||
+33
-15
@@ -85,10 +85,6 @@ void freeClientReplyValue(void *o) {
|
||||
zfree(o);
|
||||
}
|
||||
|
||||
int listMatchObjects(void *a, void *b) {
|
||||
return equalStringObjects(a,b);
|
||||
}
|
||||
|
||||
/* This function links the client to the global linked list of clients.
|
||||
* unlinkClient() does the opposite, among other things. */
|
||||
void linkClient(client *c) {
|
||||
@@ -197,7 +193,7 @@ client *createClient(connection *conn) {
|
||||
c->woff = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->pubsub_channels = dictCreate(&objectKeyPointerValueDictType);
|
||||
c->pubsub_patterns = listCreate();
|
||||
c->pubsub_patterns = dictCreate(&objectKeyPointerValueDictType);
|
||||
c->pubsubshard_channels = dictCreate(&objectKeyPointerValueDictType);
|
||||
c->peerid = NULL;
|
||||
c->sockname = NULL;
|
||||
@@ -214,8 +210,6 @@ client *createClient(connection *conn) {
|
||||
c->auth_callback_privdata = NULL;
|
||||
c->auth_module = NULL;
|
||||
listInitNode(&c->clients_pending_write_node, c);
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
c->mem_usage_bucket = NULL;
|
||||
c->mem_usage_bucket_node = NULL;
|
||||
if (conn) linkClient(c);
|
||||
@@ -351,8 +345,8 @@ size_t _addReplyToBuffer(client *c, const char *s, size_t len) {
|
||||
|
||||
/* Adds the reply to the reply linked list.
|
||||
* Note: some edits to this function need to be relayed to AddReplyFromClient. */
|
||||
void _addReplyProtoToList(client *c, const char *s, size_t len) {
|
||||
listNode *ln = listLast(c->reply);
|
||||
void _addReplyProtoToList(client *c, list *reply_list, const char *s, size_t len) {
|
||||
listNode *ln = listLast(reply_list);
|
||||
clientReplyBlock *tail = ln? listNodeValue(ln): NULL;
|
||||
|
||||
/* Note that 'tail' may be NULL even if we have a tail node, because when
|
||||
@@ -380,13 +374,23 @@ void _addReplyProtoToList(client *c, const char *s, size_t len) {
|
||||
tail->size = usable_size - sizeof(clientReplyBlock);
|
||||
tail->used = len;
|
||||
memcpy(tail->buf, s, len);
|
||||
listAddNodeTail(c->reply, tail);
|
||||
listAddNodeTail(reply_list, tail);
|
||||
c->reply_bytes += tail->size;
|
||||
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* The subscribe / unsubscribe command family has a push as a reply,
|
||||
* or in other words, it responds with a push (or several of them
|
||||
* depending on how many arguments it got), and has no reply. */
|
||||
int cmdHasPushAsReply(struct redisCommand *cmd) {
|
||||
if (!cmd) return 0;
|
||||
return cmd->proc == subscribeCommand || cmd->proc == unsubscribeCommand ||
|
||||
cmd->proc == psubscribeCommand || cmd->proc == punsubscribeCommand ||
|
||||
cmd->proc == ssubscribeCommand || cmd->proc == sunsubscribeCommand;
|
||||
}
|
||||
|
||||
void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
|
||||
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;
|
||||
|
||||
@@ -405,8 +409,20 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
|
||||
* buffer offset (see function comment) */
|
||||
reqresSaveClientReplyOffset(c);
|
||||
|
||||
/* If we're processing a push message into the current client (i.e. executing PUBLISH
|
||||
* to a channel which we are subscribed to, then we wanna postpone that message to be added
|
||||
* after the command's reply (specifically important during multi-exec). the exception is
|
||||
* the SUBSCRIBE command family, which (currently) have a push message instead of a proper reply.
|
||||
* The check for executing_client also avoids affecting push messages that are part of eviction. */
|
||||
if (c == server.current_client && (c->flags & CLIENT_PUSHING) &&
|
||||
server.executing_client && !cmdHasPushAsReply(server.executing_client->cmd))
|
||||
{
|
||||
_addReplyProtoToList(c,server.pending_push_messages,s,len);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t reply_len = _addReplyToBuffer(c,s,len);
|
||||
if (len > reply_len) _addReplyProtoToList(c,s+reply_len,len-reply_len);
|
||||
if (len > reply_len) _addReplyProtoToList(c,c->reply,s+reply_len,len-reply_len);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@@ -1599,7 +1615,7 @@ void freeClient(client *c) {
|
||||
pubsubUnsubscribeShardAllChannels(c, 0);
|
||||
pubsubUnsubscribeAllPatterns(c,0);
|
||||
dictRelease(c->pubsub_channels);
|
||||
listRelease(c->pubsub_patterns);
|
||||
dictRelease(c->pubsub_patterns);
|
||||
dictRelease(c->pubsubshard_channels);
|
||||
|
||||
/* Free data structures. */
|
||||
@@ -1787,8 +1803,9 @@ client *lookupClientByID(uint64_t id) {
|
||||
* and 'nwritten' is an output parameter, it means how many bytes server write
|
||||
* to client. */
|
||||
static int _writevToClient(client *c, ssize_t *nwritten) {
|
||||
struct iovec iov[IOV_MAX];
|
||||
int iovcnt = 0;
|
||||
int iovmax = min(IOV_MAX, c->conn->iovcnt);
|
||||
struct iovec iov[iovmax];
|
||||
size_t iov_bytes_len = 0;
|
||||
/* If the static reply buffer is not empty,
|
||||
* add it to the iov array for writev() as well. */
|
||||
@@ -1804,7 +1821,7 @@ static int _writevToClient(client *c, ssize_t *nwritten) {
|
||||
listNode *next;
|
||||
clientReplyBlock *o;
|
||||
listRewind(c->reply, &iter);
|
||||
while ((next = listNext(&iter)) && iovcnt < IOV_MAX && iov_bytes_len < NET_MAX_WRITES_PER_EVENT) {
|
||||
while ((next = listNext(&iter)) && iovcnt < iovmax && iov_bytes_len < NET_MAX_WRITES_PER_EVENT) {
|
||||
o = listNodeValue(next);
|
||||
if (o->used == 0) { /* empty node, just release it and skip. */
|
||||
c->reply_bytes -= o->size;
|
||||
@@ -2809,7 +2826,7 @@ sds catClientInfoString(sds s, client *client) {
|
||||
flags,
|
||||
client->db->id,
|
||||
(int) dictSize(client->pubsub_channels),
|
||||
(int) listLength(client->pubsub_patterns),
|
||||
(int) dictSize(client->pubsub_patterns),
|
||||
(int) dictSize(client->pubsubshard_channels),
|
||||
(client->flags & CLIENT_MULTI) ? client->mstate.count : -1,
|
||||
(unsigned long long) sdslen(client->querybuf),
|
||||
@@ -3946,6 +3963,7 @@ void flushSlavesOutputBuffers(void) {
|
||||
* 3. Obviously if the slave is not ONLINE.
|
||||
*/
|
||||
if (slave->replstate == SLAVE_STATE_ONLINE &&
|
||||
!(slave->flags & CLIENT_CLOSE_ASAP) &&
|
||||
can_receive_writes &&
|
||||
!slave->repl_start_cmd_stream_on_ack &&
|
||||
clientHasPendingReplies(slave))
|
||||
|
||||
+44
-34
@@ -46,15 +46,21 @@ robj *createObject(int type, void *ptr) {
|
||||
o->encoding = OBJ_ENCODING_RAW;
|
||||
o->ptr = ptr;
|
||||
o->refcount = 1;
|
||||
o->lru = 0;
|
||||
return o;
|
||||
}
|
||||
|
||||
void initObjectLRUOrLFU(robj *o) {
|
||||
if (o->refcount == OBJ_SHARED_REFCOUNT)
|
||||
return;
|
||||
/* Set the LRU to the current lruclock (minutes resolution), or
|
||||
* alternatively the LFU counter. */
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
|
||||
o->lru = (LFUGetTimeInMinutes() << 8) | LFU_INIT_VAL;
|
||||
} else {
|
||||
o->lru = LRU_CLOCK();
|
||||
}
|
||||
return o;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set a special refcount in the object to make it "shared":
|
||||
@@ -91,11 +97,7 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) {
|
||||
o->encoding = OBJ_ENCODING_EMBSTR;
|
||||
o->ptr = sh+1;
|
||||
o->refcount = 1;
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
|
||||
} else {
|
||||
o->lru = LRU_CLOCK();
|
||||
}
|
||||
o->lru = 0;
|
||||
|
||||
sh->len = len;
|
||||
sh->alloc = len;
|
||||
@@ -140,33 +142,24 @@ robj *tryCreateStringObject(const char *ptr, size_t len) {
|
||||
return tryCreateRawStringObject(ptr,len);
|
||||
}
|
||||
|
||||
/* Create a string object from a long long value. When possible returns a
|
||||
* shared integer object, or at least an integer encoded one.
|
||||
*
|
||||
* If valueobj is non zero, the function avoids returning a shared
|
||||
* integer, because the object is going to be used as value in the Redis key
|
||||
* space (for instance when the INCR command is used), so we want LFU/LRU
|
||||
* values specific for each key. */
|
||||
robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
|
||||
/* Create a string object from a long long value according to the specified flag. */
|
||||
#define LL2STROBJ_AUTO 0 /* automatically create the optimal string object */
|
||||
#define LL2STROBJ_NO_SHARED 1 /* disallow shared objects */
|
||||
#define LL2STROBJ_NO_INT_ENC 2 /* disallow integer encoded objects. */
|
||||
robj *createStringObjectFromLongLongWithOptions(long long value, int flag) {
|
||||
robj *o;
|
||||
|
||||
if (server.maxmemory == 0 ||
|
||||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
|
||||
{
|
||||
/* If the maxmemory policy permits, we can still return shared integers
|
||||
* even if valueobj is true. */
|
||||
valueobj = 0;
|
||||
}
|
||||
|
||||
if (value >= 0 && value < OBJ_SHARED_INTEGERS && valueobj == 0) {
|
||||
if (value >= 0 && value < OBJ_SHARED_INTEGERS && flag == LL2STROBJ_AUTO) {
|
||||
o = shared.integers[value];
|
||||
} else {
|
||||
if (value >= LONG_MIN && value <= LONG_MAX) {
|
||||
if ((value >= LONG_MIN && value <= LONG_MAX) && flag != LL2STROBJ_NO_INT_ENC) {
|
||||
o = createObject(OBJ_STRING, NULL);
|
||||
o->encoding = OBJ_ENCODING_INT;
|
||||
o->ptr = (void*)((long)value);
|
||||
} else {
|
||||
o = createObject(OBJ_STRING,sdsfromlonglong(value));
|
||||
char buf[LONG_STR_SIZE];
|
||||
int len = ll2string(buf, sizeof(buf), value);
|
||||
o = createStringObject(buf, len);
|
||||
}
|
||||
}
|
||||
return o;
|
||||
@@ -175,15 +168,27 @@ robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
|
||||
/* Wrapper for createStringObjectFromLongLongWithOptions() always demanding
|
||||
* to create a shared object if possible. */
|
||||
robj *createStringObjectFromLongLong(long long value) {
|
||||
return createStringObjectFromLongLongWithOptions(value,0);
|
||||
return createStringObjectFromLongLongWithOptions(value, LL2STROBJ_AUTO);
|
||||
}
|
||||
|
||||
/* Wrapper for createStringObjectFromLongLongWithOptions() avoiding a shared
|
||||
* object when LFU/LRU info are needed, that is, when the object is used
|
||||
* as a value in the key space, and Redis is configured to evict based on
|
||||
* LFU/LRU. */
|
||||
/* The function avoids returning a shared integer when LFU/LRU info
|
||||
* are needed, that is, when the object is used as a value in the key
|
||||
* space(for instance when the INCR command is used), and Redis is
|
||||
* configured to evict based on LFU/LRU, so we want LFU/LRU values
|
||||
* specific for each key. */
|
||||
robj *createStringObjectFromLongLongForValue(long long value) {
|
||||
return createStringObjectFromLongLongWithOptions(value,1);
|
||||
if (server.maxmemory == 0 || !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) {
|
||||
/* If the maxmemory policy permits, we can still return shared integers */
|
||||
return createStringObjectFromLongLongWithOptions(value, LL2STROBJ_AUTO);
|
||||
} else {
|
||||
return createStringObjectFromLongLongWithOptions(value, LL2STROBJ_NO_SHARED);
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a string object that contains an sds inside it. That means it can't be
|
||||
* integer encoded (OBJ_ENCODING_INT), and it'll always be an EMBSTR type. */
|
||||
robj *createStringObjectFromLongLongWithSds(long long value) {
|
||||
return createStringObjectFromLongLongWithOptions(value, LL2STROBJ_NO_INT_ENC);
|
||||
}
|
||||
|
||||
/* Create a string object from a long double. If humanfriendly is non-zero
|
||||
@@ -627,7 +632,7 @@ void trimStringObjectIfNeeded(robj *o, int trim_small_values) {
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space */
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
robj *tryObjectEncodingEx(robj *o, int try_trim) {
|
||||
long value;
|
||||
sds s = o->ptr;
|
||||
size_t len;
|
||||
@@ -692,12 +697,17 @@ robj *tryObjectEncoding(robj *o) {
|
||||
|
||||
/* We can't encode the object...
|
||||
* Do the last try, and at least optimize the SDS string inside */
|
||||
trimStringObjectIfNeeded(o, 0);
|
||||
if (try_trim)
|
||||
trimStringObjectIfNeeded(o, 0);
|
||||
|
||||
/* Return the original object. */
|
||||
return o;
|
||||
}
|
||||
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
return tryObjectEncodingEx(o, 1);
|
||||
}
|
||||
|
||||
/* Get a decoded version of an encoded object (returned as a new object).
|
||||
* If the object is already raw-encoded just increment the ref count. */
|
||||
robj *getDecodedObject(robj *o) {
|
||||
|
||||
+14
-12
@@ -219,7 +219,7 @@ int serverPubsubShardSubscriptionCount(void) {
|
||||
|
||||
/* Return the number of channels + patterns a client is subscribed to. */
|
||||
int clientSubscriptionsCount(client *c) {
|
||||
return dictSize(c->pubsub_channels) + listLength(c->pubsub_patterns);
|
||||
return dictSize(c->pubsub_channels) + dictSize(c->pubsub_patterns);
|
||||
}
|
||||
|
||||
/* Return the number of shard level channels a client is subscribed to. */
|
||||
@@ -345,9 +345,8 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
list *clients;
|
||||
int retval = 0;
|
||||
|
||||
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
|
||||
if (dictAdd(c->pubsub_patterns, pattern, NULL) == DICT_OK) {
|
||||
retval = 1;
|
||||
listAddNodeTail(c->pubsub_patterns,pattern);
|
||||
incrRefCount(pattern);
|
||||
/* Add the client to the pattern -> list of clients hash table */
|
||||
de = dictFind(server.pubsub_patterns,pattern);
|
||||
@@ -374,9 +373,8 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
int retval = 0;
|
||||
|
||||
incrRefCount(pattern); /* Protect the object. May be the same we remove */
|
||||
if ((ln = listSearchKey(c->pubsub_patterns,pattern)) != NULL) {
|
||||
if (dictDelete(c->pubsub_patterns, pattern) == DICT_OK) {
|
||||
retval = 1;
|
||||
listDelNode(c->pubsub_patterns,ln);
|
||||
/* Remove the client from the pattern -> clients list hash table */
|
||||
de = dictFind(server.pubsub_patterns,pattern);
|
||||
serverAssertWithInfo(c,NULL,de != NULL);
|
||||
@@ -448,16 +446,20 @@ void pubsubUnsubscribeShardChannels(robj **channels, unsigned int count) {
|
||||
/* Unsubscribe from all the patterns. Return the number of patterns the
|
||||
* client was subscribed from. */
|
||||
int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
int count = 0;
|
||||
|
||||
listRewind(c->pubsub_patterns,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
robj *pattern = ln->value;
|
||||
if (dictSize(c->pubsub_patterns) > 0) {
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_patterns);
|
||||
dictEntry *de;
|
||||
|
||||
count += pubsubUnsubscribePattern(c,pattern,notify);
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
robj *pattern = dictGetKey(de);
|
||||
count += pubsubUnsubscribePattern(c, pattern, notify);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
/* We were subscribed to nothing? Still reply to the client. */
|
||||
if (notify && count == 0) addReplyPubsubPatUnsubscribed(c,NULL);
|
||||
return count;
|
||||
}
|
||||
@@ -743,7 +745,7 @@ void sunsubscribeCommand(client *c) {
|
||||
|
||||
size_t pubsubMemOverhead(client *c) {
|
||||
/* PubSub patterns */
|
||||
size_t mem = listLength(c->pubsub_patterns) * sizeof(listNode);
|
||||
size_t mem = dictMemUsage(c->pubsub_patterns);
|
||||
/* Global PubSub channels */
|
||||
mem += dictMemUsage(c->pubsub_channels);
|
||||
/* Sharded PubSub channels */
|
||||
|
||||
@@ -326,7 +326,7 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||
} else if (encode) {
|
||||
return createStringObjectFromLongLongForValue(val);
|
||||
} else {
|
||||
return createObject(OBJ_STRING,sdsfromlonglong(val));
|
||||
return createStringObjectFromLongLongWithSds(val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1844,7 +1844,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, sds key, int dbid, int *error) {
|
||||
if (rdbtype == RDB_TYPE_STRING) {
|
||||
/* Read string value */
|
||||
if ((o = rdbLoadEncodedStringObject(rdb)) == NULL) return NULL;
|
||||
o = tryObjectEncoding(o);
|
||||
o = tryObjectEncodingEx(o, 0);
|
||||
} else if (rdbtype == RDB_TYPE_LIST) {
|
||||
/* Read list value */
|
||||
if ((len = rdbLoadLen(rdb,NULL)) == RDB_LENERR) return NULL;
|
||||
|
||||
@@ -1614,7 +1614,9 @@ usage:
|
||||
" -a <password> Password for Redis Auth\n"
|
||||
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||
" -u <uri> Server URI.\n"
|
||||
" -c <clients> Number of parallel connections (default 50)\n"
|
||||
" -c <clients> Number of parallel connections (default 50).\n"
|
||||
" Note: If --cluster is used then number of clients has to be\n"
|
||||
" the same or higher than the number of nodes.\n"
|
||||
" -n <requests> Total number of requests (default 100000)\n"
|
||||
" -d <size> Data size of SET/GET value in bytes (default 3)\n"
|
||||
" --dbnum <db> SELECT the specified db number (default 0)\n"
|
||||
|
||||
+1
-1
@@ -3067,7 +3067,7 @@ version,tls_usage);
|
||||
" --replica Simulate a replica showing commands received from the master.\n"
|
||||
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
|
||||
" Use filename of \"-\" to write to stdout.\n"
|
||||
" --functions-rdb <filename> Like --rdb but only get the functions (not the keys)\n"
|
||||
" --functions-rdb <filename> Like --rdb but only get the functions (not the keys)\n"
|
||||
" when getting the RDB dump file.\n"
|
||||
" --pipe Transfer raw Redis protocol from stdin to server.\n"
|
||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
||||
|
||||
@@ -1259,6 +1259,7 @@ REDISMODULE_API RedisModuleString * (*RedisModule_CommandFilterArgGet)(RedisModu
|
||||
REDISMODULE_API int (*RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR;
|
||||
REDISMODULE_API unsigned long long (*RedisModule_CommandFilterGetClientId)(RedisModuleCommandFilterCtx *fctx) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_SendChildHeartbeat)(double progress) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR;
|
||||
@@ -1619,6 +1620,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
||||
REDISMODULE_GET_API(CommandFilterArgInsert);
|
||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||
REDISMODULE_GET_API(CommandFilterGetClientId);
|
||||
REDISMODULE_GET_API(Fork);
|
||||
REDISMODULE_GET_API(SendChildHeartbeat);
|
||||
REDISMODULE_GET_API(ExitFromChild);
|
||||
|
||||
+6
-6
@@ -418,13 +418,13 @@ void feedReplicationBuffer(char *s, size_t len) {
|
||||
}
|
||||
if (add_new_block) {
|
||||
createReplicationBacklogIndex(listLast(server.repl_buffer_blocks));
|
||||
|
||||
/* It is important to trim after adding replication data to keep the backlog size close to
|
||||
* repl_backlog_size in the common case. We wait until we add a new block to avoid repeated
|
||||
* unnecessary trimming attempts when small amounts of data are added. See comments in
|
||||
* freeMemoryGetNotCountedMemory() for details on replication backlog memory tracking. */
|
||||
incrementalTrimReplicationBacklog(REPL_BACKLOG_TRIM_BLOCKS_PER_CALL);
|
||||
}
|
||||
/* Try to trim replication backlog since replication backlog may exceed
|
||||
* our setting when we add replication stream. Note that it is important to
|
||||
* try to trim at least one node since in the common case this is where one
|
||||
* new backlog node is added and one should be removed. See also comments
|
||||
* in freeMemoryGetNotCountedMemory for details. */
|
||||
incrementalTrimReplicationBacklog(REPL_BACKLOG_TRIM_BLOCKS_PER_CALL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -292,6 +292,7 @@ void scriptKill(client *c, int is_eval) {
|
||||
if (mustObeyClient(curr_run_ctx->original_client)) {
|
||||
addReplyError(c,
|
||||
"-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.");
|
||||
return;
|
||||
}
|
||||
if (curr_run_ctx->flags & SCRIPT_WRITE_DIRTY) {
|
||||
addReplyError(c,
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <limits.h>
|
||||
#include "sds.h"
|
||||
#include "sdsalloc.h"
|
||||
#include "util.h"
|
||||
|
||||
const char *SDS_NOINIT = "SDS_NOINIT";
|
||||
|
||||
@@ -526,90 +527,13 @@ sds sdscpy(sds s, const char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Helper for sdscatlonglong() doing the actual number -> string
|
||||
* conversion. 's' must point to a string with room for at least
|
||||
* SDS_LLSTR_SIZE bytes.
|
||||
*
|
||||
* The function returns the length of the null-terminated string
|
||||
* representation stored at 's'. */
|
||||
#define SDS_LLSTR_SIZE 21
|
||||
int sdsll2str(char *s, long long value) {
|
||||
char *p, aux;
|
||||
unsigned long long v;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* a reversed string. */
|
||||
if (value < 0) {
|
||||
/* Since v is unsigned, if value==LLONG_MIN, -LLONG_MIN will overflow. */
|
||||
if (value != LLONG_MIN) {
|
||||
v = -value;
|
||||
} else {
|
||||
v = ((unsigned long long)LLONG_MAX) + 1;
|
||||
}
|
||||
} else {
|
||||
v = value;
|
||||
}
|
||||
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p++ = '-';
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Identical sdsll2str(), but for unsigned long long type. */
|
||||
int sdsull2str(char *s, unsigned long long v) {
|
||||
char *p, aux;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* a reversed string. */
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
int len = sdsll2str(buf,value);
|
||||
char buf[LONG_STR_SIZE];
|
||||
int len = ll2string(buf,sizeof(buf),value);
|
||||
|
||||
return sdsnewlen(buf,len);
|
||||
}
|
||||
@@ -745,8 +669,8 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
else
|
||||
num = va_arg(ap,long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsll2str(buf,num);
|
||||
char buf[LONG_STR_SIZE];
|
||||
l = ll2string(buf,sizeof(buf),num);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
}
|
||||
@@ -762,8 +686,8 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
else
|
||||
unum = va_arg(ap,unsigned long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsull2str(buf,unum);
|
||||
char buf[LONG_STR_SIZE];
|
||||
l = ull2string(buf,sizeof(buf),unum);
|
||||
if (sdsavail(s) < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
}
|
||||
|
||||
+174
-92
@@ -3178,6 +3178,13 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
|
||||
}
|
||||
|
||||
/* =========================== SENTINEL command ============================= */
|
||||
static void populateDict(dict *options_dict, char **options) {
|
||||
for (int i=0; options[i]; i++) {
|
||||
sds option = sdsnew(options[i]);
|
||||
if (dictAdd(options_dict, option, NULL)==DICT_ERR)
|
||||
sdsfree(option);
|
||||
}
|
||||
}
|
||||
|
||||
const char* getLogLevel(void) {
|
||||
switch (server.verbosity) {
|
||||
@@ -3185,56 +3192,120 @@ const char* getLogLevel(void) {
|
||||
case LL_VERBOSE: return "verbose";
|
||||
case LL_NOTICE: return "notice";
|
||||
case LL_WARNING: return "warning";
|
||||
case LL_NOTHING: return "nothing";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/* SENTINEL CONFIG SET <option> <value>*/
|
||||
/* SENTINEL CONFIG SET option value [option value ...] */
|
||||
void sentinelConfigSetCommand(client *c) {
|
||||
robj *o = c->argv[3];
|
||||
robj *val = c->argv[4];
|
||||
long long numval;
|
||||
int drop_conns = 0;
|
||||
char *option;
|
||||
robj *val;
|
||||
char *options[] = {
|
||||
"announce-ip",
|
||||
"sentinel-user",
|
||||
"sentinel-pass",
|
||||
"resolve-hostnames",
|
||||
"announce-port",
|
||||
"announce-hostnames",
|
||||
"loglevel",
|
||||
NULL};
|
||||
static dict *options_dict = NULL;
|
||||
if (!options_dict) {
|
||||
options_dict = dictCreate(&stringSetDictType);
|
||||
populateDict(options_dict, options);
|
||||
}
|
||||
dict *set_configs = dictCreate(&stringSetDictType);
|
||||
|
||||
if (!strcasecmp(o->ptr, "resolve-hostnames")) {
|
||||
if ((numval = yesnotoi(val->ptr)) == -1) goto badfmt;
|
||||
sentinel.resolve_hostnames = numval;
|
||||
} else if (!strcasecmp(o->ptr, "announce-hostnames")) {
|
||||
if ((numval = yesnotoi(val->ptr)) == -1) goto badfmt;
|
||||
sentinel.announce_hostnames = numval;
|
||||
} else if (!strcasecmp(o->ptr, "announce-ip")) {
|
||||
if (sentinel.announce_ip) sdsfree(sentinel.announce_ip);
|
||||
sentinel.announce_ip = sdsnew(val->ptr);
|
||||
} else if (!strcasecmp(o->ptr, "announce-port")) {
|
||||
if (getLongLongFromObject(val, &numval) == C_ERR ||
|
||||
numval < 0 || numval > 65535)
|
||||
goto badfmt;
|
||||
sentinel.announce_port = numval;
|
||||
} else if (!strcasecmp(o->ptr, "sentinel-user")) {
|
||||
sdsfree(sentinel.sentinel_auth_user);
|
||||
sentinel.sentinel_auth_user = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else if (!strcasecmp(o->ptr, "sentinel-pass")) {
|
||||
sdsfree(sentinel.sentinel_auth_pass);
|
||||
sentinel.sentinel_auth_pass = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else if (!strcasecmp(o->ptr, "loglevel")) {
|
||||
if (!strcasecmp(val->ptr, "debug"))
|
||||
server.verbosity = LL_DEBUG;
|
||||
else if (!strcasecmp(val->ptr, "verbose"))
|
||||
server.verbosity = LL_VERBOSE;
|
||||
else if (!strcasecmp(val->ptr, "notice"))
|
||||
server.verbosity = LL_NOTICE;
|
||||
else if (!strcasecmp(val->ptr, "warning"))
|
||||
server.verbosity = LL_WARNING;
|
||||
else
|
||||
goto badfmt;
|
||||
} else {
|
||||
addReplyErrorFormat(c, "Invalid argument '%s' to SENTINEL CONFIG SET",
|
||||
(char *) o->ptr);
|
||||
return;
|
||||
/* Validate arguments are valid */
|
||||
for (int i = 3; i < c->argc; i++) {
|
||||
option = c->argv[i]->ptr;
|
||||
|
||||
/* Validate option is valid */
|
||||
if (dictFind(options_dict, option) == NULL) {
|
||||
addReplyErrorFormat(c, "Invalid argument '%s' to SENTINEL CONFIG SET", option);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Check duplicates */
|
||||
if (dictFind(set_configs, option) != NULL) {
|
||||
addReplyErrorFormat(c, "Duplicate argument '%s' to SENTINEL CONFIG SET", option);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
serverAssert(dictAdd(set_configs, sdsnew(option), NULL) == C_OK);
|
||||
|
||||
/* Validate argument */
|
||||
if (i + 1 == c->argc) {
|
||||
addReplyErrorFormat(c, "Missing argument '%s' value", option);
|
||||
goto exit;
|
||||
}
|
||||
val = c->argv[++i];
|
||||
|
||||
if (!strcasecmp(option, "resolve-hostnames")) {
|
||||
if ((yesnotoi(val->ptr)) == -1) goto badfmt;
|
||||
} else if (!strcasecmp(option, "announce-hostnames")) {
|
||||
if ((yesnotoi(val->ptr)) == -1) goto badfmt;
|
||||
} else if (!strcasecmp(option, "announce-port")) {
|
||||
if (getLongLongFromObject(val, &numval) == C_ERR ||
|
||||
numval < 0 || numval > 65535) goto badfmt;
|
||||
} else if (!strcasecmp(option, "loglevel")) {
|
||||
if (!(!strcasecmp(val->ptr, "debug") || !strcasecmp(val->ptr, "verbose") ||
|
||||
!strcasecmp(val->ptr, "notice") || !strcasecmp(val->ptr, "warning") ||
|
||||
!strcasecmp(val->ptr, "nothing"))) goto badfmt;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply changes */
|
||||
for (int i = 3; i < c->argc; i++) {
|
||||
int moreargs = (c->argc-1) - i;
|
||||
option = c->argv[i]->ptr;
|
||||
if (!strcasecmp(option, "loglevel") && moreargs > 0) {
|
||||
val = c->argv[++i];
|
||||
if (!strcasecmp(val->ptr, "debug"))
|
||||
server.verbosity = LL_DEBUG;
|
||||
else if (!strcasecmp(val->ptr, "verbose"))
|
||||
server.verbosity = LL_VERBOSE;
|
||||
else if (!strcasecmp(val->ptr, "notice"))
|
||||
server.verbosity = LL_NOTICE;
|
||||
else if (!strcasecmp(val->ptr, "warning"))
|
||||
server.verbosity = LL_WARNING;
|
||||
else if (!strcasecmp(val->ptr, "nothing"))
|
||||
server.verbosity = LL_NOTHING;
|
||||
} else if (!strcasecmp(option, "resolve-hostnames") && moreargs > 0) {
|
||||
val = c->argv[++i];
|
||||
numval = yesnotoi(val->ptr);
|
||||
sentinel.resolve_hostnames = numval;
|
||||
} else if (!strcasecmp(option, "announce-hostnames") && moreargs > 0) {
|
||||
val = c->argv[++i];
|
||||
numval = yesnotoi(val->ptr);
|
||||
sentinel.announce_hostnames = numval;
|
||||
} else if (!strcasecmp(option, "announce-ip") && moreargs > 0) {
|
||||
val = c->argv[++i];
|
||||
if (sentinel.announce_ip) sdsfree(sentinel.announce_ip);
|
||||
sentinel.announce_ip = sdsnew(val->ptr);
|
||||
} else if (!strcasecmp(option, "announce-port") && moreargs > 0) {
|
||||
val = c->argv[++i];
|
||||
getLongLongFromObject(val, &numval);
|
||||
sentinel.announce_port = numval;
|
||||
} else if (!strcasecmp(option, "sentinel-user") && moreargs > 0) {
|
||||
val = c->argv[++i];
|
||||
sdsfree(sentinel.sentinel_auth_user);
|
||||
sentinel.sentinel_auth_user = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else if (!strcasecmp(option, "sentinel-pass") && moreargs > 0) {
|
||||
val = c->argv[++i];
|
||||
sdsfree(sentinel.sentinel_auth_pass);
|
||||
sentinel.sentinel_auth_pass = sdslen(val->ptr) == 0 ?
|
||||
NULL : sdsdup(val->ptr);
|
||||
drop_conns = 1;
|
||||
} else {
|
||||
/* Should never reach here */
|
||||
serverAssert(0);
|
||||
}
|
||||
}
|
||||
|
||||
sentinelFlushConfigAndReply(c);
|
||||
@@ -3243,61 +3314,72 @@ void sentinelConfigSetCommand(client *c) {
|
||||
if (drop_conns)
|
||||
sentinelDropConnections();
|
||||
|
||||
exit:
|
||||
dictRelease(set_configs);
|
||||
return;
|
||||
|
||||
badfmt:
|
||||
addReplyErrorFormat(c, "Invalid value '%s' to SENTINEL CONFIG SET '%s'",
|
||||
(char *) val->ptr, (char *) o->ptr);
|
||||
(char *) val->ptr, option);
|
||||
dictRelease(set_configs);
|
||||
}
|
||||
|
||||
/* SENTINEL CONFIG GET <option> */
|
||||
/* SENTINEL CONFIG GET <option> [<option> ...] */
|
||||
void sentinelConfigGetCommand(client *c) {
|
||||
robj *o = c->argv[3];
|
||||
const char *pattern = o->ptr;
|
||||
char *pattern;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int matches = 0;
|
||||
|
||||
if (stringmatch(pattern,"resolve-hostnames",1)) {
|
||||
addReplyBulkCString(c,"resolve-hostnames");
|
||||
addReplyBulkCString(c,sentinel.resolve_hostnames ? "yes" : "no");
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern, "announce-hostnames", 1)) {
|
||||
addReplyBulkCString(c,"announce-hostnames");
|
||||
addReplyBulkCString(c,sentinel.announce_hostnames ? "yes" : "no");
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern, "announce-ip", 1)) {
|
||||
addReplyBulkCString(c,"announce-ip");
|
||||
addReplyBulkCString(c,sentinel.announce_ip ? sentinel.announce_ip : "");
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern, "announce-port", 1)) {
|
||||
addReplyBulkCString(c, "announce-port");
|
||||
addReplyBulkLongLong(c, sentinel.announce_port);
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern, "sentinel-user", 1)) {
|
||||
addReplyBulkCString(c, "sentinel-user");
|
||||
addReplyBulkCString(c, sentinel.sentinel_auth_user ? sentinel.sentinel_auth_user : "");
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern, "sentinel-pass", 1)) {
|
||||
addReplyBulkCString(c, "sentinel-pass");
|
||||
addReplyBulkCString(c, sentinel.sentinel_auth_pass ? sentinel.sentinel_auth_pass : "");
|
||||
matches++;
|
||||
}
|
||||
|
||||
if (stringmatch(pattern, "loglevel", 1)) {
|
||||
addReplyBulkCString(c, "loglevel");
|
||||
addReplyBulkCString(c, getLogLevel());
|
||||
matches++;
|
||||
/* Create a dictionary to store the input configs,to avoid adding duplicate twice */
|
||||
dict *d = dictCreate(&externalStringType);
|
||||
for (int i = 3; i < c->argc; i++) {
|
||||
pattern = c->argv[i]->ptr;
|
||||
/* If the string doesn't contain glob patterns and available in dictionary, don't look further, just continue. */
|
||||
if (!strpbrk(pattern, "[*?") && dictFind(d, pattern)) continue;
|
||||
/* we want to print all the matched patterns and avoid printing duplicates twice */
|
||||
if (stringmatch(pattern,"resolve-hostnames",1) && !dictFind(d, "resolve-hostnames")) {
|
||||
addReplyBulkCString(c,"resolve-hostnames");
|
||||
addReplyBulkCString(c,sentinel.resolve_hostnames ? "yes" : "no");
|
||||
dictAdd(d, "resolve-hostnames", NULL);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern, "announce-hostnames", 1) && !dictFind(d, "announce-hostnames")) {
|
||||
addReplyBulkCString(c,"announce-hostnames");
|
||||
addReplyBulkCString(c,sentinel.announce_hostnames ? "yes" : "no");
|
||||
dictAdd(d, "announce-hostnames", NULL);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern, "announce-ip", 1) && !dictFind(d, "announce-ip")) {
|
||||
addReplyBulkCString(c,"announce-ip");
|
||||
addReplyBulkCString(c,sentinel.announce_ip ? sentinel.announce_ip : "");
|
||||
dictAdd(d, "announce-ip", NULL);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern, "announce-port", 1) && !dictFind(d, "announce-port")) {
|
||||
addReplyBulkCString(c, "announce-port");
|
||||
addReplyBulkLongLong(c, sentinel.announce_port);
|
||||
dictAdd(d, "announce-port", NULL);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern, "sentinel-user", 1) && !dictFind(d, "sentinel-user")) {
|
||||
addReplyBulkCString(c, "sentinel-user");
|
||||
addReplyBulkCString(c, sentinel.sentinel_auth_user ? sentinel.sentinel_auth_user : "");
|
||||
dictAdd(d, "sentinel-user", NULL);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern, "sentinel-pass", 1) && !dictFind(d, "sentinel-pass")) {
|
||||
addReplyBulkCString(c, "sentinel-pass");
|
||||
addReplyBulkCString(c, sentinel.sentinel_auth_pass ? sentinel.sentinel_auth_pass : "");
|
||||
dictAdd(d, "sentinel-pass", NULL);
|
||||
matches++;
|
||||
}
|
||||
if (stringmatch(pattern, "loglevel", 1) && !dictFind(d, "loglevel")) {
|
||||
addReplyBulkCString(c, "loglevel");
|
||||
addReplyBulkCString(c, getLogLevel());
|
||||
dictAdd(d, "loglevel", NULL);
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
dictRelease(d);
|
||||
setDeferredMapLen(c, replylen, matches);
|
||||
}
|
||||
|
||||
@@ -3787,9 +3869,9 @@ void sentinelCommand(client *c) {
|
||||
" Check if the current Sentinel configuration is able to reach the quorum",
|
||||
" needed to failover a master and the majority needed to authorize the",
|
||||
" failover.",
|
||||
"CONFIG SET <param> <value>",
|
||||
"CONFIG SET param value [param value ...]",
|
||||
" Set a global Sentinel configuration parameter.",
|
||||
"CONFIG GET <param>",
|
||||
"CONFIG GET <param> [param param param ...]",
|
||||
" Get global Sentinel configuration parameter.",
|
||||
"DEBUG [<param> <value> ...]",
|
||||
" Show a list of configurable time parameters and their values (milliseconds).",
|
||||
@@ -4042,12 +4124,12 @@ NULL
|
||||
sentinelSetCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"config")) {
|
||||
if (c->argc < 4) goto numargserr;
|
||||
if (!strcasecmp(c->argv[2]->ptr,"set") && c->argc == 5)
|
||||
if (!strcasecmp(c->argv[2]->ptr,"set") && c->argc >= 5)
|
||||
sentinelConfigSetCommand(c);
|
||||
else if (!strcasecmp(c->argv[2]->ptr,"get") && c->argc == 4)
|
||||
else if (!strcasecmp(c->argv[2]->ptr,"get") && c->argc >= 4)
|
||||
sentinelConfigGetCommand(c);
|
||||
else
|
||||
addReplyError(c, "Only SENTINEL CONFIG GET <option> / SET <option> <value> are supported.");
|
||||
addReplyError(c, "Only SENTINEL CONFIG GET <param> [<param> <param> ...]/ SET <param> <value> [<param> <value> ...] are supported.");
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
|
||||
/* SENTINEL INFO-CACHE <name> */
|
||||
if (c->argc < 2) goto numargserr;
|
||||
|
||||
+27
-37
@@ -1324,8 +1324,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
*
|
||||
* Note that you can change the resolution altering the
|
||||
* LRU_CLOCK_RESOLUTION define. */
|
||||
unsigned int lruclock = getLRUClock();
|
||||
atomicSet(server.lruclock,lruclock);
|
||||
server.lruclock = getLRUClock();
|
||||
|
||||
cronUpdateMemoryStats();
|
||||
|
||||
@@ -1649,9 +1648,6 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handle precise timeouts of blocked clients. */
|
||||
handleBlockedClientsTimeout();
|
||||
|
||||
/* We should handle pending reads clients ASAP after event loop. */
|
||||
handleClientsWithPendingReadsUsingThreads();
|
||||
|
||||
@@ -1661,40 +1657,33 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
/* If any connection type(typical TLS) still has pending unread data don't sleep at all. */
|
||||
aeSetDontWait(server.el, connTypeHasPendingData());
|
||||
|
||||
/* Call the Redis Cluster before sleep function. Note that this function
|
||||
* may change the state of Redis Cluster (from ok to fail or vice versa),
|
||||
* so it's a good idea to call it before serving the unblocked clients
|
||||
* later in this function, must be done before blockedBeforeSleep. */
|
||||
if (server.cluster_enabled) clusterBeforeSleep();
|
||||
|
||||
/* Handle blocked clients.
|
||||
* must be done before flushAppendOnlyFile, in case of appendfsync=always,
|
||||
* since the unblocked clients may write data. */
|
||||
blockedBeforeSleep();
|
||||
|
||||
/* Record cron time in beforeSleep, which is the sum of active-expire, active-defrag and all other
|
||||
* tasks done by cron and beforeSleep, but excluding read, write and AOF, that are counted by other
|
||||
* sets of metrics. */
|
||||
monotime cron_start_time_before_aof = getMonotonicUs();
|
||||
|
||||
/* Call the Redis Cluster before sleep function. Note that this function
|
||||
* may change the state of Redis Cluster (from ok to fail or vice versa),
|
||||
* so it's a good idea to call it before serving the unblocked clients
|
||||
* later in this function. */
|
||||
if (server.cluster_enabled) clusterBeforeSleep();
|
||||
|
||||
/* Run a fast expire cycle (the called function will return
|
||||
* ASAP if a fast cycle is not needed). */
|
||||
if (server.active_expire_enabled && iAmMaster())
|
||||
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);
|
||||
|
||||
/* Unblock all the clients blocked for synchronous replication
|
||||
* in WAIT or WAITAOF. */
|
||||
if (listLength(server.clients_waiting_acks))
|
||||
processClientsWaitingReplicas();
|
||||
|
||||
/* Check if there are clients unblocked by modules that implement
|
||||
* blocking commands. */
|
||||
if (moduleCount()) {
|
||||
moduleFireServerEvent(REDISMODULE_EVENT_EVENTLOOP,
|
||||
REDISMODULE_SUBEVENT_EVENTLOOP_BEFORE_SLEEP,
|
||||
NULL);
|
||||
moduleHandleBlockedClients();
|
||||
}
|
||||
|
||||
/* Try to process pending commands for clients that were just unblocked. */
|
||||
if (listLength(server.unblocked_clients))
|
||||
processUnblockedClients();
|
||||
|
||||
/* Send all the slaves an ACK request if at least one client blocked
|
||||
* during the previous event loop iteration. Note that we do this after
|
||||
* processUnblockedClients(), so if there are multiple pipelined WAITs
|
||||
@@ -1718,20 +1707,12 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
* we have to flush them after each command, so when we get here, the list
|
||||
* must be empty. */
|
||||
serverAssert(listLength(server.tracking_pending_keys) == 0);
|
||||
serverAssert(listLength(server.pending_push_messages) == 0);
|
||||
|
||||
/* Send the invalidation messages to clients participating to the
|
||||
* client side caching protocol in broadcasting (BCAST) mode. */
|
||||
trackingBroadcastInvalidationMessages();
|
||||
|
||||
/* Try to process blocked clients every once in while.
|
||||
*
|
||||
* Example: A module calls RM_SignalKeyAsReady from within a timer callback
|
||||
* (So we don't visit processCommand() at all).
|
||||
*
|
||||
* must be done before flushAppendOnlyFile, in case of appendfsync=always,
|
||||
* since the unblocked clients may write data. */
|
||||
handleClientsBlockedOnKeys();
|
||||
|
||||
/* Record time consumption of AOF writing. */
|
||||
monotime aof_start_time = getMonotonicUs();
|
||||
/* Record cron time in beforeSleep. This does not include the time consumed by AOF writing and IO writing below. */
|
||||
@@ -1985,6 +1966,7 @@ void createSharedObjects(void) {
|
||||
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
|
||||
shared.integers[j] =
|
||||
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
|
||||
initObjectLRUOrLFU(shared.integers[j]);
|
||||
shared.integers[j]->encoding = OBJ_ENCODING_INT;
|
||||
}
|
||||
for (j = 0; j < OBJ_SHARED_BULKHDR_LEN; j++) {
|
||||
@@ -2089,8 +2071,7 @@ void initServerConfig(void) {
|
||||
server.latency_tracking_info_percentiles[1] = 99.0; /* p99 */
|
||||
server.latency_tracking_info_percentiles[2] = 99.9; /* p999 */
|
||||
|
||||
unsigned int lruclock = getLRUClock();
|
||||
atomicSet(server.lruclock,lruclock);
|
||||
server.lruclock = getLRUClock();
|
||||
resetServerSaveParams();
|
||||
|
||||
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
|
||||
@@ -2612,6 +2593,7 @@ void initServer(void) {
|
||||
server.unblocked_clients = listCreate();
|
||||
server.ready_keys = listCreate();
|
||||
server.tracking_pending_keys = listCreate();
|
||||
server.pending_push_messages = listCreate();
|
||||
server.clients_waiting_acks = listCreate();
|
||||
server.get_ack_from_slaves = 0;
|
||||
server.paused_actions = 0;
|
||||
@@ -3715,9 +3697,11 @@ void call(client *c, int flags) {
|
||||
* various pre-execution checks. it returns the appropriate error to the client.
|
||||
* If there's a transaction is flags it as dirty, and if the command is EXEC,
|
||||
* it aborts the transaction.
|
||||
* The duration is reset, since we reject the command, and it did not record.
|
||||
* Note: 'reply' is expected to end with \r\n */
|
||||
void rejectCommand(client *c, robj *reply) {
|
||||
flagTransaction(c);
|
||||
c->duration = 0;
|
||||
if (c->cmd) c->cmd->rejected_calls++;
|
||||
if (c->cmd && c->cmd->proc == execCommand) {
|
||||
execCommandAbort(c, reply->ptr);
|
||||
@@ -3729,6 +3713,7 @@ void rejectCommand(client *c, robj *reply) {
|
||||
|
||||
void rejectCommandSds(client *c, sds s) {
|
||||
flagTransaction(c);
|
||||
c->duration = 0;
|
||||
if (c->cmd) c->cmd->rejected_calls++;
|
||||
if (c->cmd && c->cmd->proc == execCommand) {
|
||||
execCommandAbort(c, s);
|
||||
@@ -3756,7 +3741,14 @@ void afterCommand(client *c) {
|
||||
/* Should be done before trackingHandlePendingKeyInvalidations so that we
|
||||
* reply to client before invalidating cache (makes more sense) */
|
||||
postExecutionUnitOperations();
|
||||
|
||||
/* Flush pending tracking invalidations. */
|
||||
trackingHandlePendingKeyInvalidations();
|
||||
|
||||
/* Flush other pending push messages. only when we are not in nested call.
|
||||
* So the messages are not interleaved with transaction response. */
|
||||
if (!server.execution_nesting)
|
||||
listJoin(c->reply, server.pending_push_messages);
|
||||
}
|
||||
|
||||
/* Check if c->cmd exists, fills `err` with details in case it doesn't.
|
||||
@@ -5496,8 +5488,6 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
call_uname = 0;
|
||||
}
|
||||
|
||||
unsigned int lruclock;
|
||||
atomicGet(server.lruclock,lruclock);
|
||||
info = sdscatfmt(info,
|
||||
"# Server\r\n"
|
||||
"redis_version:%s\r\n"
|
||||
@@ -5548,7 +5538,7 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
||||
(int64_t)(uptime/(3600*24)),
|
||||
server.hz,
|
||||
server.config_hz,
|
||||
lruclock,
|
||||
server.lruclock,
|
||||
server.executable ? server.executable : "",
|
||||
server.configfile ? server.configfile : "",
|
||||
server.io_threads_active);
|
||||
|
||||
+14
-5
@@ -507,6 +507,7 @@ typedef enum {
|
||||
#define LL_VERBOSE 1
|
||||
#define LL_NOTICE 2
|
||||
#define LL_WARNING 3
|
||||
#define LL_NOTHING 4
|
||||
#define LL_RAW (1<<10) /* Modifier to log without timestamp */
|
||||
|
||||
/* Supervision options */
|
||||
@@ -591,9 +592,9 @@ typedef enum {
|
||||
#define CMD_CALL_PROPAGATE_AOF (1<<0)
|
||||
#define CMD_CALL_PROPAGATE_REPL (1<<1)
|
||||
#define CMD_CALL_REPROCESSING (1<<2)
|
||||
#define CMD_CALL_FROM_MODULE (1<<3) /* From RM_Call */
|
||||
#define CMD_CALL_PROPAGATE (CMD_CALL_PROPAGATE_AOF|CMD_CALL_PROPAGATE_REPL)
|
||||
#define CMD_CALL_FULL (CMD_CALL_PROPAGATE)
|
||||
#define CMD_CALL_FROM_MODULE (1<<2) /* From RM_Call */
|
||||
|
||||
/* Command propagation flags, see propagateNow() function */
|
||||
#define PROPAGATE_NONE 0
|
||||
@@ -709,6 +710,7 @@ typedef enum {
|
||||
* encoding version. */
|
||||
#define OBJ_MODULE 5 /* Module object. */
|
||||
#define OBJ_STREAM 6 /* Stream object. */
|
||||
#define OBJ_TYPE_MAX 7 /* Maximum number of object types */
|
||||
|
||||
/* Extract encver / signature from a module type ID. */
|
||||
#define REDISMODULE_TYPE_ENCVER_BITS 10
|
||||
@@ -1214,7 +1216,7 @@ typedef struct client {
|
||||
long long woff; /* Last write global replication offset. */
|
||||
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
|
||||
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
dict *pubsub_patterns; /* patterns a client is interested in (PSUBSCRIBE) */
|
||||
dict *pubsubshard_channels; /* shard level channels a client is interested in (SSUBSCRIBE) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
sds sockname; /* Cached connection target address. */
|
||||
@@ -1548,7 +1550,7 @@ struct redisServer {
|
||||
dict *orig_commands; /* Command table before command renaming. */
|
||||
aeEventLoop *el;
|
||||
rax *errors; /* Errors table */
|
||||
redisAtomic unsigned int lruclock; /* Clock for LRU eviction */
|
||||
unsigned int lruclock; /* Clock for LRU eviction */
|
||||
volatile sig_atomic_t shutdown_asap; /* Shutdown ordered by signal handler. */
|
||||
mstime_t shutdown_mstime; /* Timestamp to limit graceful shutdown. */
|
||||
int last_sig_received; /* Indicates the last SIGNAL received, if any (e.g., SIGINT or SIGTERM). */
|
||||
@@ -1927,6 +1929,7 @@ struct redisServer {
|
||||
unsigned int tracking_clients; /* # of clients with tracking enabled.*/
|
||||
size_t tracking_table_max_keys; /* Max number of keys in tracking table. */
|
||||
list *tracking_pending_keys; /* tracking invalidation keys pending to flush */
|
||||
list *pending_push_messages; /* pending publish or other push messages to flush */
|
||||
/* Sort parameters - qsort_r() is only available under BSD so we
|
||||
* have to take this state global, in order to pass it to sortCompare() */
|
||||
int sort_desc;
|
||||
@@ -1978,6 +1981,7 @@ struct redisServer {
|
||||
if the master is in failure state. */
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
char *cluster_announce_hostname; /* hostname to announce on cluster bus. */
|
||||
char *cluster_announce_human_nodename; /* Human readable node name assigned to a node. */
|
||||
int cluster_preferred_endpoint_type; /* Use the announced hostname when available. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
int cluster_announce_tls_port; /* TLS port to announce on cluster bus. */
|
||||
@@ -2468,6 +2472,8 @@ void moduleLoadFromQueue(void);
|
||||
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
|
||||
int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
|
||||
moduleType *moduleTypeLookupModuleByID(uint64_t id);
|
||||
moduleType *moduleTypeLookupModuleByName(const char *name);
|
||||
moduleType *moduleTypeLookupModuleByNameIgnoreCase(const char *name);
|
||||
void moduleTypeNameByID(char *name, uint64_t moduleid);
|
||||
const char *moduleTypeModuleName(moduleType *mt);
|
||||
const char *moduleNameFromCommand(struct redisCommand *cmd);
|
||||
@@ -2727,6 +2733,7 @@ void freeZsetObject(robj *o);
|
||||
void freeHashObject(robj *o);
|
||||
void dismissObject(robj *o, size_t dump_size);
|
||||
robj *createObject(int type, void *ptr);
|
||||
void initObjectLRUOrLFU(robj *o);
|
||||
robj *createStringObject(const char *ptr, size_t len);
|
||||
robj *createRawStringObject(const char *ptr, size_t len);
|
||||
robj *createEmbeddedStringObject(const char *ptr, size_t len);
|
||||
@@ -2736,10 +2743,12 @@ robj *dupStringObject(const robj *o);
|
||||
int isSdsRepresentableAsLongLong(sds s, long long *llval);
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
|
||||
robj *tryObjectEncoding(robj *o);
|
||||
robj *tryObjectEncodingEx(robj *o, int try_trim);
|
||||
robj *getDecodedObject(robj *o);
|
||||
size_t stringObjectLen(robj *o);
|
||||
robj *createStringObjectFromLongLong(long long value);
|
||||
robj *createStringObjectFromLongLongForValue(long long value);
|
||||
robj *createStringObjectFromLongLongWithSds(long long value);
|
||||
robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
|
||||
robj *createQuicklistObject(void);
|
||||
robj *createListListpackObject(void);
|
||||
@@ -3092,7 +3101,6 @@ void setTypeReleaseIterator(setTypeIterator *si);
|
||||
int setTypeNext(setTypeIterator *si, char **str, size_t *len, int64_t *llele);
|
||||
sds setTypeNextObject(setTypeIterator *si);
|
||||
int setTypeRandomElement(robj *setobj, char **str, size_t *len, int64_t *llele);
|
||||
unsigned long setTypeRandomElements(robj *set, unsigned long count, robj *aux_set);
|
||||
unsigned long setTypeSize(const robj *subject);
|
||||
void setTypeConvert(robj *subject, int enc);
|
||||
int setTypeConvertAndExpand(robj *setobj, int enc, unsigned long cap, int panic);
|
||||
@@ -3246,6 +3254,7 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val);
|
||||
#define SETKEY_NO_SIGNAL 2
|
||||
#define SETKEY_ALREADY_EXIST 4
|
||||
#define SETKEY_DOESNT_EXIST 8
|
||||
#define SETKEY_ADD_OR_UPDATE 16 /* Key most likely doesn't exists */
|
||||
void setKey(client *c, redisDb *db, robj *key, robj *val, int flags);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
int dbGenericDelete(redisDb *db, robj *key, int async, int flags);
|
||||
@@ -3377,7 +3386,7 @@ void signalDeletedKeyAsReady(redisDb *db, robj *key, int type);
|
||||
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us, int had_errors);
|
||||
void scanDatabaseForDeletedKeys(redisDb *emptied, redisDb *replaced_with);
|
||||
void totalNumberOfBlockingKeys(unsigned long *blocking_keys, unsigned long *bloking_keys_on_nokey);
|
||||
|
||||
void blockedBeforeSleep(void);
|
||||
|
||||
/* timeout.c -- Blocked clients timeout and connections timeout. */
|
||||
void addClientToTimeoutTable(client *c);
|
||||
|
||||
@@ -78,6 +78,7 @@ static connection *connCreateSocket(void) {
|
||||
connection *conn = zcalloc(sizeof(connection));
|
||||
conn->type = &CT_Socket;
|
||||
conn->fd = -1;
|
||||
conn->iovcnt = IOV_MAX;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
+22
-15
@@ -45,7 +45,7 @@ void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
|
||||
|
||||
/* We guess that most of the values in the input are unique, so
|
||||
* if there are enough arguments we create a pre-sized hash, which
|
||||
* might overallocate memory if their are duplicates. */
|
||||
* might over allocate memory if there are duplicates. */
|
||||
size_t new_fields = (end - start + 1) / 2;
|
||||
if (new_fields > server.hash_max_listpack_entries) {
|
||||
hashTypeConvert(o, OBJ_ENCODING_HT);
|
||||
@@ -1014,6 +1014,26 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* CASE 2.5 listpack only. Sampling unique elements, in non-random order.
|
||||
* Listpack encoded hashes are meant to be relatively small, so
|
||||
* HRANDFIELD_SUB_STRATEGY_MUL isn't necessary and we rather not make
|
||||
* copies of the entries. Instead, we emit them directly to the output
|
||||
* buffer.
|
||||
*
|
||||
* And it is inefficient to repeatedly pick one random element from a
|
||||
* listpack in CASE 4. So we use this instead. */
|
||||
if (hash->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
listpackEntry *keys, *vals = NULL;
|
||||
keys = zmalloc(sizeof(listpackEntry)*count);
|
||||
if (withvalues)
|
||||
vals = zmalloc(sizeof(listpackEntry)*count);
|
||||
serverAssert(lpRandomPairsUnique(hash->ptr, count, keys, vals) == count);
|
||||
hrandfieldReplyWithListpack(c, count, keys, vals);
|
||||
zfree(keys);
|
||||
zfree(vals);
|
||||
return;
|
||||
}
|
||||
|
||||
/* CASE 3:
|
||||
* The number of elements inside the hash is not greater than
|
||||
* HRANDFIELD_SUB_STRATEGY_MUL times the number of requested elements.
|
||||
@@ -1024,6 +1044,7 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
* a bit less than the number of elements in the hash, the natural approach
|
||||
* used into CASE 4 is highly inefficient. */
|
||||
if (count*HRANDFIELD_SUB_STRATEGY_MUL > size) {
|
||||
/* Hashtable encoding (generic implementation) */
|
||||
dict *d = dictCreate(&sdsReplyDictType);
|
||||
dictExpand(d, size);
|
||||
hashTypeIterator *hi = hashTypeInitIterator(hash);
|
||||
@@ -1077,20 +1098,6 @@ void hrandfieldWithCountCommand(client *c, long l, int withvalues) {
|
||||
* to the temporary hash, trying to eventually get enough unique elements
|
||||
* to reach the specified count. */
|
||||
else {
|
||||
if (hash->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
/* it is inefficient to repeatedly pick one random element from a
|
||||
* listpack. so we use this instead: */
|
||||
listpackEntry *keys, *vals = NULL;
|
||||
keys = zmalloc(sizeof(listpackEntry)*count);
|
||||
if (withvalues)
|
||||
vals = zmalloc(sizeof(listpackEntry)*count);
|
||||
serverAssert(lpRandomPairsUnique(hash->ptr, count, keys, vals) == count);
|
||||
hrandfieldReplyWithListpack(c, count, keys, vals);
|
||||
zfree(keys);
|
||||
zfree(vals);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Hashtable encoding (generic implementation) */
|
||||
unsigned long added = 0;
|
||||
listpackEntry key, value;
|
||||
|
||||
+1
-1
@@ -240,7 +240,7 @@ listTypeIterator *listTypeInitIterator(robj *subject, long index,
|
||||
li->direction = direction;
|
||||
li->iter = NULL;
|
||||
/* LIST_HEAD means start at TAIL and move *towards* head.
|
||||
* LIST_TAIL means start at HEAD and move *towards tail. */
|
||||
* LIST_TAIL means start at HEAD and move *towards* tail. */
|
||||
if (li->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
int iter_direction = direction == LIST_HEAD ? AL_START_TAIL : AL_START_HEAD;
|
||||
li->iter = quicklistGetIteratorAtIdx(li->subject->ptr,
|
||||
|
||||
+59
-34
@@ -38,19 +38,19 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
robj *dstkey, int op);
|
||||
|
||||
/* Factory method to return a set that *can* hold "value". When the object has
|
||||
* an integer-encodable value, an intset will be returned. Otherwise a regular
|
||||
* hash table.
|
||||
* an integer-encodable value, an intset will be returned. Otherwise a listpack
|
||||
* or a regular hash table.
|
||||
*
|
||||
* The size hint indicates approximately how many items will be added which is
|
||||
* used to determine the initial representation. */
|
||||
robj *setTypeCreate(sds value, size_t size_hint) {
|
||||
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK && size_hint < server.set_max_intset_entries)
|
||||
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK && size_hint <= server.set_max_intset_entries)
|
||||
return createIntsetObject();
|
||||
if (size_hint < server.set_max_listpack_entries)
|
||||
if (size_hint <= server.set_max_listpack_entries)
|
||||
return createSetListpackObject();
|
||||
|
||||
/* We may oversize the set by using the hint if the hint is not accurate,
|
||||
* but we will assume this is accpetable to maximize performance. */
|
||||
* but we will assume this is acceptable to maximize performance. */
|
||||
robj *o = createSetObject();
|
||||
dictExpand(o->ptr, size_hint);
|
||||
return o;
|
||||
@@ -59,8 +59,8 @@ robj *setTypeCreate(sds value, size_t size_hint) {
|
||||
/* Check if the existing set should be converted to another encoding based off the
|
||||
* the size hint. */
|
||||
void setTypeMaybeConvert(robj *set, size_t size_hint) {
|
||||
if ((set->encoding == OBJ_ENCODING_LISTPACK && size_hint >= server.set_max_listpack_entries)
|
||||
|| (set->encoding == OBJ_ENCODING_INTSET && size_hint >= server.set_max_intset_entries))
|
||||
if ((set->encoding == OBJ_ENCODING_LISTPACK && size_hint > server.set_max_listpack_entries)
|
||||
|| (set->encoding == OBJ_ENCODING_INTSET && size_hint > server.set_max_intset_entries))
|
||||
{
|
||||
setTypeConvertAndExpand(set, OBJ_ENCODING_HT, size_hint, 1);
|
||||
}
|
||||
@@ -798,8 +798,9 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
/* todo: Move the spop notification to be executed after the command logic. */
|
||||
|
||||
/* Propagate this command as a DEL operation */
|
||||
rewriteClientCommandVector(c,2,shared.del,c->argv[1]);
|
||||
/* Propagate this command as a DEL or UNLINK operation */
|
||||
robj *aux = server.lazyfree_lazy_server_del ? shared.unlink : shared.del;
|
||||
rewriteClientCommandVector(c, 2, aux, c->argv[1]);
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
return;
|
||||
}
|
||||
@@ -807,13 +808,14 @@ void spopWithCountCommand(client *c) {
|
||||
/* Case 2 and 3 require to replicate SPOP as a set of SREM commands.
|
||||
* Prepare our replication argument vector. Also send the array length
|
||||
* which is common to both the code paths. */
|
||||
robj *propargv[3];
|
||||
unsigned long batchsize = count > 1024 ? 1024 : count;
|
||||
robj **propargv = zmalloc(sizeof(robj *) * (2 + batchsize));
|
||||
propargv[0] = shared.srem;
|
||||
propargv[1] = c->argv[1];
|
||||
unsigned long propindex = 2;
|
||||
addReplySetLen(c,count);
|
||||
|
||||
/* Common iteration vars. */
|
||||
robj *objele;
|
||||
char *str;
|
||||
size_t len;
|
||||
int64_t llele;
|
||||
@@ -841,16 +843,19 @@ void spopWithCountCommand(client *c) {
|
||||
|
||||
if (str) {
|
||||
addReplyBulkCBuffer(c, str, len);
|
||||
objele = createStringObject(str, len);
|
||||
propargv[propindex++] = createStringObject(str, len);
|
||||
} else {
|
||||
addReplyBulkLongLong(c, llele);
|
||||
objele = createStringObjectFromLongLong(llele);
|
||||
propargv[propindex++] = createStringObjectFromLongLong(llele);
|
||||
}
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
propargv[2] = objele;
|
||||
alsoPropagate(c->db->id,propargv,3,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(objele);
|
||||
if (propindex == 2 + batchsize) {
|
||||
alsoPropagate(c->db->id, propargv, propindex, PROPAGATE_AOF | PROPAGATE_REPL);
|
||||
for (unsigned long j = 2; j < propindex; j++) {
|
||||
decrRefCount(propargv[j]);
|
||||
}
|
||||
propindex = 2;
|
||||
}
|
||||
|
||||
/* Store pointer for later deletion and move to next. */
|
||||
ps[i] = p;
|
||||
@@ -861,14 +866,18 @@ void spopWithCountCommand(client *c) {
|
||||
zfree(ps);
|
||||
set->ptr = lp;
|
||||
} else if (remaining*SPOP_MOVE_STRATEGY_MUL > count) {
|
||||
while(count--) {
|
||||
objele = setTypePopRandom(set);
|
||||
addReplyBulk(c, objele);
|
||||
|
||||
for (unsigned long i = 0; i < count; i++) {
|
||||
propargv[propindex] = setTypePopRandom(set);
|
||||
addReplyBulk(c, propargv[propindex]);
|
||||
propindex++;
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
propargv[2] = objele;
|
||||
alsoPropagate(c->db->id,propargv,3,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(objele);
|
||||
if (propindex == 2 + batchsize) {
|
||||
alsoPropagate(c->db->id, propargv, propindex, PROPAGATE_AOF | PROPAGATE_REPL);
|
||||
for (unsigned long j = 2; j < propindex; j++) {
|
||||
decrRefCount(propargv[j]);
|
||||
}
|
||||
propindex = 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* CASE 3: The number of elements to return is very big, approaching
|
||||
@@ -918,16 +927,19 @@ void spopWithCountCommand(client *c) {
|
||||
while (setTypeNext(si, &str, &len, &llele) != -1) {
|
||||
if (str == NULL) {
|
||||
addReplyBulkLongLong(c,llele);
|
||||
objele = createStringObjectFromLongLong(llele);
|
||||
propargv[propindex++] = createStringObjectFromLongLong(llele);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c, str, len);
|
||||
objele = createStringObject(str, len);
|
||||
propargv[propindex++] = createStringObject(str, len);
|
||||
}
|
||||
|
||||
/* Replicate/AOF this command as an SREM operation */
|
||||
propargv[2] = objele;
|
||||
alsoPropagate(c->db->id,propargv,3,PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
decrRefCount(objele);
|
||||
if (propindex == 2 + batchsize) {
|
||||
alsoPropagate(c->db->id, propargv, propindex, PROPAGATE_AOF | PROPAGATE_REPL);
|
||||
for (unsigned long i = 2; i < propindex; i++) {
|
||||
decrRefCount(propargv[i]);
|
||||
}
|
||||
propindex = 2;
|
||||
}
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
|
||||
@@ -935,6 +947,16 @@ void spopWithCountCommand(client *c) {
|
||||
dbReplaceValue(c->db,c->argv[1],newset);
|
||||
}
|
||||
|
||||
/* Replicate/AOF the remaining elements as an SREM operation */
|
||||
if (propindex != 2) {
|
||||
alsoPropagate(c->db->id, propargv, propindex, PROPAGATE_AOF | PROPAGATE_REPL);
|
||||
for (unsigned long i = 2; i < propindex; i++) {
|
||||
decrRefCount(propargv[i]);
|
||||
}
|
||||
propindex = 2;
|
||||
}
|
||||
zfree(propargv);
|
||||
|
||||
/* Don't propagate the command itself even if we incremented the
|
||||
* dirty counter. We don't want to propagate an SPOP command since
|
||||
* we propagated the command as a set of SREMs operations using
|
||||
@@ -1093,7 +1115,10 @@ void srandmemberWithCountCommand(client *c) {
|
||||
* Listpack encoded sets are meant to be relatively small, so
|
||||
* SRANDMEMBER_SUB_STRATEGY_MUL isn't necessary and we rather not make
|
||||
* copies of the entries. Instead, we emit them directly to the output
|
||||
* buffer. */
|
||||
* buffer.
|
||||
*
|
||||
* And it is inefficient to repeatedly pick one random element from a
|
||||
* listpack in CASE 4. So we use this instead. */
|
||||
if (set->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
unsigned char *lp = set->ptr;
|
||||
unsigned char *p = lpFirst(lp);
|
||||
@@ -1316,7 +1341,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
|
||||
} else if (sets[0]->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
/* To avoid many reallocs, we estimate that the result is a listpack
|
||||
* of approximately the same size as the first set. Then we shrink
|
||||
* it or possibly convert it to intset it in the end. */
|
||||
* it or possibly convert it to intset in the end. */
|
||||
unsigned char *lp = lpNew(lpBytes(sets[0]->ptr));
|
||||
dstset = createObject(OBJ_SET, lp);
|
||||
dstset->encoding = OBJ_ENCODING_LISTPACK;
|
||||
@@ -1516,8 +1541,8 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
|
||||
}
|
||||
}
|
||||
|
||||
/* We need a temp set object to store our union. If the dstkey
|
||||
* is not NULL (that is, we are inside an SUNIONSTORE operation) then
|
||||
/* We need a temp set object to store our union/diff. If the dstkey
|
||||
* is not NULL (that is, we are inside an SUNIONSTORE/SDIFFSTORE operation) then
|
||||
* this set object will be the resulting object to set into the target key*/
|
||||
dstset = createIntsetObject();
|
||||
|
||||
|
||||
+4
-3
@@ -461,7 +461,7 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
}
|
||||
|
||||
/* Avoid overflow when trying to add an element to the stream (listpack
|
||||
* can only host up to 32bit length sttrings, and also a total listpack size
|
||||
* can only host up to 32bit length strings, and also a total listpack size
|
||||
* can't be bigger than 32bit length. */
|
||||
size_t totelelen = 0;
|
||||
for (int64_t i = 0; i < numfields*2; i++) {
|
||||
@@ -2208,9 +2208,10 @@ void xreadCommand(client *c) {
|
||||
streams_arg = i+1;
|
||||
streams_count = (c->argc-streams_arg);
|
||||
if ((streams_count % 2) != 0) {
|
||||
char symbol = xreadgroup ? '>' : '$';
|
||||
addReplyErrorFormat(c,"Unbalanced '%s' list of streams: "
|
||||
"for each stream key an ID or '>' must be "
|
||||
"specified.", c->cmd->fullname);
|
||||
"for each stream key an ID or '%c' must be "
|
||||
"specified.", c->cmd->fullname,symbol);
|
||||
return;
|
||||
}
|
||||
streams_count /= 2; /* We have two arguments for each stream. */
|
||||
|
||||
+12
-7
@@ -116,10 +116,12 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
if (expire) {
|
||||
setExpire(c,c->db,key,milliseconds);
|
||||
/* Propagate as SET Key Value PXAT millisecond-timestamp if there is
|
||||
* EX/PX/EXAT/PXAT flag. */
|
||||
robj *milliseconds_obj = createStringObjectFromLongLong(milliseconds);
|
||||
rewriteClientCommandVector(c, 5, shared.set, key, val, shared.pxat, milliseconds_obj);
|
||||
decrRefCount(milliseconds_obj);
|
||||
* EX/PX/EXAT flag. */
|
||||
if (!(flags & OBJ_PXAT)) {
|
||||
robj *milliseconds_obj = createStringObjectFromLongLong(milliseconds);
|
||||
rewriteClientCommandVector(c, 5, shared.set, key, val, shared.pxat, milliseconds_obj);
|
||||
decrRefCount(milliseconds_obj);
|
||||
}
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
|
||||
}
|
||||
|
||||
@@ -388,8 +390,7 @@ void getexCommand(client *c) {
|
||||
if (((flags & OBJ_PXAT) || (flags & OBJ_EXAT)) && checkAlreadyExpired(milliseconds)) {
|
||||
/* When PXAT/EXAT absolute timestamp is specified, there can be a chance that timestamp
|
||||
* has already elapsed so delete the key in that case. */
|
||||
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db, c->argv[1]) :
|
||||
dbSyncDelete(c->db, c->argv[1]);
|
||||
int deleted = dbGenericDelete(c->db, c->argv[1], server.lazyfree_lazy_expire, DB_FLAG_KEY_EXPIRED);
|
||||
serverAssert(deleted);
|
||||
robj *aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
|
||||
rewriteClientCommandVector(c,2,aux,c->argv[1]);
|
||||
@@ -576,10 +577,14 @@ void msetGenericCommand(client *c, int nx) {
|
||||
}
|
||||
}
|
||||
|
||||
int setkey_flags = nx ? SETKEY_DOESNT_EXIST : 0;
|
||||
for (j = 1; j < c->argc; j += 2) {
|
||||
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
|
||||
setKey(c, c->db, c->argv[j], c->argv[j + 1], 0);
|
||||
setKey(c, c->db, c->argv[j], c->argv[j + 1], setkey_flags);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",c->argv[j],c->db->id);
|
||||
/* In MSETNX, It could be that we're overriding the same key, we can't be sure it doesn't exist. */
|
||||
if (nx)
|
||||
setkey_flags = SETKEY_ADD_OR_UPDATE;
|
||||
}
|
||||
server.dirty += (c->argc-1)/2;
|
||||
addReply(c, nx ? shared.cone : shared.ok);
|
||||
|
||||
+82
-32
@@ -66,6 +66,7 @@
|
||||
|
||||
int zslLexValueGteMin(sds value, zlexrangespec *spec);
|
||||
int zslLexValueLteMax(sds value, zlexrangespec *spec);
|
||||
void zsetConvertAndExpand(robj *zobj, int encoding, unsigned long cap);
|
||||
|
||||
/* Create a skiplist node with the specified number of levels.
|
||||
* The SDS string 'ele' is referenced by the node after the call. */
|
||||
@@ -1165,7 +1166,45 @@ unsigned long zsetLength(const robj *zobj) {
|
||||
return length;
|
||||
}
|
||||
|
||||
/* Factory method to return a zset.
|
||||
*
|
||||
* The size hint indicates approximately how many items will be added,
|
||||
* and the value len hint indicates the approximate individual size of the added elements,
|
||||
* they are used to determine the initial representation.
|
||||
*
|
||||
* If the hints are not known, and underestimation or 0 is suitable. */
|
||||
robj *zsetTypeCreate(size_t size_hint, size_t val_len_hint) {
|
||||
if (size_hint <= server.zset_max_listpack_entries &&
|
||||
val_len_hint <= server.zset_max_listpack_value)
|
||||
{
|
||||
return createZsetListpackObject();
|
||||
}
|
||||
|
||||
robj *zobj = createZsetObject();
|
||||
zset *zs = zobj->ptr;
|
||||
dictExpand(zs->dict, size_hint);
|
||||
return zobj;
|
||||
}
|
||||
|
||||
/* Check if the existing zset should be converted to another encoding based off the
|
||||
* the size hint. */
|
||||
void zsetTypeMaybeConvert(robj *zobj, size_t size_hint) {
|
||||
if (zobj->encoding == OBJ_ENCODING_LISTPACK &&
|
||||
size_hint > server.zset_max_listpack_entries)
|
||||
{
|
||||
zsetConvertAndExpand(zobj, OBJ_ENCODING_SKIPLIST, size_hint);
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert the zset to specified encoding. The zset dict (when converting
|
||||
* to a skiplist) is presized to hold the number of elements in the original
|
||||
* zset. */
|
||||
void zsetConvert(robj *zobj, int encoding) {
|
||||
zsetConvertAndExpand(zobj, encoding, zsetLength(zobj));
|
||||
}
|
||||
|
||||
/* Converts a zset to the specified encoding, pre-sizing it for 'cap' elements. */
|
||||
void zsetConvertAndExpand(robj *zobj, int encoding, unsigned long cap) {
|
||||
zset *zs;
|
||||
zskiplistNode *node, *next;
|
||||
sds ele;
|
||||
@@ -1186,6 +1225,9 @@ void zsetConvert(robj *zobj, int encoding) {
|
||||
zs->dict = dictCreate(&zsetDictType);
|
||||
zs->zsl = zslCreate();
|
||||
|
||||
/* Presize the dict to avoid rehashing */
|
||||
dictExpand(zs->dict, cap);
|
||||
|
||||
eptr = lpSeek(zl,0);
|
||||
if (eptr != NULL) {
|
||||
sptr = lpNext(zl,eptr);
|
||||
@@ -1375,7 +1417,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int in_flags, int *out_flags, dou
|
||||
sdslen(ele) > server.zset_max_listpack_value ||
|
||||
!lpSafeToAdd(zobj->ptr, sdslen(ele)))
|
||||
{
|
||||
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
|
||||
zsetConvertAndExpand(zobj, OBJ_ENCODING_SKIPLIST, zsetLength(zobj) + 1);
|
||||
} else {
|
||||
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
|
||||
if (newscore) *newscore = score;
|
||||
@@ -1749,14 +1791,10 @@ void zaddGenericCommand(client *c, int flags) {
|
||||
if (checkType(c,zobj,OBJ_ZSET)) goto cleanup;
|
||||
if (zobj == NULL) {
|
||||
if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
|
||||
if (server.zset_max_listpack_entries == 0 ||
|
||||
server.zset_max_listpack_value < sdslen(c->argv[scoreidx+1]->ptr))
|
||||
{
|
||||
zobj = createZsetObject();
|
||||
} else {
|
||||
zobj = createZsetListpackObject();
|
||||
}
|
||||
zobj = zsetTypeCreate(elements, sdslen(c->argv[scoreidx+1]->ptr));
|
||||
dbAdd(c->db,key,zobj);
|
||||
} else {
|
||||
zsetTypeMaybeConvert(zobj, elements);
|
||||
}
|
||||
|
||||
for (j = 0; j < elements; j++) {
|
||||
@@ -2537,8 +2575,8 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
zsetopval zval;
|
||||
sds tmp;
|
||||
size_t maxelelen = 0, totelelen = 0;
|
||||
robj *dstobj;
|
||||
zset *dstzset;
|
||||
robj *dstobj = NULL;
|
||||
zset *dstzset = NULL;
|
||||
zskiplistNode *znode;
|
||||
int withscores = 0;
|
||||
unsigned long cardinality = 0;
|
||||
@@ -2648,8 +2686,14 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
qsort(src,setnum,sizeof(zsetopsrc),zuiCompareByCardinality);
|
||||
}
|
||||
|
||||
dstobj = createZsetObject();
|
||||
dstzset = dstobj->ptr;
|
||||
/* We need a temp zset object to store our union/inter/diff. If the dstkey
|
||||
* is not NULL (that is, we are inside an ZUNIONSTORE/ZINTERSTORE/ZDIFFSTORE operation) then
|
||||
* this zset object will be the resulting object to zset into the target key.
|
||||
* In SINTERCARD case, we don't need the temp obj, so we can avoid creating it. */
|
||||
if (!cardinality_only) {
|
||||
dstobj = createZsetObject();
|
||||
dstzset = dstobj->ptr;
|
||||
}
|
||||
memset(&zval, 0, sizeof(zval));
|
||||
|
||||
if (op == SET_OP_INTER) {
|
||||
@@ -2788,6 +2832,7 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
decrRefCount(dstobj);
|
||||
} else if (cardinality_only) {
|
||||
addReplyLongLong(c, cardinality);
|
||||
} else {
|
||||
@@ -2808,8 +2853,9 @@ void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIndex, in
|
||||
if (withscores) addReplyDouble(c,zn->score);
|
||||
zn = zn->level[0].forward;
|
||||
}
|
||||
server.lazyfree_lazy_server_del ? freeObjAsync(NULL, dstobj, -1) :
|
||||
decrRefCount(dstobj);
|
||||
}
|
||||
decrRefCount(dstobj);
|
||||
zfree(src);
|
||||
}
|
||||
|
||||
@@ -2955,10 +3001,7 @@ static void zrangeResultFinalizeClient(zrange_result_handler *handler,
|
||||
/* Result handler methods for storing the ZRANGESTORE to a zset. */
|
||||
static void zrangeResultBeginStore(zrange_result_handler *handler, long length)
|
||||
{
|
||||
if (length > (long)server.zset_max_listpack_entries)
|
||||
handler->dstobj = createZsetObject();
|
||||
else
|
||||
handler->dstobj = createZsetListpackObject();
|
||||
handler->dstobj = zsetTypeCreate(length, 0);
|
||||
}
|
||||
|
||||
static void zrangeResultEmitCBufferForStore(zrange_result_handler *handler,
|
||||
@@ -4208,6 +4251,27 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* CASE 2.5 listpack only. Sampling unique elements, in non-random order.
|
||||
* Listpack encoded zsets are meant to be relatively small, so
|
||||
* ZRANDMEMBER_SUB_STRATEGY_MUL isn't necessary and we rather not make
|
||||
* copies of the entries. Instead, we emit them directly to the output
|
||||
* buffer.
|
||||
*
|
||||
* And it is inefficient to repeatedly pick one random element from a
|
||||
* listpack in CASE 4. So we use this instead. */
|
||||
if (zsetobj->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
listpackEntry *keys, *vals = NULL;
|
||||
keys = zmalloc(sizeof(listpackEntry)*count);
|
||||
if (withscores)
|
||||
vals = zmalloc(sizeof(listpackEntry)*count);
|
||||
serverAssert(lpRandomPairsUnique(zsetobj->ptr, count, keys, vals) == count);
|
||||
zrandmemberReplyWithListpack(c, count, keys, vals);
|
||||
zfree(keys);
|
||||
zfree(vals);
|
||||
zuiClearIterator(&src);
|
||||
return;
|
||||
}
|
||||
|
||||
/* CASE 3:
|
||||
* The number of elements inside the zset is not greater than
|
||||
* ZRANDMEMBER_SUB_STRATEGY_MUL times the number of requested elements.
|
||||
@@ -4218,6 +4282,7 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
* a bit less than the number of elements in the set, the natural approach
|
||||
* used into CASE 4 is highly inefficient. */
|
||||
if (count*ZRANDMEMBER_SUB_STRATEGY_MUL > size) {
|
||||
/* Hashtable encoding (generic implementation) */
|
||||
dict *d = dictCreate(&sdsReplyDictType);
|
||||
dictExpand(d, size);
|
||||
/* Add all the elements into the temporary dictionary. */
|
||||
@@ -4261,21 +4326,6 @@ void zrandmemberWithCountCommand(client *c, long l, int withscores) {
|
||||
* to the temporary set, trying to eventually get enough unique elements
|
||||
* to reach the specified count. */
|
||||
else {
|
||||
if (zsetobj->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
/* it is inefficient to repeatedly pick one random element from a
|
||||
* listpack. so we use this instead: */
|
||||
listpackEntry *keys, *vals = NULL;
|
||||
keys = zmalloc(sizeof(listpackEntry)*count);
|
||||
if (withscores)
|
||||
vals = zmalloc(sizeof(listpackEntry)*count);
|
||||
serverAssert(lpRandomPairsUnique(zsetobj->ptr, count, keys, vals) == count);
|
||||
zrandmemberReplyWithListpack(c, count, keys, vals);
|
||||
zfree(keys);
|
||||
zfree(vals);
|
||||
zuiClearIterator(&src);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Hashtable encoding (generic implementation) */
|
||||
unsigned long added = 0;
|
||||
dict *d = dictCreate(&hashDictType);
|
||||
|
||||
@@ -462,6 +462,7 @@ static connection *createTLSConnection(int client_side) {
|
||||
tls_connection *conn = zcalloc(sizeof(tls_connection));
|
||||
conn->c.type = &CT_TLS;
|
||||
conn->c.fd = -1;
|
||||
conn->c.iovcnt = IOV_MAX;
|
||||
conn->ssl = SSL_new(ctx);
|
||||
return (connection *) conn;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ static connection *connCreateUnix(void) {
|
||||
connection *conn = zcalloc(sizeof(connection));
|
||||
conn->type = &CT_Unix;
|
||||
conn->fd = -1;
|
||||
conn->iovcnt = IOV_MAX;
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
@@ -74,12 +74,16 @@
|
||||
|
||||
#if !defined(NO_MALLOC_USABLE_SIZE) && \
|
||||
(defined(__GLIBC__) || defined(__FreeBSD__) || \
|
||||
defined(__DragonFly__) || defined(__HAIKU__) || \
|
||||
defined(USE_MALLOC_USABLE_SIZE))
|
||||
|
||||
/* Includes for malloc_usable_size() */
|
||||
#ifdef __FreeBSD__
|
||||
#include <malloc_np.h>
|
||||
#else
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user