diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ecbaf50bd..43547a431 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml
index 42a13f100..72720e6dd 100644
--- a/.github/workflows/daily.yml
+++ b/.github/workflows/daily.yml
@@ -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
diff --git a/deps/Makefile b/deps/Makefile
index 34656201d..3bf0363d5 100644
--- a/deps/Makefile
+++ b/deps/Makefile
@@ -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
diff --git a/deps/README.md b/deps/README.md
index bd1280e5e..6d434c21f 100644
--- a/deps/README.md
+++ b/deps/README.md
@@ -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
---
diff --git a/deps/hiredis/.github/release-drafter-config.yml b/deps/hiredis/.github/release-drafter-config.yml
new file mode 100644
index 000000000..81afa9f97
--- /dev/null
+++ b/deps/hiredis/.github/release-drafter-config.yml
@@ -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
+
diff --git a/deps/hiredis/.github/workflows/build.yml b/deps/hiredis/.github/workflows/build.yml
index 362bc77b7..1a1ef5153 100644
--- a/deps/hiredis/.github/workflows/build.yml
+++ b/deps/hiredis/.github/workflows/build.yml
@@ -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
diff --git a/deps/hiredis/.github/workflows/release-drafter.yml b/deps/hiredis/.github/workflows/release-drafter.yml
new file mode 100644
index 000000000..ec2d88bf6
--- /dev/null
+++ b/deps/hiredis/.github/workflows/release-drafter.yml
@@ -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 }}
diff --git a/deps/hiredis/.github/workflows/test.yml b/deps/hiredis/.github/workflows/test.yml
new file mode 100644
index 000000000..7812af6f7
--- /dev/null
+++ b/deps/hiredis/.github/workflows/test.yml
@@ -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
diff --git a/deps/hiredis/CHANGELOG.md b/deps/hiredis/CHANGELOG.md
index 2a2bc314a..a2e065b2c 100644
--- a/deps/hiredis/CHANGELOG.md
+++ b/deps/hiredis/CHANGELOG.md
@@ -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!
+
+
+
+
+
+
+## [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!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
## [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`.
diff --git a/deps/hiredis/CMakeLists.txt b/deps/hiredis/CMakeLists.txt
index fe6720b28..b7d6ee8d8 100644
--- a/deps/hiredis/CMakeLists.txt
+++ b/deps/hiredis/CMakeLists.txt
@@ -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 $ $)
-TARGET_INCLUDE_DIRECTORIES(hiredis_static PUBLIC $ $)
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 $
DESTINATION ${CMAKE_INSTALL_BINDIR}
CONFIGURATIONS Debug RelWithDebInfo)
- INSTALL(FILES $/$.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 $
DESTINATION ${CMAKE_INSTALL_BINDIR}
CONFIGURATIONS Debug RelWithDebInfo)
- INSTALL(FILES $/$.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)
diff --git a/deps/hiredis/Makefile b/deps/hiredis/Makefile
index a2ad84c6b..f31293e90 100644
--- a/deps/hiredis/Makefile
+++ b/deps/hiredis/Makefile
@@ -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. /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=""
diff --git a/deps/hiredis/README.md b/deps/hiredis/README.md
index ed66220c7..74364b411 100644
--- a/deps/hiredis/README.md
+++ b/deps/hiredis/README.md
@@ -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`
NOTE: v1.0.1 erroneously bumped SONAME, which is why it is skipped here.
@@ -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
REDIS\_OPT\_PREFER_IPV6
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.
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
+#include
```
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
diff --git a/deps/hiredis/adapters/libhv.h b/deps/hiredis/adapters/libhv.h
new file mode 100644
index 000000000..3b54c70f4
--- /dev/null
+++ b/deps/hiredis/adapters/libhv.h
@@ -0,0 +1,123 @@
+#ifndef __HIREDIS_LIBHV_H__
+#define __HIREDIS_LIBHV_H__
+
+#include
+#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
diff --git a/deps/hiredis/adapters/libsdevent.h b/deps/hiredis/adapters/libsdevent.h
new file mode 100644
index 000000000..1268ed9f9
--- /dev/null
+++ b/deps/hiredis/adapters/libsdevent.h
@@ -0,0 +1,177 @@
+#ifndef HIREDIS_LIBSDEVENT_H
+#define HIREDIS_LIBSDEVENT_H
+#include
+#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
diff --git a/deps/hiredis/adapters/libuv.h b/deps/hiredis/adapters/libuv.h
index df0a84578..268edab79 100644
--- a/deps/hiredis/adapters/libuv.h
+++ b/deps/hiredis/adapters/libuv.h
@@ -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);
diff --git a/deps/hiredis/adapters/poll.h b/deps/hiredis/adapters/poll.h
new file mode 100644
index 000000000..f138650f9
--- /dev/null
+++ b/deps/hiredis/adapters/poll.h
@@ -0,0 +1,197 @@
+
+#ifndef HIREDIS_POLL_H
+#define HIREDIS_POLL_H
+
+#include "../async.h"
+#include "../sockcompat.h"
+#include // for memset
+#include
+
+/* 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 */
diff --git a/deps/hiredis/adapters/redismoduleapi.h b/deps/hiredis/adapters/redismoduleapi.h
new file mode 100644
index 000000000..8a076fe46
--- /dev/null
+++ b/deps/hiredis/adapters/redismoduleapi.h
@@ -0,0 +1,144 @@
+#ifndef __HIREDIS_REDISMODULEAPI_H__
+#define __HIREDIS_REDISMODULEAPI_H__
+
+#include "redismodule.h"
+
+#include "../async.h"
+#include "../hiredis.h"
+
+#include
+
+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
diff --git a/deps/hiredis/async.c b/deps/hiredis/async.c
index d73d09fb1..3d39cfaf8 100644
--- a/deps/hiredis/async.c
+++ b/deps/hiredis/async.c
@@ -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. */
diff --git a/deps/hiredis/async.h b/deps/hiredis/async.h
index 4c65203c1..4f94660b1 100644
--- a/deps/hiredis/async.h
+++ b/deps/hiredis/async.h
@@ -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);
diff --git a/deps/hiredis/examples/CMakeLists.txt b/deps/hiredis/examples/CMakeLists.txt
index 49cd8d440..214898b07 100644
--- a/deps/hiredis/examples/CMakeLists.txt
+++ b/deps/hiredis/examples/CMakeLists.txt
@@ -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)
diff --git a/deps/hiredis/examples/example-libevent-ssl.c b/deps/hiredis/examples/example-libevent-ssl.c
index 7d99af1ba..d0998bab3 100644
--- a/deps/hiredis/examples/example-libevent-ssl.c
+++ b/deps/hiredis/examples/example-libevent-ssl.c
@@ -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();
diff --git a/deps/hiredis/examples/example-libhv.c b/deps/hiredis/examples/example-libhv.c
new file mode 100644
index 000000000..ac68b0086
--- /dev/null
+++ b/deps/hiredis/examples/example-libhv.c
@@ -0,0 +1,70 @@
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+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;
+}
diff --git a/deps/hiredis/examples/example-libsdevent.c b/deps/hiredis/examples/example-libsdevent.c
new file mode 100644
index 000000000..c3b902b4e
--- /dev/null
+++ b/deps/hiredis/examples/example-libsdevent.c
@@ -0,0 +1,86 @@
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+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;
+}
diff --git a/deps/hiredis/examples/example-poll.c b/deps/hiredis/examples/example-poll.c
new file mode 100644
index 000000000..954673dac
--- /dev/null
+++ b/deps/hiredis/examples/example-poll.c
@@ -0,0 +1,62 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+/* 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;
+}
diff --git a/deps/hiredis/examples/example-redismoduleapi.c b/deps/hiredis/examples/example-redismoduleapi.c
new file mode 100644
index 000000000..7d12f8a06
--- /dev/null
+++ b/deps/hiredis/examples/example-redismoduleapi.c
@@ -0,0 +1,101 @@
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+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;
+}
diff --git a/deps/hiredis/examples/example-ssl.c b/deps/hiredis/examples/example-ssl.c
index b8ca44281..6d1e32e75 100644
--- a/deps/hiredis/examples/example-ssl.c
+++ b/deps/hiredis/examples/example-ssl.c
@@ -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);
}
diff --git a/deps/hiredis/examples/example.c b/deps/hiredis/examples/example.c
index f1b8b4a85..c0a9bb734 100644
--- a/deps/hiredis/examples/example.c
+++ b/deps/hiredis/examples/example.c
@@ -7,6 +7,54 @@
#include /* 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);
diff --git a/deps/hiredis/fuzzing/format_command_fuzzer.c b/deps/hiredis/fuzzing/format_command_fuzzer.c
index 91adeac58..de125e08d 100644
--- a/deps/hiredis/fuzzing/format_command_fuzzer.c
+++ b/deps/hiredis/fuzzing/format_command_fuzzer.c
@@ -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;
}
diff --git a/deps/hiredis/hiredis-config.cmake.in b/deps/hiredis/hiredis-config.cmake.in
index 98851dcee..033985852 100644
--- a/deps/hiredis/hiredis-config.cmake.in
+++ b/deps/hiredis/hiredis-config.cmake.in
@@ -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)
diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c
index f7b61c206..fd200173f 100644
--- a/deps/hiredis/hiredis.c
+++ b/deps/hiredis/hiredis.c
@@ -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) {
diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h
index 9c65901bd..044a344e0 100644
--- a/deps/hiredis/hiredis.h
+++ b/deps/hiredis/hiredis.h
@@ -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);
diff --git a/deps/hiredis/hiredis.pc.in b/deps/hiredis/hiredis.pc.in
index 91b773183..c7b8e0e36 100644
--- a/deps/hiredis/hiredis.pc.in
+++ b/deps/hiredis/hiredis.pc.in
@@ -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
diff --git a/deps/hiredis/hiredis_ssl-config.cmake.in b/deps/hiredis/hiredis_ssl-config.cmake.in
index 9a283dfc2..eeb19d1d1 100644
--- a/deps/hiredis/hiredis_ssl-config.cmake.in
+++ b/deps/hiredis/hiredis_ssl-config.cmake.in
@@ -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()
diff --git a/deps/hiredis/hiredis_ssl.h b/deps/hiredis/hiredis_ssl.h
index 50fb77cd8..5f92cca9b 100644
--- a/deps/hiredis/hiredis_ssl.h
+++ b/deps/hiredis/hiredis_ssl.h
@@ -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.
*/
diff --git a/deps/hiredis/hiredis_ssl.pc.in b/deps/hiredis/hiredis_ssl.pc.in
index 588a978a5..f7bdd99d4 100644
--- a/deps/hiredis/hiredis_ssl.pc.in
+++ b/deps/hiredis/hiredis_ssl.pc.in
@@ -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
diff --git a/deps/hiredis/net.c b/deps/hiredis/net.c
index 88f9aff25..d75a96658 100644
--- a/deps/hiredis/net.c
+++ b/deps/hiredis/net.c
@@ -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:
diff --git a/deps/hiredis/net.h b/deps/hiredis/net.h
index 9f43283a5..e15d46264 100644
--- a/deps/hiredis/net.h
+++ b/deps/hiredis/net.h
@@ -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
diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c
index 6c19c5a5e..dd038c55b 100644
--- a/deps/hiredis/read.c
+++ b/deps/hiredis/read.c
@@ -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);
diff --git a/deps/hiredis/sds.c b/deps/hiredis/sds.c
index 114fa49a6..f99962eb6 100644
--- a/deps/hiredis/sds.c
+++ b/deps/hiredis/sds.c
@@ -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]);
}
diff --git a/deps/hiredis/sds.h b/deps/hiredis/sds.h
index 573d6dd19..80784bc83 100644
--- a/deps/hiredis/sds.h
+++ b/deps/hiredis/sds.h
@@ -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
diff --git a/deps/hiredis/sockcompat.c b/deps/hiredis/sockcompat.c
index f99d14b05..378745f4b 100644
--- a/deps/hiredis/sockcompat.c
+++ b/deps/hiredis/sockcompat.c
@@ -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 */
diff --git a/deps/hiredis/sockcompat.h b/deps/hiredis/sockcompat.h
index 85810e848..6ca5d9fca 100644
--- a/deps/hiredis/sockcompat.h
+++ b/deps/hiredis/sockcompat.h
@@ -50,6 +50,7 @@
#include
#include
#include
+#include
#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
diff --git a/deps/hiredis/ssl.c b/deps/hiredis/ssl.c
index c10af7c54..1431803ad 100644
--- a/deps/hiredis/ssl.c
+++ b/deps/hiredis/ssl.c
@@ -32,6 +32,7 @@
#include "hiredis.h"
#include "async.h"
+#include "net.h"
#include
#include
@@ -39,6 +40,14 @@
#ifdef _WIN32
#include
#include
+#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
#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,
diff --git a/deps/hiredis/test.c b/deps/hiredis/test.c
index 306aa5559..ecfe075b7 100644
--- a/deps/hiredis/test.c
+++ b/deps/hiredis/test.c
@@ -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) {
diff --git a/deps/hiredis/test.sh b/deps/hiredis/test.sh
index c72bcb0dc..0a1afb923 100755
--- a/deps/hiredis/test.sh
+++ b/deps/hiredis/test.sh
@@ -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 <> ${tmpdir}/redis.conf <> ${tmpdir}/redis.conf <
#include
#include
+#include
#include
#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");
diff --git a/deps/lua/src/lua_cmsgpack.c b/deps/lua/src/lua_cmsgpack.c
index 49c6dc7b0..5f8929d45 100644
--- a/deps/lua/src/lua_cmsgpack.c
+++ b/deps/lua/src/lua_cmsgpack.c
@@ -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);
diff --git a/deps/lua/src/strbuf.c b/deps/lua/src/strbuf.c
index f0f7f4b9a..775e8baf1 100644
--- a/deps/lua/src/strbuf.c
+++ b/deps/lua/src/strbuf.c
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#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:
*/
diff --git a/deps/lua/src/strbuf.h b/deps/lua/src/strbuf.h
index d861108c1..c10f83f0d 100644
--- a/deps/lua/src/strbuf.h
+++ b/deps/lua/src/strbuf.h
@@ -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;
diff --git a/redis.conf b/redis.conf
index a58e46e5a..97f077b0d 100644
--- a/redis.conf
+++ b/redis.conf
@@ -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
diff --git a/sentinel.conf b/sentinel.conf
index 60eb9ff4d..b7b3604f0 100644
--- a/sentinel.conf
+++ b/sentinel.conf
@@ -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
diff --git a/src/Makefile b/src/Makefile
index 4708d1a0a..ecbd2753d 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -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 " > 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
diff --git a/src/acl.c b/src/acl.c
index ebf152aaa..aa42c58dc 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -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) {
diff --git a/src/bio.c b/src/bio.c
index 640fbb024..10ecf8db2 100644
--- a/src/bio.c
+++ b/src/bio.c
@@ -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;
diff --git a/src/blocked.c b/src/blocked.c
index 1b3a804b1..6ad4667db 100644
--- a/src/blocked.c
+++ b/src/blocked.c
@@ -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();
+}
diff --git a/src/cli_common.c b/src/cli_common.c
index 0b13db5dc..421e7d34a 100644
--- a/src/cli_common.c
+++ b/src/cli_common.c
@@ -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);
diff --git a/src/cluster.c b/src/cluster.c
index 69029164e..9e4cfd273 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -31,6 +31,7 @@
#include "server.h"
#include "cluster.h"
#include "endianconv.h"
+#include "connection.h"
#include
#include
@@ -66,6 +67,8 @@ void clusterSetMaster(clusterNode *n);
void clusterHandleSlaveFailover(void);
void clusterHandleSlaveMigration(int max_slaves);
int bitmapTestBit(unsigned char *bitmap, int pos);
+void bitmapSetBit(unsigned char *bitmap, int pos);
+void bitmapClearBit(unsigned char *bitmap, int pos);
void clusterDoBeforeSleep(int flags);
void clusterSendUpdate(clusterLink *link, clusterNode *node);
void resetManualFailover(void);
@@ -89,8 +92,33 @@ void clusterRemoveNodeFromShard(clusterNode *node);
int auxShardIdSetter(clusterNode *n, void *value, int length);
sds auxShardIdGetter(clusterNode *n, sds s);
int auxShardIdPresent(clusterNode *n);
+int auxHumanNodenameSetter(clusterNode *n, void *value, int length);
+sds auxHumanNodenameGetter(clusterNode *n, sds s);
+int auxHumanNodenamePresent(clusterNode *n);
+int auxTcpPortSetter(clusterNode *n, void *value, int length);
+sds auxTcpPortGetter(clusterNode *n, sds s);
+int auxTcpPortPresent(clusterNode *n);
+int auxTlsPortSetter(clusterNode *n, void *value, int length);
+sds auxTlsPortGetter(clusterNode *n, sds s);
+int auxTlsPortPresent(clusterNode *n);
static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen);
+int getNodeDefaultClientPort(clusterNode *n) {
+ return server.tls_cluster ? n->tls_port : n->tcp_port;
+}
+
+static inline int getNodeDefaultReplicationPort(clusterNode *n) {
+ return server.tls_replication ? n->tls_port : n->tcp_port;
+}
+
+static inline int getNodeClientPort(clusterNode *n, int use_tls) {
+ return use_tls ? n->tls_port : n->tcp_port;
+}
+
+static inline int defaultClientPort(void) {
+ return server.tls_cluster ? server.tls_port : server.port;
+}
+
/* Links to the next and previous entries for keys in the same slot are stored
* in the dict entry metadata. See Slot to Key API below. */
#define dictEntryNextInSlot(de) \
@@ -98,6 +126,10 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen);
#define dictEntryPrevInSlot(de) \
(((clusterDictEntryMetadata *)dictEntryMetadata(de))->prev)
+#define isSlotUnclaimed(slot) \
+ (server.cluster->slots[slot] == NULL || \
+ bitmapTestBit(server.cluster->owner_not_claiming_slot, slot))
+
#define RCVBUF_INIT_LEN 1024
#define RCVBUF_MAX_PREALLOC (1<<20) /* 1MB */
@@ -171,8 +203,10 @@ typedef struct {
/* Assign index to each aux field */
typedef enum {
- af_start,
- af_shard_id = af_start,
+ af_shard_id,
+ af_human_nodename,
+ af_tcp_port,
+ af_tls_port,
af_count,
} auxFieldIndex;
@@ -182,14 +216,17 @@ typedef enum {
* 2. aux name can contain characters that pass the isValidAuxChar check only */
auxFieldHandler auxFieldHandlers[] = {
{"shard-id", auxShardIdSetter, auxShardIdGetter, auxShardIdPresent},
+ {"nodename", auxHumanNodenameSetter, auxHumanNodenameGetter, auxHumanNodenamePresent},
+ {"tcp-port", auxTcpPortSetter, auxTcpPortGetter, auxTcpPortPresent},
+ {"tls-port", auxTlsPortSetter, auxTlsPortGetter, auxTlsPortPresent},
};
int isValidAuxChar(int c) {
- return isalnum(c) || (strchr("!#$%&()*+-.:;<>?@[]^_{|}~", c) != NULL);
+ return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~", c) == NULL);
}
-int isValidAuxString(sds s) {
- for (unsigned i = 0; i < sdslen(s); i++) {
+int isValidAuxString(char *s, unsigned int length) {
+ for (unsigned i = 0; i < length; i++) {
if (!isValidAuxChar(s[i])) return 0;
}
return 1;
@@ -219,6 +256,68 @@ int auxShardIdPresent(clusterNode *n) {
return strlen(n->shard_id);
}
+int auxHumanNodenameSetter(clusterNode *n, void *value, int length) {
+ if (n && !strncmp(value, n->human_nodename, length)) {
+ return C_OK;
+ } else if (!n && (length == 0)) {
+ return C_OK;
+ }
+ if (n) {
+ n->human_nodename = sdscpylen(n->human_nodename, value, length);
+ } else if (sdslen(n->human_nodename) != 0) {
+ sdsclear(n->human_nodename);
+ } else {
+ return C_ERR;
+ }
+ return C_OK;
+}
+
+sds auxHumanNodenameGetter(clusterNode *n, sds s) {
+ return sdscatprintf(s, "%s", n->human_nodename);
+}
+
+int auxHumanNodenamePresent(clusterNode *n) {
+ return sdslen(n->human_nodename);
+}
+
+int auxTcpPortSetter(clusterNode *n, void *value, int length) {
+ if (length > 5 || length < 1) {
+ return C_ERR;
+ }
+ char buf[length + 1];
+ memcpy(buf, (char*)value, length);
+ buf[length] = '\0';
+ n->tcp_port = atoi(buf);
+ return (n->tcp_port < 0 || n->tcp_port >= 65536) ? C_ERR : C_OK;
+}
+
+sds auxTcpPortGetter(clusterNode *n, sds s) {
+ return sdscatprintf(s, "%d", n->tcp_port);
+}
+
+int auxTcpPortPresent(clusterNode *n) {
+ return n->tcp_port >= 0 && n->tcp_port < 65536;
+}
+
+int auxTlsPortSetter(clusterNode *n, void *value, int length) {
+ if (length > 5 || length < 1) {
+ return C_ERR;
+ }
+ char buf[length + 1];
+ memcpy(buf, (char*)value, length);
+ buf[length] = '\0';
+ n->tls_port = atoi(buf);
+ return (n->tls_port < 0 || n->tls_port >= 65536) ? C_ERR : C_OK;
+}
+
+sds auxTlsPortGetter(clusterNode *n, sds s) {
+ return sdscatprintf(s, "%d", n->tls_port);
+}
+
+int auxTlsPortPresent(clusterNode *n) {
+ return n->tls_port >= 0 && n->tls_port < 65536;
+}
+
/* clusterLink send queue blocks */
typedef struct {
size_t totlen; /* Total length of this block including the message */
@@ -328,7 +427,7 @@ int clusterLoadConfig(char *filename) {
clusterAddNode(n);
}
/* Format for the node address and auxiliary argument information:
- * ip:port[@cport][,hostname[,aux=val]*] */
+ * ip:port[@cport][,hostname][,aux=val]*] */
aux_argv = sdssplitlen(argv[1], sdslen(argv[1]), ",", 1, &aux_argc);
if (aux_argv == NULL) {
@@ -348,7 +447,8 @@ int clusterLoadConfig(char *filename) {
* the format of "aux=val" where both aux and val can contain
* characters that pass the isValidAuxChar check only. The order
* of the aux fields is insignificant. */
-
+ int aux_tcp_port = 0;
+ int aux_tls_port = 0;
for (int i = 2; i < aux_argc; i++) {
int field_argc;
sds *field_argv;
@@ -362,7 +462,7 @@ int clusterLoadConfig(char *filename) {
/* Validate that both aux and value contain valid characters only */
for (unsigned j = 0; j < 2; j++) {
- if (!isValidAuxString(field_argv[j])) {
+ if (!isValidAuxString(field_argv[j],sdslen(field_argv[j]))){
/* Invalid aux field format */
sdsfreesplitres(field_argv, field_argc);
sdsfreesplitres(argv,argc);
@@ -379,6 +479,8 @@ int clusterLoadConfig(char *filename) {
continue;
}
field_found = 1;
+ aux_tcp_port |= j == af_tcp_port;
+ aux_tls_port |= j == af_tls_port;
if (auxFieldHandlers[j].setter(n, field_argv[1], sdslen(field_argv[1])) != C_OK) {
/* Invalid aux field format */
sdsfreesplitres(field_argv, field_argc);
@@ -396,7 +498,6 @@ int clusterLoadConfig(char *filename) {
sdsfreesplitres(field_argv, field_argc);
}
-
/* Address and port */
if ((p = strrchr(aux_argv[0],':')) == NULL) {
sdsfreesplitres(aux_argv, aux_argc);
@@ -411,11 +512,23 @@ int clusterLoadConfig(char *filename) {
*busp = '\0';
busp++;
}
- n->port = atoi(port);
+ /* If neither TCP or TLS port is found in aux field, it is considered
+ * an old version of nodes.conf file.*/
+ if (!aux_tcp_port && !aux_tls_port) {
+ if (server.tls_cluster) {
+ n->tls_port = atoi(port);
+ } else {
+ n->tcp_port = atoi(port);
+ }
+ } else if (!aux_tcp_port) {
+ n->tcp_port = atoi(port);
+ } else if (!aux_tls_port) {
+ n->tls_port = atoi(port);
+ }
/* In older versions of nodes.conf the "@busport" part is missing.
* In this case we set it to the default offset of 10000 from the
* base port. */
- n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR;
+ n->cport = busp ? atoi(busp) : (getNodeDefaultClientPort(n) + CLUSTER_PORT_INCR);
/* The plaintext port for client in a TLS cluster (n->pport) is not
* stored in nodes.conf. It is received later over the bus protocol. */
@@ -601,7 +714,7 @@ int clusterSaveConfig(int do_fsync) {
/* Get the nodes description and concatenate our "vars" directive to
* save currentEpoch and lastVoteEpoch. */
- ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE, 0);
+ ci = clusterGenNodesDescription(NULL, CLUSTER_NODE_HANDSHAKE, 0);
ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n",
(unsigned long long) server.cluster->currentEpoch,
(unsigned long long) server.cluster->lastVoteEpoch);
@@ -720,23 +833,20 @@ int clusterLockConfig(char *filename) {
}
/* Derives our ports to be announced in the cluster bus. */
-void deriveAnnouncedPorts(int *announced_port, int *announced_pport,
+void deriveAnnouncedPorts(int *announced_tcp_port, int *announced_tls_port,
int *announced_cport) {
- int port = server.tls_cluster ? server.tls_port : server.port;
- /* Default announced ports. */
- *announced_port = port;
- *announced_pport = server.tls_cluster ? server.port : 0;
- *announced_cport = server.cluster_port ? server.cluster_port : port + CLUSTER_PORT_INCR;
-
/* Config overriding announced ports. */
- if (server.tls_cluster && server.cluster_announce_tls_port) {
- *announced_port = server.cluster_announce_tls_port;
- *announced_pport = server.cluster_announce_port;
- } else if (server.cluster_announce_port) {
- *announced_port = server.cluster_announce_port;
- }
+ *announced_tcp_port = server.cluster_announce_port ?
+ server.cluster_announce_port : server.port;
+ *announced_tls_port = server.cluster_announce_tls_port ?
+ server.cluster_announce_tls_port : server.tls_port;
+ /* Derive cluster bus port. */
if (server.cluster_announce_bus_port) {
*announced_cport = server.cluster_announce_bus_port;
+ } else if (server.cluster_port) {
+ *announced_cport = server.cluster_port;
+ } else {
+ *announced_cport = defaultClientPort() + CLUSTER_PORT_INCR;
}
}
@@ -763,7 +873,7 @@ void clusterUpdateMyselfFlags(void) {
* The option can be set at runtime via CONFIG SET. */
void clusterUpdateMyselfAnnouncedPorts(void) {
if (!myself) return;
- deriveAnnouncedPorts(&myself->port,&myself->pport,&myself->cport);
+ deriveAnnouncedPorts(&myself->tcp_port,&myself->tls_port,&myself->cport);
}
/* We want to take myself->ip in sync with the cluster-announce-ip option.
@@ -811,6 +921,22 @@ static void updateAnnouncedHostname(clusterNode *node, char *new) {
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
}
+static void updateAnnouncedHumanNodename(clusterNode *node, char *new) {
+ if (new && !strcmp(new, node->human_nodename)) {
+ return;
+ } else if (!new && (sdslen(node->human_nodename) == 0)) {
+ return;
+ }
+
+ if (new) {
+ node->human_nodename = sdscpy(node->human_nodename, new);
+ } else if (sdslen(node->human_nodename) != 0) {
+ sdsclear(node->human_nodename);
+ }
+ clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
+}
+
+
static void updateShardId(clusterNode *node, const char *shard_id) {
if (memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
clusterRemoveNodeFromShard(node);
@@ -836,6 +962,11 @@ void clusterUpdateMyselfHostname(void) {
updateAnnouncedHostname(myself, server.cluster_announce_hostname);
}
+void clusterUpdateMyselfHumanNodename(void) {
+ if (!myself) return;
+ updateAnnouncedHumanNodename(myself, server.cluster_announce_human_nodename);
+}
+
void clusterInit(void) {
int saveconf = 0;
@@ -867,6 +998,8 @@ void clusterInit(void) {
memset(server.cluster->slots,0, sizeof(server.cluster->slots));
clusterCloseAllSlots();
+ memset(server.cluster->owner_not_claiming_slot, 0, sizeof(server.cluster->owner_not_claiming_slot));
+
/* Lock the cluster config file to make sure every node uses
* its own nodes.conf. */
server.cluster_config_file_lock_fd = -1;
@@ -890,7 +1023,7 @@ void clusterInit(void) {
/* Port sanity check II
* The other handshake port check is triggered too late to stop
* us from trying to use a too-high cluster port number. */
- int port = server.tls_cluster ? server.tls_port : server.port;
+ int port = defaultClientPort();
if (!server.cluster_port && port > (65535-CLUSTER_PORT_INCR)) {
serverLog(LL_WARNING, "Redis port number too high. "
"Cluster communication port is 10,000 port "
@@ -911,7 +1044,7 @@ void clusterInit(void) {
/* Set myself->port/cport/pport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */
- deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport);
+ deriveAnnouncedPorts(&myself->tcp_port, &myself->tls_port, &myself->cport);
server.cluster->mf_end = 0;
server.cluster->mf_slave = NULL;
@@ -919,6 +1052,7 @@ void clusterInit(void) {
clusterUpdateMyselfFlags();
clusterUpdateMyselfIp();
clusterUpdateMyselfHostname();
+ clusterUpdateMyselfHumanNodename();
}
void clusterInitListeners(void) {
@@ -927,7 +1061,7 @@ void clusterInitListeners(void) {
exit(1);
}
- int port = server.tls_cluster ? server.tls_port : server.port;
+ int port = defaultClientPort();
connListener *listener = &server.clistener;
listener->count = 0;
listener->bindaddr = server.bindaddr;
@@ -1256,9 +1390,10 @@ clusterNode *createClusterNode(char *nodename, int flags) {
node->inbound_link = NULL;
memset(node->ip,0,sizeof(node->ip));
node->hostname = sdsempty();
- node->port = 0;
+ node->human_nodename = sdsempty();
+ node->tcp_port = 0;
node->cport = 0;
- node->pport = 0;
+ node->tls_port = 0;
node->fail_reports = listCreate();
node->voted_time = 0;
node->orphaned_time = 0;
@@ -1422,6 +1557,7 @@ void freeClusterNode(clusterNode *n) {
serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK);
sdsfree(nodename);
sdsfree(n->hostname);
+ sdsfree(n->human_nodename);
/* Release links and associated data structures. */
if (n->link) freeClusterLink(n->link);
@@ -1694,9 +1830,9 @@ void clusterHandleConfigEpochCollision(clusterNode *sender) {
myself->configEpoch = server.cluster->currentEpoch;
clusterSaveConfigOrDie(1);
serverLog(LL_VERBOSE,
- "WARNING: configEpoch collision with node %.40s."
+ "WARNING: configEpoch collision with node %.40s (%s)."
" configEpoch set to %llu",
- sender->name,
+ sender->name,sender->human_nodename,
(unsigned long long) myself->configEpoch);
}
@@ -1812,7 +1948,7 @@ void markNodeAsFailingIfNeeded(clusterNode *node) {
if (failures < needed_quorum) return; /* No weak agreement from masters. */
serverLog(LL_NOTICE,
- "Marking node %.40s as failing (quorum reached).", node->name);
+ "Marking node %.40s (%s) as failing (quorum reached).", node->name, node->human_nodename);
/* Mark the node as failing. */
node->flags &= ~CLUSTER_NODE_PFAIL;
@@ -1840,8 +1976,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
* node again. */
if (nodeIsSlave(node) || node->numslots == 0) {
serverLog(LL_NOTICE,
- "Clear FAIL state for node %.40s: %s is reachable again.",
- node->name,
+ "Clear FAIL state for node %.40s (%s):%s is reachable again.",
+ node->name,node->human_nodename,
nodeIsSlave(node) ? "replica" : "master without slots");
node->flags &= ~CLUSTER_NODE_FAIL;
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
@@ -1856,8 +1992,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
(server.cluster_node_timeout * CLUSTER_FAIL_UNDO_TIME_MULT))
{
serverLog(LL_NOTICE,
- "Clear FAIL state for node %.40s: is reachable again and nobody is serving its slots after some time.",
- node->name);
+ "Clear FAIL state for node %.40s (%s): is reachable again and nobody is serving its slots after some time.",
+ node->name, node->human_nodename);
node->flags &= ~CLUSTER_NODE_FAIL;
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
}
@@ -1876,7 +2012,7 @@ int clusterHandshakeInProgress(char *ip, int port, int cport) {
if (!nodeInHandshake(node)) continue;
if (!strcasecmp(node->ip,ip) &&
- node->port == port &&
+ getNodeDefaultClientPort(node) == port &&
node->cport == cport) break;
}
dictReleaseIterator(di);
@@ -1937,12 +2073,36 @@ int clusterStartHandshake(char *ip, int port, int cport) {
* handshake. */
n = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_MEET);
memcpy(n->ip,norm_ip,sizeof(n->ip));
- n->port = port;
+ if (server.tls_cluster) {
+ n->tls_port = port;
+ } else {
+ n->tcp_port = port;
+ }
n->cport = cport;
clusterAddNode(n);
return 1;
}
+static void getClientPortFromClusterMsg(clusterMsg *hdr, int *tls_port, int *tcp_port) {
+ if (server.tls_cluster) {
+ *tls_port = ntohs(hdr->port);
+ *tcp_port = ntohs(hdr->pport);
+ } else {
+ *tls_port = ntohs(hdr->pport);
+ *tcp_port = ntohs(hdr->port);
+ }
+}
+
+static void getClientPortFromGossip(clusterMsgDataGossip *g, int *tls_port, int *tcp_port) {
+ if (server.tls_cluster) {
+ *tls_port = ntohs(g->port);
+ *tcp_port = ntohs(g->pport);
+ } else {
+ *tls_port = ntohs(g->pport);
+ *tcp_port = ntohs(g->port);
+ }
+}
+
/* Process the gossip section of PING or PONG packets.
* Note that this function assumes that the packet is already sanity-checked
* by the caller, not in the content of the gossip section, but in the
@@ -1968,6 +2128,10 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
sdsfree(ci);
}
+ /* Convert port and pport into TCP port and TLS port. */
+ int msg_tls_port, msg_tcp_port;
+ getClientPortFromGossip(g, &msg_tls_port, &msg_tcp_port);
+
/* Update our state accordingly to the gossip sections */
node = clusterLookupNode(g->nodename, CLUSTER_NAMELEN);
if (node) {
@@ -1977,15 +2141,15 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
if (flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) {
if (clusterNodeAddFailureReport(node,sender)) {
serverLog(LL_VERBOSE,
- "Node %.40s reported node %.40s as not reachable.",
- sender->name, node->name);
+ "Node %.40s (%s) reported node %.40s (%s) as not reachable.",
+ sender->name, sender->human_nodename, node->name, node->human_nodename);
}
markNodeAsFailingIfNeeded(node);
} else {
if (clusterNodeDelFailureReport(node,sender)) {
serverLog(LL_VERBOSE,
- "Node %.40s reported node %.40s is back online.",
- sender->name, node->name);
+ "Node %.40s (%s) reported node %.40s (%s) is back online.",
+ sender->name, sender->human_nodename, node->name, node->human_nodename);
}
}
}
@@ -2021,13 +2185,14 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
!(flags & CLUSTER_NODE_NOADDR) &&
!(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) &&
(strcasecmp(node->ip,g->ip) ||
- node->port != ntohs(g->port) ||
+ node->tls_port != (server.tls_cluster ? ntohs(g->port) : ntohs(g->pport)) ||
+ node->tcp_port != (server.tls_cluster ? ntohs(g->pport) : ntohs(g->port)) ||
node->cport != ntohs(g->cport)))
{
if (node->link) freeClusterLink(node->link);
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
- node->port = ntohs(g->port);
- node->pport = ntohs(g->pport);
+ node->tcp_port = msg_tcp_port;
+ node->tls_port = msg_tls_port;
node->cport = ntohs(g->cport);
node->flags &= ~CLUSTER_NODE_NOADDR;
}
@@ -2048,8 +2213,8 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
clusterNode *node;
node = createClusterNode(g->nodename, flags);
memcpy(node->ip,g->ip,NET_IP_STR_LEN);
- node->port = ntohs(g->port);
- node->pport = ntohs(g->pport);
+ node->tcp_port = msg_tcp_port;
+ node->tls_port = msg_tls_port;
node->cport = ntohs(g->cport);
clusterAddNode(node);
}
@@ -2094,9 +2259,9 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
clusterMsg *hdr)
{
char ip[NET_IP_STR_LEN] = {0};
- int port = ntohs(hdr->port);
- int pport = ntohs(hdr->pport);
int cport = ntohs(hdr->cport);
+ int tcp_port, tls_port;
+ getClientPortFromClusterMsg(hdr, &tls_port, &tcp_port);
/* We don't proceed if the link is the same as the sender link, as this
* function is designed to see if the node link is consistent with the
@@ -2111,23 +2276,23 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
* in the next round of PINGs */
if (nodeIp2String(ip,link,hdr->myip) == C_ERR) return 0;
- if (node->port == port && node->cport == cport && node->pport == pport &&
+ if (node->tcp_port == tcp_port && node->cport == cport && node->tls_port == tls_port &&
strcmp(ip,node->ip) == 0) return 0;
/* IP / port is different, update it. */
memcpy(node->ip,ip,sizeof(ip));
- node->port = port;
- node->pport = pport;
+ node->tcp_port = tcp_port;
+ node->tls_port = tls_port;
node->cport = cport;
if (node->link) freeClusterLink(node->link);
node->flags &= ~CLUSTER_NODE_NOADDR;
- serverLog(LL_NOTICE,"Address updated for node %.40s, now %s:%d",
- node->name, node->ip, node->port);
+ serverLog(LL_NOTICE,"Address updated for node %.40s (%s), now %s:%d",
+ node->name, node->human_nodename, node->ip, getNodeDefaultClientPort(node));
/* Check if this is our master and we have to change the
* replication target as well. */
if (nodeIsSlave(myself) && myself->slaveof == node)
- replicationSetMaster(node->ip, node->port);
+ replicationSetMaster(node->ip, getNodeDefaultReplicationPort(node));
return 1;
}
@@ -2195,7 +2360,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
sender_slots++;
/* The slot is already bound to the sender of this message. */
- if (server.cluster->slots[j] == sender) continue;
+ if (server.cluster->slots[j] == sender) {
+ bitmapClearBit(server.cluster->owner_not_claiming_slot, j);
+ continue;
+ }
/* The slot is in importing state, it should be modified only
* manually via redis-cli (example: a resharding is in progress
@@ -2204,10 +2372,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
if (server.cluster->importing_slots_from[j]) continue;
/* We rebind the slot to the new node claiming it if:
- * 1) The slot was unassigned or the new node claims it with a
- * greater configEpoch.
+ * 1) The slot was unassigned or the previous owner no longer owns the slot or
+ * the new node claims it with a greater configEpoch.
* 2) We are not currently importing the slot. */
- if (server.cluster->slots[j] == NULL ||
+ if (isSlotUnclaimed(j) ||
server.cluster->slots[j]->configEpoch < senderConfigEpoch)
{
/* Was this slot mine, and still contains keys? Mark it as
@@ -2226,10 +2394,20 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
}
clusterDelSlot(j);
clusterAddSlot(sender,j);
+ bitmapClearBit(server.cluster->owner_not_claiming_slot, j);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE|
CLUSTER_TODO_FSYNC_CONFIG);
}
+ } else if (server.cluster->slots[j] == sender) {
+ /* The slot is currently bound to the sender but the sender is no longer
+ * claiming it. We don't want to unbind the slot yet as it can cause the cluster
+ * to move to FAIL state and also throw client error. Keeping the slot bound to
+ * the previous owner will cause a few client side redirects, but won't throw
+ * any errors. We will keep track of the uncertainty in ownership to avoid
+ * propagating misinformation about this slot's ownership using UPDATE
+ * messages. */
+ bitmapSetBit(server.cluster->owner_not_claiming_slot, j);
}
}
@@ -2251,7 +2429,7 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
sender_slots == migrated_our_slots)) {
serverLog(LL_NOTICE,
"Configuration change detected. Reconfiguring myself "
- "as a replica of %.40s", sender->name);
+ "as a replica of %.40s (%s)", sender->name, sender->human_nodename);
clusterSetMaster(sender);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE|
@@ -2267,8 +2445,8 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
* into a replica if its last slot is removed. If no other node takes
* over the slot, there is nothing else to trigger replica migration. */
serverLog(LL_NOTICE,
- "I'm a sub-replica! Reconfiguring myself as a replica of grandmaster %.40s",
- myself->slaveof->slaveof->name);
+ "I'm a sub-replica! Reconfiguring myself as a replica of grandmaster %.40s (%s)",
+ myself->slaveof->slaveof->name, myself->slaveof->slaveof->human_nodename);
clusterSetMaster(myself->slaveof->slaveof);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE|
@@ -2328,6 +2506,13 @@ uint32_t getHostnamePingExtSize(void) {
return getAlignedPingExtSize(sdslen(myself->hostname) + 1);
}
+uint32_t getHumanNodenamePingExtSize(void) {
+ if (sdslen(myself->human_nodename) == 0) {
+ return 0;
+ }
+ return getAlignedPingExtSize(sdslen(myself->human_nodename) + 1);
+}
+
uint32_t getShardIdPingExtSize(void) {
return getAlignedPingExtSize(sizeof(clusterMsgPingExtShardId));
}
@@ -2376,6 +2561,20 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) {
extensions++;
}
+ if (sdslen(myself->human_nodename) != 0) {
+ if (cursor != NULL) {
+ /* Populate human_nodename */
+ clusterMsgPingExtHumanNodename *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME, getHumanNodenamePingExtSize());
+ memcpy(ext->human_nodename, myself->human_nodename, sdslen(myself->human_nodename));
+
+ /* Move the write cursor */
+ cursor = nextPingExt(cursor);
+ }
+
+ totlen += getHumanNodenamePingExtSize();
+ extensions++;
+ }
+
/* Gossip forgotten nodes */
if (dictSize(server.cluster->nodes_black_list) > 0) {
dictIterator *di = dictGetIterator(server.cluster->nodes_black_list);
@@ -2424,6 +2623,7 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) {
void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN);
char *ext_hostname = NULL;
+ char *ext_humannodename = NULL;
char *ext_shardid = NULL;
uint16_t extensions = ntohs(hdr->extensions);
/* Loop through all the extensions and process them */
@@ -2433,6 +2633,9 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
if (type == CLUSTERMSG_EXT_TYPE_HOSTNAME) {
clusterMsgPingExtHostname *hostname_ext = (clusterMsgPingExtHostname *) &(ext->ext[0].hostname);
ext_hostname = hostname_ext->hostname;
+ } else if (type == CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME) {
+ clusterMsgPingExtHumanNodename *humannodename_ext = (clusterMsgPingExtHumanNodename *) &(ext->ext[0].human_nodename);
+ ext_humannodename = humannodename_ext->human_nodename;
} else if (type == CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE) {
clusterMsgPingExtForgottenNode *forgotten_node_ext = &(ext->ext[0].forgotten_node);
clusterNode *n = clusterLookupNode(forgotten_node_ext->name, CLUSTER_NAMELEN);
@@ -2461,6 +2664,7 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
* they don't have an announced hostname. Otherwise, we'll
* set it now. */
updateAnnouncedHostname(sender, ext_hostname);
+ updateAnnouncedHumanNodename(sender, ext_humannodename);
updateShardId(sender, ext_shardid);
}
@@ -2662,8 +2866,7 @@ int clusterProcessPacket(clusterLink *link) {
node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE);
serverAssert(nodeIp2String(node->ip,link,hdr->myip) == C_OK);
- node->port = ntohs(hdr->port);
- node->pport = ntohs(hdr->pport);
+ getClientPortFromClusterMsg(hdr, &node->tls_port, &node->tcp_port);
node->cport = ntohs(hdr->cport);
clusterAddNode(node);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
@@ -2692,8 +2895,8 @@ int clusterProcessPacket(clusterLink *link) {
* IP/port of the node with the new one. */
if (sender) {
serverLog(LL_VERBOSE,
- "Handshake: we already know node %.40s, "
- "updating the address if needed.", sender->name);
+ "Handshake: we already know node %.40s (%s), "
+ "updating the address if needed.", sender->name, sender->human_nodename);
if (nodeUpdateAddressIfNeeded(sender,link,hdr))
{
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
@@ -2725,8 +2928,8 @@ int clusterProcessPacket(clusterLink *link) {
link->node->flags);
link->node->flags |= CLUSTER_NODE_NOADDR;
link->node->ip[0] = '\0';
- link->node->port = 0;
- link->node->pport = 0;
+ link->node->tcp_port = 0;
+ link->node->tls_port = 0;
link->node->cport = 0;
freeClusterLink(link);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
@@ -2861,7 +3064,7 @@ int clusterProcessPacket(clusterLink *link) {
for (j = 0; j < CLUSTER_SLOTS; j++) {
if (bitmapTestBit(hdr->myslots,j)) {
if (server.cluster->slots[j] == sender ||
- server.cluster->slots[j] == NULL) continue;
+ isSlotUnclaimed(j)) continue;
if (server.cluster->slots[j]->configEpoch >
senderConfigEpoch)
{
@@ -2904,8 +3107,8 @@ int clusterProcessPacket(clusterLink *link) {
!(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF)))
{
serverLog(LL_NOTICE,
- "FAIL message received from %.40s about %.40s",
- hdr->sender, hdr->data.fail.about.nodename);
+ "FAIL message received from %.40s (%s) about %.40s (%s)",
+ hdr->sender, sender->human_nodename, hdr->data.fail.about.nodename, failing->human_nodename);
failing->flags |= CLUSTER_NODE_FAIL;
failing->fail_time = now;
failing->flags &= ~CLUSTER_NODE_PFAIL;
@@ -2969,8 +3172,8 @@ int clusterProcessPacket(clusterLink *link) {
pauseActions(PAUSE_DURING_FAILOVER,
now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT),
PAUSE_ACTIONS_CLIENT_WRITE_SET);
- serverLog(LL_NOTICE,"Manual failover requested by replica %.40s.",
- sender->name);
+ serverLog(LL_NOTICE,"Manual failover requested by replica %.40s (%s).",
+ sender->name, sender->human_nodename);
/* We need to send a ping message to the replica, as it would carry
* `server.cluster->mf_master_offset`, which means the master paused clients
* at offset `server.cluster->mf_master_offset`, so that the replica would
@@ -3274,15 +3477,20 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) {
}
/* Handle cluster-announce-[tls-|bus-]port. */
- int announced_port, announced_pport, announced_cport;
- deriveAnnouncedPorts(&announced_port, &announced_pport, &announced_cport);
+ int announced_tcp_port, announced_tls_port, announced_cport;
+ deriveAnnouncedPorts(&announced_tcp_port, &announced_tls_port, &announced_cport);
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
if (myself->slaveof != NULL)
memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN);
- hdr->port = htons(announced_port);
- hdr->pport = htons(announced_pport);
+ if (server.tls_cluster) {
+ hdr->port = htons(announced_tls_port);
+ hdr->pport = htons(announced_tcp_port);
+ } else {
+ hdr->port = htons(announced_tcp_port);
+ hdr->pport = htons(announced_tls_port);
+ }
hdr->cport = htons(announced_cport);
hdr->flags = htons(myself->flags);
hdr->state = server.cluster->state;
@@ -3314,10 +3522,15 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
gossip->ping_sent = htonl(n->ping_sent/1000);
gossip->pong_received = htonl(n->pong_received/1000);
memcpy(gossip->ip,n->ip,sizeof(n->ip));
- gossip->port = htons(n->port);
+ if (server.tls_cluster) {
+ gossip->port = htons(n->tls_port);
+ gossip->pport = htons(n->tcp_port);
+ } else {
+ gossip->port = htons(n->tcp_port);
+ gossip->pport = htons(n->tls_port);
+ }
gossip->cport = htons(n->cport);
gossip->flags = htons(n->flags);
- gossip->pport = htons(n->pport);
gossip->notused1 = 0;
}
@@ -3375,7 +3588,6 @@ void clusterSendPing(clusterLink *link, int type) {
estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted));
estlen += writePingExt(NULL, 0);
-
/* Note: clusterBuildMessageHdr() expects the buffer to be always at least
* sizeof(clusterMsg) or more. */
if (estlen < (int)sizeof(clusterMsg)) estlen = sizeof(clusterMsg);
@@ -3555,6 +3767,10 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
+ for (unsigned int i = 0; i < sizeof(node->slots); i++) {
+ /* Don't advertise slots that the node stopped claiming */
+ hdr->data.update.nodecfg.slots[i] = hdr->data.update.nodecfg.slots[i] & (~server.cluster->owner_not_claiming_slot[i]);
+ }
clusterSendMessage(link,msgblock);
clusterMsgSendBlockDecrRefCount(msgblock);
@@ -3703,8 +3919,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
* request, if the request epoch was greater. */
if (requestCurrentEpoch < server.cluster->currentEpoch) {
serverLog(LL_WARNING,
- "Failover auth denied to %.40s: reqEpoch (%llu) < curEpoch(%llu)",
- node->name,
+ "Failover auth denied to %.40s (%s): reqEpoch (%llu) < curEpoch(%llu)",
+ node->name, node->human_nodename,
(unsigned long long) requestCurrentEpoch,
(unsigned long long) server.cluster->currentEpoch);
return;
@@ -3713,8 +3929,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
/* I already voted for this epoch? Return ASAP. */
if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) {
serverLog(LL_WARNING,
- "Failover auth denied to %.40s: already voted for epoch %llu",
- node->name,
+ "Failover auth denied to %.40s (%s): already voted for epoch %llu",
+ node->name, node->human_nodename,
(unsigned long long) server.cluster->currentEpoch);
return;
}
@@ -3727,16 +3943,16 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
{
if (nodeIsMaster(node)) {
serverLog(LL_WARNING,
- "Failover auth denied to %.40s: it is a master node",
- node->name);
+ "Failover auth denied to %.40s (%s): it is a master node",
+ node->name, node->human_nodename);
} else if (master == NULL) {
serverLog(LL_WARNING,
- "Failover auth denied to %.40s: I don't know its master",
- node->name);
+ "Failover auth denied to %.40s (%s): I don't know its master",
+ node->name, node->human_nodename);
} else if (!nodeFailed(master)) {
serverLog(LL_WARNING,
- "Failover auth denied to %.40s: its master is up",
- node->name);
+ "Failover auth denied to %.40s (%s): its master is up",
+ node->name, node->human_nodename);
}
return;
}
@@ -3747,9 +3963,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2)
{
serverLog(LL_WARNING,
- "Failover auth denied to %.40s: "
+ "Failover auth denied to %.40s %s: "
"can't vote about this master before %lld milliseconds",
- node->name,
+ node->name, node->human_nodename,
(long long) ((server.cluster_node_timeout*2)-
(mstime() - node->slaveof->voted_time)));
return;
@@ -3760,7 +3976,7 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
* slots in the current configuration. */
for (j = 0; j < CLUSTER_SLOTS; j++) {
if (bitmapTestBit(claimed_slots, j) == 0) continue;
- if (server.cluster->slots[j] == NULL ||
+ if (isSlotUnclaimed(j) ||
server.cluster->slots[j]->configEpoch <= requestConfigEpoch)
{
continue;
@@ -3769,9 +3985,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
* is served by a master with a greater configEpoch than the one claimed
* by the slave requesting our vote. Refuse to vote for this slave. */
serverLog(LL_WARNING,
- "Failover auth denied to %.40s: "
+ "Failover auth denied to %.40s (%s): "
"slot %d epoch (%llu) > reqEpoch (%llu)",
- node->name, j,
+ node->name, node->human_nodename, j,
(unsigned long long) server.cluster->slots[j]->configEpoch,
(unsigned long long) requestConfigEpoch);
return;
@@ -3782,8 +3998,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
node->slaveof->voted_time = mstime();
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
clusterSendFailoverAuth(node);
- serverLog(LL_NOTICE, "Failover auth granted to %.40s for epoch %llu",
- node->name, (unsigned long long) server.cluster->currentEpoch);
+ serverLog(LL_NOTICE, "Failover auth granted to %.40s (%s) for epoch %llu",
+ node->name, node->human_nodename, (unsigned long long) server.cluster->currentEpoch);
}
/* This function returns the "rank" of this instance, a slave, in the context
@@ -4538,7 +4754,7 @@ void clusterCron(void) {
myself->slaveof &&
nodeHasAddr(myself->slaveof))
{
- replicationSetMaster(myself->slaveof->ip, myself->slaveof->port);
+ replicationSetMaster(myself->slaveof->ip, getNodeDefaultReplicationPort(myself->slaveof));
}
/* Abort a manual failover if the timeout is reached. */
@@ -4941,7 +5157,7 @@ void clusterSetMaster(clusterNode *n) {
myself->slaveof = n;
updateShardId(myself, n->shard_id);
clusterNodeAddSlave(n,myself);
- replicationSetMaster(n->ip, n->port);
+ replicationSetMaster(n->ip, getNodeDefaultReplicationPort(n));
resetManualFailover();
}
@@ -5000,32 +5216,34 @@ sds representSlotInfo(sds ci, uint16_t *slot_info_pairs, int slot_info_pairs_cou
* See clusterGenNodesDescription() top comment for more information.
*
* The function returns the string representation as an SDS string. */
-sds clusterGenNodeDescription(clusterNode *node, int use_pport) {
+sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary) {
int j, start;
sds ci;
- int port = use_pport && node->pport ? node->pport : node->port;
+ int port = getNodeClientPort(node, tls_primary);
/* Node coordinates */
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
- /* Node's ip/port and optional announced hostname */
+ ci = sdscatfmt(ci," %s:%i@%i",
+ node->ip,
+ port,
+ node->cport);
if (sdslen(node->hostname) != 0) {
- ci = sdscatprintf(ci," %s:%i@%i,%s",
- node->ip,
- port,
- node->cport,
- node->hostname);
- } else {
- ci = sdscatprintf(ci," %s:%i@%i,",
- node->ip,
- port,
- node->cport);
+ ci = sdscatfmt(ci,",%s", node->hostname);
}
-
- /* Node's aux fields */
- for (int i = af_start; i < af_count; i++) {
- if (auxFieldHandlers[i].isPresent(node)) {
- ci = sdscatprintf(ci, ",%s=", auxFieldHandlers[i].field);
- ci = auxFieldHandlers[i].getter(node, ci);
+ if (sdslen(node->hostname) == 0) {
+ ci = sdscatfmt(ci,",", 1);
+ }
+ /* Don't expose aux fields to any clients yet but do allow them
+ * to be persisted to nodes.conf */
+ if (c == NULL) {
+ for (int i = af_count-1; i >=0; i--) {
+ if ((tls_primary && i == af_tls_port) || (!tls_primary && i == af_tcp_port)) {
+ continue;
+ }
+ if (auxFieldHandlers[i].isPresent(node)) {
+ ci = sdscatprintf(ci, ",%s=", auxFieldHandlers[i].field);
+ ci = auxFieldHandlers[i].getter(node, ci);
+ }
}
}
@@ -5144,13 +5362,13 @@ void clusterFreeNodesSlotsInfo(clusterNode *n) {
* include all the known nodes in the representation, including nodes in
* the HANDSHAKE state.
*
- * Setting use_pport to 1 in a TLS cluster makes the result contain the
- * plaintext client port rather then the TLS client port of each node.
+ * Setting tls_primary to 1 to put TLS port in the main :
+ * field and put TCP port in aux field, instead of the opposite way.
*
* The representation obtained using this function is used for the output
* of the CLUSTER NODES function, and as format for the cluster
* configuration file (nodes.conf) for a given node. */
-sds clusterGenNodesDescription(int filter, int use_pport) {
+sds clusterGenNodesDescription(client *c, int filter, int tls_primary) {
sds ci = sdsempty(), ni;
dictIterator *di;
dictEntry *de;
@@ -5163,7 +5381,7 @@ sds clusterGenNodesDescription(int filter, int use_pport) {
clusterNode *node = dictGetVal(de);
if (node->flags & filter) continue;
- ni = clusterGenNodeDescription(node, use_pport);
+ ni = clusterGenNodeDescription(c, node, tls_primary);
ci = sdscatsds(ci,ni);
sdsfree(ni);
ci = sdscatlen(ci,"\n",1);
@@ -5351,10 +5569,8 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
serverPanic("Unrecognized preferred endpoint type");
}
- /* Report non-TLS ports to non-TLS client in TLS cluster if available. */
- int use_pport = (server.tls_cluster &&
- c->conn && (c->conn->type != connectionTypeTls()));
- addReplyLongLong(c, use_pport && node->pport ? node->pport : node->port);
+ /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
+ addReplyLongLong(c, getNodeClientPort(node, connIsTLS(c->conn)));
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Add the additional endpoint information, this is all the known networking information
@@ -5417,19 +5633,15 @@ void addNodeDetailsToShardReply(client *c, clusterNode *node) {
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
reply_count++;
- /* We use server.tls_cluster as a proxy for whether or not
- * the remote port is the tls port or not */
- int plaintext_port = server.tls_cluster ? node->pport : node->port;
- int tls_port = server.tls_cluster ? node->port : 0;
- if (plaintext_port) {
+ if (node->tcp_port) {
addReplyBulkCString(c, "port");
- addReplyLongLong(c, plaintext_port);
+ addReplyLongLong(c, node->tcp_port);
reply_count++;
}
- if (tls_port) {
+ if (node->tls_port) {
addReplyBulkCString(c, "tls-port");
- addReplyLongLong(c, tls_port);
+ addReplyLongLong(c, node->tls_port);
reply_count++;
}
@@ -5708,14 +5920,14 @@ NULL
long long port, cport;
if (getLongLongFromObject(c->argv[3], &port) != C_OK) {
- addReplyErrorFormat(c,"Invalid TCP base port specified: %s",
+ addReplyErrorFormat(c,"Invalid base port specified: %s",
(char*)c->argv[3]->ptr);
return;
}
if (c->argc == 5) {
if (getLongLongFromObject(c->argv[4], &cport) != C_OK) {
- addReplyErrorFormat(c,"Invalid TCP bus port specified: %s",
+ addReplyErrorFormat(c,"Invalid bus port specified: %s",
(char*)c->argv[4]->ptr);
return;
}
@@ -5733,11 +5945,8 @@ NULL
}
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
/* CLUSTER NODES */
- /* Report plaintext ports, only if cluster is TLS but client is known to
- * be non-TLS). */
- int use_pport = (server.tls_cluster &&
- c->conn && (c->conn->type != connectionTypeTls()));
- sds nodes = clusterGenNodesDescription(0, use_pport);
+ /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
+ sds nodes = clusterGenNodesDescription(c, 0, connIsTLS(c->conn));
addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
sdsfree(nodes);
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
@@ -5923,7 +6132,7 @@ NULL
{
serverLog(LL_NOTICE,
"Configuration change detected. Reconfiguring myself "
- "as a replica of %.40s", n->name);
+ "as a replica of %.40s (%s)", n->name, n->human_nodename);
clusterSetMaster(n);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
CLUSTER_TODO_UPDATE_STATE |
@@ -6099,12 +6308,10 @@ NULL
return;
}
- /* Use plaintext port if cluster is TLS but client is non-TLS. */
- int use_pport = (server.tls_cluster &&
- c->conn && (c->conn->type != connectionTypeTls()));
+ /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
addReplyArrayLen(c,n->numslaves);
for (j = 0; j < n->numslaves; j++) {
- sds ni = clusterGenNodeDescription(n->slaves[j], use_pport);
+ sds ni = clusterGenNodeDescription(c, n->slaves[j], connIsTLS(c->conn));
addReplyBulkCString(c,ni);
sdsfree(ni);
}
@@ -6436,7 +6643,8 @@ void restoreCommand(client *c) {
if (ttl && !absttl) ttl+=commandTimeSnapshot();
if (ttl && checkAlreadyExpired(ttl)) {
if (deleted) {
- rewriteClientCommandVector(c,2,shared.del,key);
+ robj *aux = server.lazyfree_lazy_server_del ? shared.unlink : shared.del;
+ rewriteClientCommandVector(c, 2, aux, key);
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
server.dirty++;
@@ -7228,11 +7436,8 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
} else if (error_code == CLUSTER_REDIR_MOVED ||
error_code == CLUSTER_REDIR_ASK)
{
- /* Redirect to IP:port. Include plaintext port if cluster is TLS but
- * client is non-TLS. */
- int use_pport = (server.tls_cluster &&
- c->conn && (c->conn->type != connectionTypeTls()));
- int port = use_pport && n->pport ? n->pport : n->port;
+ /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
+ int port = getNodeClientPort(n, connIsTLS(c->conn));
addReplyErrorSds(c,sdscatprintf(sdsempty(),
"-%s %d %s:%d",
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
diff --git a/src/cluster.h b/src/cluster.h
index b5ab7d5a5..21c9c4499 100644
--- a/src/cluster.h
+++ b/src/cluster.h
@@ -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 */
diff --git a/src/commands.def b/src/commands.def
index 33e309bfb..7e575648c 100644
--- a/src/commands.def
+++ b/src/commands.def
@@ -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},
diff --git a/src/commands/cluster-shards.json b/src/commands/cluster-shards.json
index cfc7250b0..dcaad3ea3 100644
--- a/src/commands/cluster-shards.json
+++ b/src/commands/cluster-shards.json
@@ -9,6 +9,7 @@
"function": "clusterCommand",
"history": [],
"command_flags": [
+ "LOADING",
"STALE"
],
"command_tips": [
diff --git a/src/commands/cluster-slots.json b/src/commands/cluster-slots.json
index fc98ba79a..b00cc60f4 100644
--- a/src/commands/cluster-slots.json
+++ b/src/commands/cluster-slots.json
@@ -23,6 +23,7 @@
]
],
"command_flags": [
+ "LOADING",
"STALE"
],
"command_tips": [
diff --git a/src/commands/psubscribe.json b/src/commands/psubscribe.json
index db9592139..cab5d14ef 100644
--- a/src/commands/psubscribe.json
+++ b/src/commands/psubscribe.json
@@ -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,
diff --git a/src/commands/punsubscribe.json b/src/commands/punsubscribe.json
index 3a074517b..cb977d8ca 100644
--- a/src/commands/punsubscribe.json
+++ b/src/commands/punsubscribe.json
@@ -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,
diff --git a/src/commands/sentinel-config.json b/src/commands/sentinel-config.json
index ecc317603..a406125a5 100644
--- a/src/commands/sentinel-config.json
+++ b/src/commands/sentinel-config.json
@@ -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
}
]
}
diff --git a/src/commands/sunsubscribe.json b/src/commands/sunsubscribe.json
index 7965a05ea..2d68fc7b1 100644
--- a/src/commands/sunsubscribe.json
+++ b/src/commands/sunsubscribe.json
@@ -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,
diff --git a/src/commands/unsubscribe.json b/src/commands/unsubscribe.json
index 530facde4..e8586bc7a 100644
--- a/src/commands/unsubscribe.json
+++ b/src/commands/unsubscribe.json
@@ -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,
diff --git a/src/commands/zrank.json b/src/commands/zrank.json
index f8ebda94a..f5f427c66 100644
--- a/src/commands/zrank.json
+++ b/src/commands/zrank.json
@@ -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": [
diff --git a/src/commands/zrevrank.json b/src/commands/zrevrank.json
index 43ac13bd8..39897cae3 100644
--- a/src/commands/zrevrank.json
+++ b/src/commands/zrevrank.json
@@ -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": [
diff --git a/src/config.c b/src/config.c
index 6978cc5e7..b26704283 100644
--- a/src/config.c
+++ b/src/config.c
@@ -38,6 +38,7 @@
#include
#include
#include
+#include
/*-----------------------------------------------------------------------------
* 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),
diff --git a/src/connection.h b/src/connection.h
index 7c26ac635..d0a17ab4d 100644
--- a/src/connection.h
+++ b/src/connection.h
@@ -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 */
diff --git a/src/db.c b/src/db.c
index 38881a7f8..7b34fcb47 100644
--- a/src/db.c
+++ b/src/db.c
@@ -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;
diff --git a/src/debug.c b/src/debug.c
index b2c3de69a..a57b1fde9 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -413,9 +413,9 @@ void debugCommand(client *c) {
" Create a memory leak of the input string.",
"LOG ",
" Write to the server log.",
-"HTSTATS ",
+"HTSTATS [full]",
" Return hash table statistics of the specified Redis database.",
-"HTSTATS-KEY ",
+"HTSTATS-KEY [full]",
" Like HTSTATS but for the hash table stored at '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;
}
diff --git a/src/dict.c b/src/dict.c
index b3d7fa406..6760da154 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -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 ---------------------------------*/
diff --git a/src/dict.h b/src/dict.h
index 06d4bff78..e96cd44eb 100644
--- a/src/dict.h
+++ b/src/dict.h
@@ -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*));
diff --git a/src/evict.c b/src/evict.c
index 4ca9f62ed..909714b43 100644
--- a/src/evict.c
+++ b/src/evict.c
@@ -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();
}
diff --git a/src/geo.c b/src/geo.c
index 4ebd678b5..ac25a20c6 100644
--- a/src/geo.c
+++ b/src/geo.c
@@ -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;
diff --git a/src/module.c b/src/module.c
index 7277e3e72..0addeecde 100644
--- a/src/module.c
+++ b/src/module.c
@@ -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);
diff --git a/src/networking.c b/src/networking.c
index 25d2b4c34..56273fc7e 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -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))
diff --git a/src/object.c b/src/object.c
index e089fde87..4b3526a02 100644
--- a/src/object.c
+++ b/src/object.c
@@ -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) {
diff --git a/src/pubsub.c b/src/pubsub.c
index ed7350843..a13c5a61f 100644
--- a/src/pubsub.c
+++ b/src/pubsub.c
@@ -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 */
diff --git a/src/rdb.c b/src/rdb.c
index c52d8faf7..cfc92e815 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -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;
diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c
index 2eb689e9f..1ee562f20 100644
--- a/src/redis-benchmark.c
+++ b/src/redis-benchmark.c
@@ -1614,7 +1614,9 @@ usage:
" -a Password for Redis Auth\n"
" --user Used to send ACL style 'AUTH username pass'. Needs -a.\n"
" -u Server URI.\n"
-" -c Number of parallel connections (default 50)\n"
+" -c 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 Total number of requests (default 100000)\n"
" -d Data size of SET/GET value in bytes (default 3)\n"
" --dbnum SELECT the specified db number (default 0)\n"
diff --git a/src/redis-cli.c b/src/redis-cli.c
index 3f5827fbe..de34965b4 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -3067,7 +3067,7 @@ version,tls_usage);
" --replica Simulate a replica showing commands received from the master.\n"
" --rdb Transfer an RDB dump from remote server to local file.\n"
" Use filename of \"-\" to write to stdout.\n"
-" --functions-rdb Like --rdb but only get the functions (not the keys)\n"
+" --functions-rdb 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 In --pipe mode, abort with error if after sending all data.\n"
diff --git a/src/redismodule.h b/src/redismodule.h
index 049b0f340..4378126e2 100644
--- a/src/redismodule.h
+++ b/src/redismodule.h
@@ -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);
diff --git a/src/replication.c b/src/replication.c
index ca3287491..97e01b64d 100644
--- a/src/replication.c
+++ b/src/replication.c
@@ -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);
}
}
diff --git a/src/script.c b/src/script.c
index 70680e3ea..6a798a6e1 100644
--- a/src/script.c
+++ b/src/script.c
@@ -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,
diff --git a/src/sds.c b/src/sds.c
index 563f1f0fe..e383e3caa 100644
--- a/src/sds.c
+++ b/src/sds.c
@@ -38,6 +38,7 @@
#include
#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);
}
diff --git a/src/sentinel.c b/src/sentinel.c
index 326def135..238be905f 100644
--- a/src/sentinel.c
+++ b/src/sentinel.c
@@ -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